From fa3fc7dc84097cd4d2ccb8df395870b8d3834012 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Mon, 6 Feb 2023 20:21:45 +0900 Subject: [PATCH 01/77] =?UTF-8?q?=E2=9C=A8=20Add=20function=20to=20solve?= =?UTF-8?q?=20a=20cubic=20&=20quartic=20equation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- raysect/core/math/cython/utility.pxd | 7 + raysect/core/math/cython/utility.pyx | 201 ++++++++++++++++++++++++++- 2 files changed, 207 insertions(+), 1 deletion(-) diff --git a/raysect/core/math/cython/utility.pxd b/raysect/core/math/cython/utility.pxd index dc60576b..e5eb364c 100644 --- a/raysect/core/math/cython/utility.pxd +++ b/raysect/core/math/cython/utility.pxd @@ -70,6 +70,13 @@ cdef inline double lerp(double x0, double x1, double y0, double y1, double x) no cdef bint solve_quadratic(double a, double b, double c, double *t0, double *t1) nogil +cdef bint is_zero(double v) nogil + +cdef int solve_cubic(double a, double b, double c, double d, double *t0, double *t1, double *t2) nogil + +cdef int solve_quartic(double a, double b, double c, double d, double e, + double *t0, double *t1, double *t2, double *t3) nogil + cdef bint winding2d(double[:,::1] vertices) nogil cdef bint point_inside_polygon(double[:,::1] vertices, double ptx, double pty) diff --git a/raysect/core/math/cython/utility.pyx b/raysect/core/math/cython/utility.pyx index 0cc0a134..b1b21319 100644 --- a/raysect/core/math/cython/utility.pyx +++ b/raysect/core/math/cython/utility.pyx @@ -29,11 +29,13 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from libc.math cimport sqrt +from libc.math cimport sqrt, cbrt, acos, cos, M_PI cimport cython #TODO: Write unit tests! +DEF EQN_EPS = 1.0e-9 + @cython.cdivision(True) @cython.boundscheck(False) @cython.wraparound(False) @@ -420,6 +422,182 @@ cdef bint solve_quadratic(double a, double b, double c, double *t0, double *t1) t1[0] = c / q return True +cdef inline bint is_zero(double v) nogil: + return v < EQN_EPS and v > -EQN_EPS + +@cython.cdivision(True) +cdef int solve_cubic(double a, double b, double c, double d, double *t0, double *t1, double *t2) nogil: + """ + Calculates the real roots of a cubic equation. + + The a, b, c and d arguments are the four constants of the cubic equation: + + f = a.x^3 + b.x^2 + c.x + d + + The cubic equation has 1, 2 or 3 real roots, and this function will return the number of real roots. + + The values of the real roots, are returned by setting the values of the + memory locations pointed to by t0, t1, and t2. In the case of two real roots, + both t0/t1 or t1/t2 or t2/t0 will have the same value. If there is only one real root, the values + of t1 and t2 will be undefined. + + :param double a: Cubic constant. + :param double b: Cubic constant. + :param double c: Cubic constant. + :param double d: Cubic constant. + :param double t0: 1st root of the cubic. + :param double t1: 2nd root of the cubic. + :param double t2: 3rd root of the cubic. + :return: Number of real roots. + :rtype: int + """ + cdef: + int num + double p, q, sq_b, cb_p, D, cbrt_q, phi, u, sqrt_D + + # normal form: x^3 + bx^2 + cx + d = 0 + b /= a + c /= a + d /= a + + # substitute x = y - b/3 to eliminate quadric term: y^3 + 3py + 2q = 0 + sq_b = b * b + p = 1.0 / 3.0 * (c - sq_b / 3.0) + q = 0.5 * (2.0 * b * sq_b / 27.0 - b * c / 3.0 + d) + + # calculate discriminant + cb_p = p * p * p + D = cb_p + q * q + + if is_zero(D): + + # one triple solution + if is_zero(q): + t0[0] = 0 + t1[0] = 0 + t2[0] = 0 + num = 1 + + # one single and one double solution + else: + cbrt_q = cbrt(q) + t0[0] = cbrt_q + t1[0] = cbrt_q + t2[0] = -2 * cbrt_q + num = 2 + + # Trigonometric solution for three real roots + elif D < 0: + phi = 1.0 / 3.0 * acos(-q / sqrt(-cb_p)) + u = 2.0 * sqrt(-p) + + t0[0] = u * cos(phi) + t1[0] = -u * cos(phi + M_PI / 3.0) + t2[0] = -u * cos(phi - M_PI / 3.0) + + num = 3 + + # one real solution + else: + sqrt_D = sqrt(D) + t0[0] = cbrt(sqrt_D - q) - cbrt(sqrt_D + q) + num = 1 + + # resubstitute + t0[0] -= b / 3.0 + t1[0] -= b / 3.0 + t2[0] -= b / 3.0 + + return num + + +@cython.cdivision(True) +cdef int solve_quartic(double a, double b, double c, double d, double e, + double *t0, double *t1, double *t2, double *t3) nogil: + """ + Calculates the real roots of a quartic equation. + + The a, b, c, d and e arguments are the five constants of the quartic equation: + + f = a.x^4 + b.x^3 + c.x^2 + d.x + e + + The quartic equation has 0, 1, 2, 3 or 4 real roots, and this function will return the number of real roots. + + The values of the real roots, are returned by setting the values of the + memory locations pointed to by t0, t1, t2, and t3. If there is one or two real root, + the values of t2 and t3 will be undefined. If there is no real root, + all values will be undefined. + + :param double a: Qurtic constant. + :param double b: Qurtic constant. + :param double c: Qurtic constant. + :param double d: Qurtic constant. + :param double e: Qurtic constant. + :param double t0: 1st root of the quartic. + :param double t1: 2nd root of the quartic. + :param double t2: 3rd root of the quartic. + :param double t3: 4th root of the quartic. + :return: Number of real roots. + :rtype: int + """ + cdef: + double p, q, r, sq_b, v, z + int cubic_num, num = 0 + double s0, s1, s2 + + # normal form: x^4 + bx^3 + cx^2 + dx + e = 0 + b /= a + c /= a + d /= a + e /= a + + # substitute x = y - b / 4 to eliminate quadric term: y^4 + py^2 + qy + r = 0 + sq_b = b * b + p = c - 3 * sq_b / 8.0 + q = sq_b * b / 8.0 - 0.5 * b * c + d + r = -3.0 * sq_b * sq_b / 256.0 + sq_b * c / 16.0 - b * d / 4.0 + e + + if is_zero(r): + # no absolute term: y(y^3 + py + q) = 0 + t0[0] = 0 + cubic_num = solve_cubic(1, 0, p, q, t1, t2, t3) + num = 1 + cubic_num + + else: + # solve resolvent cubic + cubic_num = solve_cubic(8.0, -4.0 * p, -8.0 * r, 4.0 * p * r - q * q, &s0, &s1, &s2) + + # take the minimum one real solution + if cubic_num == 1: + z = s0 + else: + z = max(s0, s1, s2) + + # build two quadratic equation + if 2.0 * z < p: + return 0 + else: + v = sqrt(2.0 * z - p) + + # solve two quadratic equation + if solve_quadratic(1.0, v, z - 0.5 * q / v, t0, t1): + num += 2 + if solve_quadratic(1.0, -v, z + 0.5 * q / v, t2, t3): + num += 2 + else: + if solve_quadratic(1.0, -v, z + 0.5 * q / v, t0, t1): + num += 2 + else: + return 0 + + # resubstitute + t0[0] -= b / 4.0 + t1[0] -= b / 4.0 + t2[0] -= b / 4.0 + t3[0] -= b / 4.0 + + return num + @cython.boundscheck(False) @cython.wraparound(False) @@ -540,3 +718,24 @@ def _test_winding2d(p): def _point_inside_polygon(vertices, ptx, pty): """Expose cython function for testing.""" return point_inside_polygon(vertices, ptx, pty) + +def _solve_cubic(a, b, c, d): + """Expose cython function for testing.""" + t0 = 0.0 + t1 = 0.0 + t2 = 0.0 + num = 0.0 + num = solve_cubic(a, b, c, d, &t0, &t1, &t2) + + return (t0, t1, t2, num) + +def _solve_quartic(a, b, c, d, e): + """Expose cython function for testing.""" + t0 = 0.0 + t1 = 0.0 + t2 = 0.0 + t3 = 0.0 + num = 0.0 + num = solve_quartic(a, b, c, d, e, &t0, &t1, &t2, &t3) + + return (t0, t1, t2, t3, num) From e0da6c2e351e384351205c6f9a0d060d0c513442 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Mon, 6 Feb 2023 20:22:22 +0900 Subject: [PATCH 02/77] =?UTF-8?q?=E2=9C=A8=20Add=20torus=20primitive=20fir?= =?UTF-8?q?stly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- raysect/primitive/torus.pxd | 43 +++++ raysect/primitive/torus.pyx | 306 ++++++++++++++++++++++++++++++++++++ 2 files changed, 349 insertions(+) create mode 100644 raysect/primitive/torus.pxd create mode 100644 raysect/primitive/torus.pyx diff --git a/raysect/primitive/torus.pxd b/raysect/primitive/torus.pxd new file mode 100644 index 00000000..d3296328 --- /dev/null +++ b/raysect/primitive/torus.pxd @@ -0,0 +1,43 @@ +# cython: language_level=3 + +# Copyright (c) 2014-2021, Dr Alex Meakins, Raysect Project +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the Raysect Project nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from raysect.core cimport Primitive, Point3D, Vector3D, Ray, Intersection + +cdef class Torus(Primitive): + + cdef double _mjor_radius, _minor_radius + cdef bint _further_intersection + cdef double _next_t + cdef Point3D _cached_origin + cdef Vector3D _cached_direction + cdef Ray _cached_ray + + cdef Intersection _generate_intersection(self, Ray ray, Point3D origin, Vector3D direction, double ray_distance) diff --git a/raysect/primitive/torus.pyx b/raysect/primitive/torus.pyx new file mode 100644 index 00000000..420da2f3 --- /dev/null +++ b/raysect/primitive/torus.pyx @@ -0,0 +1,306 @@ +# cython: language_level=3 + +# Copyright (c) 2014-2021, Dr Alex Meakins, Raysect Project +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the Raysect Project nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + + +from raysect.core cimport Material, new_intersection, BoundingBox3D, BoundingSphere3D, new_point3d, new_normal3d, Normal3D, AffineMatrix3D +from raysect.core.math.cython cimport solve_quartic, swap_double +from libc.math import atan2, cos, sin + + +# bounding box and sphere are padded by small amounts to avoid numerical accuracy issues +DEF BOX_PADDING = 1e-9 +DEF SPHERE_PADDING = 1.000000001 + +# additional ray distance to avoid re-hitting the same surface point +DEF EPSILON = 1e-9 + + +cdef class Torus(Primitive): + """ + A torus primitive. + + The torus is defined by major and minor radius. + The major radius is the distance from the center of the tube to the center of the torus. + The minor radius is the radius of the tube. + The center of the torus corresponds to the origin of the local co-ordinate system. + The axis of revolution coincides with the z-axis + + :param float major_radius: Major radius of the torus in meters (default = 1.0). + :param float minor_radius: Minor radius of the torus in meters (default = 0.5). + :param Node parent: Scene-graph parent node or None (default = None). + :param AffineMatrix3D transform: An AffineMatrix3D defining the local co-ordinate system relative to the scene-graph parent (default = identity matrix). + :param Material material: A Material object defining the torus's material (default = None). + :param str name: A string specifying a user-friendly name for the torus (default = ""). + + :ivar float mjor_radius: The major radius of the torus in meters. + :ivar float minor_radius: The minor radius of the torus in meters. + + .. code-block:: pycon + + >>> from raysect.core import translate + >>> from raysect.primitive import Torus + >>> from raysect.optical import World + >>> from raysect.optical.material import UniformSurfaceEmitter + >>> from raysect.optical.library.spectra.colours import orange + >>> + >>> world = World() + >>> + >>> torus = Torus(1.0, 0.5, parent=world, transform=translate(3, 0, 0), + material=UniformSurfaceEmitter(orange), name="orange torus") + """ + + def __init__(self, double major_radius=1.0, double minor_radius=0.5, object parent=None, + AffineMatrix3D transform=None, Material material=None, str name=None): + + super().__init__(parent, transform, material, name) + + if major_radius < minor_radius or minor_radius < 0.0: + raise ValueError("Torus minor radius cannot be less than zero and greater than major radius.") + + self._major_radius = major_radius + self._minor_radius = minor_radius + + # initialise next intersection caching and control attributes + self._further_intersection = False + self._next_t = 0.0 + self._cached_origin = None + self._cached_direction = None + self._cached_ray = None + + @property + def major_radius(self): + """ + The major radius of this torus. + + :rtype: float + """ + return self._major_radius + + @major_radius.setter + def major_radius(self, double major_radius): + + # don't do anything if the value is unchanged + if major_radius == self._major_radius: + return + + if major_radius < 0.0: + raise ValueError("Torus major radius cannot be less than zero.") + if major_radius < self._minor_radius: + raise ValueError("Torus major radius cannot be less than minor radius.") + self._major_radius = major_radius + + # the next intersection cache has been invalidated by the major radius change + self._further_intersection = False + + # any geometry caching in the root node is now invalid, inform root + self.notify_geometry_change() + + @property + def minor_radius(self): + """ + The minor radius of this torus. + + :rtype: float + """ + return self._minor_radius + + @minor_radius.setter + def minor_radius(self, double minor_radius): + + # don't do anything if the value is unchanged + if minor_radius == self._minor_radius: + return + + if minor_radius < 0.0: + raise ValueError("Torus minor radius cannot be less than zero.") + if minor_radius > self._major_radius: + raise ValueError("Torus minor radius cannot be greater than major radius.") + self._minor_radius = minor_radius + + # the next intersection cache has been invalidated by the minor radius change + self._further_intersection = False + + # any geometry caching in the root node is now invalid, inform root + self.notify_geometry_change() + + cpdef Intersection hit(self, Ray ray): + + cdef Point3D origin + cdef Vector3D direction + cdef double sq_origin, sq_r, sq_R, dot_origin_direction, f, b, c, d, e, t0, t1, t2, t3, t_closest + + # reset further intersection state + self._further_intersection = False + + # convert ray parameters to local space + origin = ray.origin.transform(self.to_local()) + direction = ray.direction.transform(self.to_local()).normalise() + + # coefficients of quartic equation + sq_origin = origin.x * origin.x + origin.y * origin.y + origin.z * origin.z + dot_origin_direction = direction.x * origin.x + direction.y * origin.y + direction.z * origin.z + sq_r = self._minor_radius * self._minor_radius + sq_R = self._major_radius * self._major_radius + f = sq_origin - (sq_r + sq_R) + + b = 4.0 * dot_origin_direction + c = 2.0 * f + 4.0 * dot_origin_direction * dot_origin_direction + 4.0 * sq_R * direction.y * direction.y + d = 4.0 * f * dot_origin_direction + 8.0 * sq_R * origin.y * direction.y + e = f * f - 4.0 * sq_R * (sq_r - origin.y * origin.y) + + # calculate intersection distances by solving the quartic equation + # ray misses if there are no real roots of the quartic + num = solve_quartic(1.0, b, c, d, e, &t0, &t1, &t2, &t3) + + if num == 0: + return None + + elif num == 1: + # test the intersection points inside the ray search range [0, max_distance] + if t0 > ray.max_distance or t0 < 0.0: + return None + else: + t_closest = t0 + return self._generate_intersection(ray, origin, direction, t_closest) + + elif num == 2: + # ensure t0 is always smaller than t1 + if t0 > t1: + swap_double(&t0, &t1) + + + + # ensure t0 is always smaller than t1 + if t0 > t1: + swap_double(&t0, &t1) + + # test the intersection points inside the ray search range [0, max_distance] + if t0 > ray.max_distance or t1 < 0.0: + return None + + if t0 >= 0.0: + t_closest = t0 + if t1 <= ray.max_distance: + self._further_intersection = True + self._cached_ray = ray + self._cached_origin = origin + self._cached_direction = direction + self._next_t = t1 + elif t1 <= ray.max_distance: + t_closest = t1 + else: + return None + + return self._generate_intersection(ray, origin, direction, t_closest) + + cpdef Intersection next_intersection(self): + + if not self._further_intersection: + return None + + # this is the 2nd and therefore last intersection + self._further_intersection = False + return self._generate_intersection(self._cached_ray, self._cached_origin, self._cached_direction, self._next_t) + + cdef Intersection _generate_intersection(self, Ray ray, Point3D origin, Vector3D direction, double ray_distance): + + cdef Point3D hit_point, tube_centre, inside_point, outside_point + cdef Normal3D normal + cdef double phi, delta_x, delta_y, delta_z + cdef bint exiting + + # point of surface intersection in local space + hit_point = new_point3d(origin.x + ray_distance * direction.x, + origin.y + ray_distance * direction.y, + origin.z + ray_distance * direction.z) + + # normal is normalised vector from torus tube centre to hit_point + phi = atan2(hit_point.y, hit_point.x) + tube_centre = new_point3d(self._major_radius * cos(phi), self._major_radius * sin(phi), 0.0) + tube_to_hit = tube_centre.vector_to(hit_point) + normal = new_normal3d(tube_to_hit.x, tube_to_hit.y, tube_to_hit.z) + normal = normal.normalise() + + # calculate points inside and outside of surface for daughter rays to + # spawn from - these points are displaced from the surface to avoid + # re-hitting the same surface + delta_x = EPSILON * normal.x + delta_y = EPSILON * normal.y + delta_z = EPSILON * normal.z + + inside_point = new_point3d(hit_point.x - delta_x, hit_point.y - delta_y, hit_point.z - delta_z) + outside_point = new_point3d(hit_point.x + delta_x, hit_point.y + delta_y, hit_point.z + delta_z) + + # is ray exiting surface + exiting = direction.dot(normal) >= 0.0 + + return new_intersection(ray, ray_distance, self, hit_point, inside_point, outside_point, + normal, exiting, self.to_local(), self.to_root()) + + cpdef bint contains(self, Point3D point) except -1: + + cdef Point3D local_point + cdef double discriminant, distance_xy, distance_sqr, sq_R, R2_r2 + + # convert world space point to local space + local_point = point.transform(self.to_local()) + + # calculate the interior discriminant + distance_xy = local_point.x * local_point.x + local_point.y * local_point.y + distance_sqr = distance_xy + local_point.z * local_point.z + sq_R = self._major_radius * self._major_radius + R2_r2 = sq_R - self._minor_radius * self._minor_radius + discriminant = distance_sqr * distance_sqr + 2.0 * distance_sqr * R2_r2 + R2_r2 * R2_r2 - 4.0 * sq_R * distance_xy + + # point is outside torus if discriminant is greater than 0 + return discriminant <= 0.0 + + cpdef BoundingBox3D bounding_box(self): + + cdef double extent + cdef Point3D origin, lower, upper + + # obtain torus origin in world space + origin = new_point3d(0, 0, 0).transform(self.to_root()) + + # calculate upper and lower corners of box + extent = self._major_radius + self._minor_radius + BOX_PADDING + lower = new_point3d(origin.x - extent, origin.y - extent, origin.z - extent) + upper = new_point3d(origin.x + extent, origin.y + extent, origin.z + extent) + + return BoundingBox3D(lower, upper) + + cpdef BoundingSphere3D bounding_sphere(self): + cdef Point3D centre = new_point3d(0, 0, 0).transform(self.to_root()) + return BoundingSphere3D(centre, (self._major_radius + self._minor_radius) * SPHERE_PADDING) + + cpdef object instance(self, object parent=None, AffineMatrix3D transform=None, Material material=None, str name=None): + return Torus(self._major_radius, self._minor_radius, parent, transform, material, name) \ No newline at end of file From ff9efcce98d6926863064d5f079d9514269e41f6 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Tue, 7 Feb 2023 11:51:18 +0900 Subject: [PATCH 03/77] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20fix=20typo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- raysect/primitive/torus.pxd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raysect/primitive/torus.pxd b/raysect/primitive/torus.pxd index d3296328..5b2c7d48 100644 --- a/raysect/primitive/torus.pxd +++ b/raysect/primitive/torus.pxd @@ -33,7 +33,7 @@ from raysect.core cimport Primitive, Point3D, Vector3D, Ray, Intersection cdef class Torus(Primitive): - cdef double _mjor_radius, _minor_radius + cdef double _major_radius, _minor_radius cdef bint _further_intersection cdef double _next_t cdef Point3D _cached_origin From 3f4eff8f1f0b3071daaa3d1431dc8956320cfd62 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Wed, 8 Feb 2023 00:01:33 +0900 Subject: [PATCH 04/77] =?UTF-8?q?=F0=9F=8E=A8=20change=20to=20use=20descar?= =?UTF-8?q?tes-euler=20methd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- raysect/core/math/cython/utility.pxd | 18 ++- raysect/core/math/cython/utility.pyx | 170 +++++++++++++++++++++------ 2 files changed, 153 insertions(+), 35 deletions(-) diff --git a/raysect/core/math/cython/utility.pxd b/raysect/core/math/cython/utility.pxd index e5eb364c..9c7e8c52 100644 --- a/raysect/core/math/cython/utility.pxd +++ b/raysect/core/math/cython/utility.pxd @@ -31,6 +31,8 @@ cimport cython +DEF EQN_EPS = 1.0e-9 + cdef int find_index(double[::1] x, double v) nogil cdef double interpolate(double[::1] x, double[::1] y, double p) nogil @@ -58,20 +60,32 @@ cdef inline void swap_double(double *a, double *b) nogil: a[0] = b[0] b[0] = temp +cdef inline void sort_3double(double *a, double *b, double *c) nogil: + if a[0] > b[0]: + swap_double(a, b) + if b[0] > c[0]: + swap_double(b, c) + if a[0] > c[0]: + swap_double(a, c) + cdef inline void swap_int(int *a, int *b) nogil: cdef int temp temp = a[0] a[0] = b[0] b[0] = temp +cdef inline bint is_zero(double v) nogil: + return v < EQN_EPS and v > -EQN_EPS + +cdef inline void csqrt(double x, double y, double *a, double *b) nogil: + raise NotImplementedError("Not impremented yet") + @cython.cdivision(True) cdef inline double lerp(double x0, double x1, double y0, double y1, double x) nogil: return ((y1 - y0) / (x1 - x0)) * (x - x0) + y0 cdef bint solve_quadratic(double a, double b, double c, double *t0, double *t1) nogil -cdef bint is_zero(double v) nogil - cdef int solve_cubic(double a, double b, double c, double d, double *t0, double *t1, double *t2) nogil cdef int solve_quartic(double a, double b, double c, double d, double e, diff --git a/raysect/core/math/cython/utility.pyx b/raysect/core/math/cython/utility.pyx index b1b21319..c834edd1 100644 --- a/raysect/core/math/cython/utility.pyx +++ b/raysect/core/math/cython/utility.pyx @@ -34,8 +34,6 @@ cimport cython #TODO: Write unit tests! -DEF EQN_EPS = 1.0e-9 - @cython.cdivision(True) @cython.boundscheck(False) @cython.wraparound(False) @@ -422,8 +420,6 @@ cdef bint solve_quadratic(double a, double b, double c, double *t0, double *t1) t1[0] = c / q return True -cdef inline bint is_zero(double v) nogil: - return v < EQN_EPS and v > -EQN_EPS @cython.cdivision(True) cdef int solve_cubic(double a, double b, double c, double d, double *t0, double *t1, double *t2) nogil: @@ -511,11 +507,134 @@ cdef int solve_cubic(double a, double b, double c, double d, double *t0, double return num +cdef int solve_biquadratic(double a, double c, double e, double *t0, double *t1, double *t2, double *t3) nogil: + """ + Calculate the real roots of a bi quadratic equation. + + The a, c, and e arguments are the 3 constants of the biquadratic equation: + + f = a.x^4 + c.x^2 + e + + The biquadratic equation has 0, 2, or 4 real roots, and this function will return the number of real roots. + + The values of the real roots, are returned by setting the values of the + memory locations pointed to by t0, t1, t2, and t3. If there are two or four real roots, + the values of t2 and t3 will be undefined. If there is no real root, + all values will be undefined. + + :param double a: Biquadratic constant. + :param double c: Biquadratic constant. + :param double e: Biquadratic constant. + :param double t0: 1st root of the biquadratic. + :param double t1: 2nd root of the biquadratic. + :param double t2: 3rd root of the biquadratic. + :param double t3: 4th root of the biquadratic. + :return: Number of real roots. + :rtype: int + """ + cdef double s0, s1, sx0, sx1 + + # solve quadratic for x^2 + if not solve_quadratic(a, c, e, &s0, &s1): + return 0 + + # ensure s0 < s1 + if s0 > s1: + swap_double(&s0, &s1) + + # 0 <= s0 <= s1, 4 real roots + if s0 >= 0: + sx0 = sqrt(s0) + sx1 = sqrt(s1) + t0[0] = -sx1 + t1[0] = -sx0 + t2[0] = sx0 + t3[0] = sx1 + return 4 + + # s0 < 0 <= s1, 2 real roots + elif s1 >= 0: + sx1 = sqrt(s1) + t0[0] = -sx1 + t1[0] = sx1 + return 2 + + # s0 < s1 <= 0, no real root + else: + return 0 + + +cdef int _solve_depressed_quartic(double p, double q, double r, double *t0, double *t1, double *t2, double *t3) nogil: + """ + Solve depressed quartic: x^4 + p.x^2 + q.x + r + """ + cdef: + int num + double s0, s1, s2, sr, si + + if is_zero(q): + return solve_biquadratic(1.0, p, r, t0, t1, t2, t3) + + # solve resolvent cubic: t^3 - 2pt^2 + (p^2-4r)t - q^2 = 0 + num = solve_cubic(1.0, 2.0 * p, p * p - 4.0 * r, -q * q, t0, t1, t2) + + if num > 1: + # sort roots to t0 <= t1 <= t2 + sort_3double(t0, t1, t2) + + # t0 > 0 => all roots are positive because vieta's therem: t0*t1*t2 = q^2 != 0 + if t0[0] > 0: + s0 = sqrt(t0[0]) + s1 = sqrt(t1[0]) + s2 = sqrt(t2[0]) + + if q > 0: + t0[0] = 0.5 * (-s0 -s1 -s2) + t1[0] = 0.5 * (-s0 +s1 +s2) + t2[0] = 0.5 * (+s0 -s1 +s2) + t3[0] = 0.5 * (+s0 +s1 -s2) + return 4 + else: + t0[0] = 0.5 * (-s0 -s1 +s2) + t1[0] = 0.5 * (-s0 +s1 -s2) + t2[0] = 0.5 * (+s0 -s1 -s2) + t3[0] = 0.5 * (+s0 +s1 +s2) + return 4 + + # resolvent cubic solution have 1 real root + if t0[0] < 0: + t0[0] = 0.0 + + s0 = sqrt(t0[0]) + + # calculate (sr + i*si)^2 = t1[0] + i*t2[0] + # TODO + csqrt(t0[0], t1[0], &sr, &si) + + if q > 0: + t0[0] = -0.5 * s0 - sr + t0[0] = -0.5 * s0 + sr + return 2 + else: + t0[0] = 0.5 * s0 - sr + t0[0] = 0.5 * s0 + sr + return 2 + + +@cython.cdivision(True) +cdef void one_newton_step(double b, double c, double d, double e, double *x) nogil: + cdef double fx, dfx + dfx = ((4.0 * x[0] + 3 * b) * x[0] + 2.0 * d) * x[0] + e + if not is_zero(dfx): + fx = (((x[0] + b) * x[0] + c) * x[0] + d) * x[0] + e + x[0] = x[0] - fx / dfx + + @cython.cdivision(True) cdef int solve_quartic(double a, double b, double c, double d, double e, double *t0, double *t1, double *t2, double *t3) nogil: """ - Calculates the real roots of a quartic equation. + Calculates the real roots of a quartic equation with Descartes-Euler method. The a, b, c, d and e arguments are the five constants of the quartic equation: @@ -524,7 +643,7 @@ cdef int solve_quartic(double a, double b, double c, double d, double e, The quartic equation has 0, 1, 2, 3 or 4 real roots, and this function will return the number of real roots. The values of the real roots, are returned by setting the values of the - memory locations pointed to by t0, t1, t2, and t3. If there is one or two real root, + memory locations pointed to by t0, t1, t2, and t3. If there are one or two real roots, the values of t2 and t3 will be undefined. If there is no real root, all values will be undefined. @@ -541,9 +660,8 @@ cdef int solve_quartic(double a, double b, double c, double d, double e, :rtype: int """ cdef: - double p, q, r, sq_b, v, z + double p, q, r, sq_b int cubic_num, num = 0 - double s0, s1, s2 # normal form: x^4 + bx^3 + cx^2 + dx + e = 0 b /= a @@ -564,31 +682,8 @@ cdef int solve_quartic(double a, double b, double c, double d, double e, num = 1 + cubic_num else: - # solve resolvent cubic - cubic_num = solve_cubic(8.0, -4.0 * p, -8.0 * r, 4.0 * p * r - q * q, &s0, &s1, &s2) - - # take the minimum one real solution - if cubic_num == 1: - z = s0 - else: - z = max(s0, s1, s2) - - # build two quadratic equation - if 2.0 * z < p: - return 0 - else: - v = sqrt(2.0 * z - p) - - # solve two quadratic equation - if solve_quadratic(1.0, v, z - 0.5 * q / v, t0, t1): - num += 2 - if solve_quadratic(1.0, -v, z + 0.5 * q / v, t2, t3): - num += 2 - else: - if solve_quadratic(1.0, -v, z + 0.5 * q / v, t0, t1): - num += 2 - else: - return 0 + # solve depressed quartic + num = _solve_depressed_quartic(p, q, r, t0, t1, t2, t3) # resubstitute t0[0] -= b / 4.0 @@ -596,6 +691,15 @@ cdef int solve_quartic(double a, double b, double c, double d, double e, t2[0] -= b / 4.0 t3[0] -= b / 4.0 + # One newton step for each real root + if num > 0: + one_newton_step(b, c, d, e, t0) + one_newton_step(b, c, d, e, t1) + if num > 2: + one_newton_step(b, c, d, e, t2) + if num > 3: + one_newton_step(b, c, d, e, t3) + return num From b2666894bce38a8dfac0361fc5c33e24353fc4ab Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Wed, 8 Feb 2023 23:23:41 +0900 Subject: [PATCH 05/77] =?UTF-8?q?=F0=9F=8E=A8=20alter=20solvers=20for=20cu?= =?UTF-8?q?bic=20and=20quartic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- raysect/core/math/cython/utility.pxd | 3 - raysect/core/math/cython/utility.pyx | 218 ++++++++++++++++----------- 2 files changed, 129 insertions(+), 92 deletions(-) diff --git a/raysect/core/math/cython/utility.pxd b/raysect/core/math/cython/utility.pxd index 9c7e8c52..bd5e8890 100644 --- a/raysect/core/math/cython/utility.pxd +++ b/raysect/core/math/cython/utility.pxd @@ -77,9 +77,6 @@ cdef inline void swap_int(int *a, int *b) nogil: cdef inline bint is_zero(double v) nogil: return v < EQN_EPS and v > -EQN_EPS -cdef inline void csqrt(double x, double y, double *a, double *b) nogil: - raise NotImplementedError("Not impremented yet") - @cython.cdivision(True) cdef inline double lerp(double x0, double x1, double y0, double y1, double x) nogil: return ((y1 - y0) / (x1 - x0)) * (x - x0) + y0 diff --git a/raysect/core/math/cython/utility.pyx b/raysect/core/math/cython/utility.pyx index c834edd1..90621142 100644 --- a/raysect/core/math/cython/utility.pyx +++ b/raysect/core/math/cython/utility.pyx @@ -29,7 +29,7 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from libc.math cimport sqrt, cbrt, acos, cos, M_PI +from libc.math cimport sqrt, fabs, cbrt, acos, cos, M_PI cimport cython #TODO: Write unit tests! @@ -424,87 +424,75 @@ cdef bint solve_quadratic(double a, double b, double c, double *t0, double *t1) @cython.cdivision(True) cdef int solve_cubic(double a, double b, double c, double d, double *t0, double *t1, double *t2) nogil: """ - Calculates the real roots of a cubic equation. + Calculates the roots of a cubic equation. The a, b, c and d arguments are the four constants of the cubic equation: f = a.x^3 + b.x^2 + c.x + d The cubic equation has 1, 2 or 3 real roots, and this function will return the number of real roots. + The values of the roots, are returned by setting the values of the memory locations pointed to by t0, t1, and t2. + + In the case of three real roots, the roots themselves back in t0, t1 and t2. + In the case of two real roots, a pair in t0, t1, and t2 will have the same value, and the other is a different one. + In the case of one real root, t0 will be the real root, and t1 + i * t2 will a pair of coplex-conjugated roots. - The values of the real roots, are returned by setting the values of the - memory locations pointed to by t0, t1, and t2. In the case of two real roots, - both t0/t1 or t1/t2 or t2/t0 will have the same value. If there is only one real root, the values - of t1 and t2 will be undefined. + The practical algorithm is followed by https://quarticequations.com :param double a: Cubic constant. :param double b: Cubic constant. :param double c: Cubic constant. :param double d: Cubic constant. - :param double t0: 1st root of the cubic. - :param double t1: 2nd root of the cubic. - :param double t2: 3rd root of the cubic. + :param double t0: 1st real root. + :param double t1: either 2nd real root or real part of coplex-conjugated roots. + :param double t2: either 3rd real root or imaginary part of coplex-conjugated roots. :return: Number of real roots. :rtype: int """ cdef: - int num - double p, q, sq_b, cb_p, D, cbrt_q, phi, u, sqrt_D + double q, r, sq_b, cb_q, D, A, phi, u # normal form: x^3 + bx^2 + cx + d = 0 b /= a c /= a d /= a - # substitute x = y - b/3 to eliminate quadric term: y^3 + 3py + 2q = 0 + # convert depressed cubic: y^3 + 3qy - 2r = 0 sq_b = b * b - p = 1.0 / 3.0 * (c - sq_b / 3.0) - q = 0.5 * (2.0 * b * sq_b / 27.0 - b * c / 3.0 + d) + q = (3.0 * c - sq_b) / 9.0 + r = (c * b - 3.0 * d) / 6.0 - b * sq_b / 27.0 # calculate discriminant - cb_p = p * p * p - D = cb_p + q * q - - if is_zero(D): - - # one triple solution - if is_zero(q): - t0[0] = 0 - t1[0] = 0 - t2[0] = 0 - num = 1 - - # one single and one double solution + cb_q = q * q * q + D = cb_q + r * r + + # one real root and a pair of complex-conjugate roots + if D > 0: + A = cbrt(fabs(r) + sqrt(D)) + if r < 0: + t0[0] = q / A - A - b / 3.0 else: - cbrt_q = cbrt(q) - t0[0] = cbrt_q - t1[0] = cbrt_q - t2[0] = -2 * cbrt_q - num = 2 - - # Trigonometric solution for three real roots - elif D < 0: - phi = 1.0 / 3.0 * acos(-q / sqrt(-cb_p)) - u = 2.0 * sqrt(-p) - - t0[0] = u * cos(phi) - t1[0] = -u * cos(phi + M_PI / 3.0) - t2[0] = -u * cos(phi - M_PI / 3.0) + t0[0] = A - q / A - b / 3.0 + + t1[0] = -0.5 * t0[0] - b / 3.0 + t2[0] = 0.5 * sqrt(3.0) * (A + q / A) - num = 3 + return 1 - # one real solution + # Trigonometric solution for three real roots else: - sqrt_D = sqrt(D) - t0[0] = cbrt(sqrt_D - q) - cbrt(sqrt_D + q) - num = 1 + if is_zero(q): + phi = 0.0 + elif q < 0: + phi = acos(r / sqrt(-cb_q)) / 3.0 + + u = 2.0 * sqrt(-q) - # resubstitute - t0[0] -= b / 3.0 - t1[0] -= b / 3.0 - t2[0] -= b / 3.0 + t0[0] = u * cos(phi) - b / 3.0 + t1[0] = -u * cos(phi + M_PI / 3.0) - b / 3.0 + t2[0] = -u * cos(phi - M_PI / 3.0) - b / 3.0 - return num + return 3 cdef int solve_biquadratic(double a, double c, double e, double *t0, double *t1, double *t2, double *t3) nogil: @@ -570,59 +558,109 @@ cdef int _solve_depressed_quartic(double p, double q, double r, double *t0, doub """ cdef: int num - double s0, s1, s2, sr, si + double s0, sigma, A, B, sq_A, sq_B + + if q > 0: + sigma = 1.0 + else: + sigma = -1.0 + # q = 0 => x^4 + p.x^2 + r = 0 if is_zero(q): return solve_biquadratic(1.0, p, r, t0, t1, t2, t3) - # solve resolvent cubic: t^3 - 2pt^2 + (p^2-4r)t - q^2 = 0 - num = solve_cubic(1.0, 2.0 * p, p * p - 4.0 * r, -q * q, t0, t1, t2) + # solve resolvent cubic: t^3 - 2pt^2 + (p^2-4r)t + q^2 = 0 + # using Van der Waerden's method + num = solve_cubic(1.0, -2.0 * p, p * p - 4.0 * r, q * q, t0, t1, t2) if num > 1: - # sort roots to t0 <= t1 <= t2 + # sort roots to t0 < t1 < t2 sort_3double(t0, t1, t2) - # t0 > 0 => all roots are positive because vieta's therem: t0*t1*t2 = q^2 != 0 - if t0[0] > 0: - s0 = sqrt(t0[0]) - s1 = sqrt(t1[0]) - s2 = sqrt(t2[0]) - - if q > 0: - t0[0] = 0.5 * (-s0 -s1 -s2) - t1[0] = 0.5 * (-s0 +s1 +s2) - t2[0] = 0.5 * (+s0 -s1 +s2) - t3[0] = 0.5 * (+s0 +s1 -s2) + # t0 <= 0 => t1*t2 >= 0 because vieta's therem: -t0*t1*t2 = q^2 + if t0[0] <= 0: + s0 = sqrt(-t0[0]) + A = -t1[0] - t2[0] - 2.0 * sigma * sqrt(t1[0] * t2[0]) + B = -t1[0] - t2[0] + 2.0 * sigma * sqrt(t1[0] * t2[0]) + + # four real roots + if A >= 0 and B >= 0: + sq_A = sqrt(A) + sq_B = sqrt(B) + t0[0] = 0.5 * (s0 + sq_A) + t1[0] = 0.5 * (s0 - sq_A) + t2[0] = 0.5 * (-s0 + sq_B) + t3[0] = 0.5 * (-s0 - sq_B) return 4 + + # two real roots + elif A < 0 and B >= 0: + sq_B = sqrt(B) + t0[0] = 0.5 * (-s0 + sq_B) + t1[0] = 0.5 * (-s0 - sq_B) + return 2 + + # two real roots + elif A >= 0 and B < 0: + sq_A = sqrt(A) + t0[0] = 0.5 * (s0 + sq_A) + t1[0] = 0.5 * (s0 - sq_A) + return 2 + + # no real root else: - t0[0] = 0.5 * (-s0 -s1 +s2) - t1[0] = 0.5 * (-s0 +s1 -s2) - t2[0] = 0.5 * (+s0 -s1 -s2) - t3[0] = 0.5 * (+s0 +s1 +s2) + return 0 + + # if resolvent cubic solutions have only one real root t0 + else: + if t0[0] <= 0: + s0 = sqrt(-t0[0]) + A = -2.0 * t1[0] - 2.0 * sigma * sqrt(t1[0] * t1[0] + t2[0] * t2[0]) + A = -2.0 * t1[0] + 2.0 * sigma * sqrt(t1[0] * t1[0] + t2[0] * t2[0]) + + # four real roots + if A >= 0 and B >= 0: + sq_A = sqrt(A) + sq_B = sqrt(B) + t0[0] = 0.5 * (s0 + sq_A) + t1[0] = 0.5 * (s0 - sq_A) + t2[0] = 0.5 * (-s0 + sq_B) + t3[0] = 0.5 * (-s0 - sq_B) return 4 - - # resolvent cubic solution have 1 real root - if t0[0] < 0: - t0[0] = 0.0 - - s0 = sqrt(t0[0]) - # calculate (sr + i*si)^2 = t1[0] + i*t2[0] - # TODO - csqrt(t0[0], t1[0], &sr, &si) + # two real roots + elif A < 0 and B >= 0: + sq_B = sqrt(B) + t0[0] = 0.5 * (-s0 + sq_B) + t1[0] = 0.5 * (-s0 - sq_B) + return 2 + + # two real roots + elif A >= 0 and B < 0: + sq_A = sqrt(A) + t0[0] = 0.5 * (s0 + sq_A) + t1[0] = 0.5 * (s0 - sq_A) + return 2 + + # no real root + else: + return 0 - if q > 0: - t0[0] = -0.5 * s0 - sr - t0[0] = -0.5 * s0 + sr - return 2 - else: - t0[0] = 0.5 * s0 - sr - t0[0] = 0.5 * s0 + sr - return 2 + # no real root if -t0 < 0 + return 0 @cython.cdivision(True) cdef void one_newton_step(double b, double c, double d, double e, double *x) nogil: + """ + One step Newton's method for monic quartic polinomial: x^4 + b.x^3 + c.x^2 * d.x + e = 0 + + :param double b: Qurtic constant. + :param double c: Qurtic constant. + :param double d: Qurtic constant. + :param double e: Qurtic constant. + :param double x: one root of the quartic. + """ cdef double fx, dfx dfx = ((4.0 * x[0] + 3 * b) * x[0] + 2.0 * d) * x[0] + e if not is_zero(dfx): @@ -634,7 +672,7 @@ cdef void one_newton_step(double b, double c, double d, double e, double *x) nog cdef int solve_quartic(double a, double b, double c, double d, double e, double *t0, double *t1, double *t2, double *t3) nogil: """ - Calculates the real roots of a quartic equation with Descartes-Euler method. + Calculates the real roots of a quartic equation with Van der Waerden method. The a, b, c, d and e arguments are the five constants of the quartic equation: @@ -647,6 +685,8 @@ cdef int solve_quartic(double a, double b, double c, double d, double e, the values of t2 and t3 will be undefined. If there is no real root, all values will be undefined. + The practical algorithm of Van der Waerden method is followed by https://quarticequations.com + :param double a: Qurtic constant. :param double b: Qurtic constant. :param double c: Qurtic constant. @@ -678,7 +718,7 @@ cdef int solve_quartic(double a, double b, double c, double d, double e, if is_zero(r): # no absolute term: y(y^3 + py + q) = 0 t0[0] = 0 - cubic_num = solve_cubic(1, 0, p, q, t1, t2, t3) + cubic_num = solve_cubic(1.0, 0.0, p, q, t1, t2, t3) num = 1 + cubic_num else: From bd9e062dab1fe50402f42e4b5ffbb7171b48adf6 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Thu, 9 Feb 2023 15:00:55 +0900 Subject: [PATCH 06/77] =?UTF-8?q?=F0=9F=90=9B=20fix=20bug=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- raysect/core/math/cython/utility.pyx | 29 +++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/raysect/core/math/cython/utility.pyx b/raysect/core/math/cython/utility.pyx index 90621142..d2dadc30 100644 --- a/raysect/core/math/cython/utility.pyx +++ b/raysect/core/math/cython/utility.pyx @@ -382,10 +382,10 @@ cdef bint solve_quadratic(double a, double b, double c, double *t0, double *t1) The a, b and c arguments are the three constants of the quadratic equation: f = a.x^2 + b.x^2 + c - + If the quadratic equation has 1 or 2 real roots, this function will return True. If there are no real roots this method will return False. - + The values of the real roots, are returned by setting the values of the memory locations pointed to by t0 and t1. In the case of a single root, both t0 and t1 will have the same value. If there are not roots, the values @@ -432,7 +432,7 @@ cdef int solve_cubic(double a, double b, double c, double d, double *t0, double The cubic equation has 1, 2 or 3 real roots, and this function will return the number of real roots. The values of the roots, are returned by setting the values of the memory locations pointed to by t0, t1, and t2. - + In the case of three real roots, the roots themselves back in t0, t1 and t2. In the case of two real roots, a pair in t0, t1, and t2 will have the same value, and the other is a different one. In the case of one real root, t0 will be the real root, and t1 + i * t2 will a pair of coplex-conjugated roots. @@ -450,8 +450,8 @@ cdef int solve_cubic(double a, double b, double c, double d, double *t0, double :rtype: int """ cdef: - double q, r, sq_b, cb_q, D, A, phi, u - + double q, r, sq_b, cb_q, D, A, z0, phi, u + # normal form: x^3 + bx^2 + cx + d = 0 b /= a c /= a @@ -465,16 +465,17 @@ cdef int solve_cubic(double a, double b, double c, double d, double *t0, double # calculate discriminant cb_q = q * q * q D = cb_q + r * r - + # one real root and a pair of complex-conjugate roots if D > 0: A = cbrt(fabs(r) + sqrt(D)) if r < 0: - t0[0] = q / A - A - b / 3.0 + z0 = q / A - A else: - t0[0] = A - q / A - b / 3.0 - - t1[0] = -0.5 * t0[0] - b / 3.0 + z0 = A - q / A + + t0[0] = z0 - b / 3.0 + t1[0] = -0.5 * z0 - b / 3.0 t2[0] = 0.5 * sqrt(3.0) * (A + q / A) return 1 @@ -483,9 +484,11 @@ cdef int solve_cubic(double a, double b, double c, double d, double *t0, double else: if is_zero(q): phi = 0.0 - elif q < 0: + + # otherwise q < 0 because of D = q^3 + r^2 < 0 + else: phi = acos(r / sqrt(-cb_q)) / 3.0 - + u = 2.0 * sqrt(-q) t0[0] = u * cos(phi) - b / 3.0 @@ -616,7 +619,7 @@ cdef int _solve_depressed_quartic(double p, double q, double r, double *t0, doub if t0[0] <= 0: s0 = sqrt(-t0[0]) A = -2.0 * t1[0] - 2.0 * sigma * sqrt(t1[0] * t1[0] + t2[0] * t2[0]) - A = -2.0 * t1[0] + 2.0 * sigma * sqrt(t1[0] * t1[0] + t2[0] * t2[0]) + B = -2.0 * t1[0] + 2.0 * sigma * sqrt(t1[0] * t1[0] + t2[0] * t2[0]) # four real roots if A >= 0 and B >= 0: From b1842c6df20615f187a2260fa491f776622b2eac Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Thu, 9 Feb 2023 19:52:23 +0900 Subject: [PATCH 07/77] =?UTF-8?q?=F0=9F=8E=A8=20add=20sorting=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- raysect/core/math/cython/utility.pxd | 26 ++++++++++++++++++++------ raysect/core/math/cython/utility.pyx | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/raysect/core/math/cython/utility.pxd b/raysect/core/math/cython/utility.pxd index bd5e8890..9f244d02 100644 --- a/raysect/core/math/cython/utility.pxd +++ b/raysect/core/math/cython/utility.pxd @@ -60,7 +60,13 @@ cdef inline void swap_double(double *a, double *b) nogil: a[0] = b[0] b[0] = temp -cdef inline void sort_3double(double *a, double *b, double *c) nogil: +cdef inline void swap_int(int *a, int *b) nogil: + cdef int temp + temp = a[0] + a[0] = b[0] + b[0] = temp + +cdef inline void sort_three_doubles(double *a, double *b, double *c) nogil: if a[0] > b[0]: swap_double(a, b) if b[0] > c[0]: @@ -68,11 +74,19 @@ cdef inline void sort_3double(double *a, double *b, double *c) nogil: if a[0] > c[0]: swap_double(a, c) -cdef inline void swap_int(int *a, int *b) nogil: - cdef int temp - temp = a[0] - a[0] = b[0] - b[0] = temp +cdef inline void sort_four_doubles(double *a, double *b, double *c, double *d) nogil: + if a[0] > b[0]: + swap_double(a, b) + if b[0] > c[0]: + swap_double(b, c) + if c[0] > d[0]: + swap_double(c, d) + if a[0] > b[0]: + swap_double(a, b) + if b[0] > c[0]: + swap_double(b, c) + if a[0] > b[0]: + swap_double(a, b) cdef inline bint is_zero(double v) nogil: return v < EQN_EPS and v > -EQN_EPS diff --git a/raysect/core/math/cython/utility.pyx b/raysect/core/math/cython/utility.pyx index d2dadc30..22d84d1b 100644 --- a/raysect/core/math/cython/utility.pyx +++ b/raysect/core/math/cython/utility.pyx @@ -578,7 +578,7 @@ cdef int _solve_depressed_quartic(double p, double q, double r, double *t0, doub if num > 1: # sort roots to t0 < t1 < t2 - sort_3double(t0, t1, t2) + sort_three_doubles(t0, t1, t2) # t0 <= 0 => t1*t2 >= 0 because vieta's therem: -t0*t1*t2 = q^2 if t0[0] <= 0: From 16ab94aa65af69c1e34e2fecd0028a896a2e9d72 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Fri, 10 Feb 2023 00:08:13 +0900 Subject: [PATCH 08/77] =?UTF-8?q?=F0=9F=8E=A8=20add=20torus=20class=20to?= =?UTF-8?q?=20=5F=5Finit=5F=5F=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- raysect/primitive/__init__.pxd | 1 + raysect/primitive/__init__.py | 1 + 2 files changed, 2 insertions(+) diff --git a/raysect/primitive/__init__.pxd b/raysect/primitive/__init__.pxd index a8d91dd8..da0c9016 100644 --- a/raysect/primitive/__init__.pxd +++ b/raysect/primitive/__init__.pxd @@ -29,6 +29,7 @@ from raysect.primitive.box cimport Box from raysect.primitive.sphere cimport Sphere +from raysect.primitive.torus cimport Torus from raysect.primitive.cylinder cimport Cylinder from raysect.primitive.csg cimport Union, Intersect, Subtract from raysect.primitive.mesh cimport Mesh diff --git a/raysect/primitive/__init__.py b/raysect/primitive/__init__.py index 26e8cf29..f9e0e738 100644 --- a/raysect/primitive/__init__.py +++ b/raysect/primitive/__init__.py @@ -28,6 +28,7 @@ # POSSIBILITY OF SUCH DAMAGE. from .box import Box +from .torus import Torus from .sphere import Sphere from .cylinder import Cylinder from .csg import Union, Intersect, Subtract From 7ff6788403c6c8f3f63e05b83cc19d0675b762da Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Fri, 10 Feb 2023 00:09:41 +0900 Subject: [PATCH 09/77] =?UTF-8?q?=F0=9F=92=A9=20=F0=9F=8E=A8=20improve=20c?= =?UTF-8?q?ode=20and=20some=20bugs=20remain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- raysect/primitive/torus.pyx | 129 ++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 50 deletions(-) diff --git a/raysect/primitive/torus.pyx b/raysect/primitive/torus.pyx index 420da2f3..6d15f2c2 100644 --- a/raysect/primitive/torus.pyx +++ b/raysect/primitive/torus.pyx @@ -31,8 +31,8 @@ from raysect.core cimport Material, new_intersection, BoundingBox3D, BoundingSphere3D, new_point3d, new_normal3d, Normal3D, AffineMatrix3D -from raysect.core.math.cython cimport solve_quartic, swap_double -from libc.math import atan2, cos, sin +from raysect.core.math.cython cimport solve_quartic, swap_double, sort_three_doubles, sort_four_doubles +from libc.math cimport hypot # bounding box and sphere are padded by small amounts to avoid numerical accuracy issues @@ -51,7 +51,8 @@ cdef class Torus(Primitive): The major radius is the distance from the center of the tube to the center of the torus. The minor radius is the radius of the tube. The center of the torus corresponds to the origin of the local co-ordinate system. - The axis of revolution coincides with the z-axis + The axis of revolution coincides with the z-axis, and The center of the torus tube lies + on the x-y plane. :param float major_radius: Major radius of the torus in meters (default = 1.0). :param float minor_radius: Minor radius of the torus in meters (default = 0.5). @@ -60,7 +61,7 @@ cdef class Torus(Primitive): :param Material material: A Material object defining the torus's material (default = None). :param str name: A string specifying a user-friendly name for the torus (default = ""). - :ivar float mjor_radius: The major radius of the torus in meters. + :ivar float major_radius: The major radius of the torus in meters. :ivar float minor_radius: The minor radius of the torus in meters. .. code-block:: pycon @@ -153,9 +154,12 @@ cdef class Torus(Primitive): cpdef Intersection hit(self, Ray ray): - cdef Point3D origin - cdef Vector3D direction - cdef double sq_origin, sq_r, sq_R, dot_origin_direction, f, b, c, d, e, t0, t1, t2, t3, t_closest + cdef: + Point3D origin + Vector3D direction + double sq_origin, sq_r, sq_R, dot_origin_direction, f, b, c, d, e, t_closest + double[4] t + int i # reset further intersection state self._further_intersection = False @@ -178,46 +182,62 @@ cdef class Torus(Primitive): # calculate intersection distances by solving the quartic equation # ray misses if there are no real roots of the quartic - num = solve_quartic(1.0, b, c, d, e, &t0, &t1, &t2, &t3) + num = solve_quartic(1.0, b, c, d, e, &t[0], &t[1], &t[2], &t[3]) if num == 0: return None elif num == 1: # test the intersection points inside the ray search range [0, max_distance] - if t0 > ray.max_distance or t0 < 0.0: + if t[0] > ray.max_distance or t[0] < 0.0: return None else: - t_closest = t0 - return self._generate_intersection(ray, origin, direction, t_closest) - - elif num == 2: - # ensure t0 is always smaller than t1 - if t0 > t1: - swap_double(&t0, &t1) - + t_closest = t[0] + + else: + # sorting solutions in each number of them + if num == 2: + # ensure t0 < t1 + if t[0] > t[1]: + swap_double(&t[0], &t[1]) + + # substitute the last value into undefined variables + t[2] = t[1] + t[3] = t[1] + + elif num == 3: + # ensure t0 < t1 < t2 + sort_three_doubles(&t[0], &t[1], &t[2]) + + # substitute the last value into undefined variables + t[3] = t[2] + + elif num == 4: + # ensure t0 < t1 < t2 < t3 + sort_four_doubles(&t[0], &t[1], &t[2], &t[3]) + else: + return None + # test the intersection points inside the ray search range [0, max_distance] + if t[0] > ray.max_distance or t[3] < 0.0: + return None - # ensure t0 is always smaller than t1 - if t0 > t1: - swap_double(&t0, &t1) + for i in range(num - 1): + if t[i] >= 0.0: + t_closest = t[i] + if t[i + 1] <= ray.max_distance: + self._further_intersection = True + self._cached_ray = ray + self._cached_origin = origin + self._cached_direction = direction + self._next_t = t[i + 1] - # test the intersection points inside the ray search range [0, max_distance] - if t0 > ray.max_distance or t1 < 0.0: - return None + return self._generate_intersection(ray, origin, direction, t_closest) - if t0 >= 0.0: - t_closest = t0 - if t1 <= ray.max_distance: - self._further_intersection = True - self._cached_ray = ray - self._cached_origin = origin - self._cached_direction = direction - self._next_t = t1 - elif t1 <= ray.max_distance: - t_closest = t1 - else: - return None + if t[num - 1] <= ray.max_distance: + t_closest = t[num - 1] + else: + return None return self._generate_intersection(ray, origin, direction, t_closest) @@ -226,15 +246,15 @@ cdef class Torus(Primitive): if not self._further_intersection: return None - # this is the 2nd and therefore last intersection + # this is the 2nd intersection self._further_intersection = False return self._generate_intersection(self._cached_ray, self._cached_origin, self._cached_direction, self._next_t) cdef Intersection _generate_intersection(self, Ray ray, Point3D origin, Vector3D direction, double ray_distance): - cdef Point3D hit_point, tube_centre, inside_point, outside_point + cdef Point3D hit_point, inside_point, outside_point cdef Normal3D normal - cdef double phi, delta_x, delta_y, delta_z + cdef double alpha, delta_x, delta_y, delta_z cdef bint exiting # point of surface intersection in local space @@ -243,10 +263,8 @@ cdef class Torus(Primitive): origin.z + ray_distance * direction.z) # normal is normalised vector from torus tube centre to hit_point - phi = atan2(hit_point.y, hit_point.x) - tube_centre = new_point3d(self._major_radius * cos(phi), self._major_radius * sin(phi), 0.0) - tube_to_hit = tube_centre.vector_to(hit_point) - normal = new_normal3d(tube_to_hit.x, tube_to_hit.y, tube_to_hit.z) + alpha = self._major_radius / hypot(hit_point.x, hit_point.y) + normal = new_normal3d((1.0 - alpha) * hit_point.x, (1.0 - alpha) * hit_point.y, hit_point.z) normal = normal.normalise() # calculate points inside and outside of surface for daughter rays to @@ -285,18 +303,29 @@ cdef class Torus(Primitive): cpdef BoundingBox3D bounding_box(self): - cdef double extent - cdef Point3D origin, lower, upper + cdef: + double extent + list points + Point3D point + BoundingBox3D box - # obtain torus origin in world space - origin = new_point3d(0, 0, 0).transform(self.to_root()) + box = BoundingBox3D() - # calculate upper and lower corners of box + # calculate local bounds extent = self._major_radius + self._minor_radius + BOX_PADDING - lower = new_point3d(origin.x - extent, origin.y - extent, origin.z - extent) - upper = new_point3d(origin.x + extent, origin.y + extent, origin.z + extent) + box.lower = new_point3d(-extent, -extent, -self._minor_radius - BOX_PADDING) + box.upper = new_point3d(extent, extent, self._minor_radius + BOX_PADDING) + + # obtain local space vertices + points = box.vertices() + + # convert points to world space and build an enclosing world space bounding box + # a small degree of padding is added to avoid potential numerical accuracy issues + box = BoundingBox3D() + for point in points: + box.extend(point.transform(self.to_root()), BOX_PADDING) - return BoundingBox3D(lower, upper) + return box cpdef BoundingSphere3D bounding_sphere(self): cdef Point3D centre = new_point3d(0, 0, 0).transform(self.to_root()) From efe111de1b5fe45d9546245b601dcbece2943ba6 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Fri, 10 Feb 2023 22:27:49 +0900 Subject: [PATCH 10/77] =?UTF-8?q?=F0=9F=90=9B=20Fix=20ray-torus=20interect?= =?UTF-8?q?ion=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- raysect/primitive/torus.pyx | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/raysect/primitive/torus.pyx b/raysect/primitive/torus.pyx index 6d15f2c2..21db4652 100644 --- a/raysect/primitive/torus.pyx +++ b/raysect/primitive/torus.pyx @@ -157,32 +157,43 @@ cdef class Torus(Primitive): cdef: Point3D origin Vector3D direction - double sq_origin, sq_r, sq_R, dot_origin_direction, f, b, c, d, e, t_closest + double sq_origin_xy, sq_direction_xy, sq_origin, sq_direction + double origin_direction_xy, origin_dot_direction, sq_r, sq_R, R2_r2 + double a, b, c, d, e, t_closest double[4] t - int i + int num, i # reset further intersection state self._further_intersection = False # convert ray parameters to local space origin = ray.origin.transform(self.to_local()) - direction = ray.direction.transform(self.to_local()).normalise() + direction = ray.direction.transform(self.to_local()) + + # calculate temporary values + sq_origin_xy = origin.x * origin.x + origin.y * origin.y + sq_direction_xy = direction.x * direction.x + direction.y * direction.y + + sq_origin = sq_origin_xy + origin.z * origin.z + sq_direction = sq_direction_xy + direction.z * direction.z + + origin_direction_xy = origin.x * direction.x + origin.y * direction.y + origin_dot_direction = origin_direction_xy + origin.z * direction.z - # coefficients of quartic equation - sq_origin = origin.x * origin.x + origin.y * origin.y + origin.z * origin.z - dot_origin_direction = direction.x * origin.x + direction.y * origin.y + direction.z * origin.z sq_r = self._minor_radius * self._minor_radius sq_R = self._major_radius * self._major_radius - f = sq_origin - (sq_r + sq_R) + R2_r2 = sq_R - sq_r - b = 4.0 * dot_origin_direction - c = 2.0 * f + 4.0 * dot_origin_direction * dot_origin_direction + 4.0 * sq_R * direction.y * direction.y - d = 4.0 * f * dot_origin_direction + 8.0 * sq_R * origin.y * direction.y - e = f * f - 4.0 * sq_R * (sq_r - origin.y * origin.y) + # coefficients of quartic equation + a = sq_direction * sq_direction + b = 4.0 * sq_direction * origin_dot_direction + c = 2.0 * (2.0 * origin_dot_direction * origin_dot_direction + sq_direction * (sq_origin + R2_r2)) - 4.0 * sq_R * sq_direction_xy + d = 4.0 * origin_dot_direction * (sq_origin + R2_r2) - 8.0 * sq_R * origin_direction_xy + e = (sq_origin + R2_r2) * (sq_origin + R2_r2) - 4.0 * sq_R * sq_origin_xy # calculate intersection distances by solving the quartic equation # ray misses if there are no real roots of the quartic - num = solve_quartic(1.0, b, c, d, e, &t[0], &t[1], &t[2], &t[3]) + num = solve_quartic(a, b, c, d, e, &t[0], &t[1], &t[2], &t[3]) if num == 0: return None From f3177008319370a65728a25b8aefe0f15a27c9d6 Mon Sep 17 00:00:00 2001 From: CnlPepper Date: Sun, 12 Feb 2023 18:00:00 +0000 Subject: [PATCH 11/77] Updated build_wheels.sh. --- dev/build_wheels.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/build_wheels.sh b/dev/build_wheels.sh index 0be389ef..41ced4bf 100755 --- a/dev/build_wheels.sh +++ b/dev/build_wheels.sh @@ -28,7 +28,7 @@ auditwheel repair dist/raysect-$VERSION-cp38-cp38-linux_x86_64.whl --plat $PLAT auditwheel repair dist/raysect-$VERSION-cp39-cp39-linux_x86_64.whl --plat $PLAT # python 3.10 -/opt/python/cp310-cp310/bin/python -m pip install cython numpy==1.19.5 -/opt/python/cp310-cp310/bin/python setup.py build_ext -j$CORES -/opt/python/cp310-cp310/bin/python setup.py bdist_wheel -auditwheel repair dist/raysect-$VERSION-cp310-cp310-linux_x86_64.whl --plat $PLAT \ No newline at end of file +#/opt/python/cp310-cp310/bin/python -m pip install cython numpy==1.19.5 +#/opt/python/cp310-cp310/bin/python setup.py build_ext -j$CORES +#/opt/python/cp310-cp310/bin/python setup.py bdist_wheel +#auditwheel repair dist/raysect-$VERSION-cp310-cp310-linux_x86_64.whl --plat $PLAT \ No newline at end of file From 793e532b5f3c810b3a231804ca1ead6170ab16ce Mon Sep 17 00:00:00 2001 From: CnlPepper Date: Sun, 12 Feb 2023 18:24:33 +0000 Subject: [PATCH 12/77] Update version number. --- dev/build_wheels.sh | 2 +- dev/notes/building_bdist_with_manylinux.txt | 4 ++-- raysect/VERSION | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/build_wheels.sh b/dev/build_wheels.sh index 41ced4bf..1ddf3e0d 100755 --- a/dev/build_wheels.sh +++ b/dev/build_wheels.sh @@ -3,7 +3,7 @@ # sudo docker run -ti -v $(pwd):/io quay.io/pypa/manylinux2010_x86_64 /bin/bash # in the container run dev/build_wheels.sh -VERSION=0.8.1 +VERSION=0.8.2 PLAT=manylinux2010_x86_64 CORES=32 diff --git a/dev/notes/building_bdist_with_manylinux.txt b/dev/notes/building_bdist_with_manylinux.txt index 648dceb1..d43508f8 100644 --- a/dev/notes/building_bdist_with_manylinux.txt +++ b/dev/notes/building_bdist_with_manylinux.txt @@ -15,9 +15,9 @@ This will drop you into the manylinux container terminal. The /opt/python folder cd io/source /opt/python/cp37-cp37m/bin/python -m pip install cython numpy==1.14.6 /opt/python/cp37-cp37m/bin/python setup.py bdist_wheel - auditwheel repair dist/raysect-0.8.1-cp37-cp37m-linux_x86_64.whl --plat manylinux2010_x86_64 + auditwheel repair dist/raysect-0.8.2-cp37-cp37m-linux_x86_64.whl --plat manylinux2010_x86_64 -This will compile the wheel and repair any library references to produce the manylinux wheel files in a folder ./wheelhouse. e.g. raysect-0.8.1-cp37-cp37m-manylinux1_x86_64.whl and raysect-0.8.1-cp37-cp37m-manylinux2010_x86_64.whl. +This will compile the wheel and repair any library references to produce the manylinux wheel files in a folder ./wheelhouse. e.g. raysect-0.8.2-cp37-cp37m-manylinux1_x86_64.whl and raysect-0.8.2-cp37-cp37m-manylinux2010_x86_64.whl. These can then be uploaded to pypi (just the manylinux2010 packages for now). diff --git a/raysect/VERSION b/raysect/VERSION index c18d72be..53a48a1e 100644 --- a/raysect/VERSION +++ b/raysect/VERSION @@ -1 +1 @@ -0.8.1 \ No newline at end of file +0.8.2 \ No newline at end of file From 6143bdcf72ee63750e3c59b0fba2db3da17a2d3e Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Mon, 13 Feb 2023 14:38:06 +0900 Subject: [PATCH 13/77] =?UTF-8?q?=E2=9C=A8=20add=20demo=20script=20using?= =?UTF-8?q?=20a=20torus=20primitive?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- demos/primitives/simple_torus.py | 64 ++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 demos/primitives/simple_torus.py diff --git a/demos/primitives/simple_torus.py b/demos/primitives/simple_torus.py new file mode 100644 index 00000000..4081283b --- /dev/null +++ b/demos/primitives/simple_torus.py @@ -0,0 +1,64 @@ +from matplotlib import pyplot as plt + +from raysect.optical import ConstantSF, Point3D, World, d65_white, rotate, translate +from raysect.optical.library.metal import Copper +from raysect.optical.material import Lambert, UniformSurfaceEmitter +from raysect.optical.observer import PinholeCamera, RGBAdaptiveSampler2D, RGBPipeline2D +from raysect.primitive import Box, Cylinder, Torus + +world = World() + +# Torus +torus = Torus( + 1.0, + 0.5, + world, + transform=translate(0, 0.0, 0.6), + material=Copper(), +) + +# floor +Box( + Point3D(-100, -100, -10), + Point3D(100, 100, 0), + parent=world, + material=Lambert(ConstantSF(1.0)), +) + +# emitter +Cylinder( + 3.0, + 100.0, + parent=world, + transform=translate(0, 0, 8) * rotate(90, 0, 0) * translate(0, 0, -50), + material=UniformSurfaceEmitter(d65_white, 1.0), +) + +# camera +rgb = RGBPipeline2D(display_unsaturated_fraction=0.995) +sampler = RGBAdaptiveSampler2D(rgb, min_samples=500, fraction=0.1, cutoff=0.01) +camera = PinholeCamera( + (512, 512), + parent=world, + transform=rotate(0, 45, 0) * translate(0, 0, 5) * rotate(0, -180, 0), + pipelines=[rgb], + frame_sampler=sampler, +) +camera.spectral_bins = 21 +camera.spectral_rays = 21 +camera.pixel_samples = 250 +camera.ray_max_depth = 10000 +camera.ray_extinction_min_depth = 3 +camera.ray_extinction_prob = 0.01 + + +# start ray tracing +plt.ion() +for p in range(0, 1000): + print(f"Rendering pass {p}...") + camera.observe() + print() + +plt.ioff() +rgb.display() +plt.show() From 66376659e85595cae9b66f5c36cd483cc6d4a645 Mon Sep 17 00:00:00 2001 From: Vladislav Neverov Date: Mon, 13 Feb 2023 17:08:18 +0300 Subject: [PATCH 14/77] Fix demos incompatibility with latest Raysect version. --- demos/materials/modifiers/transform.py | 2 +- demos/materials/volume.py | 2 +- demos/observers/metal_with_lens.py | 3 +++ demos/optics/etendue_of_pinhole.py | 11 ++++------- demos/optics/logging_trajectories.py | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/demos/materials/modifiers/transform.py b/demos/materials/modifiers/transform.py index 924b3288..e7151d02 100644 --- a/demos/materials/modifiers/transform.py +++ b/demos/materials/modifiers/transform.py @@ -18,7 +18,7 @@ def emission_function(self, point, direction, spectrum, world, ray, primitive, t wvl_range = spectrum.min_wavelength - spectrum.max_wavelength shift = 2 * (spectrum.wavelengths - wvl_centre) / wvl_range radius = sqrt(point.x**2 + point.y**2) - spectrum.samples += cos((shift + 5) * radius)**4 + spectrum.samples[:] += cos((shift + 5) * radius)**4 return spectrum diff --git a/demos/materials/volume.py b/demos/materials/volume.py index 8ab7c002..ddcc9134 100644 --- a/demos/materials/volume.py +++ b/demos/materials/volume.py @@ -20,7 +20,7 @@ def emission_function(self, point, direction, spectrum, world, ray, primitive, t wvl_range = spectrum.min_wavelength - spectrum.max_wavelength shift = 2 * (spectrum.wavelengths - wvl_centre) / wvl_range radius = sqrt(point.x**2 + point.y**2) - spectrum.samples += cos((shift + 5) * radius)**4 + spectrum.samples[:] += cos((shift + 5) * radius)**4 return spectrum diff --git a/demos/observers/metal_with_lens.py b/demos/observers/metal_with_lens.py index c9bd9163..e1bbaa8c 100644 --- a/demos/observers/metal_with_lens.py +++ b/demos/observers/metal_with_lens.py @@ -65,3 +65,6 @@ # ccd.pipelines[0].save("demo_metal_lens_{}.png".format(p)) print() p += 1 + +ioff() +show() diff --git a/demos/optics/etendue_of_pinhole.py b/demos/optics/etendue_of_pinhole.py index 5f15dabc..38a85b9b 100644 --- a/demos/optics/etendue_of_pinhole.py +++ b/demos/optics/etendue_of_pinhole.py @@ -110,12 +110,10 @@ def raytraced_etendue(distance, detector_radius=0.001, ray_count=100000, batches raytraced_values = np.array(raytraced_values) raytraced_errors = np.array(raytraced_errors) -plt.ion() - plt.figure() ax = plt.gca() -ax.set_xscale("log", nonposx='clip') -ax.set_yscale("log", nonposy='clip') +ax.set_xscale("log", nonpositive='clip') +ax.set_yscale("log", nonpositive='clip') plt.axhline(y=detector_etendue, linestyle='--', color='k', label='detector etendue') plt.plot(distance_samples, analytic_values, label='analytic etendue') plt.errorbar(distance_samples, raytraced_values, raytraced_errors, label='ray-traced etendue') @@ -126,12 +124,11 @@ def raytraced_etendue(distance, detector_radius=0.001, ray_count=100000, batches # plt.figure() # ax = plt.gca() -# ax.set_xscale("log", nonposx='clip') +# ax.set_xscale("log", nonpositive='clip') # plt.errorbar(distance_samples, np.abs(raytraced_values-analytic_values)/raytraced_values, raytraced_errors/raytraced_values) # plt.xlim(0.001, 0.1) # plt.ylim(0, 0.5) # plt.xlabel('Distance between slit and detector (m)') # plt.ylabel('Fractional error') -# plt.show() - +plt.show() diff --git a/demos/optics/logging_trajectories.py b/demos/optics/logging_trajectories.py index 97817a59..c5bb9c39 100644 --- a/demos/optics/logging_trajectories.py +++ b/demos/optics/logging_trajectories.py @@ -28,8 +28,7 @@ # for each sample direction trace a logging ray and plot the ray trajectory plt.ion() -fig = plt.figure() -ax = fig.gca(projection='3d') +ax = plt.axes(projection='3d') for u in np.linspace(-0.006, 0.006, 5): for v in np.linspace(-0.012, 0.012, 11): @@ -39,7 +38,8 @@ log_ray.trace(world) p = [(start.x, start.y, start.z)] - for point in log_ray.log: + for intersection in log_ray.log: + point = intersection.hit_point p.append((point.x, point.y, point.z)) p = np.array(p) From 3019130a433ea9b4c1be59cdcaa69ca21d86079f Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Mon, 18 Dec 2023 23:09:12 +0900 Subject: [PATCH 15/77] =?UTF-8?q?=F0=9F=A9=B9=20fix=20spectral=20rays=20in?= =?UTF-8?q?=20camera=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- demos/primitives/simple_torus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demos/primitives/simple_torus.py b/demos/primitives/simple_torus.py index 4081283b..91c0d66e 100644 --- a/demos/primitives/simple_torus.py +++ b/demos/primitives/simple_torus.py @@ -45,7 +45,7 @@ frame_sampler=sampler, ) camera.spectral_bins = 21 -camera.spectral_rays = 21 +camera.spectral_rays = 1 camera.pixel_samples = 250 camera.ray_max_depth = 10000 camera.ray_extinction_min_depth = 3 From 893c4eb4db8f429a959b494bb0037bea9a2934c6 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Mon, 18 Dec 2023 23:10:44 +0900 Subject: [PATCH 16/77] =?UTF-8?q?=F0=9F=8E=A8=20=E2=9C=8F=EF=B8=8F=20Fix?= =?UTF-8?q?=20formatting=20and=20typos=20in=20utility.pyx=20and=20torus.py?= =?UTF-8?q?x?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- raysect/core/math/cython/utility.pyx | 23 +++++++++++++---------- raysect/primitive/torus.pyx | 13 ++++++------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/raysect/core/math/cython/utility.pyx b/raysect/core/math/cython/utility.pyx index 22d84d1b..f5fa3e19 100644 --- a/raysect/core/math/cython/utility.pyx +++ b/raysect/core/math/cython/utility.pyx @@ -389,9 +389,9 @@ cdef bint solve_quadratic(double a, double b, double c, double *t0, double *t1) The values of the real roots, are returned by setting the values of the memory locations pointed to by t0 and t1. In the case of a single root, both t0 and t1 will have the same value. If there are not roots, the values - of t0 and t1 will be undefined. + of t0 and t1 will be undefined. - :param double a: Quadratic constant. + :param double a: Quadratic constant. :param double b: Quadratic constant. :param double c: Quadratic constant. :param double t0: 1st root of the quadratic. @@ -430,16 +430,19 @@ cdef int solve_cubic(double a, double b, double c, double d, double *t0, double f = a.x^3 + b.x^2 + c.x + d - The cubic equation has 1, 2 or 3 real roots, and this function will return the number of real roots. + The cubic equation has 1, 2 or 3 real roots, and this function returns either 1 or 3, but in a special case, + 2 of the 3 roots found by this function will be equal to each other up to machine epsilon. + + The values of the roots, are returned by setting the values of the memory locations pointed to by t0, t1, and t2. In the case of three real roots, the roots themselves back in t0, t1 and t2. In the case of two real roots, a pair in t0, t1, and t2 will have the same value, and the other is a different one. - In the case of one real root, t0 will be the real root, and t1 + i * t2 will a pair of coplex-conjugated roots. + In the case of one real root, t0 will be the real root, and t1 +- i * t2 will a pair of complex-conjugated roots. The practical algorithm is followed by https://quarticequations.com - :param double a: Cubic constant. + :param double a: Cubic constant. :param double b: Cubic constant. :param double c: Cubic constant. :param double d: Cubic constant. @@ -513,7 +516,7 @@ cdef int solve_biquadratic(double a, double c, double e, double *t0, double *t1, the values of t2 and t3 will be undefined. If there is no real root, all values will be undefined. - :param double a: Biquadratic constant. + :param double a: Biquadratic constant. :param double c: Biquadratic constant. :param double e: Biquadratic constant. :param double t0: 1st root of the biquadratic. @@ -521,7 +524,7 @@ cdef int solve_biquadratic(double a, double c, double e, double *t0, double *t1, :param double t2: 3rd root of the biquadratic. :param double t3: 4th root of the biquadratic. :return: Number of real roots. - :rtype: int + :rtype: int """ cdef double s0, s1, sx0, sx1 @@ -542,7 +545,7 @@ cdef int solve_biquadratic(double a, double c, double e, double *t0, double *t1, t2[0] = sx0 t3[0] = sx1 return 4 - + # s0 < 0 <= s1, 2 real roots elif s1 >= 0: sx1 = sqrt(s1) @@ -562,7 +565,7 @@ cdef int _solve_depressed_quartic(double p, double q, double r, double *t0, doub cdef: int num double s0, sigma, A, B, sq_A, sq_B - + if q > 0: sigma = 1.0 else: @@ -690,7 +693,7 @@ cdef int solve_quartic(double a, double b, double c, double d, double e, The practical algorithm of Van der Waerden method is followed by https://quarticequations.com - :param double a: Qurtic constant. + :param double a: Qurtic constant. :param double b: Qurtic constant. :param double c: Qurtic constant. :param double d: Qurtic constant. diff --git a/raysect/primitive/torus.pyx b/raysect/primitive/torus.pyx index 21db4652..b284752b 100644 --- a/raysect/primitive/torus.pyx +++ b/raysect/primitive/torus.pyx @@ -50,14 +50,14 @@ cdef class Torus(Primitive): The torus is defined by major and minor radius. The major radius is the distance from the center of the tube to the center of the torus. The minor radius is the radius of the tube. - The center of the torus corresponds to the origin of the local co-ordinate system. + The center of the torus corresponds to the origin of the local coordinate system. The axis of revolution coincides with the z-axis, and The center of the torus tube lies on the x-y plane. :param float major_radius: Major radius of the torus in meters (default = 1.0). :param float minor_radius: Minor radius of the torus in meters (default = 0.5). :param Node parent: Scene-graph parent node or None (default = None). - :param AffineMatrix3D transform: An AffineMatrix3D defining the local co-ordinate system relative to the scene-graph parent (default = identity matrix). + :param AffineMatrix3D transform: An AffineMatrix3D defining the local coordinate system relative to the scene-graph parent (default = identity matrix). :param Material material: A Material object defining the torus's material (default = None). :param str name: A string specifying a user-friendly name for the torus (default = ""). @@ -197,14 +197,14 @@ cdef class Torus(Primitive): if num == 0: return None - + elif num == 1: # test the intersection points inside the ray search range [0, max_distance] if t[0] > ray.max_distance or t[0] < 0.0: return None else: t_closest = t[0] - + else: # sorting solutions in each number of them if num == 2: @@ -330,11 +330,10 @@ cdef class Torus(Primitive): # obtain local space vertices points = box.vertices() - # convert points to world space and build an enclosing world space bounding box - # a small degree of padding is added to avoid potential numerical accuracy issues + # convert points to world space box = BoundingBox3D() for point in points: - box.extend(point.transform(self.to_root()), BOX_PADDING) + box.extend(point.transform(self.to_root())) return box From ea72675c0a8bd6292738244e6161d9343972e251 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Thu, 27 Jul 2023 21:30:35 +0900 Subject: [PATCH 17/77] add reverse method like `__radd__`, `__rmul__`, etc. This commit works in progress --- .../math/function/float/function1d/base.pyx | 163 ++++++++------ raysect/core/math/normal.pyx | 96 ++++---- raysect/core/math/point.pyx | 69 +++--- raysect/core/math/quaternion.pyx | 82 ++++--- raysect/core/math/vector.pyx | 205 +++++++++++------- 5 files changed, 334 insertions(+), 281 deletions(-) diff --git a/raysect/core/math/function/float/function1d/base.pyx b/raysect/core/math/function/float/function1d/base.pyx index 2426f3cd..c1d86d4d 100644 --- a/raysect/core/math/function/float/function1d/base.pyx +++ b/raysect/core/math/function/float/function1d/base.pyx @@ -65,104 +65,123 @@ cdef class Function1D(FloatFunction): def __repr__(self): return 'Function1D(x)' - def __add__(object a, object b): - if is_callable(a): - if is_callable(b): - # a() + b() - return AddFunction1D(a, b) - elif isinstance(b, numbers.Real): - # a() + B -> B + a() - return AddScalar1D( b, a) - elif isinstance(a, numbers.Real): - if is_callable(b): - # A + b() - return AddScalar1D( a, b) + def __add__(self, object b): + if is_callable(b): + # a() + b() + return AddFunction1D(self, b) + elif isinstance(b, numbers.Real): + # a() + B -> B + a() + return AddScalar1D( b, self) return NotImplemented - - def __sub__(object a, object b): + + def __radd__(self, object a): + return self.__add__(a) + + def __sub__(self, object b): + if is_callable(b): + # a() - b() + return SubtractFunction1D(self, b) + elif isinstance(b, numbers.Real): + # a() - B -> -B + a() + return AddScalar1D(-( b), self) + return NotImplemented + + def __rsub__(self, object a): if is_callable(a): - if is_callable(b): - # a() - b() - return SubtractFunction1D(a, b) - elif isinstance(b, numbers.Real): - # a() - B -> -B + a() - return AddScalar1D(-( b), a) + # a() - b() + return SubtractFunction1D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A - b() - return SubtractScalar1D( a, b) + # A - b() + return SubtractScalar1D( a, self) return NotImplemented - def __mul__(object a, object b): - if is_callable(a): - if is_callable(b): - # a() * b() - return MultiplyFunction1D(a, b) - elif isinstance(b, numbers.Real): - # a() * B -> B * a() - return MultiplyScalar1D( b, a) - elif isinstance(a, numbers.Real): - if is_callable(b): - # A * b() - return MultiplyScalar1D( a, b) + def __mul__(self, object b): + if is_callable(b): + # a() * b() + return MultiplyFunction1D(self, b) + elif isinstance(b, numbers.Real): + # a() * B -> B * a() + return MultiplyScalar1D( b, self) return NotImplemented + + def __rmul__(self, object a): + return self.__mul__(a) @cython.cdivision(True) - def __truediv__(object a, object b): + def __truediv__(self, object b): cdef double v + if is_callable(b): + # a() / b() + return DivideFunction1D(self, b) + elif isinstance(b, numbers.Real): + # a() / B -> 1/B * a() + v = b + if v == 0.0: + raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") + return MultiplyScalar1D(1/v, self) + return NotImplemented + + @cython.cdivision(True) + def __rtruediv__(self, object a): if is_callable(a): - if is_callable(b): - # a() / b() - return DivideFunction1D(a, b) - elif isinstance(b, numbers.Real): - # a() / B -> 1/B * a() - v = b - if v == 0.0: - raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") - return MultiplyScalar1D(1/v, a) + # a() / b() + return DivideFunction1D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A * b() - return DivideScalar1D( a, b) + # A / b() + return DivideScalar1D( a, self) return NotImplemented - def __mod__(object a, object b): + def __mod__(self, object b): cdef double v + if is_callable(b): + # a() % b() + return ModuloFunction1D(self, b) + elif isinstance(b, numbers.Real): + # a() % B + v = b + if v == 0.0: + raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") + return ModuloFunctionScalar1D(self, v) + return NotImplemented + + def __rmod__(self, object a): if is_callable(a): - if is_callable(b): - # a() % b() - return ModuloFunction1D(a, b) - elif isinstance(b, numbers.Real): - # a() % B - v = b - if v == 0.0: - raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") - return ModuloFunctionScalar1D(a, v) + # a() % b() + return ModuloFunction1D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A % b() - return ModuloScalarFunction1D( a, b) + # A % b() + return ModuloScalarFunction1D( a, self) return NotImplemented def __neg__(self): return MultiplyScalar1D(-1, self) - def __pow__(object a, object b, object c): + def __pow__(self, object b, object c): + if c is not None: + # Optimised implementation of pow(a, b, c) not available: fall back + # to general implementation + return PowFunction1D(self, b) % c + + if is_callable(b): + # a() ** b() + return PowFunction1D(self, b) + elif isinstance(b, numbers.Real): + # a() ** b + return PowFunctionScalar1D(self, b) + return NotImplemented + + def __rpow__(self, object a, object c): if c is not None: # Optimised implementation of pow(a, b, c) not available: fall back # to general implementation - return (a ** b) % c + return PowFunction1D(a, self) % c + if is_callable(a): - if is_callable(b): - # a() ** b() - return PowFunction1D(a, b) - elif isinstance(b, numbers.Real): - # a() ** b - return PowFunctionScalar1D(a, b) + # a() ** b() + return PowFunction1D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # a ** b() - return PowScalarFunction1D( a, b) + # A ** b() + return PowScalarFunction1D( a, self) return NotImplemented def __abs__(self): diff --git a/raysect/core/math/normal.pyx b/raysect/core/math/normal.pyx index c0045a7b..6334c781 100644 --- a/raysect/core/math/normal.pyx +++ b/raysect/core/math/normal.pyx @@ -93,89 +93,96 @@ cdef class Normal3D(_Vec3): -self.y, -self.z) - def __add__(object x, object y): + def __add__(self, object y): """Addition operator.""" - cdef _Vec3 vx, vy + cdef _Vec3 v - if isinstance(x, _Vec3) and isinstance(y, _Vec3): + if isinstance(y, _Vec3): - vx = <_Vec3>x - vy = <_Vec3>y + v = <_Vec3>y - return new_normal3d(vx.x + vy.x, - vx.y + vy.y, - vx.z + vy.z) + return new_normal3d(self.x + v.x, + self.y + v.y, + self.z + v.z) else: return NotImplemented + + def __radd__(self, object x): + """Reverse addition operator.""" + + return self.__add__(x) - def __sub__(object x, object y): + def __sub__(self, object y): """Subtract operator.""" - cdef _Vec3 vx, vy + cdef _Vec3 v - if isinstance(x, _Vec3) and isinstance(y, _Vec3): + if isinstance(y, _Vec3): - vx = <_Vec3>x - vy = <_Vec3>y + v = <_Vec3>y - return new_normal3d(vx.x - vy.x, - vx.y - vy.y, - vx.z - vy.z) + return new_normal3d(self.x - v.x, + self.y - v.y, + self.z - v.z) else: return NotImplemented - def __mul__(object x, object y): - """Multiply operator.""" + def __rsub__(self, object x): + """Reverse subtract operator.""" - cdef double s - cdef Normal3D v - cdef AffineMatrix3D m, minv + cdef _Vec3 v + + if isinstance(x, _Vec3): + + v = <_Vec3>x + + return new_normal3d(v.x - self.x, + v.y - self.y, + v.z - self.z) - if isinstance(x, numbers.Real) and isinstance(y, Normal3D): + else: + + return NotImplemented - s = x - v = y + def __mul__(self, object y): + """Multiply operator.""" - return new_normal3d(s * v.x, - s * v.y, - s * v.z) + cdef double s + cdef AffineMatrix3D m, minv - elif isinstance(x, Normal3D) and isinstance(y, numbers.Real): + if isinstance(y, numbers.Real): s = y - v = x - return new_normal3d(s * v.x, - s * v.y, - s * v.z) + return new_normal3d(s * self.x, + s * self.y, + s * self.z) - elif isinstance(x, AffineMatrix3D) and isinstance(y, Normal3D): + elif isinstance(y, AffineMatrix3D): - m = x - v = y + m = y minv = m.inverse() - return new_normal3d(minv.m[0][0] * v.x + minv.m[1][0] * v.y + minv.m[2][0] * v.z, - minv.m[0][1] * v.x + minv.m[1][1] * v.y + minv.m[2][1] * v.z, - minv.m[0][2] * v.x + minv.m[1][2] * v.y + minv.m[2][2] * v.z) + return new_normal3d(minv.m[0][0] * self.x + minv.m[1][0] * self.y + minv.m[2][0] * self.z, + minv.m[0][1] * self.x + minv.m[1][1] * self.y + minv.m[2][1] * self.z, + minv.m[0][2] * self.x + minv.m[1][2] * self.y + minv.m[2][2] * self.z) else: return NotImplemented @cython.cdivision(True) - def __truediv__(object x, object y): + def __truediv__(self, object y): """Division operator.""" cdef double d - cdef Normal3D v - if isinstance(x, Normal3D) and isinstance(y, numbers.Real): + if isinstance(y, numbers.Real): d = y @@ -184,12 +191,11 @@ cdef class Normal3D(_Vec3): raise ZeroDivisionError("Cannot divide a vector by a zero scalar.") - v = x d = 1.0 / d - return new_normal3d(d * v.x, - d * v.y, - d * v.z) + return new_normal3d(d * self.x, + d * self.y, + d * self.z) else: diff --git a/raysect/core/math/point.pyx b/raysect/core/math/point.pyx index 7ebdc2b2..ba4280d6 100644 --- a/raysect/core/math/point.pyx +++ b/raysect/core/math/point.pyx @@ -136,73 +136,66 @@ cdef class Point3D: yield self.y yield self.z - def __add__(object x, object y): + def __add__(self, object y): """Addition operator. >>> Point3D(1, 0, 0) + Vector3D(0, 1, 0) Point3D(1.0, 1.0, 0.0) """ - cdef Point3D p cdef _Vec3 v - if isinstance(x, Point3D) and isinstance(y, _Vec3): + if isinstance(y, _Vec3): - p = x v = <_Vec3>y else: return NotImplemented - return new_point3d(p.x + v.x, - p.y + v.y, - p.z + v.z) + return new_point3d(self.x + v.x, + self.y + v.y, + self.z + v.z) - def __sub__(object x, object y): + def __sub__(self, object y): """Subtraction operator. >>> Point3D(1, 0, 0) - Vector3D(0, 1, 0) Point3D(1.0, -1.0, 0.0) """ - cdef Point3D p cdef _Vec3 v - if isinstance(x, Point3D) and isinstance(y, _Vec3): + if isinstance(y, _Vec3): - p = x v = <_Vec3>y - return new_point3d(p.x - v.x, - p.y - v.y, - p.z - v.z) + return new_point3d(self.x - v.x, + self.y - v.y, + self.z - v.z) else: return NotImplemented @cython.cdivision(True) - def __mul__(object x, object y): + def __rmul__(self, object x): """Multiplication operator. :param AffineMatrix3D x: transformation matrix x - :param Point3D y: point to transform :return: Matrix multiplication of a 3D transformation matrix with the input point. :rtype: Point3D """ cdef AffineMatrix3D m - cdef Point3D v cdef double w - if isinstance(x, AffineMatrix3D) and isinstance(y, Point3D): + if isinstance(x, AffineMatrix3D): m = x - v = y # 4th element of homogeneous coordinate - w = m.m[3][0] * v.x + m.m[3][1] * v.y + m.m[3][2] * v.z + m.m[3][3] + w = m.m[3][0] * self.x + m.m[3][1] * self.y + m.m[3][2] * self.z + m.m[3][3] if w == 0.0: raise ZeroDivisionError("Bad matrix transform, 4th element of homogeneous coordinate is zero.") @@ -210,9 +203,9 @@ cdef class Point3D: # pre divide for speed (dividing is much slower than multiplying) w = 1.0 / w - return new_point3d((m.m[0][0] * v.x + m.m[0][1] * v.y + m.m[0][2] * v.z + m.m[0][3]) * w, - (m.m[1][0] * v.x + m.m[1][1] * v.y + m.m[1][2] * v.z + m.m[1][3]) * w, - (m.m[2][0] * v.x + m.m[2][1] * v.y + m.m[2][2] * v.z + m.m[2][3]) * w) + return new_point3d((m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z + m.m[0][3]) * w, + (m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z + m.m[1][3]) * w, + (m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z + m.m[2][3]) * w) return NotImplemented @@ -472,64 +465,58 @@ cdef class Point2D: yield self.x yield self.y - def __add__(object x, object y): + def __add__(self, object y): """Addition operator. >>> Point2D(1, 0) + Vector2D(0, 1) Point2D(1.0, 1.0) """ - cdef Point2D p cdef Vector2D v - if isinstance(x, Point2D) and isinstance(y, Vector2D): + if isinstance(y, Vector2D): - p = x v = y else: return NotImplemented - return new_point2d(p.x + v.x, p.y + v.y) + return new_point2d(self.x + v.x, self.y + v.y) - def __sub__(object x, object y): + def __sub__(self, object y): """Subtraction operator. >>> Point2D(1, 0) - Vector2D(0, 1) Point2D(1.0, -1.0) """ - cdef Point2D p cdef Vector2D v - if isinstance(x, Point2D) and isinstance(y, Vector2D): + if isinstance(y, Vector2D): - p = x v = y - return new_point2d(p.x - v.x, p.y - v.y) + return new_point2d(self.x - v.x, self.y - v.y) else: return NotImplemented @cython.cdivision(True) - def __mul__(object x, object y): + def __rmul__(self, object x): """Multiply operator.""" raise NotImplemented # cdef AffineMatrix3D m - # cdef Point3D v # cdef double w # - # if isinstance(x, AffineMatrix3D) and isinstance(y, Point3D): + # if isinstance(x, AffineMatrix3D): # # m = x - # v = y # # # 4th element of homogeneous coordinate - # w = m.m[3][0] * v.x + m.m[3][1] * v.y + m.m[3][2] * v.z + m.m[3][3] + # w = m.m[3][0] * self.x + m.m[3][1] * self.y + m.m[3][2] * self.z + m.m[3][3] # if w == 0.0: # # raise ZeroDivisionError("Bad matrix transform, 4th element of homogeneous coordinate is zero.") @@ -537,9 +524,9 @@ cdef class Point2D: # # pre divide for speed (dividing is much slower than multiplying) # w = 1.0 / w # - # return new_point3d((m.m[0][0] * v.x + m.m[0][1] * v.y + m.m[0][2] * v.z + m.m[0][3]) * w, - # (m.m[1][0] * v.x + m.m[1][1] * v.y + m.m[1][2] * v.z + m.m[1][3]) * w, - # (m.m[2][0] * v.x + m.m[2][1] * v.y + m.m[2][2] * v.z + m.m[2][3]) * w) + # return new_point3d((m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z + m.m[0][3]) * w, + # (m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z + m.m[1][3]) * w, + # (m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z + m.m[2][3]) * w) # # return NotImplemented diff --git a/raysect/core/math/quaternion.pyx b/raysect/core/math/quaternion.pyx index 76038a78..51f8a755 100644 --- a/raysect/core/math/quaternion.pyx +++ b/raysect/core/math/quaternion.pyx @@ -127,7 +127,7 @@ cdef class Quaternion: return new_quaternion(-self.x, -self.y, -self.z, -self.s) - def __eq__(object x, object y): + def __eq__(self, object y): """ Equality operator. @@ -137,18 +137,17 @@ cdef class Quaternion: True """ - cdef Quaternion q1, q2 + cdef Quaternion q - if isinstance(x, Quaternion) and isinstance(y, Quaternion): + if isinstance(y, Quaternion): - q1 = x - q2 = y - return q1.x == q2.x and q1.y == q2.y and q1.z == q2.z and q1.s == q2.s + q = y + return self.x == q.x and self.y == q.y and self.z == q.z and self.s == q.s else: raise TypeError('A quaternion can only be equality tested against another quaternion.') - def __add__(object x, object y): + def __add__(self, object y): """ Addition operator. @@ -158,18 +157,17 @@ cdef class Quaternion: Quaternion(0.0, 1.0, 0.0, 1.0) """ - cdef Quaternion q1, q2 + cdef Quaternion q - if isinstance(x, Quaternion) and isinstance(y, Quaternion): + if isinstance(y, Quaternion): - q1 = x - q2 = y - return new_quaternion(q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.s + q2.s) + q = y + return new_quaternion(self.x + q.x, self.y + q.y, self.z + q.z, self.s + q.s) else: return NotImplemented - def __sub__(object x, object y): + def __sub__(self, object y): """Subtraction operator. .. code-block:: pycon @@ -178,18 +176,17 @@ cdef class Quaternion: Quaternion(0.0, -1.0, 0, 1.0) """ - cdef Quaternion q1, q2 + cdef Quaternion q - if isinstance(x, Quaternion) and isinstance(y, Quaternion): + if isinstance(y, Quaternion): - q1 = x - q2 = y - return new_quaternion(q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.s - q2.s) + q = y + return new_quaternion(self.x - q.x, self.y - q.y, self.z - q.z, self.s - q.s) else: return NotImplemented - def __mul__(object x, object y): + def __mul__(self, object y): """Multiplication operator. .. code-block:: pycon @@ -201,31 +198,34 @@ cdef class Quaternion: """ cdef double s - cdef Quaternion q1, q2 + cdef Quaternion q - if isinstance(x, numbers.Real) and isinstance(y, Quaternion): + if isinstance(y, numbers.Real): - s = x - q1 = y - return q1.mul_scalar(s) - - elif isinstance(x, Quaternion) and isinstance(y, numbers.Real): - - q1 = x s = y - return q1.mul_scalar(s) + return self.mul_scalar(s) - elif isinstance(x, Quaternion) and isinstance(y, Quaternion): + elif isinstance(y, Quaternion): - q1 = x - q2 = y - return q1.mul_quaternion(q2) + q = y + return self.mul_quaternion(q) else: return NotImplemented() + def __rmul__(self, x): + """Reverse multiplication operator. + + .. code-block:: pycon + + >>> 2 * Quaternion(0, 0, 1, 1) + Quaternion(0.0, 0.0, 2.0, 2.0) + """ + + return self.__mul__(x) + @cython.cdivision(True) - def __truediv__(object x, object y): + def __truediv__(self, object y): """Division operator. .. code-block:: pycon @@ -237,19 +237,17 @@ cdef class Quaternion: """ cdef double d - cdef Quaternion q1, q2, q2_inv + cdef Quaternion q - if isinstance(x, Quaternion) and isinstance(y, numbers.Real): + if isinstance(y, numbers.Real): d = y - q1 = x - return q1.div_scalar(d) + return self.div_scalar(d) - elif isinstance(x, Quaternion) and isinstance(y, Quaternion): + elif isinstance(y, Quaternion): - q1 = x - q2 = y - return q1.div_quaternion(q2) + q = y + return self.div_quaternion(q) else: raise TypeError('Unsupported operand type. Expects a real number.') diff --git a/raysect/core/math/vector.pyx b/raysect/core/math/vector.pyx index 08e88a90..2571548a 100644 --- a/raysect/core/math/vector.pyx +++ b/raysect/core/math/vector.pyx @@ -147,99 +147,141 @@ cdef class Vector3D(_Vec3): -self.y, -self.z) - def __add__(object x, object y): + def __add__(self, object y): """Addition operator. >>> Vector3D(1, 0, 0) + Vector3D(0, 1, 0) Vector3D(1.0, 1.0, 0.0) """ - cdef _Vec3 vx, vy + cdef _Vec3 vy - if isinstance(x, _Vec3) and isinstance(y, _Vec3): + if isinstance(y, _Vec3): - vx = <_Vec3>x vy = <_Vec3>y - return new_vector3d(vx.x + vy.x, - vx.y + vy.y, - vx.z + vy.z) + return new_vector3d(self.x + vy.x, + self.y + vy.y, + self.z + vy.z) else: return NotImplemented + + def __radd__(self, object x): + """ Reverse addition operator. + + >>> Vector3D(1, 0, 0) + Vector3D(0, 1, 0) + Vector3D(1.0, 1.0, 0.0) + """ + + return self.__add__(x) - def __sub__(object x, object y): + def __sub__(self, object y): """Subtraction operator. >>> Vector3D(1, 0, 0) - Vector3D(0, 1, 0) Vector3D(1.0, -1.0, 0.0) """ - cdef _Vec3 vx, vy + cdef _Vec3 vy - if isinstance(x, _Vec3) and isinstance(y, _Vec3): + if isinstance(y, _Vec3): - vx = <_Vec3>x vy = <_Vec3>y - return new_vector3d(vx.x - vy.x, - vx.y - vy.y, - vx.z - vy.z) + return new_vector3d(self.x - vy.x, + self.y - vy.y, + self.z - vy.z) + + else: + + return NotImplemented + + def __rsub__(self, object x): + """ Reverse subtraction operator. + + >>> Vector3D(1, 0, 0) - Vector3D(0, 1, 0) + Vector3D(1.0, -1.0, 0.0) + """ + + cdef _Vec3 vx + + if isinstance(x, _Vec3): + + vx = <_Vec3>x + + return new_vector3d(vx.x - self.x, + vx.y - self.y, + vx.z - self.z) else: return NotImplemented - def __mul__(object x, object y): + def __mul__(self, object y): """Multiplication operator. 3D vectors can be multiplied with both scalars and transformation matrices. >>> from raysect.core import Vector3D, rotate_x - >>> 2 * Vector3D(1, 2, 3) + >>> Vector3D(1, 2, 3) * 2 Vector3D(2.0, 4.0, 6.0) - >>> rotate_x(90) * Vector3D(0, 0, 1) - Vector3D(0.0, -1.0, 0.0) """ cdef double s - cdef Vector3D v cdef AffineMatrix3D m - if isinstance(x, numbers.Real) and isinstance(y, Vector3D): + if isinstance(y, numbers.Real): - s = x - v = y + s = y - return new_vector3d(s * v.x, - s * v.y, - s * v.z) + return new_vector3d(s * self.x, + s * self.y, + s * self.z) - elif isinstance(x, Vector3D) and isinstance(y, numbers.Real): + else: - s = y - v = x + return NotImplemented + + def __rmul__(self, object x): + """ Reverse multiplication operator. + + 3D vectors can be multiplied with both scalars and transformation matrices. - return new_vector3d(s * v.x, - s * v.y, - s * v.z) + >>> from raysect.core import Vector3D, rotate_x + >>> 2 * Vector3D(1, 2, 3) + Vector3D(2.0, 4.0, 6.0) + >>> rotate_x(90) * Vector3D(0, 0, 1) + Vector3D(0.0, -1.0, 0.0) + """ - elif isinstance(x, AffineMatrix3D) and isinstance(y, Vector3D): + cdef double s + cdef AffineMatrix3D m - m = x - v = y + if isinstance(x, numbers.Real): - return new_vector3d(m.m[0][0] * v.x + m.m[0][1] * v.y + m.m[0][2] * v.z, - m.m[1][0] * v.x + m.m[1][1] * v.y + m.m[1][2] * v.z, - m.m[2][0] * v.x + m.m[2][1] * v.y + m.m[2][2] * v.z) + s = x - else: + return new_vector3d(s * self.x, + s * self.y, + s * self.z) + + elif isinstance(x, AffineMatrix3D): + + m = x + + return new_vector3d(m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z, + m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z, + m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z) + + else: + + return NotImplemented - return NotImplemented @cython.cdivision(True) - def __truediv__(object x, object y): + def __truediv__(self, object y): """Division operator. >>> Vector3D(1, 1, 1) / 2 @@ -247,9 +289,8 @@ cdef class Vector3D(_Vec3): """ cdef double d - cdef Vector3D v - if isinstance(x, Vector3D) and isinstance(y, numbers.Real): + if isinstance(y, numbers.Real): d = y @@ -258,12 +299,11 @@ cdef class Vector3D(_Vec3): raise ZeroDivisionError("Cannot divide a vector by a zero scalar.") - v = x d = 1.0 / d - return new_vector3d(d * v.x, - d * v.y, - d * v.z) + return new_vector3d(d * self.x, + d * self.y, + d * self.z) else: @@ -705,87 +745,92 @@ cdef class Vector2D: return new_vector2d(-self.x, -self.y) - def __add__(object x, object y): + def __add__(self, object y): """Addition operator. >>> Vector2D(1, 0) + Vector2D(0, 1) Vector2D(1.0, 1.0) """ - cdef Vector2D vx, vy + cdef Vector2D vy - if isinstance(x, Vector2D) and isinstance(y, Vector2D): + if isinstance(y, Vector2D): - vx = x vy = y - return new_vector2d(vx.x + vy.x, vx.y + vy.y) + return new_vector2d(self.x + vy.x, self.y + vy.y) else: return NotImplemented - def __sub__(object x, object y): + def __sub__(self, object y): """Subtraction operator. >>> Vector2D(1, 0) - Vector2D(0, 1) Vector2D(1.0, -1.0) """ - cdef Vector2D vx, vy + cdef Vector2D vy - if isinstance(x, Vector2D) and isinstance(y, Vector2D): + if isinstance(y, Vector2D): - vx = x vy = y - return new_vector2d(vx.x - vy.x, vx.y - vy.y) + return new_vector2d(self.x - vy.x, self.y - vy.y) else: return NotImplemented - # TODO - add 2D affine transformations - def __mul__(object x, object y): + def __mul__(self, object y): """Multiplication operator. - >>> 2 * Vector3D(1, 2) + >>> Vector3D(1, 2) * 2 Vector2D(2.0, 4.0) """ cdef double s - cdef Vector2D v # cdef AffineMatrix2D m - if isinstance(x, numbers.Real) and isinstance(y, Vector2D): + if isinstance(y, numbers.Real): - s = x - v = y + s = y - return new_vector2d(s * v.x, s * v.y,) + return new_vector2d(s * self.x, s * self.y) - elif isinstance(x, Vector2D) and isinstance(y, numbers.Real): + else: - s = y - v = x + return NotImplemented + + # TODO - add 2D affine transformations + def __rmul__(self, object x): + """Reverse multiplication operator. + + >>> 2 * Vector3D(1, 2) + Vector2D(2.0, 4.0) + """ - return new_vector2d(s * v.x, s * v.y) + cdef double s + # cdef AffineMatrix2D m + + if isinstance(x, numbers.Real): + + s = x - # elif isinstance(x, AffineMatrix3D) and isinstance(y, Vector3D): + return new_vector2d(s * self.x, s * self.y) + + # elif isinstance(x, AffineMatrix2D): # - # m = x - # v = y + # m = x # - # return new_vector3d(m.m[0][0] * v.x + m.m[0][1] * v.y + m.m[0][2] * v.z, - # m.m[1][0] * v.x + m.m[1][1] * v.y + m.m[1][2] * v.z, - # m.m[2][0] * v.x + m.m[2][1] * v.y + m.m[2][2] * v.z) + # return new_vector2d(m.m[0][0] * self.x + m.m[0][1] * self.y, + # m.m[1][0] * self.x + m.m[1][1] * self.y) - else: - return NotImplemented @cython.cdivision(True) - def __truediv__(object x, object y): + def __truediv__(self, object y): """Division operator. >>> Vector2D(1, 1) / 2 @@ -793,9 +838,8 @@ cdef class Vector2D: """ cdef double d - cdef Vector2D v - if isinstance(x, Vector2D) and isinstance(y, numbers.Real): + if isinstance(y, numbers.Real): d = y @@ -804,10 +848,9 @@ cdef class Vector2D: raise ZeroDivisionError("Cannot divide a vector by a zero scalar.") - v = x d = 1.0 / d - return new_vector2d(d * v.x, d * v.y) + return new_vector2d(d * self.x, d * self.y) else: From 4095c99331a2f03ab27fbe74ef480e3bc5bf8346 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Fri, 28 Jul 2023 18:06:45 +0900 Subject: [PATCH 18/77] fix inconsistent indentation --- raysect/core/math/vector.pyx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/raysect/core/math/vector.pyx b/raysect/core/math/vector.pyx index 2571548a..d05539bd 100644 --- a/raysect/core/math/vector.pyx +++ b/raysect/core/math/vector.pyx @@ -266,18 +266,18 @@ cdef class Vector3D(_Vec3): return new_vector3d(s * self.x, s * self.y, s * self.z) - + elif isinstance(x, AffineMatrix3D): - - m = x - - return new_vector3d(m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z, - m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z, - m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z) - - else: - - return NotImplemented + + m = x + + return new_vector3d(m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z, + m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z, + m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z) + + else: + + return NotImplemented @cython.cdivision(True) From 2d1a680d0fe7f2f7ebf898438135d035730c528d Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Fri, 28 Jul 2023 18:51:24 +0900 Subject: [PATCH 19/77] fix binary arithmetic operators in `math` module - mainly function base modules are modified --- raysect/core/math/affinematrix.pyx | 6 +- .../math/function/float/function2d/base.pyx | 159 ++++++++++-------- .../math/function/float/function3d/base.pyx | 159 ++++++++++-------- .../function/vector3d/function1d/base.pyx | 41 ++--- .../function/vector3d/function2d/base.pyx | 41 ++--- .../function/vector3d/function3d/base.pyx | 39 +++-- raysect/core/math/normal.pyx | 23 ++- 7 files changed, 264 insertions(+), 204 deletions(-) diff --git a/raysect/core/math/affinematrix.pyx b/raysect/core/math/affinematrix.pyx index ff729f75..33c3413d 100644 --- a/raysect/core/math/affinematrix.pyx +++ b/raysect/core/math/affinematrix.pyx @@ -133,7 +133,7 @@ cdef class AffineMatrix3D(_Mat4): s += ", " return s + "])" - def __mul__(object x, object y): + def __mul__(self, object y): """Multiplication operator. >>> from raysect.core import translate, rotate_x @@ -146,9 +146,9 @@ cdef class AffineMatrix3D(_Mat4): cdef AffineMatrix3D mx, my - if isinstance(x, AffineMatrix3D) and isinstance(y, AffineMatrix3D): + if isinstance(y, AffineMatrix3D): - mx = x + mx = self my = y return new_affinematrix3d(mx.m[0][0] * my.m[0][0] + mx.m[0][1] * my.m[1][0] + mx.m[0][2] * my.m[2][0] + mx.m[0][3] * my.m[3][0], mx.m[0][0] * my.m[0][1] + mx.m[0][1] * my.m[1][1] + mx.m[0][2] * my.m[2][1] + mx.m[0][3] * my.m[3][1], diff --git a/raysect/core/math/function/float/function2d/base.pyx b/raysect/core/math/function/float/function2d/base.pyx index f8d80567..2661ab86 100644 --- a/raysect/core/math/function/float/function2d/base.pyx +++ b/raysect/core/math/function/float/function2d/base.pyx @@ -63,104 +63,121 @@ cdef class Function2D(FloatFunction): """ return self.evaluate(x, y) - def __add__(object a, object b): - if is_callable(a): - if is_callable(b): - # a() + b() - return AddFunction2D(a, b) - elif isinstance(b, numbers.Real): - # a() + B -> B + a() - return AddScalar2D( b, a) - elif isinstance(a, numbers.Real): - if is_callable(b): - # A + b() - return AddScalar2D( a, b) + def __add__(self, object b): + if is_callable(b): + # a() + b() + return AddFunction2D(self, b) + elif isinstance(b, numbers.Real): + # a() + B -> B + a() + return AddScalar2D( b, self) return NotImplemented - def __sub__(object a, object b): - if is_callable(a): - if is_callable(b): - # a() - b() - return SubtractFunction2D(a, b) - elif isinstance(b, numbers.Real): - # a() - B -> -B + a() - return AddScalar2D(-( b), a) - elif isinstance(a, numbers.Real): - if is_callable(b): - # A - b() - return SubtractScalar2D( a, b) + def __radd__(self, object a): + return self.__add__(a) + + def __sub__(self, object b): + if is_callable(b): + # a() - b() + return SubtractFunction2D(self, b) + elif isinstance(b, numbers.Real): + # a() - B -> -B + a() + return AddScalar2D(-( b), self) return NotImplemented - def __mul__(object a, object b): + def __rsub__(self, object a): if is_callable(a): - if is_callable(b): - # a() * b() - return MultiplyFunction2D(a, b) - elif isinstance(b, numbers.Real): - # a() * B -> B * a() - return MultiplyScalar2D( b, a) + # a() - b() + return SubtractFunction2D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A * b() - return MultiplyScalar2D( a, b) + # A - b() + return SubtractScalar2D( a, self) return NotImplemented + def __mul__(self, object b): + if is_callable(b): + # a() * b() + return MultiplyFunction2D(self, b) + elif isinstance(b, numbers.Real): + # a() * B -> B * a() + return MultiplyScalar2D( b, self) + return NotImplemented + + def __rmul__(self, object a): + return self.__mul__(a) + @cython.cdivision(True) - def __truediv__(object a, object b): + def __truediv__(self, object b): cdef double v + if is_callable(b): + # a() / b() + return DivideFunction2D(self, b) + elif isinstance(b, numbers.Real): + # a() / B -> 1/B * a() + v = b + if v == 0.0: + raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") + return MultiplyScalar2D(1/v, self) + return NotImplemented + + @cython.cdivision(True) + def __rtruediv__(self, object a): if is_callable(a): - if is_callable(b): - # a() / b() - return DivideFunction2D(a, b) - elif isinstance(b, numbers.Real): - # a() / B -> 1/B * a() - v = b - if v == 0.0: - raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") - return MultiplyScalar2D(1/v, a) + # a() / b() + return DivideFunction2D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A * b() - return DivideScalar2D( a, b) + # A / b() + return DivideScalar2D( a, self) return NotImplemented - def __mod__(object a, object b): + def __mod__(self, object b): cdef double v + if is_callable(b): + # a() % b() + return ModuloFunction2D(self, b) + elif isinstance(b, numbers.Real): + # a() % B + v = b + if v == 0.0: + raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") + return ModuloFunctionScalar2D(self, v) + return NotImplemented + + def __rmod__(self, object a): if is_callable(a): - if is_callable(b): - # a() % b() - return ModuloFunction2D(a, b) - elif isinstance(b, numbers.Real): - # a() % B - v = b - if v == 0.0: - raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") - return ModuloFunctionScalar2D(a, v) + # a() % b() + return ModuloFunction2D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A % b() - return ModuloScalarFunction2D( a, b) + # A % b() + return ModuloScalarFunction2D( a, self) return NotImplemented def __neg__(self): return MultiplyScalar2D(-1, self) - def __pow__(object a, object b, object c): + def __pow__(self, object b, object c): + if c is not None: + # Optimised implementation of pow(a, b, c) not available: fall back + # to general implementation + return PowFunction2D(self, b) % c + if is_callable(b): + # a() ** b() + return PowFunction2D(self, b) + elif isinstance(b, numbers.Real): + # a() ** b + return PowFunctionScalar2D(self, b) + return NotImplemented + + def __rpow__(self, object a, object c): if c is not None: # Optimised implementation of pow(a, b, c) not available: fall back # to general implementation - return (a ** b) % c + return PowFunction2D(a, self) % c if is_callable(a): - if is_callable(b): - # a() ** b() - return PowFunction2D(a, b) - elif isinstance(b, numbers.Real): - # a() ** b - return PowFunctionScalar2D(a, b) + # a() ** b() + return PowFunction2D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # a ** b() - return PowScalarFunction2D( a, b) + # A ** b() + return PowScalarFunction2D( a, self) return NotImplemented def __abs__(self): diff --git a/raysect/core/math/function/float/function3d/base.pyx b/raysect/core/math/function/float/function3d/base.pyx index b5f1933f..36310a8b 100644 --- a/raysect/core/math/function/float/function3d/base.pyx +++ b/raysect/core/math/function/float/function3d/base.pyx @@ -64,104 +64,121 @@ cdef class Function3D(FloatFunction): """ return self.evaluate(x, y, z) - def __add__(object a, object b): - if is_callable(a): - if is_callable(b): - # a() + b() - return AddFunction3D(a, b) - elif isinstance(b, numbers.Real): - # a() + B -> B + a() - return AddScalar3D( b, a) - elif isinstance(a, numbers.Real): - if is_callable(b): - # A + b() - return AddScalar3D( a, b) + def __add__(self, object b): + if is_callable(b): + # a() + b() + return AddFunction3D(self, b) + elif isinstance(b, numbers.Real): + # a() + B -> B + a() + return AddScalar3D( b, self) return NotImplemented - def __sub__(object a, object b): + def __radd__(self, object a): + return self.__add__(a) + + def __sub__(self, object b): + if is_callable(b): + # a() - b() + return SubtractFunction3D(self, b) + elif isinstance(b, numbers.Real): + # a() - B -> -B + a() + return AddScalar3D(-( b), self) + return NotImplemented + + def __rsub__(self, object a): if is_callable(a): - if is_callable(b): - # a() - b() - return SubtractFunction3D(a, b) - elif isinstance(b, numbers.Real): - # a() - B -> -B + a() - return AddScalar3D(-( b), a) + # a() - b() + return SubtractFunction3D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A - b() - return SubtractScalar3D( a, b) + # A - b() + return SubtractScalar3D( a, self) return NotImplemented - def __mul__(object a, object b): - if is_callable(a): - if is_callable(b): - # a() * b() - return MultiplyFunction3D(a, b) - elif isinstance(b, numbers.Real): - # a() * B -> B * a() - return MultiplyScalar3D( b, a) - elif isinstance(a, numbers.Real): - if is_callable(b): - # A * b() - return MultiplyScalar3D( a, b) + def __mul__(self, object b): + if is_callable(b): + # a() * b() + return MultiplyFunction3D(self, b) + elif isinstance(b, numbers.Real): + # a() * B -> B * a() + return MultiplyScalar3D( b, self) return NotImplemented + + def __rmul__(self, object a): + return self.__mul__(a) @cython.cdivision(True) - def __truediv__(object a, object b): + def __truediv__(self, object b): cdef double v + if is_callable(b): + # a() / b() + return DivideFunction3D(self, b) + elif isinstance(b, numbers.Real): + # a() / B -> 1/B * a() + v = b + if v == 0.0: + raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") + return MultiplyScalar3D(1/v, self) + return NotImplemented + + @cython.cdivision(True) + def __rtruediv__(self, object a): if is_callable(a): - if is_callable(b): - # a() / b() - return DivideFunction3D(a, b) - elif isinstance(b, numbers.Real): - # a() / B -> 1/B * a() - v = b - if v == 0.0: - raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") - return MultiplyScalar3D(1/v, a) + # a() / b() + return DivideFunction3D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A * b() - return DivideScalar3D( a, b) + # A / b() + return DivideScalar3D( a, self) return NotImplemented - def __mod__(object a, object b): + def __mod__(self, object b): cdef double v + if is_callable(b): + # a() % b() + return ModuloFunction3D(self, b) + elif isinstance(b, numbers.Real): + # a() % B + v = b + if v == 0.0: + raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") + return ModuloFunctionScalar3D(self, v) + return NotImplemented + + def __rmod__(self, object a): if is_callable(a): - if is_callable(b): - # a() % b() - return ModuloFunction3D(a, b) - elif isinstance(b, numbers.Real): - # a() % B - v = b - if v == 0.0: - raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") - return ModuloFunctionScalar3D(a, v) + # a() % b() + return ModuloFunction3D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # A % b() - return ModuloScalarFunction3D( a, b) + # A % b() + return ModuloScalarFunction3D( a, self) return NotImplemented def __neg__(self): return MultiplyScalar3D(-1, self) - def __pow__(object a, object b, object c): + def __pow__(self, object b, object c): + if c is not None: + # Optimised implementation of pow(a, b, c) not available: fall back + # to general implementation + return PowFunction3D(self, b) % c + if is_callable(b): + # a() ** b() + return PowFunction3D(self, b) + elif isinstance(b, numbers.Real): + # a() ** b + return PowFunctionScalar3D(self, b) + return NotImplemented + + def __rpow__(self, object a, object c): if c is not None: # Optimised implementation of pow(a, b, c) not available: fall back # to general implementation - return (a ** b) % c + return PowFunction3D(a, self) % c if is_callable(a): - if is_callable(b): - # a() ** b() - return PowFunction3D(a, b) - elif isinstance(b, numbers.Real): - # a() ** b - return PowFunctionScalar3D(a, b) + # a() ** b() + return PowFunction3D(a, self) elif isinstance(a, numbers.Real): - if is_callable(b): - # a ** b() - return PowScalarFunction3D( a, b) + # A ** b() + return PowScalarFunction3D( a, self) return NotImplemented def __abs__(self): diff --git a/raysect/core/math/function/vector3d/function1d/base.pyx b/raysect/core/math/function/vector3d/function1d/base.pyx index 2cc9942b..eb34070f 100644 --- a/raysect/core/math/function/vector3d/function1d/base.pyx +++ b/raysect/core/math/function/vector3d/function1d/base.pyx @@ -65,31 +65,34 @@ cdef class Function1D(Vector3DFunction): """ return self.evaluate(x) - def __add__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if is_callable(b) or isinstance(b, Vector3D): - return AddFunction1D(a, b) + def __add__(self, object b): + if is_callable(b) or isinstance(b, Vector3D): + return AddFunction1D(self, b) return NotImplemented + + def __radd__(self, object a): + return self.__add__(a) - def __sub__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if is_callable(b) or isinstance(b, Vector3D): - return SubtractFunction1D(a, b) + def __sub__(self, object b): + if is_callable(b) or isinstance(b, Vector3D): + return SubtractFunction1D(self, b) return NotImplemented - - def __mul__(object a, object b): + + def __rsub__(self, object a): if is_callable(a) or isinstance(a, Vector3D): - if float_is_callable(b) or isinstance(b, numbers.Real): - return MultiplyFunction1D(a, b) - if is_callable(b) or isinstance(b, Vector3D): - if float_is_callable(a) or isinstance(a, numbers.Real): - return MultiplyFunction1D(b, a) + return SubtractFunction1D(a, self) + + def __mul__(self, object b): + if float_is_callable(b) or isinstance(b, numbers.Real): + return MultiplyFunction1D(self, b) return NotImplemented + + def __rmul__(self, object a): + return self.__mul__(a) - def __truediv__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if float_is_callable(b) or isinstance(b, numbers.Real): - return DivideFunction1D(a, b) + def __truediv__(self, object b): + if float_is_callable(b) or isinstance(b, numbers.Real): + return DivideFunction1D(self, b) return NotImplemented def __neg__(self): diff --git a/raysect/core/math/function/vector3d/function2d/base.pyx b/raysect/core/math/function/vector3d/function2d/base.pyx index 6f3f4ff1..3d578155 100644 --- a/raysect/core/math/function/vector3d/function2d/base.pyx +++ b/raysect/core/math/function/vector3d/function2d/base.pyx @@ -66,31 +66,34 @@ cdef class Function2D(Vector3DFunction): """ return self.evaluate(x, y) - def __add__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if is_callable(b) or isinstance(b, Vector3D): - return AddFunction2D(a, b) + def __add__(self, object b): + if is_callable(b) or isinstance(b, Vector3D): + return AddFunction2D(self, b) return NotImplemented - def __sub__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if is_callable(b) or isinstance(b, Vector3D): - return SubtractFunction2D(a, b) - return NotImplemented + def __radd__(self, object a): + return self.__add__(a) - def __mul__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if float_is_callable(b) or isinstance(b, numbers.Real): - return MultiplyFunction2D(a, b) + def __sub__(self, object b): if is_callable(b) or isinstance(b, Vector3D): - if float_is_callable(a) or isinstance(a, numbers.Real): - return MultiplyFunction2D(b, a) + return SubtractFunction2D(self, b) return NotImplemented - - def __truediv__(object a, object b): + + def __rsub__(self, object a): if is_callable(a) or isinstance(a, Vector3D): - if float_is_callable(b) or isinstance(b, numbers.Real): - return DivideFunction2D(a, b) + return SubtractFunction2D(a, self) + + def __mul__(self, object b): + if float_is_callable(b) or isinstance(b, numbers.Real): + return MultiplyFunction2D(self, b) + return NotImplemented + + def __rmul__(self, object a): + return self.__mul__(a) + + def __truediv__(self, object b): + if float_is_callable(b) or isinstance(b, numbers.Real): + return DivideFunction2D(self, b) return NotImplemented def __neg__(self): diff --git a/raysect/core/math/function/vector3d/function3d/base.pyx b/raysect/core/math/function/vector3d/function3d/base.pyx index 79242d5d..7ae4e03b 100644 --- a/raysect/core/math/function/vector3d/function3d/base.pyx +++ b/raysect/core/math/function/vector3d/function3d/base.pyx @@ -66,31 +66,34 @@ cdef class Function3D(Vector3DFunction): """ return self.evaluate(x, y, z) - def __add__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if is_callable(b) or isinstance(b, Vector3D): - return AddFunction3D(a, b) + def __add__(self, object b): + if is_callable(b) or isinstance(b, Vector3D): + return AddFunction3D(self, b) return NotImplemented - def __sub__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if is_callable(b) or isinstance(b, Vector3D): - return SubtractFunction3D(a, b) - return NotImplemented + def __radd__(self, object a): + return self.__add__(a) - def __mul__(object a, object b): - if is_callable(a) or isinstance(a, Vector3D): - if float_is_callable(b) or isinstance(b, numbers.Real): - return MultiplyFunction3D(a, b) + def __sub__(self, object b): if is_callable(b) or isinstance(b, Vector3D): - if float_is_callable(a) or isinstance(a, numbers.Real): - return MultiplyFunction3D(b, a) + return SubtractFunction3D(self, b) return NotImplemented - def __truediv__(object a, object b): + def __rsub__(self, object a): if is_callable(a) or isinstance(a, Vector3D): - if float_is_callable(b) or isinstance(b, numbers.Real): - return DivideFunction3D(a, b) + return SubtractFunction3D(a, self) + + def __mul__(self, object b): + if float_is_callable(b) or isinstance(b, numbers.Real): + return MultiplyFunction3D(self, b) + return NotImplemented + + def __rmul__(self, object a): + return self.__mul__(a) + + def __truediv__(self, object b): + if float_is_callable(b) or isinstance(b, numbers.Real): + return DivideFunction3D(self, b) return NotImplemented def __neg__(self): diff --git a/raysect/core/math/normal.pyx b/raysect/core/math/normal.pyx index 6334c781..29b29288 100644 --- a/raysect/core/math/normal.pyx +++ b/raysect/core/math/normal.pyx @@ -153,7 +153,6 @@ cdef class Normal3D(_Vec3): """Multiply operator.""" cdef double s - cdef AffineMatrix3D m, minv if isinstance(y, numbers.Real): @@ -163,9 +162,27 @@ cdef class Normal3D(_Vec3): s * self.y, s * self.z) - elif isinstance(y, AffineMatrix3D): + else: + + return NotImplemented + + def __rmul__(self, object x): + """Reverse multiply operator.""" + + cdef double s + cdef AffineMatrix3D m, minv + + if isinstance(x, numbers.Real): + + s = x + + return new_normal3d(s * self.x, + s * self.y, + s * self.z) + + elif isinstance(x, AffineMatrix3D): - m = y + m = x minv = m.inverse() return new_normal3d(minv.m[0][0] * self.x + minv.m[1][0] * self.y + minv.m[2][0] * self.z, From e9800f3ff8a3b9ec7cda2af0dfa4ab5c02f4fe92 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Fri, 28 Jul 2023 18:52:34 +0900 Subject: [PATCH 20/77] add `noexcept` to `_edge_compare` function this modification is derived from the cython 3.0 compile error. --- raysect/core/math/spatial/kdtree2d.pyx | 2 +- raysect/core/math/spatial/kdtree3d.pyx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/raysect/core/math/spatial/kdtree2d.pyx b/raysect/core/math/spatial/kdtree2d.pyx index 2e26888e..80f6224c 100644 --- a/raysect/core/math/spatial/kdtree2d.pyx +++ b/raysect/core/math/spatial/kdtree2d.pyx @@ -74,7 +74,7 @@ cdef class Item2D: self.box = box -cdef int _edge_compare(const void *p1, const void *p2) nogil: +cdef int _edge_compare(const void *p1, const void *p2) noexcept nogil: cdef edge e1, e2 diff --git a/raysect/core/math/spatial/kdtree3d.pyx b/raysect/core/math/spatial/kdtree3d.pyx index e82306f4..e5901329 100644 --- a/raysect/core/math/spatial/kdtree3d.pyx +++ b/raysect/core/math/spatial/kdtree3d.pyx @@ -75,7 +75,7 @@ cdef class Item3D: self.box = box -cdef int _edge_compare(const void *p1, const void *p2) nogil: +cdef int _edge_compare(const void *p1, const void *p2) noexcept nogil: cdef edge e1, e2 From 245faf7153c3842482b7586f162d731d78d98389 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Wed, 2 Aug 2023 16:39:24 +0900 Subject: [PATCH 21/77] add ignoring `numpy` deplicated warning --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index ed029aa6..38b04f76 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,7 @@ # 'auto_pickle': True, 'language_level': 3 } +macros = [("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")] setup_path = path.dirname(path.abspath(__file__)) if line_profile: @@ -59,7 +60,7 @@ if path.splitext(file)[1] == ".pyx": pyx_file = path.relpath(path.join(root, file), setup_path) module = path.splitext(pyx_file)[0].replace("/", ".") - extensions.append(Extension(module, [pyx_file], include_dirs=compilation_includes, extra_compile_args=compilation_args),) + extensions.append(Extension(module, [pyx_file], include_dirs=compilation_includes, extra_compile_args=compilation_args, define_macros=macros),) if profile: cython_directives["profile"] = True @@ -77,7 +78,7 @@ if path.splitext(file)[1] == ".c": c_file = path.relpath(path.join(root, file), setup_path) module = path.splitext(c_file)[0].replace("/", ".") - extensions.append(Extension(module, [c_file], include_dirs=compilation_includes, extra_compile_args=compilation_args),) + extensions.append(Extension(module, [c_file], include_dirs=compilation_includes, extra_compile_args=compilation_args, define_macros=macros),) # parse the package version number with open(path.join(path.dirname(__file__), 'raysect/VERSION')) as version_file: From b993ac6e213b04b60801101e1ac0ba841171eeb0 Mon Sep 17 00:00:00 2001 From: Koyo Munechika Date: Thu, 27 Jul 2023 21:30:35 +0900 Subject: [PATCH 22/77] change to return from raise --- raysect/core/math/point.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raysect/core/math/point.pyx b/raysect/core/math/point.pyx index ba4280d6..8d95693b 100644 --- a/raysect/core/math/point.pyx +++ b/raysect/core/math/point.pyx @@ -506,7 +506,7 @@ cdef class Point2D: @cython.cdivision(True) def __rmul__(self, object x): """Multiply operator.""" - raise NotImplemented + return NotImplemented # cdef AffineMatrix3D m # cdef double w From 7a12a044135d0fbcfca1bf5e2ea2449428b9f4c6 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Wed, 27 Nov 2024 11:22:57 +0100 Subject: [PATCH 23/77] update cython version constraint in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e1fcde71..a917a861 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [build-system] -requires = ["setuptools>=42.0", "wheel", "oldest-supported-numpy", "cython>=0.28"] +requires = ["setuptools>=42.0", "wheel", "oldest-supported-numpy", "cython~=3.0"] build-backend = "setuptools.build_meta" From 80a6f5f7d9cf698e12e999dc1a6a11786a5bbde7 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Fri, 18 Jul 2025 13:56:06 -0400 Subject: [PATCH 24/77] fix: replace `DEF` statements with `cdef enum`, `cdef const` and use `INFINITY` in `libc.math` . --- raysect/core/boundingbox.pyx | 13 ++-- .../float/function2d/interpolate/common.pyx | 13 ++-- .../float/function3d/interpolate/common.pyx | 19 +++--- raysect/core/math/quaternion.pyx | 20 +++--- raysect/core/math/random.pyx | 5 +- raysect/core/math/sampler/solidangle.pyx | 9 ++- raysect/core/math/spatial/kdtree2d.pyx | 17 ++--- raysect/core/math/spatial/kdtree3d.pyx | 23 +++---- raysect/core/math/transform.pyx | 5 +- raysect/core/math/vector.pyx | 22 ++----- raysect/core/ray.pyx | 4 +- raysect/optical/loggingray.pyx | 5 +- .../optical/material/modifiers/roughen.pyx | 3 +- .../observer/imaging/targetted_ccd.pyx | 2 +- .../observer/nonimaging/fibreoptic.pyx | 2 +- .../observer/nonimaging/mesh_camera.pyx | 15 +++-- .../observer/nonimaging/mesh_pixel.pyx | 19 +++--- .../observer/nonimaging/targetted_pixel.pyx | 2 +- raysect/optical/ray.pyx | 5 +- raysect/optical/spectrum.pyx | 2 +- raysect/primitive/box.pyx | 32 +++++----- raysect/primitive/cone.pyx | 16 ++--- raysect/primitive/csg.pyx | 9 ++- raysect/primitive/cylinder.pyx | 30 ++++----- raysect/primitive/lens/spherical.pyx | 10 +-- raysect/primitive/mesh/mesh.pyx | 64 +++++++++---------- raysect/primitive/parabola.pyx | 16 ++--- raysect/primitive/sphere.pyx | 6 +- 28 files changed, 184 insertions(+), 204 deletions(-) diff --git a/raysect/core/boundingbox.pyx b/raysect/core/boundingbox.pyx index eb616121..304597d3 100644 --- a/raysect/core/boundingbox.pyx +++ b/raysect/core/boundingbox.pyx @@ -32,18 +32,17 @@ # TODO: add docstrings cimport cython +from libc.math cimport INFINITY from raysect.core.math cimport new_point3d, new_point2d -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - # axis defines -DEF X_AXIS = 0 -DEF Y_AXIS = 1 -DEF Z_AXIS = 2 +cdef enum: + X_AXIS = 0 + Y_AXIS = 1 + Z_AXIS = 2 # defines the padding on the sphere which encloses the BoundingBox3D. -DEF SPHERE_PADDING = 1.000001 +cdef const double SPHERE_PADDING = 1.000001 @cython.freelist(256) diff --git a/raysect/core/math/function/float/function2d/interpolate/common.pyx b/raysect/core/math/function/float/function2d/interpolate/common.pyx index ddfb68f7..a20ca9f8 100644 --- a/raysect/core/math/function/float/function2d/interpolate/common.pyx +++ b/raysect/core/math/function/float/function2d/interpolate/common.pyx @@ -38,15 +38,16 @@ from raysect.core.math.cython cimport barycentric_inside_triangle, barycentric_c cimport cython # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-6 +cdef const double BOX_PADDING = 1e-6 # convenience defines -DEF V1 = 0 -DEF V2 = 1 -DEF V3 = 2 +cdef enum: + V1 = 0 + V2 = 1 + V3 = 2 -DEF X = 0 -DEF Y = 1 + X = 0 + Y = 1 cdef class MeshKDTree2D(KDTree2DCore): diff --git a/raysect/core/math/function/float/function3d/interpolate/common.pyx b/raysect/core/math/function/float/function3d/interpolate/common.pyx index dee3cc4e..40976828 100644 --- a/raysect/core/math/function/float/function3d/interpolate/common.pyx +++ b/raysect/core/math/function/float/function3d/interpolate/common.pyx @@ -38,17 +38,18 @@ from raysect.core.math.cython cimport barycentric_inside_tetrahedra, barycentric cimport cython # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-6 +cdef const double BOX_PADDING = 1e-6 # convenience defines -DEF V1 = 0 -DEF V2 = 1 -DEF V3 = 2 -DEF V4 = 3 - -DEF X = 0 -DEF Y = 1 -DEF Z = 2 +cdef enum: + V1 = 0 + V2 = 1 + V3 = 2 + V4 = 3 + + X = 0 + Y = 1 + Z = 2 cdef class MeshKDTree3D(KDTree3DCore): diff --git a/raysect/core/math/quaternion.pyx b/raysect/core/math/quaternion.pyx index 51f8a755..210c2a7d 100644 --- a/raysect/core/math/quaternion.pyx +++ b/raysect/core/math/quaternion.pyx @@ -36,8 +36,8 @@ from libc.math cimport sqrt, sin, cos, asin, acos, atan2, fabs, M_PI, copysign from raysect.core.math.vector cimport new_vector3d from raysect.core.math.affinematrix cimport new_affinematrix3d, AffineMatrix3D -DEF RAD2DEG = 57.29577951308232000 # 180 / pi -DEF DEG2RAD = 0.017453292519943295 # pi / 180 +cdef const double RAD2DEG = 57.29577951308232000 # 180 / pi +cdef const double DEG2RAD = 0.017453292519943295 # pi / 180 cdef class Quaternion: @@ -288,8 +288,8 @@ cdef class Quaternion: cpdef Quaternion conjugate(self): """ - Complex conjugate operator. - + Complex conjugate operator. + .. code-block:: pycon >>> Quaternion(1, 2, 3, 0).conjugate() @@ -320,7 +320,7 @@ cdef class Quaternion: The returned quaternion is normalised to have norm length 1.0 - a unit quaternion. .. code-block:: pycon - + >>> a = Quaternion(1, 2, 3, 0) >>> a.normalise() Quaternion(0.26726, 0.53452, 0.80178, 0.0) @@ -419,12 +419,12 @@ cdef class Quaternion: This method calculates the quaternion required to map this quaternion onto the supplied quaternion. Both quaternions will be normalised and a normalised quaternion will be returned. - + .. code-block:: pycon - + >>> from raysect.core.math import Quaternion >>> - >>> q1 = Quaternion.from_axis_angle(Vector3D(1,0,0), 10) + >>> q1 = Quaternion.from_axis_angle(Vector3D(1,0,0), 10) >>> q2 = Quaternion.from_axis_angle(Vector3D(1,0,0), 25) >>> d = q1.quaternion_to(q2) >>> d @@ -433,9 +433,9 @@ cdef class Quaternion: 15.000000000000027 >>> d.axis Vector3D(1.0, 0.0, 0.0) - + :param Quaternion q: The target quaternion. - :return: A new Quaternion object representing the specified rotation. + :return: A new Quaternion object representing the specified rotation. """ return q.normalise().mul_quaternion(self.normalise().conjugate()).normalise() diff --git a/raysect/core/math/random.pyx b/raysect/core/math/random.pyx index 096acb04..029751a3 100644 --- a/raysect/core/math/random.pyx +++ b/raysect/core/math/random.pyx @@ -96,8 +96,9 @@ from libc.math cimport cos, sin, asin, log, fabs, sqrt, M_PI as PI from libc.stdint cimport uint64_t, int64_t cimport cython -DEF NN = 312 -DEF MM = 156 +cdef enum: + NN = 312 + MM = 156 # The array for the state vector cdef uint64_t mt[NN] diff --git a/raysect/core/math/sampler/solidangle.pyx b/raysect/core/math/sampler/solidangle.pyx index 55a76e33..9546e214 100644 --- a/raysect/core/math/sampler/solidangle.pyx +++ b/raysect/core/math/sampler/solidangle.pyx @@ -34,9 +34,8 @@ from raysect.core.math cimport Vector3D, new_vector3d from raysect.core.math.random cimport uniform # TODO: add tests - idea: solve the lighting equation with a uniform emitting surface with each sampler and check the mean radiance is unity - -DEF R_2_PI = 0.15915494309189535 # 1 / (2 * pi) -DEF R_4_PI = 0.07957747154594767 # 1 / (4 * pi) +cdef const double R_2_PI = 0.15915494309189535 # 1 / (2 * pi) +cdef const double R_4_PI = 0.07957747154594767 # 1 / (4 * pi) cdef class SolidAngleSampler: @@ -73,7 +72,7 @@ cdef class SolidAngleSampler: cpdef double pdf(self, Vector3D sample): """ Generates a pdf for a given sample value. - + Vectors *must* be normalised. :param Vector3D sample: The sample point at which to get the pdf. @@ -242,7 +241,7 @@ cdef class ConeUniformSampler(SolidAngleSampler): Generates a uniform weighted random vector from a cone. The cone is aligned along the z-axis. - + :param angle: Angle of the cone in degrees (default=45). .. code-block:: pycon diff --git a/raysect/core/math/spatial/kdtree2d.pyx b/raysect/core/math/spatial/kdtree2d.pyx index 80f6224c..96270c3f 100644 --- a/raysect/core/math/spatial/kdtree2d.pyx +++ b/raysect/core/math/spatial/kdtree2d.pyx @@ -40,16 +40,17 @@ from libc.stdint cimport int32_t from libc.math cimport log, ceil cimport cython -# this number of nodes will be pre-allocated when the kd-tree is initially created -DEF INITIAL_NODE_COUNT = 128 +cdef enum: + # this number of nodes will be pre-allocated when the kd-tree is initially created + INITIAL_NODE_COUNT = 128 -# friendly name for first node -DEF ROOT_NODE = 0 + # friendly name for first node + ROOT_NODE = 0 -# node types -DEF LEAF = -1 # leaf node -DEF X_AXIS = 0 # branch, x-axis split -DEF Y_AXIS = 1 # branch, y-axis split + # node types + LEAF = -1 # leaf node + X_AXIS = 0 # branch, x-axis split + Y_AXIS = 1 # branch, y-axis split cdef class Item2D: diff --git a/raysect/core/math/spatial/kdtree3d.pyx b/raysect/core/math/spatial/kdtree3d.pyx index e5901329..62c0565d 100644 --- a/raysect/core/math/spatial/kdtree3d.pyx +++ b/raysect/core/math/spatial/kdtree3d.pyx @@ -40,17 +40,18 @@ from libc.stdint cimport int32_t from libc.math cimport log, ceil cimport cython -# this number of nodes will be pre-allocated when the kd-tree is initially created -DEF INITIAL_NODE_COUNT = 128 - -# friendly name for first node -DEF ROOT_NODE = 0 - -# node types -DEF LEAF = -1 # leaf node -DEF X_AXIS = 0 # branch, x-axis split -DEF Y_AXIS = 1 # branch, y-axis split -DEF Z_AXIS = 2 # branch, z-axis split +cdef enum: + # this number of nodes will be pre-allocated when the kd-tree is initially created + INITIAL_NODE_COUNT = 128 + + # friendly name for first node + ROOT_NODE = 0 + + # node types + LEAF = -1 # leaf node + X_AXIS = 0 # branch, x-axis split + Y_AXIS = 1 # branch, y-axis split + Z_AXIS = 2 # branch, z-axis split cdef class Item3D: diff --git a/raysect/core/math/transform.pyx b/raysect/core/math/transform.pyx index 75b5b93c..80e157e5 100644 --- a/raysect/core/math/transform.pyx +++ b/raysect/core/math/transform.pyx @@ -35,9 +35,8 @@ from raysect.core.math.point cimport new_point3d cimport cython -DEF RAD2DEG = 57.29577951308232000 # 180 / pi -DEF DEG2RAD = 0.017453292519943295 # pi / 180 - +cdef const double RAD2DEG = 57.29577951308232000 # 180 / pi +cdef const double DEG2RAD = 0.017453292519943295 # pi / 180 FORWARD = 'forward' UP = 'up' diff --git a/raysect/core/math/vector.pyx b/raysect/core/math/vector.pyx index d05539bd..13568bd9 100644 --- a/raysect/core/math/vector.pyx +++ b/raysect/core/math/vector.pyx @@ -33,7 +33,7 @@ import numbers cimport cython from libc.math cimport sqrt, fabs, NAN, acos, cos, sin -DEF EPSILON = 1e-12 +cdef const double EPSILON = 1e-12 cdef class Vector3D(_Vec3): @@ -167,7 +167,7 @@ cdef class Vector3D(_Vec3): else: return NotImplemented - + def __radd__(self, object x): """ Reverse addition operator. @@ -197,7 +197,7 @@ cdef class Vector3D(_Vec3): else: return NotImplemented - + def __rsub__(self, object x): """ Reverse subtraction operator. @@ -791,7 +791,6 @@ cdef class Vector2D: """ cdef double s - # cdef AffineMatrix2D m if isinstance(y, numbers.Real): @@ -800,10 +799,8 @@ cdef class Vector2D: return new_vector2d(s * self.x, s * self.y) else: + raise TypeError("Unsupported operand type. Expects a real number.") - return NotImplemented - - # TODO - add 2D affine transformations def __rmul__(self, object x): """Reverse multiplication operator. @@ -812,20 +809,15 @@ cdef class Vector2D: """ cdef double s - # cdef AffineMatrix2D m if isinstance(x, numbers.Real): s = x return new_vector2d(s * self.x, s * self.y) - - # elif isinstance(x, AffineMatrix2D): - # - # m = x - # - # return new_vector2d(m.m[0][0] * self.x + m.m[0][1] * self.y, - # m.m[1][0] * self.x + m.m[1][1] * self.y) + + else: + raise TypeError("Unsupported operand type. Expects a real number.") diff --git a/raysect/core/ray.pyx b/raysect/core/ray.pyx index 15d97910..9ed22008 100644 --- a/raysect/core/ray.pyx +++ b/raysect/core/ray.pyx @@ -29,12 +29,10 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +from libc.math cimport INFINITY from raysect.core.math cimport new_point3d cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - @cython.freelist(256) cdef class Ray: diff --git a/raysect/optical/loggingray.pyx b/raysect/optical/loggingray.pyx index 5fa38d5b..cc1cc038 100644 --- a/raysect/optical/loggingray.pyx +++ b/raysect/optical/loggingray.pyx @@ -29,6 +29,8 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +from libc.math cimport INFINITY + from raysect.core cimport Intersection, Point3D, Vector3D from raysect.core.math.random cimport probability @@ -39,9 +41,6 @@ from raysect.optical.material.material cimport Material cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - cdef class LoggingRay(Ray): diff --git a/raysect/optical/material/modifiers/roughen.pyx b/raysect/optical/material/modifiers/roughen.pyx index d705b1f7..88cfd5b4 100644 --- a/raysect/optical/material/modifiers/roughen.pyx +++ b/raysect/optical/material/modifiers/roughen.pyx @@ -36,7 +36,8 @@ from raysect.optical.material cimport Material # sets the maximum number of attempts to find a valid perturbed normal # it is highly unlikely (REALLY!) this number will ever be reached, it is just there for my paranoia # in the worst case 50% of the random hemisphere will always generate a valid solution... so P(fail) < 0.5^50! -DEF SAMPLE_ATTEMPTS = 50 +cdef enum: + SAMPLE_ATTEMPTS = 50 cdef HemisphereCosineSampler hemisphere_sampler = HemisphereCosineSampler() diff --git a/raysect/optical/observer/imaging/targetted_ccd.pyx b/raysect/optical/observer/imaging/targetted_ccd.pyx index cb355626..e14aaefb 100644 --- a/raysect/optical/observer/imaging/targetted_ccd.pyx +++ b/raysect/optical/observer/imaging/targetted_ccd.pyx @@ -40,7 +40,7 @@ from raysect.optical.observer.base cimport Observer2D cimport cython -DEF R_2_PI = 0.15915494309189535 # 1 / (2 * pi) +cdef const double R_2_PI = 0.15915494309189535 # 1 / (2 * pi) cdef class TargettedCCDArray(Observer2D): diff --git a/raysect/optical/observer/nonimaging/fibreoptic.pyx b/raysect/optical/observer/nonimaging/fibreoptic.pyx index c3fde275..f5592d2e 100644 --- a/raysect/optical/observer/nonimaging/fibreoptic.pyx +++ b/raysect/optical/observer/nonimaging/fibreoptic.pyx @@ -40,7 +40,7 @@ cimport cython # 1 / (2 * PI) -DEF RECIP_2_PI = 0.15915494309189535 +cdef const double RECIP_2_PI = 0.15915494309189535 # TODO - provide a function for angular fall off for collection, instead of acceptance cone. diff --git a/raysect/optical/observer/nonimaging/mesh_camera.pyx b/raysect/optical/observer/nonimaging/mesh_camera.pyx index 79b5aabf..5c2d3adf 100644 --- a/raysect/optical/observer/nonimaging/mesh_camera.pyx +++ b/raysect/optical/observer/nonimaging/mesh_camera.pyx @@ -44,13 +44,14 @@ from raysect.primitive.mesh.mesh cimport Mesh cimport cython # convenience defines -DEF X = 0 -DEF Y = 1 -DEF Z = 2 - -DEF V1 = 0 -DEF V2 = 1 -DEF V3 = 2 +cdef enum: + X = 0 + Y = 1 + Z = 2 + + V1 = 0 + V2 = 1 + V3 = 2 cdef class MeshCamera(Observer1D): diff --git a/raysect/optical/observer/nonimaging/mesh_pixel.pyx b/raysect/optical/observer/nonimaging/mesh_pixel.pyx index 61a7479a..adbcce89 100644 --- a/raysect/optical/observer/nonimaging/mesh_pixel.pyx +++ b/raysect/optical/observer/nonimaging/mesh_pixel.pyx @@ -44,13 +44,14 @@ from raysect.primitive.mesh.mesh cimport Mesh cimport cython # convenience defines -DEF X = 0 -DEF Y = 1 -DEF Z = 2 +cdef enum: + X = 0 + Y = 1 + Z = 2 -DEF V1 = 0 -DEF V2 = 1 -DEF V3 = 2 + V1 = 0 + V2 = 1 + V3 = 2 cdef class MeshPixel(Observer0D): @@ -167,8 +168,8 @@ cdef class MeshPixel(Observer0D): self._collection_area = 0.0 self._cdf = np.zeros(self._triangles_mv.shape[0]) self._cdf_mv = self._cdf - - # calculate cumulative and total area simultaneously + + # calculate cumulative and total area simultaneously for i in range(self._triangles_mv.shape[0]): # obtain vertex indices @@ -184,7 +185,7 @@ cdef class MeshPixel(Observer0D): ) self._collection_area += triangle_area self._cdf_mv[i] = self._collection_area - + # normalise cumulative area to make cdf self._cdf /= self._collection_area diff --git a/raysect/optical/observer/nonimaging/targetted_pixel.pyx b/raysect/optical/observer/nonimaging/targetted_pixel.pyx index b83b07a0..186a5312 100644 --- a/raysect/optical/observer/nonimaging/targetted_pixel.pyx +++ b/raysect/optical/observer/nonimaging/targetted_pixel.pyx @@ -39,7 +39,7 @@ from raysect.optical.observer.pipeline.spectral import SpectralPowerPipeline0D cimport cython -DEF R_2_PI = 0.15915494309189535 # 1 / (2 * pi) +cdef const double R_2_PI = 0.15915494309189535 # 1 / (2 * pi) cdef class TargettedPixel(Observer0D): diff --git a/raysect/optical/ray.pyx b/raysect/optical/ray.pyx index d45e7588..2da22e8f 100644 --- a/raysect/optical/ray.pyx +++ b/raysect/optical/ray.pyx @@ -29,7 +29,7 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from libc.math cimport M_PI as PI, asin, cos +from libc.math cimport M_PI as PI, asin, cos, INFINITY from raysect.core cimport Intersection from raysect.core.math.random cimport probability @@ -39,9 +39,6 @@ from raysect.optical.spectrum cimport new_spectrum from raysect.optical.scenegraph cimport Primitive cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - cdef class Ray(CoreRay): """ diff --git a/raysect/optical/spectrum.pyx b/raysect/optical/spectrum.pyx index 4111ab77..5f1c3c8f 100644 --- a/raysect/optical/spectrum.pyx +++ b/raysect/optical/spectrum.pyx @@ -34,7 +34,7 @@ from raysect.core.math.cython cimport integrate, interpolate from numpy cimport PyArray_SimpleNew, PyArray_FILLWBYTE, NPY_FLOAT64, npy_intp, import_array # Plank's constant * speed of light in a vacuum -DEF CONSTANT_HC = 1.9864456832693028e-25 +cdef const double CONSTANT_HC = 1.9864456832693028e-25 # required by numpy c-api import_array() diff --git a/raysect/primitive/box.pyx b/raysect/primitive/box.pyx index 01d95d09..05720259 100644 --- a/raysect/primitive/box.pyx +++ b/raysect/primitive/box.pyx @@ -30,28 +30,26 @@ # POSSIBILITY OF SUCH DAMAGE. from raysect.core cimport new_point3d, Normal3D, new_normal3d, AffineMatrix3D, Material, new_intersection, BoundingBox3D -from libc.math cimport fabs +from libc.math cimport fabs, INFINITY cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-9 +cdef const double BOX_PADDING = 1e-9 # additional ray distance to avoid re-hitting the same surface point -DEF EPSILON = 1e-9 - -# slab face enumeration -DEF NO_FACE = -1 -DEF LOWER_FACE = 0 -DEF UPPER_FACE = 1 - -# axis enumeration -DEF NO_AXIS = -1 -DEF X_AXIS = 0 -DEF Y_AXIS = 1 -DEF Z_AXIS = 2 +cdef const double EPSILON = 1e-9 + +cdef enum: + # slab face enumeration + NO_FACE = -1 + LOWER_FACE = 0 + UPPER_FACE = 1 + + # axis enumeration + NO_AXIS = -1 + X_AXIS = 0 + Y_AXIS = 1 + Z_AXIS = 2 cdef class Box(Primitive): diff --git a/raysect/primitive/cone.pyx b/raysect/primitive/cone.pyx index d1da1c9f..abe8c513 100644 --- a/raysect/primitive/cone.pyx +++ b/raysect/primitive/cone.pyx @@ -31,22 +31,20 @@ from raysect.core cimport new_point3d, Point3D, new_normal3d, AffineMatrix3D, Material, new_intersection, BoundingBox3D from raysect.core.math.cython cimport solve_quadratic, swap_double, swap_int -from libc.math cimport sqrt +from libc.math cimport sqrt, INFINITY cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-9 +cdef const double BOX_PADDING = 1e-9 # additional ray distance to avoid re-hitting the same surface point -DEF EPSILON = 1e-9 +cdef const double EPSILON = 1e-9 # object type enumeration -DEF NO_TYPE = -1 -DEF CONE = 0 -DEF BASE = 1 +cdef enum: + NO_TYPE = -1 + CONE = 0 + BASE = 1 cdef class Cone(Primitive): diff --git a/raysect/primitive/csg.pyx b/raysect/primitive/csg.pyx index 64ab3e9a..310f7db0 100644 --- a/raysect/primitive/csg.pyx +++ b/raysect/primitive/csg.pyx @@ -31,13 +31,12 @@ # TODO: add more advanced material handling +from libc.math cimport INFINITY + from raysect.core cimport _NodeBase, ChangeSignal, Material, new_ray, new_intersection, Point3D, AffineMatrix3D, BoundingBox3D # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-9 - -# cython doesn't have a built in infinity definition -DEF INFINITY = 1e999 +cdef const double BOX_PADDING = 1e-9 cdef class CSGPrimitive(Primitive): @@ -112,7 +111,7 @@ cdef class CSGPrimitive(Primitive): def primitive_b(self): """ Component primitive B of the compound CSG primitive. - + :rtype: Primitive """ return self._primitive_b.primitive diff --git a/raysect/primitive/cylinder.pyx b/raysect/primitive/cylinder.pyx index 9030e28d..8bc5804e 100644 --- a/raysect/primitive/cylinder.pyx +++ b/raysect/primitive/cylinder.pyx @@ -31,27 +31,25 @@ from raysect.core cimport AffineMatrix3D, new_normal3d, new_point3d, new_vector3d, Material, new_intersection, BoundingBox3D from raysect.core.math.cython cimport solve_quadratic, swap_double -from libc.math cimport sqrt, fabs +from libc.math cimport sqrt, fabs, INFINITY cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-9 +cdef const double BOX_PADDING = 1e-9 # additional ray distance to avoid re-hitting the same surface point -DEF EPSILON = 1e-9 - -# object type enumeration -DEF NO_TYPE = -1 -DEF CYLINDER = 0 -DEF SLAB = 1 - -# slab face enumeration -DEF NO_FACE = -1 -DEF LOWER_FACE = 0 -DEF UPPER_FACE = 1 +cdef const double EPSILON = 1e-9 + +cdef enum: + # object type enumeration + NO_TYPE = -1 + CYLINDER = 0 + SLAB = 1 + + # slab face enumeration + NO_FACE = -1 + LOWER_FACE = 0 + UPPER_FACE = 1 cdef class Cylinder(Primitive): diff --git a/raysect/primitive/lens/spherical.pyx b/raysect/primitive/lens/spherical.pyx index 31ade37b..96cd5eed 100644 --- a/raysect/primitive/lens/spherical.pyx +++ b/raysect/primitive/lens/spherical.pyx @@ -40,7 +40,7 @@ from libc.math cimport sqrt Basic spherical lens primitives. """ -DEF PADDING = 0.000001 +cdef const double PADDING = 0.000001 cdef class BiConvex(EncapsulatedPrimitive): @@ -128,7 +128,7 @@ cdef class BiConvex(EncapsulatedPrimitive): cdef bint _is_short(self): """ - Do the facing spheres overlap sufficiently to build a lens using just their intersection? + Do the facing spheres overlap sufficiently to build a lens using just their intersection? """ cdef double available_thickness = min( @@ -326,7 +326,7 @@ cdef class PlanoConvex(EncapsulatedPrimitive): # attach to local root (performed in EncapsulatedPrimitive init) super().__init__(lens, parent, transform, material, name) - + cdef void _calc_geometry(self): cdef double radius, radius_sqr @@ -343,7 +343,7 @@ cdef class PlanoConvex(EncapsulatedPrimitive): cdef bint _is_short(self): """ - Does the front sphere have sufficient radius to build the lens with just an intersection? + Does the front sphere have sufficient radius to build the lens with just an intersection? """ cdef double available_thickness = 2 * (self.curvature - self.curve_thickness) @@ -548,7 +548,7 @@ cdef class Meniscus(EncapsulatedPrimitive): cdef bint _is_short(self): """ - Does the front sphere have sufficient radius to build the lens with just an intersection? + Does the front sphere have sufficient radius to build the lens with just an intersection? """ cdef double available_thickness = 2 * self.front_curvature - self.front_thickness diff --git a/raysect/primitive/mesh/mesh.pyx b/raysect/primitive/mesh/mesh.pyx index ac27ddf7..1eec0412 100644 --- a/raysect/primitive/mesh/mesh.pyx +++ b/raysect/primitive/mesh/mesh.pyx @@ -35,7 +35,7 @@ import struct from numpy import array, float32, int32, zeros from raysect.core cimport Primitive, AffineMatrix3D, Normal3D, new_normal3d, Point3D, new_point3d, Vector3D, new_vector3d, Material, Ray, new_ray, Intersection, new_intersection, BoundingBox3D, new_boundingbox3d from raysect.core.math.spatial cimport KDTree3DCore, Item3D -from libc.math cimport fabs +from libc.math cimport fabs, INFINITY from numpy cimport float32_t, int32_t, uint8_t from cpython.bytes cimport PyBytes_AsString cimport cython @@ -45,37 +45,35 @@ The ray-triangle intersection used for the Mesh primitive is an implementation o "Watertight Ray/Triangle Intersection", S.Woop, C.Benthin, I.Wald, Journal of Computer Graphics Techniques (2013), Vol.2, No. 1 """ -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-6 +cdef const double BOX_PADDING = 1e-6 # additional ray distance to avoid re-hitting the same surface point -DEF EPSILON = 1e-6 +cdef const double EPSILON = 1e-6 # convenience defines -DEF X = 0 -DEF Y = 1 -DEF Z = 2 +cdef enum: + X = 0 + Y = 1 + Z = 2 -DEF U = 0 -DEF V = 1 -DEF W = 2 -DEF T = 3 + U = 0 + V = 1 + W = 2 + T = 3 -DEF V1 = 0 -DEF V2 = 1 -DEF V3 = 2 -DEF N1 = 3 -DEF N2 = 4 -DEF N3 = 5 + V1 = 0 + V2 = 1 + V3 = 2 + N1 = 3 + N2 = 4 + N3 = 5 -DEF NO_INTERSECTION = -1 + NO_INTERSECTION = -1 -# raysect mesh format constants -DEF RSM_VERSION_MAJOR = 1 -DEF RSM_VERSION_MINOR = 0 + # raysect mesh format constants + RSM_VERSION_MAJOR = 1 + RSM_VERSION_MINOR = 0 cdef class MeshIntersection(Intersection): @@ -279,9 +277,9 @@ cdef class MeshData(KDTree3DCore): cpdef Point3D vertex(self, int index): """ Returns the specified vertex. - + :param index: The vertex index. - :return: A Point3D object. + :return: A Point3D object. """ if index < 0 or index >= self.vertices_mv.shape[0]: @@ -296,13 +294,13 @@ cdef class MeshData(KDTree3DCore): cpdef ndarray triangle(self, int index): """ Returns the specified triangle. - - The returned data will either be a 3 or 6 element numpy array. The + + The returned data will either be a 3 or 6 element numpy array. The first three element are the triangle's vertex indices. If present, the last three elements are the triangle's vertex normal indices. - + :param index: The triangle index. - :return: A numpy array. + :return: A numpy array. """ if index < 0 or index >= self.vertices_mv.shape[0]: @@ -316,9 +314,9 @@ cdef class MeshData(KDTree3DCore): cpdef Normal3D vertex_normal(self, int index): """ Returns the specified vertex normal. - + :param index: The vertex normal's index. - :return: A Normal3D object. + :return: A Normal3D object. """ if self._vertex_normals is None: @@ -339,9 +337,9 @@ cdef class MeshData(KDTree3DCore): cpdef Normal3D face_normal(self, int index): """ Returns the specified face normal. - + :param index: The face normal's index. - :return: A Normal3D object. + :return: A Normal3D object. """ if index < 0 or index >= self.face_normals_mv.shape[0]: diff --git a/raysect/primitive/parabola.pyx b/raysect/primitive/parabola.pyx index 74f9a6e7..bb85a786 100644 --- a/raysect/primitive/parabola.pyx +++ b/raysect/primitive/parabola.pyx @@ -31,23 +31,21 @@ from raysect.core cimport new_point3d, Point3D, new_normal3d, AffineMatrix3D, Material, new_intersection, BoundingBox3D from raysect.core.math.cython cimport solve_quadratic, swap_double, swap_int -from libc.math cimport sqrt +from libc.math cimport sqrt, INFINITY cimport cython -# cython doesn't have a built-in infinity constant, this compiles to +infinity -DEF INFINITY = 1e999 - # bounding box is padded by a small amount to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-9 +cdef const double BOX_PADDING = 1e-9 # TODO - Perhaps should be calculated based on primitive scale # additional ray distance to avoid re-hitting the same surface point -DEF EPSILON = 1e-9 +cdef const double EPSILON = 1e-9 # object type enumeration -DEF NO_TYPE = -1 -DEF PARABOLA = 0 -DEF BASE = 1 +cdef enum: + NO_TYPE = -1 + PARABOLA = 0 + BASE = 1 cdef class Parabola(Primitive): diff --git a/raysect/primitive/sphere.pyx b/raysect/primitive/sphere.pyx index 081ea3e6..7b43766d 100644 --- a/raysect/primitive/sphere.pyx +++ b/raysect/primitive/sphere.pyx @@ -35,11 +35,11 @@ from raysect.core.math.cython cimport solve_quadratic, swap_double # bounding box and sphere are padded by small amounts to avoid numerical accuracy issues -DEF BOX_PADDING = 1e-9 -DEF SPHERE_PADDING = 1.000000001 +cdef const double BOX_PADDING = 1e-9 +cdef const double SPHERE_PADDING = 1.000000001 # additional ray distance to avoid re-hitting the same surface point -DEF EPSILON = 1e-9 +cdef const double EPSILON = 1e-9 cdef class Sphere(Primitive): From 4bf12d8a8aad18b11d0d96e90733aa330df97508 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Fri, 18 Jul 2025 13:56:28 -0400 Subject: [PATCH 25/77] refactor: remove redundant DEF constants for R_2_PI and R_4_PI --- raysect/core/math/sampler/solidangle.pxd | 3 --- 1 file changed, 3 deletions(-) diff --git a/raysect/core/math/sampler/solidangle.pxd b/raysect/core/math/sampler/solidangle.pxd index 146b6072..0257e312 100644 --- a/raysect/core/math/sampler/solidangle.pxd +++ b/raysect/core/math/sampler/solidangle.pxd @@ -34,9 +34,6 @@ from raysect.core.math cimport Point2D, new_point2d, Point3D, new_point3d, Vecto from raysect.core.math.random cimport uniform from raysect.core.math.cython cimport barycentric_coords, barycentric_interpolation -DEF R_2_PI = 0.15915494309189535 # 1 / (2 * pi) -DEF R_4_PI = 0.07957747154594767 # 1 / (4 * pi) - cdef class SolidAngleSampler: From a87eb2dfe171e70239d1053ba3a76af7c0fa15a0 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Fri, 18 Jul 2025 13:56:40 -0400 Subject: [PATCH 26/77] refactor: remove unused constants and clean up docstring formatting --- raysect/core/math/transform.pyx | 42 +++++++++++++++------------------ 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/raysect/core/math/transform.pyx b/raysect/core/math/transform.pyx index 80e157e5..a7e3f7f0 100644 --- a/raysect/core/math/transform.pyx +++ b/raysect/core/math/transform.pyx @@ -34,13 +34,9 @@ from raysect.core.math.affinematrix cimport new_affinematrix3d from raysect.core.math.point cimport new_point3d cimport cython - cdef const double RAD2DEG = 57.29577951308232000 # 180 / pi cdef const double DEG2RAD = 0.017453292519943295 # pi / 180 -FORWARD = 'forward' -UP = 'up' - cpdef AffineMatrix3D translate(double x, double y, double z): """ @@ -291,16 +287,16 @@ cpdef AffineMatrix3D rotate_basis(Vector3D forward, Vector3D up): cpdef tuple to_cylindrical(Point3D point): """ - Convert the given 3D point in cartesian space to cylindrical coordinates. - + Convert the given 3D point in cartesian space to cylindrical coordinates. + :param Point3D point: The 3D point to be transformed into cylindrical coordinates. :rtype: tuple :return: A tuple of r, z, phi coordinates. - + .. code-block:: pycon - + >>> from raysect.core.math import to_cylindrical, Point3D - + >>> point = Point3D(1, 1, 1) >>> to_cylindrical(point) (1.4142135623730951, 1.0, 45.0) @@ -317,15 +313,15 @@ cpdef tuple to_cylindrical(Point3D point): cpdef Point3D from_cylindrical(double r, double z, double phi): """ Convert a 3D point in cylindrical coordinates to a point in cartesian coordinates. - + :param float r: The radial coordinate. :param float z: The z-axis height coordinate. :param float phi: The azimuthal coordinate in degrees. :rtype: Point3D :return: A Point3D in cartesian space. - + .. code-block:: pycon - + >>> from raysect.core.math import from_cylindrical >>> from_cylindrical(1, 0, 45) @@ -347,21 +343,21 @@ cpdef Point3D from_cylindrical(double r, double z, double phi): cpdef (double, double, double) extract_rotation(AffineMatrix3D m, bint z_up=False): """ Extracts the rotation component of the affine matrix. - + The yaw, pitch and roll can be extracted for two common coordinate conventions by specifying the z_axis orientation: - - forward: +ve z is forward, +ve y is up, +ve x is left + + forward: +ve z is forward, +ve y is up, +ve x is left up: +ve z is up, +ve y is left, +ve x is forward The Raysect default is z axis forward. This function can be switched - to z axis up by setting the z_up parameter to True. - + to z axis up by setting the z_up parameter to True. + The matrix must consist of only rotation and translation operations. - + :param AffineMatrix3D m: An affine matrix. - :param bint z_up: Is the z-axis pointed upwards (default=False). - :return: A tuple containing (yaw, pitch, roll). + :param bint z_up: Is the z-axis pointed upwards (default=False). + :return: A tuple containing (yaw, pitch, roll). """ cdef double yaw, pitch, roll @@ -384,11 +380,11 @@ cpdef (double, double, double) extract_rotation(AffineMatrix3D m, bint z_up=Fals cpdef (double, double, double) extract_translation(AffineMatrix3D m): """ Extracts the translation component of the affine matrix. - + The matrix must consist of only rotation and translation operations. - :param AffineMatrix3D m: An affine matrix. - :return: tuple containing the x, y and z components of the translation. + :param AffineMatrix3D m: An affine matrix. + :return: tuple containing the x, y and z components of the translation. """ return m.get_element(0, 3), m.get_element(1, 3), m.get_element(2, 3) From 1ad5da53fd3dd857dcd7698e2302a4dcec8919e0 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Fri, 18 Jul 2025 13:56:53 -0400 Subject: [PATCH 27/77] chore: update cython version constraint to 3.1 in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a917a861..2e9e4bdb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,3 @@ [build-system] -requires = ["setuptools>=42.0", "wheel", "oldest-supported-numpy", "cython~=3.0"] +requires = ["setuptools>=42.0", "wheel", "oldest-supported-numpy", "cython~=3.1"] build-backend = "setuptools.build_meta" From cae19f37bde49ed34410eaa7a115f3c07506ff33 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 12:35:20 +0100 Subject: [PATCH 28/77] Update copyright date. --- dev/templates/template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/templates/template.py b/dev/templates/template.py index 287daa1d..61f12ff5 100644 --- a/dev/templates/template.py +++ b/dev/templates/template.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2018, , Raysect Project +# Copyright (c) 2014-2025, , Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without From fae8fb0fa61ac0335d6a4feece6a092088368b8a Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 16:50:18 +0100 Subject: [PATCH 29/77] Implemented a tool to automatically generate meson.build files through the project. --- dev/generate_meson_files.py | 177 ++++++++++++++++++++++++++++++++++++ dev/root-meson.build | 5 + dev/subdir-meson.build | 25 +++++ 3 files changed, 207 insertions(+) create mode 100755 dev/generate_meson_files.py create mode 100644 dev/root-meson.build create mode 100644 dev/subdir-meson.build diff --git a/dev/generate_meson_files.py b/dev/generate_meson_files.py new file mode 100755 index 00000000..932be305 --- /dev/null +++ b/dev/generate_meson_files.py @@ -0,0 +1,177 @@ +#!/bin/env python + +from pathlib import Path + +PACKAGES = ['raysect'] +SUBDIR_EXCLUSION_FILENAME = '.meson-exclude' + + +def generate_meson_files(packages): + """ + Generates the meson.build files for the project from a set of template files. + + A root build.meson will be placed in the project root and meson.build files will be generated for the specified + package folders. This script must be executed from the root folder of the project. + + If substantive changes are needed to the meson.build files throughout the project, it will be easier to modify the + templates and trigger the regeneration process. + + This script will remove any existing meson.build files, so be sure any changes are captured before re-running this + script. There are two template files: + + * root-meson.build + * subdir-meson.build + + The root-meson.build file is used to generate the root meson.build for the project. The subdir-meson.build is used + to generate the meson.build files in the project sub-directory. The templates are read and handled as a python + f-strings. See the script implementation for the variables available to the template. The meson.build files will + consist of the template followed by a set of subdir() entries for each descendant of the current sub-directory + (if not excluded). + + A sub-directory may be excluded from the generation process by placing a file in the subfolder called + ".meson-exclude". If the exclusion file is found, the sub-directory and its descendants will be ignored during + the generation process. + + :param packages: A list of package names. + """ + + root_path = Path('.') + package_paths = [Path(package) for package in packages] + + # Walk the project folder and specified packages to remove all meson.build files. + # Any stale meson.build files found in excluded directories are also removed. + _remove_meson_files(root_path, subdirs=package_paths) + + # Add root meson.build file. + _install_root_meson_file(root_path, subdirs=package_paths) + + # Walk the specified packages and add the sub-directory meson.build files. + for path in package_paths: + _install_subdir_meson_files(path) + + +def _remove_meson_files(path, subdirs=None): + """ + Removes any meson.build files found under the directory tree referenced by path. + + By default, this function recurses through the entire directory tree under the supplied path. If sub-dirs is + provided, then only the specified sub-directories will be explored. + """ + + # validate + if not path.is_dir(): + raise ValueError('The supplied path is not a directory.') + + if subdirs and any([not subdir.is_dir() for subdir in subdirs]): + raise ValueError('The list of sub-directories must only contain paths to valid directories.') + + # remove meson.build in this directory + meson_file = path / 'meson.build' + meson_file.unlink(missing_ok=True) + + # generate a list of subdirectories if none supplied + if not subdirs: + subdirs = [child for child in path.iterdir() if child.is_dir()] + + # recurse into sub-directories + for subdir in subdirs: + _remove_meson_files(subdir) + + +def _install_root_meson_file(path, subdirs): + + # validate + if not path.is_dir(): + raise ValueError('The supplied path is not a directory.') + + if any([not subdir.is_dir() for subdir in subdirs]): + raise ValueError('The list of sub-directories must only contain paths to valid directories.') + + meson_file = path / 'meson.build' + meson_file.write_text(_generate_root_meson_file(subdirs)) + + +def _install_subdir_meson_files(path): + + # validate + if not path.is_dir(): + raise ValueError('The supplied path is not a directory.') + + # generate a list of subdirectories, filtering excluded + # todo: filter pycache files etc.. + subdirs = [child for child in path.iterdir() if child.is_dir() and not (child / SUBDIR_EXCLUSION_FILENAME).exists()] + + # write meson file + meson_file = path / 'meson.build' + meson_file.write_text(_generate_subdir_meson_file(path, subdirs)) + + # recurse into sub-directories + for subdir in subdirs: + _install_subdir_meson_files(subdir) + + +def _generate_root_meson_file(subdirs): + + # read template + template_path = Path(__file__).parent / 'root-meson.build' + contents = template_path.read_text() + + # add subdir entries + contents += '\n' + for subdir in subdirs: + contents += f'subdir(\'{subdir.name}\')\n' + + return contents + + +def _generate_subdir_meson_file(path, subdirs): + + # read template + template_path = Path(__file__).parent / 'subdir-meson.build' + template = template_path.read_text() + + # build file lists + pyx = [] + pxd = [] + py = [] + data = [] + for child in path.iterdir(): + + if child.is_dir(): + continue + + elif child.suffix == '.pyx': + pyx.append(child.name) + + elif child.suffix == '.pxd': + pxd.append(child.name) + + elif child.suffix == '.py': + py.append(child.name) + + else: + data.append(child.name) + + # fill in template entries + contents = template.format( + target=f'\'{str(path)}\'', + pyx_files=str(pyx), + pxd_files=str(pxd), + py_files=str(py), + data_files=str(data) + ) + + # add subdir entries + contents += '\n' + for subdir in subdirs: + contents += f'subdir(\'{subdir.name}\')\n' + + return contents + + +if __name__ == '__main__': + + # get user confirmation + + + generate_meson_files(PACKAGES) \ No newline at end of file diff --git a/dev/root-meson.build b/dev/root-meson.build new file mode 100644 index 00000000..c591477a --- /dev/null +++ b/dev/root-meson.build @@ -0,0 +1,5 @@ +project('raysect', 'cython', default_options: ['python.install-env=auto']) + +py = import('python').find_installation(pure: false) +numpy = dependency('numpy', method: 'config-tool') +fs = import('fs') diff --git a/dev/subdir-meson.build b/dev/subdir-meson.build new file mode 100644 index 00000000..79d3d507 --- /dev/null +++ b/dev/subdir-meson.build @@ -0,0 +1,25 @@ +target_path = {target} + +# source files +py_files = {py_files} +pyx_files = {pyx_files} +pxd_files = {pxd_files} +data_files = {data_files} + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: numpy, + install: true, + subdir: target_path, + cython_args: ['--annotate'] + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) From cee8e97beb322fd709e5eaa8f8a3cc09d10a548c Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 16:50:59 +0100 Subject: [PATCH 30/77] Tidy up. --- dev/generate_meson_files.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dev/generate_meson_files.py b/dev/generate_meson_files.py index 932be305..da8f32a1 100755 --- a/dev/generate_meson_files.py +++ b/dev/generate_meson_files.py @@ -170,8 +170,4 @@ def _generate_subdir_meson_file(path, subdirs): if __name__ == '__main__': - - # get user confirmation - - generate_meson_files(PACKAGES) \ No newline at end of file From efc79fff8a49b68545b1423e35bd1a533c81309c Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 17:18:47 +0100 Subject: [PATCH 31/77] Updated build.sh and clean.sh scripts. Added abi.py to obtain the meson-python build folder. Added install_editable.sh to simplify installing the package in editable mode with verbose build information enabled for meson-python. Moved cython configuration to the root meson.build so it is shared across the project. --- dev/abi.py | 50 +++++++++++++++++++++++++++++++++++++++++ dev/build.sh | 7 +++--- dev/clean.sh | 10 ++++----- dev/install_editable.sh | 5 +++++ dev/root-meson.build | 3 +++ dev/subdir-meson.build | 4 ++-- 6 files changed, 69 insertions(+), 10 deletions(-) create mode 100755 dev/abi.py create mode 100755 dev/install_editable.sh diff --git a/dev/abi.py b/dev/abi.py new file mode 100755 index 00000000..ba72d14a --- /dev/null +++ b/dev/abi.py @@ -0,0 +1,50 @@ +#!/bin/env python + +import sys +import sysconfig +from typing import Union + +""" +Derived from the meson-python codebase. Original license terms: + +Copyright © 2022 the meson-python contributors +Copyright © 2021 Quansight Labs and Filipe Laíns + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + + +def get_cpython_abi() -> str: + version = sys.version_info + debug = pymalloc = '' + if version < (3, 8) and _get_config_var('WITH_PYMALLOC', True): + pymalloc = 'm' + return f'cp{version[0]}{version[1]}{debug}{pymalloc}' + + +def _get_config_var(name: str, default: Union[str, int, None] = None) -> Union[str, int, None]: + value: Union[str, int, None] = sysconfig.get_config_var(name) + if value is None: + return default + return value + + +if __name__ == '__main__': + print(get_cpython_abi()) diff --git a/dev/build.sh b/dev/build.sh index 732ccd5f..0cbd4336 100755 --- a/dev/build.sh +++ b/dev/build.sh @@ -1,6 +1,7 @@ #!/bin/bash +set -e # exit if an error occurs -CORES=`nproc --all` +BUILD_PATH="build/`dev/abi.py`" -echo "Rebuilding Raysect extension modules (in place)..." -python setup.py build_ext -j$CORES --inplace $1 $2 $3 $4 $5 +echo Rebuilding $BUILD_PATH... +meson compile -C $BUILD_PATH diff --git a/dev/clean.sh b/dev/clean.sh index 31ba744a..a6a5ca57 100755 --- a/dev/clean.sh +++ b/dev/clean.sh @@ -1,8 +1,8 @@ #!/bin/bash +set -e # exit if an error occurs -echo Removing all .c, .so and .html files... +BUILD_PATH="build/`dev/abi.py`" + +echo Cleaning $BUILD_PATH... +meson compile -C $BUILD_PATH --clean -find raysect -type f -name '*.c' -exec rm {} + -find raysect -type f -name '*.so' -exec rm {} + -find raysect -type f -name '*.html' -exec rm {} + -rm build -rf diff --git a/dev/install_editable.sh b/dev/install_editable.sh new file mode 100755 index 00000000..15c73653 --- /dev/null +++ b/dev/install_editable.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -e # exit if an error occurs + +echo Installing package as editable... +python -m pip install --config-settings=editable-verbose=true --no-build-isolation --upgrade --editable . \ No newline at end of file diff --git a/dev/root-meson.build b/dev/root-meson.build index c591477a..526acc1a 100644 --- a/dev/root-meson.build +++ b/dev/root-meson.build @@ -3,3 +3,6 @@ project('raysect', 'cython', default_options: ['python.install-env=auto']) py = import('python').find_installation(pure: false) numpy = dependency('numpy', method: 'config-tool') fs = import('fs') + +cython_args = ['--annotate'] +cython_dependencies = [numpy] diff --git a/dev/subdir-meson.build b/dev/subdir-meson.build index 79d3d507..951e96c0 100644 --- a/dev/subdir-meson.build +++ b/dev/subdir-meson.build @@ -11,10 +11,10 @@ foreach pyx_file: pyx_files py.extension_module( fs.replace_suffix(pyx_file, ''), pyx_file, - dependencies: numpy, + dependencies: cython_dependencies, install: true, subdir: target_path, - cython_args: ['--annotate'] + cython_args: cython_args ) endforeach From 8754c31be0beb6a9847e52df2d9cd62641354b63 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 17:45:01 +0100 Subject: [PATCH 32/77] Updated the changelog. --- CHANGELOG.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 1c23752b..36fe9096 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,20 @@ Raysect Changelog ================= +Release 0.9.0 (TBD) +------------------- + +Build changes: +* The legacy setuptools build system has been replaced with meson-python. + - The dev/build.sh and dev/clean.sh scripts have been updated to work with the new build system. + - A meson.build generator script is provided to automate the discovery of pyx, pxd,py and data files. Please see dev/generate_meson_files.py. + - When installed in editable mode, any modified pyx files will automatically trigger a rebuild when python attempts to import from the package. Please be aware that (as with the previous build system) changes to pxd files will require a clean rebuild of the project. + - A dev/install_editable.sh script is provided to simplify the installation of the package in editable mode with verbose build output enabled. + - Meson-python performs the build out of the project folder, so the project source folders will no longer be polluted with build artefacts. + - Cython build annotations are now always enabled, the annotation files can be found in the build folder under the build artefacts folder associated with each so file (*.so.a folder). +* The demos folder is no longer included in the raysect package to reduce its size. Please clone the source repository to obtain the demos. A dedicated raysect-demos package is being investigated for the future. + + Release 0.8.1 (12 Feb 2023) --------------------------- From 1e4f01ab947e451fae8f9e4ee08a3d08e29e053f Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 18:03:45 +0100 Subject: [PATCH 33/77] Added .meson-exclude to development scripts folders to prevent their inclusion in the build/built package. --- raysect/core/math/cython/interpolation/helper/.meson-exclude | 0 .../math/function/float/function1d/tests/scripts/.meson-exclude | 0 .../float/function2d/interpolate/tests/scripts/.meson-exclude | 0 .../float/function3d/interpolate/tests/scripts/.meson-exclude | 0 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 raysect/core/math/cython/interpolation/helper/.meson-exclude create mode 100644 raysect/core/math/function/float/function1d/tests/scripts/.meson-exclude create mode 100644 raysect/core/math/function/float/function2d/interpolate/tests/scripts/.meson-exclude create mode 100644 raysect/core/math/function/float/function3d/interpolate/tests/scripts/.meson-exclude diff --git a/raysect/core/math/cython/interpolation/helper/.meson-exclude b/raysect/core/math/cython/interpolation/helper/.meson-exclude new file mode 100644 index 00000000..e69de29b diff --git a/raysect/core/math/function/float/function1d/tests/scripts/.meson-exclude b/raysect/core/math/function/float/function1d/tests/scripts/.meson-exclude new file mode 100644 index 00000000..e69de29b diff --git a/raysect/core/math/function/float/function2d/interpolate/tests/scripts/.meson-exclude b/raysect/core/math/function/float/function2d/interpolate/tests/scripts/.meson-exclude new file mode 100644 index 00000000..e69de29b diff --git a/raysect/core/math/function/float/function3d/interpolate/tests/scripts/.meson-exclude b/raysect/core/math/function/float/function3d/interpolate/tests/scripts/.meson-exclude new file mode 100644 index 00000000..e69de29b From 0111aec1c1e51d40a4fa153a7f5e6dc3d3ac187d Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 18:04:33 +0100 Subject: [PATCH 34/77] Added meson.build files. --- meson.build | 10 ++++++ raysect/core/acceleration/meson.build | 27 ++++++++++++++++ raysect/core/acceleration/tests/meson.build | 26 ++++++++++++++++ .../math/cython/interpolation/meson.build | 26 ++++++++++++++++ raysect/core/math/cython/meson.build | 28 +++++++++++++++++ raysect/core/math/cython/tests/meson.build | 26 ++++++++++++++++ .../function/float/function1d/meson.build | 27 ++++++++++++++++ .../float/function1d/tests/data/meson.build | 26 ++++++++++++++++ .../float/function1d/tests/meson.build | 27 ++++++++++++++++ .../float/function2d/interpolate/meson.build | 27 ++++++++++++++++ .../interpolate/tests/data/meson.build | 26 ++++++++++++++++ .../function2d/interpolate/tests/meson.build | 27 ++++++++++++++++ .../function/float/function2d/meson.build | 28 +++++++++++++++++ .../float/function2d/tests/meson.build | 26 ++++++++++++++++ .../float/function3d/interpolate/meson.build | 27 ++++++++++++++++ .../interpolate/tests/data/meson.build | 26 ++++++++++++++++ .../function3d/interpolate/tests/meson.build | 27 ++++++++++++++++ .../function/float/function3d/meson.build | 28 +++++++++++++++++ .../float/function3d/tests/meson.build | 26 ++++++++++++++++ raysect/core/math/function/float/meson.build | 29 +++++++++++++++++ raysect/core/math/function/meson.build | 28 +++++++++++++++++ .../function/vector3d/function1d/meson.build | 27 ++++++++++++++++ .../vector3d/function1d/tests/meson.build | 26 ++++++++++++++++ .../function/vector3d/function2d/meson.build | 27 ++++++++++++++++ .../vector3d/function2d/tests/meson.build | 26 ++++++++++++++++ .../function/vector3d/function3d/meson.build | 27 ++++++++++++++++ .../vector3d/function3d/tests/meson.build | 26 ++++++++++++++++ .../core/math/function/vector3d/meson.build | 29 +++++++++++++++++ raysect/core/math/meson.build | 31 +++++++++++++++++++ raysect/core/math/sampler/meson.build | 26 ++++++++++++++++ raysect/core/math/spatial/meson.build | 26 ++++++++++++++++ raysect/core/math/tests/meson.build | 26 ++++++++++++++++ raysect/core/meson.build | 30 ++++++++++++++++++ raysect/core/scenegraph/meson.build | 27 ++++++++++++++++ raysect/core/scenegraph/tests/meson.build | 26 ++++++++++++++++ raysect/core/tests/meson.build | 26 ++++++++++++++++ raysect/meson.build | 30 ++++++++++++++++++ .../optical/library/components/meson.build | 26 ++++++++++++++++ .../optical/library/glass/data/meson.build | 26 ++++++++++++++++ raysect/optical/library/glass/meson.build | 27 ++++++++++++++++ raysect/optical/library/meson.build | 30 ++++++++++++++++++ .../optical/library/metal/data/meson.build | 26 ++++++++++++++++ raysect/optical/library/metal/meson.build | 27 ++++++++++++++++ raysect/optical/library/spectra/meson.build | 26 ++++++++++++++++ raysect/optical/material/emitter/meson.build | 26 ++++++++++++++++ raysect/optical/material/meson.build | 28 +++++++++++++++++ .../optical/material/modifiers/meson.build | 26 ++++++++++++++++ raysect/optical/meson.build | 30 ++++++++++++++++++ raysect/optical/observer/base/meson.build | 26 ++++++++++++++++ raysect/optical/observer/imaging/meson.build | 26 ++++++++++++++++ raysect/optical/observer/meson.build | 30 ++++++++++++++++++ .../optical/observer/nonimaging/meson.build | 26 ++++++++++++++++ raysect/optical/observer/pipeline/meson.build | 28 +++++++++++++++++ .../observer/pipeline/mono/meson.build | 26 ++++++++++++++++ .../observer/pipeline/spectral/meson.build | 26 ++++++++++++++++ raysect/optical/scenegraph/meson.build | 26 ++++++++++++++++ raysect/primitive/lens/meson.build | 27 ++++++++++++++++ raysect/primitive/lens/tests/meson.build | 26 ++++++++++++++++ raysect/primitive/mesh/meson.build | 26 ++++++++++++++++ raysect/primitive/meson.build | 28 +++++++++++++++++ 60 files changed, 1603 insertions(+) create mode 100644 meson.build create mode 100644 raysect/core/acceleration/meson.build create mode 100644 raysect/core/acceleration/tests/meson.build create mode 100644 raysect/core/math/cython/interpolation/meson.build create mode 100644 raysect/core/math/cython/meson.build create mode 100644 raysect/core/math/cython/tests/meson.build create mode 100644 raysect/core/math/function/float/function1d/meson.build create mode 100644 raysect/core/math/function/float/function1d/tests/data/meson.build create mode 100644 raysect/core/math/function/float/function1d/tests/meson.build create mode 100644 raysect/core/math/function/float/function2d/interpolate/meson.build create mode 100644 raysect/core/math/function/float/function2d/interpolate/tests/data/meson.build create mode 100644 raysect/core/math/function/float/function2d/interpolate/tests/meson.build create mode 100644 raysect/core/math/function/float/function2d/meson.build create mode 100644 raysect/core/math/function/float/function2d/tests/meson.build create mode 100644 raysect/core/math/function/float/function3d/interpolate/meson.build create mode 100644 raysect/core/math/function/float/function3d/interpolate/tests/data/meson.build create mode 100644 raysect/core/math/function/float/function3d/interpolate/tests/meson.build create mode 100644 raysect/core/math/function/float/function3d/meson.build create mode 100644 raysect/core/math/function/float/function3d/tests/meson.build create mode 100644 raysect/core/math/function/float/meson.build create mode 100644 raysect/core/math/function/meson.build create mode 100644 raysect/core/math/function/vector3d/function1d/meson.build create mode 100644 raysect/core/math/function/vector3d/function1d/tests/meson.build create mode 100644 raysect/core/math/function/vector3d/function2d/meson.build create mode 100644 raysect/core/math/function/vector3d/function2d/tests/meson.build create mode 100644 raysect/core/math/function/vector3d/function3d/meson.build create mode 100644 raysect/core/math/function/vector3d/function3d/tests/meson.build create mode 100644 raysect/core/math/function/vector3d/meson.build create mode 100644 raysect/core/math/meson.build create mode 100644 raysect/core/math/sampler/meson.build create mode 100644 raysect/core/math/spatial/meson.build create mode 100644 raysect/core/math/tests/meson.build create mode 100644 raysect/core/meson.build create mode 100644 raysect/core/scenegraph/meson.build create mode 100644 raysect/core/scenegraph/tests/meson.build create mode 100644 raysect/core/tests/meson.build create mode 100644 raysect/meson.build create mode 100644 raysect/optical/library/components/meson.build create mode 100644 raysect/optical/library/glass/data/meson.build create mode 100644 raysect/optical/library/glass/meson.build create mode 100644 raysect/optical/library/meson.build create mode 100644 raysect/optical/library/metal/data/meson.build create mode 100644 raysect/optical/library/metal/meson.build create mode 100644 raysect/optical/library/spectra/meson.build create mode 100644 raysect/optical/material/emitter/meson.build create mode 100644 raysect/optical/material/meson.build create mode 100644 raysect/optical/material/modifiers/meson.build create mode 100644 raysect/optical/meson.build create mode 100644 raysect/optical/observer/base/meson.build create mode 100644 raysect/optical/observer/imaging/meson.build create mode 100644 raysect/optical/observer/meson.build create mode 100644 raysect/optical/observer/nonimaging/meson.build create mode 100644 raysect/optical/observer/pipeline/meson.build create mode 100644 raysect/optical/observer/pipeline/mono/meson.build create mode 100644 raysect/optical/observer/pipeline/spectral/meson.build create mode 100644 raysect/optical/scenegraph/meson.build create mode 100644 raysect/primitive/lens/meson.build create mode 100644 raysect/primitive/lens/tests/meson.build create mode 100644 raysect/primitive/mesh/meson.build create mode 100644 raysect/primitive/meson.build diff --git a/meson.build b/meson.build new file mode 100644 index 00000000..688b2a43 --- /dev/null +++ b/meson.build @@ -0,0 +1,10 @@ +project('raysect', 'cython', default_options: ['python.install-env=auto']) + +py = import('python').find_installation(pure: false) +numpy = dependency('numpy', method: 'config-tool') +fs = import('fs') + +cython_args = ['--annotate'] +cython_dependencies = [numpy] + +subdir('raysect') diff --git a/raysect/core/acceleration/meson.build b/raysect/core/acceleration/meson.build new file mode 100644 index 00000000..59e011dc --- /dev/null +++ b/raysect/core/acceleration/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/core/acceleration' + +# source files +py_files = ['__init__.py'] +pyx_files = ['boundprimitive.pyx', 'unaccelerated.pyx', 'kdtree.pyx', 'accelerator.pyx'] +pxd_files = ['accelerator.pxd', 'unaccelerated.pxd', 'kdtree.pxd', 'boundprimitive.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('tests') diff --git a/raysect/core/acceleration/tests/meson.build b/raysect/core/acceleration/tests/meson.build new file mode 100644 index 00000000..b87c1f8d --- /dev/null +++ b/raysect/core/acceleration/tests/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/acceleration/tests' + +# source files +py_files = ['__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/math/cython/interpolation/meson.build b/raysect/core/math/cython/interpolation/meson.build new file mode 100644 index 00000000..4b6f4b99 --- /dev/null +++ b/raysect/core/math/cython/interpolation/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/math/cython/interpolation' + +# source files +py_files = [] +pyx_files = ['cubic.pyx', 'linear.pyx'] +pxd_files = ['cubic.pxd', 'linear.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/math/cython/meson.build b/raysect/core/math/cython/meson.build new file mode 100644 index 00000000..35b0773c --- /dev/null +++ b/raysect/core/math/cython/meson.build @@ -0,0 +1,28 @@ +target_path = 'raysect/core/math/cython' + +# source files +py_files = ['__init__.py'] +pyx_files = ['triangle.pyx', 'utility.pyx', 'transform.pyx', 'tetrahedra.pyx'] +pxd_files = ['tetrahedra.pxd', 'triangle.pxd', 'utility.pxd', '__init__.pxd', 'transform.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('tests') +subdir('interpolation') diff --git a/raysect/core/math/cython/tests/meson.build b/raysect/core/math/cython/tests/meson.build new file mode 100644 index 00000000..ac336cf0 --- /dev/null +++ b/raysect/core/math/cython/tests/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/math/cython/tests' + +# source files +py_files = ['test_utility.py', '__init__.py', 'test_tetrahedra.py', 'test_triangle.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/math/function/float/function1d/meson.build b/raysect/core/math/function/float/function1d/meson.build new file mode 100644 index 00000000..4925b349 --- /dev/null +++ b/raysect/core/math/function/float/function1d/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/core/math/function/float/function1d' + +# source files +py_files = ['__init__.py'] +pyx_files = ['arg.pyx', 'autowrap.pyx', 'samplers.pyx', 'base.pyx', 'blend.pyx', 'constant.pyx', 'cmath.pyx', 'interpolate.pyx'] +pxd_files = ['cmath.pxd', 'constant.pxd', 'autowrap.pxd', 'blend.pxd', 'samplers.pxd', 'base.pxd', 'interpolate.pxd', '__init__.pxd', 'arg.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('tests') diff --git a/raysect/core/math/function/float/function1d/tests/data/meson.build b/raysect/core/math/function/float/function1d/tests/data/meson.build new file mode 100644 index 00000000..69b60a18 --- /dev/null +++ b/raysect/core/math/function/float/function1d/tests/data/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/math/function/float/function1d/tests/data' + +# source files +py_files = ['interpolator1d_test_data.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/math/function/float/function1d/tests/meson.build b/raysect/core/math/function/float/function1d/tests/meson.build new file mode 100644 index 00000000..6a94862c --- /dev/null +++ b/raysect/core/math/function/float/function1d/tests/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/core/math/function/float/function1d/tests' + +# source files +py_files = ['test_autowrap.py', 'test_cmath.py', 'test_arg.py', 'test_samplers.py', 'test_constant.py', 'test_base.py', 'test_interpolator.py', '__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('data') diff --git a/raysect/core/math/function/float/function2d/interpolate/meson.build b/raysect/core/math/function/float/function2d/interpolate/meson.build new file mode 100644 index 00000000..eb1572ce --- /dev/null +++ b/raysect/core/math/function/float/function2d/interpolate/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/core/math/function/float/function2d/interpolate' + +# source files +py_files = ['__init__.py'] +pyx_files = ['interpolator2darray.pyx', 'common.pyx', 'interpolator2dmesh.pyx', 'discrete2dmesh.pyx'] +pxd_files = ['interpolator2darray.pxd', 'interpolator2dmesh.pxd', 'common.pxd', 'discrete2dmesh.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('tests') diff --git a/raysect/core/math/function/float/function2d/interpolate/tests/data/meson.build b/raysect/core/math/function/float/function2d/interpolate/tests/data/meson.build new file mode 100644 index 00000000..373d39b9 --- /dev/null +++ b/raysect/core/math/function/float/function2d/interpolate/tests/data/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/math/function/float/function2d/interpolate/tests/data' + +# source files +py_files = ['interpolator2d_test_data.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/math/function/float/function2d/interpolate/tests/meson.build b/raysect/core/math/function/float/function2d/interpolate/tests/meson.build new file mode 100644 index 00000000..0f9e6c7c --- /dev/null +++ b/raysect/core/math/function/float/function2d/interpolate/tests/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/core/math/function/float/function2d/interpolate/tests' + +# source files +py_files = ['test_interpolator_2d.py', '__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('data') diff --git a/raysect/core/math/function/float/function2d/meson.build b/raysect/core/math/function/float/function2d/meson.build new file mode 100644 index 00000000..2d18b795 --- /dev/null +++ b/raysect/core/math/function/float/function2d/meson.build @@ -0,0 +1,28 @@ +target_path = 'raysect/core/math/function/float/function2d' + +# source files +py_files = ['__init__.py'] +pyx_files = ['arg.pyx', 'autowrap.pyx', 'base.pyx', 'blend.pyx', 'constant.pyx', 'cmath.pyx'] +pxd_files = ['cmath.pxd', 'constant.pxd', 'autowrap.pxd', 'blend.pxd', 'base.pxd', '__init__.pxd', 'arg.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('tests') +subdir('interpolate') diff --git a/raysect/core/math/function/float/function2d/tests/meson.build b/raysect/core/math/function/float/function2d/tests/meson.build new file mode 100644 index 00000000..595ff898 --- /dev/null +++ b/raysect/core/math/function/float/function2d/tests/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/math/function/float/function2d/tests' + +# source files +py_files = ['test_autowrap.py', 'test_cmath.py', 'test_arg.py', 'test_constant.py', 'test_base.py', '__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/math/function/float/function3d/interpolate/meson.build b/raysect/core/math/function/float/function3d/interpolate/meson.build new file mode 100644 index 00000000..648afc02 --- /dev/null +++ b/raysect/core/math/function/float/function3d/interpolate/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/core/math/function/float/function3d/interpolate' + +# source files +py_files = ['__init__.py'] +pyx_files = ['common.pyx', 'discrete3dmesh.pyx', 'interpolator3darray.pyx'] +pxd_files = ['interpolator3darray.pxd', 'discrete3dmesh.pxd', 'common.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('tests') diff --git a/raysect/core/math/function/float/function3d/interpolate/tests/data/meson.build b/raysect/core/math/function/float/function3d/interpolate/tests/data/meson.build new file mode 100644 index 00000000..7e2c9a69 --- /dev/null +++ b/raysect/core/math/function/float/function3d/interpolate/tests/data/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/math/function/float/function3d/interpolate/tests/data' + +# source files +py_files = ['interpolator3d_test_data.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/math/function/float/function3d/interpolate/tests/meson.build b/raysect/core/math/function/float/function3d/interpolate/tests/meson.build new file mode 100644 index 00000000..e48168a9 --- /dev/null +++ b/raysect/core/math/function/float/function3d/interpolate/tests/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/core/math/function/float/function3d/interpolate/tests' + +# source files +py_files = ['test_interpolator_3d.py', '__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('data') diff --git a/raysect/core/math/function/float/function3d/meson.build b/raysect/core/math/function/float/function3d/meson.build new file mode 100644 index 00000000..07700699 --- /dev/null +++ b/raysect/core/math/function/float/function3d/meson.build @@ -0,0 +1,28 @@ +target_path = 'raysect/core/math/function/float/function3d' + +# source files +py_files = ['__init__.py'] +pyx_files = ['arg.pyx', 'autowrap.pyx', 'base.pyx', 'blend.pyx', 'constant.pyx', 'cmath.pyx'] +pxd_files = ['cmath.pxd', 'constant.pxd', 'autowrap.pxd', 'blend.pxd', 'base.pxd', '__init__.pxd', 'arg.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('tests') +subdir('interpolate') diff --git a/raysect/core/math/function/float/function3d/tests/meson.build b/raysect/core/math/function/float/function3d/tests/meson.build new file mode 100644 index 00000000..e1ea771c --- /dev/null +++ b/raysect/core/math/function/float/function3d/tests/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/math/function/float/function3d/tests' + +# source files +py_files = ['test_autowrap.py', 'test_cmath.py', 'test_arg.py', 'test_constant.py', 'test_base.py', '__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/math/function/float/meson.build b/raysect/core/math/function/float/meson.build new file mode 100644 index 00000000..3e9852c0 --- /dev/null +++ b/raysect/core/math/function/float/meson.build @@ -0,0 +1,29 @@ +target_path = 'raysect/core/math/function/float' + +# source files +py_files = ['__init__.py'] +pyx_files = ['base.pyx'] +pxd_files = ['base.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('function1d') +subdir('function3d') +subdir('function2d') diff --git a/raysect/core/math/function/meson.build b/raysect/core/math/function/meson.build new file mode 100644 index 00000000..e88dc437 --- /dev/null +++ b/raysect/core/math/function/meson.build @@ -0,0 +1,28 @@ +target_path = 'raysect/core/math/function' + +# source files +py_files = ['__init__.py'] +pyx_files = ['base.pyx'] +pxd_files = ['base.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('float') +subdir('vector3d') diff --git a/raysect/core/math/function/vector3d/function1d/meson.build b/raysect/core/math/function/vector3d/function1d/meson.build new file mode 100644 index 00000000..95868f10 --- /dev/null +++ b/raysect/core/math/function/vector3d/function1d/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/core/math/function/vector3d/function1d' + +# source files +py_files = ['__init__.py'] +pyx_files = ['autowrap.pyx', 'base.pyx', 'utility.pyx', 'blend.pyx', 'constant.pyx'] +pxd_files = ['constant.pxd', 'autowrap.pxd', 'utility.pxd', 'blend.pxd', 'base.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('tests') diff --git a/raysect/core/math/function/vector3d/function1d/tests/meson.build b/raysect/core/math/function/vector3d/function1d/tests/meson.build new file mode 100644 index 00000000..4ac3218f --- /dev/null +++ b/raysect/core/math/function/vector3d/function1d/tests/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/math/function/vector3d/function1d/tests' + +# source files +py_files = ['test_autowrap.py', 'test_float_to_vector3d.py', 'test_constant.py', 'test_base.py', '__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/math/function/vector3d/function2d/meson.build b/raysect/core/math/function/vector3d/function2d/meson.build new file mode 100644 index 00000000..2493844a --- /dev/null +++ b/raysect/core/math/function/vector3d/function2d/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/core/math/function/vector3d/function2d' + +# source files +py_files = ['__init__.py'] +pyx_files = ['autowrap.pyx', 'base.pyx', 'utility.pyx', 'blend.pyx', 'constant.pyx'] +pxd_files = ['constant.pxd', 'autowrap.pxd', 'utility.pxd', 'blend.pxd', 'base.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('tests') diff --git a/raysect/core/math/function/vector3d/function2d/tests/meson.build b/raysect/core/math/function/vector3d/function2d/tests/meson.build new file mode 100644 index 00000000..0d06ac65 --- /dev/null +++ b/raysect/core/math/function/vector3d/function2d/tests/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/math/function/vector3d/function2d/tests' + +# source files +py_files = ['test_autowrap.py', 'test_float_to_vector3d.py', 'test_constant.py', 'test_base.py', '__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/math/function/vector3d/function3d/meson.build b/raysect/core/math/function/vector3d/function3d/meson.build new file mode 100644 index 00000000..6e117736 --- /dev/null +++ b/raysect/core/math/function/vector3d/function3d/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/core/math/function/vector3d/function3d' + +# source files +py_files = ['__init__.py'] +pyx_files = ['autowrap.pyx', 'base.pyx', 'utility.pyx', 'blend.pyx', 'constant.pyx'] +pxd_files = ['constant.pxd', 'autowrap.pxd', 'utility.pxd', 'blend.pxd', 'base.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('tests') diff --git a/raysect/core/math/function/vector3d/function3d/tests/meson.build b/raysect/core/math/function/vector3d/function3d/tests/meson.build new file mode 100644 index 00000000..41a6e631 --- /dev/null +++ b/raysect/core/math/function/vector3d/function3d/tests/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/math/function/vector3d/function3d/tests' + +# source files +py_files = ['test_autowrap.py', 'test_float_to_vector3d.py', 'test_constant.py', 'test_base.py', '__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/math/function/vector3d/meson.build b/raysect/core/math/function/vector3d/meson.build new file mode 100644 index 00000000..b3275de3 --- /dev/null +++ b/raysect/core/math/function/vector3d/meson.build @@ -0,0 +1,29 @@ +target_path = 'raysect/core/math/function/vector3d' + +# source files +py_files = ['__init__.py'] +pyx_files = ['base.pyx'] +pxd_files = ['base.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('function1d') +subdir('function3d') +subdir('function2d') diff --git a/raysect/core/math/meson.build b/raysect/core/math/meson.build new file mode 100644 index 00000000..e1945434 --- /dev/null +++ b/raysect/core/math/meson.build @@ -0,0 +1,31 @@ +target_path = 'raysect/core/math' + +# source files +py_files = ['__init__.py'] +pyx_files = ['units.pyx', 'normal.pyx', 'point.pyx', 'polygon.pyx', 'quaternion.pyx', 'vector.pyx', '_mat4.pyx', 'random.pyx', '_vec3.pyx', 'transform.pyx', 'statsarray.pyx', 'affinematrix.pyx'] +pxd_files = ['units.pxd', 'random.pxd', 'point.pxd', '_vec3.pxd', 'normal.pxd', 'polygon.pxd', 'vector.pxd', 'statsarray.pxd', 'quaternion.pxd', 'affinematrix.pxd', '_mat4.pxd', '__init__.pxd', 'transform.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('function') +subdir('tests') +subdir('cython') +subdir('spatial') +subdir('sampler') diff --git a/raysect/core/math/sampler/meson.build b/raysect/core/math/sampler/meson.build new file mode 100644 index 00000000..63faac49 --- /dev/null +++ b/raysect/core/math/sampler/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/math/sampler' + +# source files +py_files = ['__init__.py'] +pyx_files = ['solidangle.pyx', 'targetted.pyx', 'surface3d.pyx'] +pxd_files = ['targetted.pxd', 'surface3d.pxd', 'solidangle.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/math/spatial/meson.build b/raysect/core/math/spatial/meson.build new file mode 100644 index 00000000..e1fee40b --- /dev/null +++ b/raysect/core/math/spatial/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/math/spatial' + +# source files +py_files = ['__init__.py'] +pyx_files = ['kdtree2d.pyx', 'kdtree3d.pyx'] +pxd_files = ['kdtree3d.pxd', 'kdtree2d.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/math/tests/meson.build b/raysect/core/math/tests/meson.build new file mode 100644 index 00000000..144922bb --- /dev/null +++ b/raysect/core/math/tests/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/math/tests' + +# source files +py_files = ['test_transform.py', 'test_random.py', 'test_normal3d.py', 'test_point3d.py', 'test_interaction3d.py', 'test_vector3d.py', 'test_affinematrix3d.py', 'test_quaternion.py', 'test_point2d.py', '__init__.py', 'test_vector2d.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/meson.build b/raysect/core/meson.build new file mode 100644 index 00000000..7c8bf71e --- /dev/null +++ b/raysect/core/meson.build @@ -0,0 +1,30 @@ +target_path = 'raysect/core' + +# source files +py_files = ['workflow.py', '__init__.py', 'constants.py'] +pyx_files = ['boundingsphere.pyx', 'material.pyx', 'containers.pyx', 'intersection.pyx', 'boundingbox.pyx', 'ray.pyx'] +pxd_files = ['material.pxd', 'containers.pxd', 'intersection.pxd', 'ray.pxd', 'boundingsphere.pxd', 'boundingbox.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('tests') +subdir('scenegraph') +subdir('math') +subdir('acceleration') diff --git a/raysect/core/scenegraph/meson.build b/raysect/core/scenegraph/meson.build new file mode 100644 index 00000000..a3d36f69 --- /dev/null +++ b/raysect/core/scenegraph/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/core/scenegraph' + +# source files +py_files = ['__init__.py'] +pyx_files = ['_nodebase.pyx', 'world.pyx', 'node.pyx', 'primitive.pyx', 'observer.pyx', 'utility.pyx', 'signal.pyx'] +pxd_files = ['_nodebase.pxd', 'observer.pxd', 'utility.pxd', 'world.pxd', 'primitive.pxd', 'signal.pxd', 'node.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('tests') diff --git a/raysect/core/scenegraph/tests/meson.build b/raysect/core/scenegraph/tests/meson.build new file mode 100644 index 00000000..c193f6d8 --- /dev/null +++ b/raysect/core/scenegraph/tests/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/scenegraph/tests' + +# source files +py_files = ['test_node.py', 'test_world.py', 'test_observer.py', '__init__.py', 'test_primitive.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/core/tests/meson.build b/raysect/core/tests/meson.build new file mode 100644 index 00000000..176dfeda --- /dev/null +++ b/raysect/core/tests/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/core/tests' + +# source files +py_files = ['__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/meson.build b/raysect/meson.build new file mode 100644 index 00000000..54f8f755 --- /dev/null +++ b/raysect/meson.build @@ -0,0 +1,30 @@ +target_path = 'raysect' + +# source files +py_files = ['__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = ['VERSION'] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('primitive') +subdir('__pycache__') +subdir('optical') +subdir('core') diff --git a/raysect/optical/library/components/meson.build b/raysect/optical/library/components/meson.build new file mode 100644 index 00000000..63029bf9 --- /dev/null +++ b/raysect/optical/library/components/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/optical/library/components' + +# source files +py_files = ['__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/optical/library/glass/data/meson.build b/raysect/optical/library/glass/data/meson.build new file mode 100644 index 00000000..5e8865b9 --- /dev/null +++ b/raysect/optical/library/glass/data/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/optical/library/glass/data' + +# source files +py_files = [] +pyx_files = [] +pxd_files = [] +data_files = ['schott_catalog_2000.csv', 'schott_catalog_2000_full.csv'] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/optical/library/glass/meson.build b/raysect/optical/library/glass/meson.build new file mode 100644 index 00000000..a65ac3ae --- /dev/null +++ b/raysect/optical/library/glass/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/optical/library/glass' + +# source files +py_files = ['schott.py', '__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('data') diff --git a/raysect/optical/library/meson.build b/raysect/optical/library/meson.build new file mode 100644 index 00000000..cde26f61 --- /dev/null +++ b/raysect/optical/library/meson.build @@ -0,0 +1,30 @@ +target_path = 'raysect/optical/library' + +# source files +py_files = ['__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('spectra') +subdir('components') +subdir('glass') +subdir('metal') diff --git a/raysect/optical/library/metal/data/meson.build b/raysect/optical/library/metal/data/meson.build new file mode 100644 index 00000000..2d2a137c --- /dev/null +++ b/raysect/optical/library/metal/data/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/optical/library/metal/data' + +# source files +py_files = ['convert_data.py'] +pyx_files = [] +pxd_files = [] +data_files = ['cobolt.json', 'tungsten.json', 'magnesium.json', 'sodium.json', 'silver.json', 'silicon.json', 'iron.json', 'palladium.json', 'gold.json', 'beryllium.json', 'lithium.json', 'copper.json', 'platinum.json', 'mercury.json', 'manganese.json', 'aluminium.json', 'titanium.json', 'nickel.json'] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/optical/library/metal/meson.build b/raysect/optical/library/metal/meson.build new file mode 100644 index 00000000..47bd36db --- /dev/null +++ b/raysect/optical/library/metal/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/optical/library/metal' + +# source files +py_files = ['metal.py', '__init__.py', 'roughmetal.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('data') diff --git a/raysect/optical/library/spectra/meson.build b/raysect/optical/library/spectra/meson.build new file mode 100644 index 00000000..81f4ea5f --- /dev/null +++ b/raysect/optical/library/spectra/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/optical/library/spectra' + +# source files +py_files = ['__init__.py', 'colours.py'] +pyx_files = ['blackbody.pyx'] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/optical/material/emitter/meson.build b/raysect/optical/material/emitter/meson.build new file mode 100644 index 00000000..931aaede --- /dev/null +++ b/raysect/optical/material/emitter/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/optical/material/emitter' + +# source files +py_files = ['__init__.py'] +pyx_files = ['checkerboard.pyx', 'uniform.pyx', 'anisotropic.pyx', 'unity.pyx', 'inhomogeneous.pyx', 'homogeneous.pyx'] +pxd_files = ['uniform.pxd', 'checkerboard.pxd', 'inhomogeneous.pxd', 'anisotropic.pxd', 'homogeneous.pxd', 'unity.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/optical/material/meson.build b/raysect/optical/material/meson.build new file mode 100644 index 00000000..d9bab581 --- /dev/null +++ b/raysect/optical/material/meson.build @@ -0,0 +1,28 @@ +target_path = 'raysect/optical/material' + +# source files +py_files = ['__init__.py'] +pyx_files = ['material.pyx', 'conductor.pyx', 'absorber.pyx', 'lambert.pyx', 'dielectric.pyx', 'debug.pyx'] +pxd_files = ['material.pxd', 'absorber.pxd', 'conductor.pxd', 'dielectric.pxd', 'debug.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('emitter') +subdir('modifiers') diff --git a/raysect/optical/material/modifiers/meson.build b/raysect/optical/material/modifiers/meson.build new file mode 100644 index 00000000..b436e879 --- /dev/null +++ b/raysect/optical/material/modifiers/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/optical/material/modifiers' + +# source files +py_files = ['__init__.py'] +pyx_files = ['roughen.pyx', 'add.pyx', 'blend.pyx', 'transform.pyx'] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/optical/meson.build b/raysect/optical/meson.build new file mode 100644 index 00000000..427d3ac4 --- /dev/null +++ b/raysect/optical/meson.build @@ -0,0 +1,30 @@ +target_path = 'raysect/optical' + +# source files +py_files = ['__init__.py'] +pyx_files = ['loggingray.pyx', 'spectralfunction.pyx', 'colour.pyx', 'spectrum.pyx', 'ray.pyx'] +pxd_files = ['colour.pxd', 'ray.pxd', 'spectralfunction.pxd', 'spectrum.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('material') +subdir('scenegraph') +subdir('observer') +subdir('library') diff --git a/raysect/optical/observer/base/meson.build b/raysect/optical/observer/base/meson.build new file mode 100644 index 00000000..8630abae --- /dev/null +++ b/raysect/optical/observer/base/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/optical/observer/base' + +# source files +py_files = ['__init__.py'] +pyx_files = ['processor.pyx', 'slice.pyx', 'observer.pyx', 'sampler.pyx', 'pipeline.pyx'] +pxd_files = ['pipeline.pxd', 'observer.pxd', 'slice.pxd', 'processor.pxd', '__init__.pxd', 'sampler.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/optical/observer/imaging/meson.build b/raysect/optical/observer/imaging/meson.build new file mode 100644 index 00000000..a4b89f75 --- /dev/null +++ b/raysect/optical/observer/imaging/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/optical/observer/imaging' + +# source files +py_files = ['__init__.py'] +pyx_files = ['targetted_ccd.pyx', 'ccd.pyx', 'vector.pyx', 'pinhole.pyx', 'opencv.pyx', 'orthographic.pyx'] +pxd_files = ['targetted_ccd.pxd', 'ccd.pxd', 'orthographic.pxd', 'vector.pxd', 'pinhole.pxd', 'opencv.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/optical/observer/meson.build b/raysect/optical/observer/meson.build new file mode 100644 index 00000000..631e3f78 --- /dev/null +++ b/raysect/optical/observer/meson.build @@ -0,0 +1,30 @@ +target_path = 'raysect/optical/observer' + +# source files +py_files = ['__init__.py'] +pyx_files = ['sampler1d.pyx', 'sampler2d.pyx'] +pxd_files = ['__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('base') +subdir('imaging') +subdir('nonimaging') +subdir('pipeline') diff --git a/raysect/optical/observer/nonimaging/meson.build b/raysect/optical/observer/nonimaging/meson.build new file mode 100644 index 00000000..29344683 --- /dev/null +++ b/raysect/optical/observer/nonimaging/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/optical/observer/nonimaging' + +# source files +py_files = ['__init__.py'] +pyx_files = ['pixel.pyx', 'mesh_camera.pyx', 'fibreoptic.pyx', 'mesh_pixel.pyx', 'sightline.pyx', 'targetted_pixel.pyx'] +pxd_files = ['pixel.pxd', 'mesh_camera.pxd', 'sightline.pxd', 'mesh_pixel.pxd', 'fibreoptic.pxd', '__init__.pxd', 'targetted_pixel.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/optical/observer/pipeline/meson.build b/raysect/optical/observer/pipeline/meson.build new file mode 100644 index 00000000..7719d89f --- /dev/null +++ b/raysect/optical/observer/pipeline/meson.build @@ -0,0 +1,28 @@ +target_path = 'raysect/optical/observer/pipeline' + +# source files +py_files = ['colormaps.py', '__init__.py'] +pyx_files = ['rgb.pyx', 'bayer.pyx'] +pxd_files = ['bayer.pxd', 'rgb.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('mono') +subdir('spectral') diff --git a/raysect/optical/observer/pipeline/mono/meson.build b/raysect/optical/observer/pipeline/mono/meson.build new file mode 100644 index 00000000..be8e64db --- /dev/null +++ b/raysect/optical/observer/pipeline/mono/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/optical/observer/pipeline/mono' + +# source files +py_files = ['__init__.py'] +pyx_files = ['radiance.pyx', 'power.pyx'] +pxd_files = ['power.pxd', 'radiance.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/optical/observer/pipeline/spectral/meson.build b/raysect/optical/observer/pipeline/spectral/meson.build new file mode 100644 index 00000000..a2f0b27e --- /dev/null +++ b/raysect/optical/observer/pipeline/spectral/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/optical/observer/pipeline/spectral' + +# source files +py_files = ['__init__.py'] +pyx_files = ['radiance.pyx', 'power.pyx'] +pxd_files = ['power.pxd', 'radiance.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/optical/scenegraph/meson.build b/raysect/optical/scenegraph/meson.build new file mode 100644 index 00000000..3b0a3415 --- /dev/null +++ b/raysect/optical/scenegraph/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/optical/scenegraph' + +# source files +py_files = ['__init__.py'] +pyx_files = ['world.pyx'] +pxd_files = ['world.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/primitive/lens/meson.build b/raysect/primitive/lens/meson.build new file mode 100644 index 00000000..6fa8cc2c --- /dev/null +++ b/raysect/primitive/lens/meson.build @@ -0,0 +1,27 @@ +target_path = 'raysect/primitive/lens' + +# source files +py_files = ['__init__.py'] +pyx_files = ['spherical.pyx'] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('tests') diff --git a/raysect/primitive/lens/tests/meson.build b/raysect/primitive/lens/tests/meson.build new file mode 100644 index 00000000..1889e9fc --- /dev/null +++ b/raysect/primitive/lens/tests/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/primitive/lens/tests' + +# source files +py_files = ['test_spherical.py', '__init__.py'] +pyx_files = [] +pxd_files = [] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/primitive/mesh/meson.build b/raysect/primitive/mesh/meson.build new file mode 100644 index 00000000..431b3409 --- /dev/null +++ b/raysect/primitive/mesh/meson.build @@ -0,0 +1,26 @@ +target_path = 'raysect/primitive/mesh' + +# source files +py_files = ['vtk.py', 'ply.py', 'obj.py', '__init__.py', 'stl.py'] +pyx_files = ['mesh.pyx'] +pxd_files = ['mesh.pxd', '__init__.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + diff --git a/raysect/primitive/meson.build b/raysect/primitive/meson.build new file mode 100644 index 00000000..9e4d80c9 --- /dev/null +++ b/raysect/primitive/meson.build @@ -0,0 +1,28 @@ +target_path = 'raysect/primitive' + +# source files +py_files = ['__init__.py'] +pyx_files = ['sphere.pyx', 'csg.pyx', 'box.pyx', 'cylinder.pyx', 'cone.pyx', 'utility.pyx', 'parabola.pyx'] +pxd_files = ['parabola.pxd', 'cone.pxd', 'csg.pxd', 'box.pxd', 'utility.pxd', 'sphere.pxd', '__init__.pxd', 'cylinder.pxd'] +data_files = [] + +# compile cython +foreach pyx_file: pyx_files + py.extension_module( + fs.replace_suffix(pyx_file, ''), + pyx_file, + dependencies: cython_dependencies, + install: true, + subdir: target_path, + cython_args: cython_args + ) +endforeach + +# add python, pxd and data files to the build +py.install_sources( + py_files + pxd_files + data_files, + subdir: target_path +) + +subdir('lens') +subdir('mesh') From 0e03c3a27f5343cd534da795b352f49b5e4d393d Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 18:37:07 +0100 Subject: [PATCH 35/77] Initial draft of pyproject.toml. --- pyproject.toml | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 17d821e2..b5644a88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,25 @@ +[project] +name = 'raysect' +version = '0.9.0' +#url='http://www.raysect.org' +#author='Dr Alex Meakins et al.' +#author_email='developers@raysect.org' +#description='A Ray-tracing Framework for Science and Engineering' +#license='BSD-3-Clause' +#license-files = ["LICENSE.txt"] +#classifiers=[ +# 'Development Status :: 5 - Production/Stable', +# 'Intended Audience :: Science/Research', +# 'Intended Audience :: Education', +# 'Intended Audience :: Developers', +# 'Natural Language :: English', +# 'Operating System :: POSIX :: Linux', +# 'Programming Language :: Cython', +# 'Programming Language :: Python :: 3', +# 'Topic :: Multimedia :: Graphics :: 3D Rendering', +# 'Topic :: Scientific/Engineering :: Physics' +#] + [build-system] -requires = ["setuptools>=42.0", "wheel", "oldest-supported-numpy", "cython>=0.28,<3.0"] -build-backend = "setuptools.build_meta" +requires = ['meson-python', 'wheel', 'oldest-supported-numpy', 'cython<3.0'] +build-backend = 'mesonpy' From e700c7b96c70be59091d6e76929fb3357c7f06dc Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 18:49:48 +0100 Subject: [PATCH 36/77] Added directory filter to exclude __pycache__ folders (and autogenerated directories we discover in the future). --- dev/generate_meson_files.py | 11 ++++++++--- raysect/meson.build | 1 - 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dev/generate_meson_files.py b/dev/generate_meson_files.py index da8f32a1..aa19d61c 100755 --- a/dev/generate_meson_files.py +++ b/dev/generate_meson_files.py @@ -3,7 +3,8 @@ from pathlib import Path PACKAGES = ['raysect'] -SUBDIR_EXCLUSION_FILENAME = '.meson-exclude' +EXCLUDE_DIR_FILE = '.meson-exclude' +EXCLUDED_DIRS = ['__pycache__'] def generate_meson_files(packages): @@ -98,8 +99,7 @@ def _install_subdir_meson_files(path): raise ValueError('The supplied path is not a directory.') # generate a list of subdirectories, filtering excluded - # todo: filter pycache files etc.. - subdirs = [child for child in path.iterdir() if child.is_dir() and not (child / SUBDIR_EXCLUSION_FILENAME).exists()] + subdirs = [child for child in path.iterdir() if child.is_dir() and not _excluded_dir(child)] # write meson file meson_file = path / 'meson.build' @@ -110,6 +110,11 @@ def _install_subdir_meson_files(path): _install_subdir_meson_files(subdir) +def _excluded_dir(path): + foo = (path / EXCLUDE_DIR_FILE).exists() or path.name in EXCLUDED_DIRS + return foo + + def _generate_root_meson_file(subdirs): # read template diff --git a/raysect/meson.build b/raysect/meson.build index 54f8f755..6a12bcff 100644 --- a/raysect/meson.build +++ b/raysect/meson.build @@ -25,6 +25,5 @@ py.install_sources( ) subdir('primitive') -subdir('__pycache__') subdir('optical') subdir('core') From 14209f0cd3b8dc11b6f6428c91fa620828c21a84 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 18:50:06 +0100 Subject: [PATCH 37/77] Deleted setup.py. --- setup.py | 122 ------------------------------------------------------- 1 file changed, 122 deletions(-) delete mode 100644 setup.py diff --git a/setup.py b/setup.py deleted file mode 100644 index a02a8973..00000000 --- a/setup.py +++ /dev/null @@ -1,122 +0,0 @@ -from setuptools import setup, find_packages, Extension -from setuptools.command.build_ext import build_ext as _build_ext -import sys -import numpy -import os -import os.path as path -import multiprocessing - -multiprocessing.set_start_method('fork') - -use_cython = True -force = False -profile = False -line_profile = False -annotate = False - -if "--skip-cython" in sys.argv: - use_cython = False - del sys.argv[sys.argv.index("--skip-cython")] - -if "--force" in sys.argv: - force = True - del sys.argv[sys.argv.index("--force")] - -if "--profile" in sys.argv: - profile = True - del sys.argv[sys.argv.index("--profile")] - -if "--line-profile" in sys.argv: - line_profile = True - del sys.argv[sys.argv.index("--line-profile")] - -if "--annotate" in sys.argv: - annotate = True - sys.argv.remove("--annotate") - -source_paths = ['raysect', 'demos'] -compilation_includes = [".", numpy.get_include()] -compilation_args = ['-O3'] -cython_directives = { - # 'auto_pickle': True, - 'language_level': 3 -} -setup_path = path.dirname(path.abspath(__file__)) - -if line_profile: - compilation_args.append("-DCYTHON_TRACE=1") - compilation_args.append("-DCYTHON_TRACE_NOGIL=1") - cython_directives["linetrace"] = True - -if use_cython: - - from Cython.Build import cythonize - - # build .pyx extension list - extensions = [] - for package in source_paths: - for root, dirs, files in os.walk(path.join(setup_path, package)): - for file in files: - if path.splitext(file)[1] == ".pyx": - pyx_file = path.relpath(path.join(root, file), setup_path) - module = path.splitext(pyx_file)[0].replace("/", ".") - extensions.append(Extension(module, [pyx_file], include_dirs=compilation_includes, extra_compile_args=compilation_args),) - - if profile: - cython_directives["profile"] = True - - # generate .c files from .pyx - extensions = cythonize(extensions, nthreads=multiprocessing.cpu_count(), force=force, compiler_directives=cython_directives, annotate=annotate) - -else: - - # build .c extension list - extensions = [] - for package in source_paths: - for root, dirs, files in os.walk(path.join(setup_path, package)): - for file in files: - if path.splitext(file)[1] == ".c": - c_file = path.relpath(path.join(root, file), setup_path) - module = path.splitext(c_file)[0].replace("/", ".") - extensions.append(Extension(module, [c_file], include_dirs=compilation_includes, extra_compile_args=compilation_args),) - -# parse the package version number -with open(path.join(path.dirname(__file__), 'raysect/VERSION')) as version_file: - version = version_file.read().strip() - -# Use multiple processes by default for building extensions -class build_ext(_build_ext): - def finalize_options(self): - super().finalize_options() - if self.parallel is None: - nproc = int(os.getenv("RAYSECT_BUILD_JOBS", str(multiprocessing.cpu_count()))) - self.parallel = nproc - -setup( - name="raysect", - version=version, - url="http://www.raysect.org", - author="Dr Alex Meakins et al.", - author_email="developers@raysect.org", - description='A Ray-tracing Framework for Science and Engineering', - license="BSD", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Science/Research", - "Intended Audience :: Education", - "Intended Audience :: Developers", - "License :: OSI Approved :: BSD License", - "Natural Language :: English", - "Operating System :: POSIX :: Linux", - "Programming Language :: Cython", - "Programming Language :: Python :: 3", - "Topic :: Multimedia :: Graphics :: 3D Rendering", - "Topic :: Scientific/Engineering :: Physics" - ], - install_requires=['numpy', 'matplotlib'], - packages=find_packages(), - include_package_data=True, - zip_safe= False, - ext_modules=extensions, - cmdclass={"build_ext": build_ext}, -) From 8bbcf0454741a82624919ccb471850eef70a75f6 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 19:02:53 +0100 Subject: [PATCH 38/77] Updated pyproject.toml. --- pyproject.toml | 50 ++++++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b5644a88..20fd13b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,25 +1,31 @@ [project] -name = 'raysect' -version = '0.9.0' -#url='http://www.raysect.org' -#author='Dr Alex Meakins et al.' -#author_email='developers@raysect.org' -#description='A Ray-tracing Framework for Science and Engineering' -#license='BSD-3-Clause' -#license-files = ["LICENSE.txt"] -#classifiers=[ -# 'Development Status :: 5 - Production/Stable', -# 'Intended Audience :: Science/Research', -# 'Intended Audience :: Education', -# 'Intended Audience :: Developers', -# 'Natural Language :: English', -# 'Operating System :: POSIX :: Linux', -# 'Programming Language :: Cython', -# 'Programming Language :: Python :: 3', -# 'Topic :: Multimedia :: Graphics :: 3D Rendering', -# 'Topic :: Scientific/Engineering :: Physics' -#] +name = "raysect" +version = "0.9.0" +requires-python = ">=3.8" +authors = [{name = "Dr Alex Meakins et al.", email = "developers@raysect.org"}] +description = "A Ray-tracing Framework for Science and Engineering" +readme = "README.txt" +license = "BSD-3-Clause" +license-files = ["LICENSE.txt"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Science/Research", + "Intended Audience :: Education", + "Intended Audience :: Developers", + "Natural Language :: English", + "Operating System :: POSIX :: Linux", + "Programming Language :: Cython", + "Programming Language :: Python :: 3", + "Topic :: Multimedia :: Graphics :: 3D Rendering", + "Topic :: Scientific/Engineering :: Physics" +] + +[project.urls] +Homepage = "https://www.raysect.org" +Repository = "https://github.com/raysect/source" +Issues = "https://github.com/raysect/source/issues" +Changelog = "https://github.com/raysect/source/blob/master/CHANGELOG.txt" [build-system] -requires = ['meson-python', 'wheel', 'oldest-supported-numpy', 'cython<3.0'] -build-backend = 'mesonpy' +requires = ["meson-python", "setuptools", "wheel", "oldest-supported-numpy", "cython<3.0"] +build-backend = "mesonpy" From 92190877776fcac6c9f656438fe0d68c8e5ebeb6 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 19:06:08 +0100 Subject: [PATCH 39/77] Fixed readme definition in pyproject.toml. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 20fd13b5..0f14f6bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ version = "0.9.0" requires-python = ">=3.8" authors = [{name = "Dr Alex Meakins et al.", email = "developers@raysect.org"}] description = "A Ray-tracing Framework for Science and Engineering" -readme = "README.txt" +readme = {file = "README.md", content-type = "text/markdown"} license = "BSD-3-Clause" license-files = ["LICENSE.txt"] classifiers = [ From c7d5336af632c9782145cca96629bb12a6c15464 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 19:06:43 +0100 Subject: [PATCH 40/77] Removed manifest file. --- MANIFEST.in | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 858ec3f7..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,6 +0,0 @@ -include README.md CHANGELOG.txt LICENSE.txt CONTRIBUTING.txt AUTHORS.txt MANIFEST.in setup.py .gitignore -include raysect/VERSION -global-exclude *.c -recursive-include raysect *.py *.pyx *.pxd *.csv *.json -recursive-include demos *.py *.pyx *.pxd *.csv *.obj *.rsm -recursive-include docs * From 04a61bcd205eb176f9db950dc8a7b8413d013323 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 19:10:52 +0100 Subject: [PATCH 41/77] Investigating issue with wheel build and numpy. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0f14f6bd..cc2ca5b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,5 +27,5 @@ Issues = "https://github.com/raysect/source/issues" Changelog = "https://github.com/raysect/source/blob/master/CHANGELOG.txt" [build-system] -requires = ["meson-python", "setuptools", "wheel", "oldest-supported-numpy", "cython<3.0"] +requires = ["meson-python", "setuptools", "wheel", "numpy", "cython<3.0"] build-backend = "mesonpy" From 36d32d951b90864d0170f0e5051419ce7f556506 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 19:19:15 +0100 Subject: [PATCH 42/77] Quick hack to see if we can get the CI functioning. --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f7081a2..15d3e52f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: numpy-version: ["oldest-supported-numpy", "numpy"] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout code uses: actions/checkout@v4 @@ -21,8 +21,8 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies - run: python -m pip install --prefer-binary setuptools "cython>=0.28,<3.0" "matplotlib>=3,<4" ${{ matrix.numpy-version }} + run: python -m pip install --prefer-binary meson-python meson ninja setuptools "cython>=0.28,<3.0" "matplotlib>=3,<4" ${{ matrix.numpy-version }} - name: Build and install Raysect - run: dev/build.sh + run: dev/install_editable.sh - name: Run tests run: dev/test.sh From 40dc2d78b217e24432855a37c078cd1e227ec840 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 19:26:39 +0100 Subject: [PATCH 43/77] Remove support for python 3.8. Removed oldest support numpy for now (until I can resolve why the build is having issues with it). --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15d3e52f..bbeae168 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,8 @@ jobs: strategy: fail-fast: false matrix: - numpy-version: ["oldest-supported-numpy", "numpy"] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + numpy-version: ["numpy"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout code uses: actions/checkout@v4 From 6d29384a9e708b3c3f5c2cdf1c0f808a379c6f11 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 20:21:06 +0100 Subject: [PATCH 44/77] Changed plan regarding demos folder. The sdist will essentially be the repository. --- CHANGELOG.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 36fe9096..d08d082d 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -12,7 +12,6 @@ Build changes: - A dev/install_editable.sh script is provided to simplify the installation of the package in editable mode with verbose build output enabled. - Meson-python performs the build out of the project folder, so the project source folders will no longer be polluted with build artefacts. - Cython build annotations are now always enabled, the annotation files can be found in the build folder under the build artefacts folder associated with each so file (*.so.a folder). -* The demos folder is no longer included in the raysect package to reduce its size. Please clone the source repository to obtain the demos. A dedicated raysect-demos package is being investigated for the future. Release 0.8.1 (12 Feb 2023) From 9c4b8444fa09e77a99056ebce116eeb1f1477eb3 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 20:22:04 +0100 Subject: [PATCH 45/77] Updated version number. --- raysect/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raysect/VERSION b/raysect/VERSION index 53a48a1e..899f24fc 100644 --- a/raysect/VERSION +++ b/raysect/VERSION @@ -1 +1 @@ -0.8.2 \ No newline at end of file +0.9.0 \ No newline at end of file From e41c1c395ae4a406adfa2cdfb3461678ddac7b2f Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 20:27:05 +0100 Subject: [PATCH 46/77] Fixed typo in changelog.txt --- CHANGELOG.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index d08d082d..7dd3faf6 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -11,7 +11,7 @@ Build changes: - When installed in editable mode, any modified pyx files will automatically trigger a rebuild when python attempts to import from the package. Please be aware that (as with the previous build system) changes to pxd files will require a clean rebuild of the project. - A dev/install_editable.sh script is provided to simplify the installation of the package in editable mode with verbose build output enabled. - Meson-python performs the build out of the project folder, so the project source folders will no longer be polluted with build artefacts. - - Cython build annotations are now always enabled, the annotation files can be found in the build folder under the build artefacts folder associated with each so file (*.so.a folder). + - Cython build annotations are now always enabled, the annotation files can be found in the build folder under the build artefacts folder associated with each so file (*.so.p folder). Release 0.8.1 (12 Feb 2023) From 3976cafa75a59fa3f0becfffafbefb665fd94d04 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 21:25:45 +0100 Subject: [PATCH 47/77] Minimum supported version of python now 3.9. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cc2ca5b8..a66e34b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "raysect" version = "0.9.0" -requires-python = ">=3.8" +requires-python = ">=3.9" authors = [{name = "Dr Alex Meakins et al.", email = "developers@raysect.org"}] description = "A Ray-tracing Framework for Science and Engineering" readme = {file = "README.md", content-type = "text/markdown"} From 53f6c84e8725a61fca6225ed3aa4b98cbca64217 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 21:57:54 +0100 Subject: [PATCH 48/77] Switch to raw string to correct syntax warning on package import. --- raysect/primitive/mesh/vtk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raysect/primitive/mesh/vtk.py b/raysect/primitive/mesh/vtk.py index 9012c26c..1b904fb5 100644 --- a/raysect/primitive/mesh/vtk.py +++ b/raysect/primitive/mesh/vtk.py @@ -102,7 +102,7 @@ def _load_ascii(cls, filename, scaling): @classmethod def _ascii_read_vertices(cls, f, scaling): - match = re.match("POINTS\s*([0-9]*)\s*float", f.readline().strip()) + match = re.match(r"POINTS\s*([0-9]*)\s*float", f.readline().strip()) if not match: raise RuntimeError("Unrecognised dataset encountered in vtk file.") num_points = int(match.group(1)) @@ -120,7 +120,7 @@ def _ascii_read_vertices(cls, f, scaling): @classmethod def _ascii_read_triangles(cls, f): - match = re.match("CELLS\s*([0-9]*)\s*([0-9]*)", f.readline()) + match = re.match(r"CELLS\s*([0-9]*)\s*([0-9]*)", f.readline()) if not match: raise RuntimeError("Unrecognised dataset encountered in vtk file.") num_triangles = int(match.group(1)) From 7a52d81934f1052064e83ad157e3a15840b2199f Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 22:36:06 +0100 Subject: [PATCH 49/77] Added a developer warning to the autogenerated meson.build files. Added the ability to protect customised meson.build files from modification by the generator script. Added venv/ to .gitignore. --- .gitignore | 3 +- dev/generate_meson_files.py | 82 ++++++++++++++++--- meson.build | 3 + raysect/core/acceleration/meson.build | 3 + raysect/core/acceleration/tests/meson.build | 3 + .../math/cython/interpolation/meson.build | 3 + raysect/core/math/cython/meson.build | 3 + raysect/core/math/cython/tests/meson.build | 3 + .../function/float/function1d/meson.build | 3 + .../float/function1d/tests/data/meson.build | 3 + .../float/function1d/tests/meson.build | 3 + .../float/function2d/interpolate/meson.build | 3 + .../interpolate/tests/data/meson.build | 3 + .../function2d/interpolate/tests/meson.build | 3 + .../function/float/function2d/meson.build | 3 + .../float/function2d/tests/meson.build | 3 + .../float/function3d/interpolate/meson.build | 3 + .../interpolate/tests/data/meson.build | 3 + .../function3d/interpolate/tests/meson.build | 3 + .../function/float/function3d/meson.build | 3 + .../float/function3d/tests/meson.build | 3 + raysect/core/math/function/float/meson.build | 3 + raysect/core/math/function/meson.build | 3 + .../function/vector3d/function1d/meson.build | 3 + .../vector3d/function1d/tests/meson.build | 3 + .../function/vector3d/function2d/meson.build | 3 + .../vector3d/function2d/tests/meson.build | 3 + .../function/vector3d/function3d/meson.build | 3 + .../vector3d/function3d/tests/meson.build | 3 + .../core/math/function/vector3d/meson.build | 3 + raysect/core/math/meson.build | 3 + raysect/core/math/sampler/meson.build | 3 + raysect/core/math/spatial/meson.build | 3 + raysect/core/math/tests/meson.build | 3 + raysect/core/meson.build | 3 + raysect/core/scenegraph/meson.build | 3 + raysect/core/scenegraph/tests/meson.build | 3 + raysect/core/tests/meson.build | 3 + raysect/meson.build | 3 + .../optical/library/components/meson.build | 3 + .../optical/library/glass/data/meson.build | 3 + raysect/optical/library/glass/meson.build | 3 + raysect/optical/library/meson.build | 3 + .../optical/library/metal/data/meson.build | 3 + raysect/optical/library/metal/meson.build | 3 + raysect/optical/library/spectra/meson.build | 3 + raysect/optical/material/emitter/meson.build | 3 + raysect/optical/material/meson.build | 3 + .../optical/material/modifiers/meson.build | 3 + raysect/optical/meson.build | 3 + raysect/optical/observer/base/meson.build | 3 + raysect/optical/observer/imaging/meson.build | 3 + raysect/optical/observer/meson.build | 3 + .../optical/observer/nonimaging/meson.build | 3 + raysect/optical/observer/pipeline/meson.build | 3 + .../observer/pipeline/mono/meson.build | 3 + .../observer/pipeline/spectral/meson.build | 3 + raysect/optical/scenegraph/meson.build | 3 + raysect/primitive/lens/meson.build | 3 + raysect/primitive/lens/tests/meson.build | 3 + raysect/primitive/mesh/meson.build | 3 + raysect/primitive/meson.build | 3 + 62 files changed, 253 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 2e0ea683..c38bf609 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,12 @@ __pycache__/ *$py.class # C extensions -*.so +*.son option to enable profiling *.c # Distribution / packaging .Python env/ +venv/ build/ develop-eggs/ dist/ diff --git a/dev/generate_meson_files.py b/dev/generate_meson_files.py index aa19d61c..ee4b4569 100755 --- a/dev/generate_meson_files.py +++ b/dev/generate_meson_files.py @@ -1,9 +1,40 @@ #!/bin/env python +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the Raysect Project nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + from pathlib import Path PACKAGES = ['raysect'] + EXCLUDE_DIR_FILE = '.meson-exclude' +CUSTOM_MESON_BUILD = '.meson-custom' EXCLUDED_DIRS = ['__pycache__'] @@ -33,13 +64,17 @@ def generate_meson_files(packages): ".meson-exclude". If the exclusion file is found, the sub-directory and its descendants will be ignored during the generation process. + Occasionally a meson.build file may need to be customised in the source tree. Placing a file called ".meson-custom" + in the same directory as the meson.build file will protect the customised file from deletion or replacement by this + script. + :param packages: A list of package names. """ root_path = Path('.') package_paths = [Path(package) for package in packages] - # Walk the project folder and specified packages to remove all meson.build files. + # Walk the project folder and specified packages to remove all (non-custom) meson.build files. # Any stale meson.build files found in excluded directories are also removed. _remove_meson_files(root_path, subdirs=package_paths) @@ -66,9 +101,10 @@ def _remove_meson_files(path, subdirs=None): if subdirs and any([not subdir.is_dir() for subdir in subdirs]): raise ValueError('The list of sub-directories must only contain paths to valid directories.') - # remove meson.build in this directory - meson_file = path / 'meson.build' - meson_file.unlink(missing_ok=True) + # remove meson.build in this directory if it is not flagged as customised + if not _custom_meson_file(path): + meson_file = path / 'meson.build' + meson_file.unlink(missing_ok=True) # generate a list of subdirectories if none supplied if not subdirs: @@ -88,8 +124,8 @@ def _install_root_meson_file(path, subdirs): if any([not subdir.is_dir() for subdir in subdirs]): raise ValueError('The list of sub-directories must only contain paths to valid directories.') - meson_file = path / 'meson.build' - meson_file.write_text(_generate_root_meson_file(subdirs)) + # write meson file + _write_meson_file(path, _generate_root_meson_file(subdirs)) def _install_subdir_meson_files(path): @@ -102,14 +138,23 @@ def _install_subdir_meson_files(path): subdirs = [child for child in path.iterdir() if child.is_dir() and not _excluded_dir(child)] # write meson file - meson_file = path / 'meson.build' - meson_file.write_text(_generate_subdir_meson_file(path, subdirs)) + _write_meson_file(path, _generate_subdir_meson_file(path, subdirs)) # recurse into sub-directories for subdir in subdirs: _install_subdir_meson_files(subdir) +def _write_meson_file(path, contents): + if not _custom_meson_file(path): + meson_file = path / 'meson.build' + meson_file.write_text(contents) + + +def _custom_meson_file(path): + return (path / CUSTOM_MESON_BUILD).exists() + + def _excluded_dir(path): foo = (path / EXCLUDE_DIR_FILE).exists() or path.name in EXCLUDED_DIRS return foo @@ -119,7 +164,16 @@ def _generate_root_meson_file(subdirs): # read template template_path = Path(__file__).parent / 'root-meson.build' - contents = template_path.read_text() + template = template_path.read_text() + + # start contents with a warning + contents = ( + "# WARNING: This file is automatically generated by dev/generate_meson_files.py.\n" + "# The template file used to generate this file is dev/root-meson.build.\n\n" + ) + + # add template + contents += template # add subdir entries contents += '\n' @@ -157,8 +211,14 @@ def _generate_subdir_meson_file(path, subdirs): else: data.append(child.name) - # fill in template entries - contents = template.format( + # start contents with a warning + contents = ( + "# WARNING: This file is automatically generated by dev/generate_meson_files.py.\n" + "# The template file used to generate this file is dev/subdir-meson.build.\n\n" + ) + + # add template, filling in the variables + contents += template.format( target=f'\'{str(path)}\'', pyx_files=str(pyx), pxd_files=str(pxd), diff --git a/meson.build b/meson.build index 688b2a43..072eed68 100644 --- a/meson.build +++ b/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/root-meson.build. + project('raysect', 'cython', default_options: ['python.install-env=auto']) py = import('python').find_installation(pure: false) diff --git a/raysect/core/acceleration/meson.build b/raysect/core/acceleration/meson.build index 59e011dc..4b45982b 100644 --- a/raysect/core/acceleration/meson.build +++ b/raysect/core/acceleration/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/acceleration' # source files diff --git a/raysect/core/acceleration/tests/meson.build b/raysect/core/acceleration/tests/meson.build index b87c1f8d..53eb7130 100644 --- a/raysect/core/acceleration/tests/meson.build +++ b/raysect/core/acceleration/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/acceleration/tests' # source files diff --git a/raysect/core/math/cython/interpolation/meson.build b/raysect/core/math/cython/interpolation/meson.build index 4b6f4b99..473a025d 100644 --- a/raysect/core/math/cython/interpolation/meson.build +++ b/raysect/core/math/cython/interpolation/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/cython/interpolation' # source files diff --git a/raysect/core/math/cython/meson.build b/raysect/core/math/cython/meson.build index 35b0773c..aac5c9dc 100644 --- a/raysect/core/math/cython/meson.build +++ b/raysect/core/math/cython/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/cython' # source files diff --git a/raysect/core/math/cython/tests/meson.build b/raysect/core/math/cython/tests/meson.build index ac336cf0..e462f440 100644 --- a/raysect/core/math/cython/tests/meson.build +++ b/raysect/core/math/cython/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/cython/tests' # source files diff --git a/raysect/core/math/function/float/function1d/meson.build b/raysect/core/math/function/float/function1d/meson.build index 4925b349..1ab0aad8 100644 --- a/raysect/core/math/function/float/function1d/meson.build +++ b/raysect/core/math/function/float/function1d/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float/function1d' # source files diff --git a/raysect/core/math/function/float/function1d/tests/data/meson.build b/raysect/core/math/function/float/function1d/tests/data/meson.build index 69b60a18..54664e7b 100644 --- a/raysect/core/math/function/float/function1d/tests/data/meson.build +++ b/raysect/core/math/function/float/function1d/tests/data/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float/function1d/tests/data' # source files diff --git a/raysect/core/math/function/float/function1d/tests/meson.build b/raysect/core/math/function/float/function1d/tests/meson.build index 6a94862c..5e2f0eba 100644 --- a/raysect/core/math/function/float/function1d/tests/meson.build +++ b/raysect/core/math/function/float/function1d/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float/function1d/tests' # source files diff --git a/raysect/core/math/function/float/function2d/interpolate/meson.build b/raysect/core/math/function/float/function2d/interpolate/meson.build index eb1572ce..595a8598 100644 --- a/raysect/core/math/function/float/function2d/interpolate/meson.build +++ b/raysect/core/math/function/float/function2d/interpolate/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float/function2d/interpolate' # source files diff --git a/raysect/core/math/function/float/function2d/interpolate/tests/data/meson.build b/raysect/core/math/function/float/function2d/interpolate/tests/data/meson.build index 373d39b9..c2c7b3c2 100644 --- a/raysect/core/math/function/float/function2d/interpolate/tests/data/meson.build +++ b/raysect/core/math/function/float/function2d/interpolate/tests/data/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float/function2d/interpolate/tests/data' # source files diff --git a/raysect/core/math/function/float/function2d/interpolate/tests/meson.build b/raysect/core/math/function/float/function2d/interpolate/tests/meson.build index 0f9e6c7c..930888be 100644 --- a/raysect/core/math/function/float/function2d/interpolate/tests/meson.build +++ b/raysect/core/math/function/float/function2d/interpolate/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float/function2d/interpolate/tests' # source files diff --git a/raysect/core/math/function/float/function2d/meson.build b/raysect/core/math/function/float/function2d/meson.build index 2d18b795..291e2a1b 100644 --- a/raysect/core/math/function/float/function2d/meson.build +++ b/raysect/core/math/function/float/function2d/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float/function2d' # source files diff --git a/raysect/core/math/function/float/function2d/tests/meson.build b/raysect/core/math/function/float/function2d/tests/meson.build index 595ff898..883f6155 100644 --- a/raysect/core/math/function/float/function2d/tests/meson.build +++ b/raysect/core/math/function/float/function2d/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float/function2d/tests' # source files diff --git a/raysect/core/math/function/float/function3d/interpolate/meson.build b/raysect/core/math/function/float/function3d/interpolate/meson.build index 648afc02..55828ab8 100644 --- a/raysect/core/math/function/float/function3d/interpolate/meson.build +++ b/raysect/core/math/function/float/function3d/interpolate/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float/function3d/interpolate' # source files diff --git a/raysect/core/math/function/float/function3d/interpolate/tests/data/meson.build b/raysect/core/math/function/float/function3d/interpolate/tests/data/meson.build index 7e2c9a69..430429d5 100644 --- a/raysect/core/math/function/float/function3d/interpolate/tests/data/meson.build +++ b/raysect/core/math/function/float/function3d/interpolate/tests/data/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float/function3d/interpolate/tests/data' # source files diff --git a/raysect/core/math/function/float/function3d/interpolate/tests/meson.build b/raysect/core/math/function/float/function3d/interpolate/tests/meson.build index e48168a9..b8244427 100644 --- a/raysect/core/math/function/float/function3d/interpolate/tests/meson.build +++ b/raysect/core/math/function/float/function3d/interpolate/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float/function3d/interpolate/tests' # source files diff --git a/raysect/core/math/function/float/function3d/meson.build b/raysect/core/math/function/float/function3d/meson.build index 07700699..e4610299 100644 --- a/raysect/core/math/function/float/function3d/meson.build +++ b/raysect/core/math/function/float/function3d/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float/function3d' # source files diff --git a/raysect/core/math/function/float/function3d/tests/meson.build b/raysect/core/math/function/float/function3d/tests/meson.build index e1ea771c..1e5ef2c8 100644 --- a/raysect/core/math/function/float/function3d/tests/meson.build +++ b/raysect/core/math/function/float/function3d/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float/function3d/tests' # source files diff --git a/raysect/core/math/function/float/meson.build b/raysect/core/math/function/float/meson.build index 3e9852c0..94fc7e45 100644 --- a/raysect/core/math/function/float/meson.build +++ b/raysect/core/math/function/float/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/float' # source files diff --git a/raysect/core/math/function/meson.build b/raysect/core/math/function/meson.build index e88dc437..fbd96960 100644 --- a/raysect/core/math/function/meson.build +++ b/raysect/core/math/function/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function' # source files diff --git a/raysect/core/math/function/vector3d/function1d/meson.build b/raysect/core/math/function/vector3d/function1d/meson.build index 95868f10..8e4b1432 100644 --- a/raysect/core/math/function/vector3d/function1d/meson.build +++ b/raysect/core/math/function/vector3d/function1d/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/vector3d/function1d' # source files diff --git a/raysect/core/math/function/vector3d/function1d/tests/meson.build b/raysect/core/math/function/vector3d/function1d/tests/meson.build index 4ac3218f..aed59801 100644 --- a/raysect/core/math/function/vector3d/function1d/tests/meson.build +++ b/raysect/core/math/function/vector3d/function1d/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/vector3d/function1d/tests' # source files diff --git a/raysect/core/math/function/vector3d/function2d/meson.build b/raysect/core/math/function/vector3d/function2d/meson.build index 2493844a..90c77b9f 100644 --- a/raysect/core/math/function/vector3d/function2d/meson.build +++ b/raysect/core/math/function/vector3d/function2d/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/vector3d/function2d' # source files diff --git a/raysect/core/math/function/vector3d/function2d/tests/meson.build b/raysect/core/math/function/vector3d/function2d/tests/meson.build index 0d06ac65..552f3c87 100644 --- a/raysect/core/math/function/vector3d/function2d/tests/meson.build +++ b/raysect/core/math/function/vector3d/function2d/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/vector3d/function2d/tests' # source files diff --git a/raysect/core/math/function/vector3d/function3d/meson.build b/raysect/core/math/function/vector3d/function3d/meson.build index 6e117736..9de64727 100644 --- a/raysect/core/math/function/vector3d/function3d/meson.build +++ b/raysect/core/math/function/vector3d/function3d/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/vector3d/function3d' # source files diff --git a/raysect/core/math/function/vector3d/function3d/tests/meson.build b/raysect/core/math/function/vector3d/function3d/tests/meson.build index 41a6e631..05026568 100644 --- a/raysect/core/math/function/vector3d/function3d/tests/meson.build +++ b/raysect/core/math/function/vector3d/function3d/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/vector3d/function3d/tests' # source files diff --git a/raysect/core/math/function/vector3d/meson.build b/raysect/core/math/function/vector3d/meson.build index b3275de3..e52a738e 100644 --- a/raysect/core/math/function/vector3d/meson.build +++ b/raysect/core/math/function/vector3d/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/function/vector3d' # source files diff --git a/raysect/core/math/meson.build b/raysect/core/math/meson.build index e1945434..c9a62c88 100644 --- a/raysect/core/math/meson.build +++ b/raysect/core/math/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math' # source files diff --git a/raysect/core/math/sampler/meson.build b/raysect/core/math/sampler/meson.build index 63faac49..06378d7f 100644 --- a/raysect/core/math/sampler/meson.build +++ b/raysect/core/math/sampler/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/sampler' # source files diff --git a/raysect/core/math/spatial/meson.build b/raysect/core/math/spatial/meson.build index e1fee40b..853ca0c1 100644 --- a/raysect/core/math/spatial/meson.build +++ b/raysect/core/math/spatial/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/spatial' # source files diff --git a/raysect/core/math/tests/meson.build b/raysect/core/math/tests/meson.build index 144922bb..672ce76e 100644 --- a/raysect/core/math/tests/meson.build +++ b/raysect/core/math/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/math/tests' # source files diff --git a/raysect/core/meson.build b/raysect/core/meson.build index 7c8bf71e..cecc18d2 100644 --- a/raysect/core/meson.build +++ b/raysect/core/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core' # source files diff --git a/raysect/core/scenegraph/meson.build b/raysect/core/scenegraph/meson.build index a3d36f69..178ba563 100644 --- a/raysect/core/scenegraph/meson.build +++ b/raysect/core/scenegraph/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/scenegraph' # source files diff --git a/raysect/core/scenegraph/tests/meson.build b/raysect/core/scenegraph/tests/meson.build index c193f6d8..01753d53 100644 --- a/raysect/core/scenegraph/tests/meson.build +++ b/raysect/core/scenegraph/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/scenegraph/tests' # source files diff --git a/raysect/core/tests/meson.build b/raysect/core/tests/meson.build index 176dfeda..088244d9 100644 --- a/raysect/core/tests/meson.build +++ b/raysect/core/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/core/tests' # source files diff --git a/raysect/meson.build b/raysect/meson.build index 6a12bcff..d9802a7d 100644 --- a/raysect/meson.build +++ b/raysect/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect' # source files diff --git a/raysect/optical/library/components/meson.build b/raysect/optical/library/components/meson.build index 63029bf9..a18e3e4b 100644 --- a/raysect/optical/library/components/meson.build +++ b/raysect/optical/library/components/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/library/components' # source files diff --git a/raysect/optical/library/glass/data/meson.build b/raysect/optical/library/glass/data/meson.build index 5e8865b9..86466f27 100644 --- a/raysect/optical/library/glass/data/meson.build +++ b/raysect/optical/library/glass/data/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/library/glass/data' # source files diff --git a/raysect/optical/library/glass/meson.build b/raysect/optical/library/glass/meson.build index a65ac3ae..358c6dad 100644 --- a/raysect/optical/library/glass/meson.build +++ b/raysect/optical/library/glass/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/library/glass' # source files diff --git a/raysect/optical/library/meson.build b/raysect/optical/library/meson.build index cde26f61..7b9112b7 100644 --- a/raysect/optical/library/meson.build +++ b/raysect/optical/library/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/library' # source files diff --git a/raysect/optical/library/metal/data/meson.build b/raysect/optical/library/metal/data/meson.build index 2d2a137c..47237562 100644 --- a/raysect/optical/library/metal/data/meson.build +++ b/raysect/optical/library/metal/data/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/library/metal/data' # source files diff --git a/raysect/optical/library/metal/meson.build b/raysect/optical/library/metal/meson.build index 47bd36db..f483ab87 100644 --- a/raysect/optical/library/metal/meson.build +++ b/raysect/optical/library/metal/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/library/metal' # source files diff --git a/raysect/optical/library/spectra/meson.build b/raysect/optical/library/spectra/meson.build index 81f4ea5f..b1580737 100644 --- a/raysect/optical/library/spectra/meson.build +++ b/raysect/optical/library/spectra/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/library/spectra' # source files diff --git a/raysect/optical/material/emitter/meson.build b/raysect/optical/material/emitter/meson.build index 931aaede..d168f854 100644 --- a/raysect/optical/material/emitter/meson.build +++ b/raysect/optical/material/emitter/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/material/emitter' # source files diff --git a/raysect/optical/material/meson.build b/raysect/optical/material/meson.build index d9bab581..014874d6 100644 --- a/raysect/optical/material/meson.build +++ b/raysect/optical/material/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/material' # source files diff --git a/raysect/optical/material/modifiers/meson.build b/raysect/optical/material/modifiers/meson.build index b436e879..74223ce0 100644 --- a/raysect/optical/material/modifiers/meson.build +++ b/raysect/optical/material/modifiers/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/material/modifiers' # source files diff --git a/raysect/optical/meson.build b/raysect/optical/meson.build index 427d3ac4..e756f95f 100644 --- a/raysect/optical/meson.build +++ b/raysect/optical/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical' # source files diff --git a/raysect/optical/observer/base/meson.build b/raysect/optical/observer/base/meson.build index 8630abae..78700e06 100644 --- a/raysect/optical/observer/base/meson.build +++ b/raysect/optical/observer/base/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/observer/base' # source files diff --git a/raysect/optical/observer/imaging/meson.build b/raysect/optical/observer/imaging/meson.build index a4b89f75..200bb394 100644 --- a/raysect/optical/observer/imaging/meson.build +++ b/raysect/optical/observer/imaging/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/observer/imaging' # source files diff --git a/raysect/optical/observer/meson.build b/raysect/optical/observer/meson.build index 631e3f78..a4ad306a 100644 --- a/raysect/optical/observer/meson.build +++ b/raysect/optical/observer/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/observer' # source files diff --git a/raysect/optical/observer/nonimaging/meson.build b/raysect/optical/observer/nonimaging/meson.build index 29344683..6cea95cf 100644 --- a/raysect/optical/observer/nonimaging/meson.build +++ b/raysect/optical/observer/nonimaging/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/observer/nonimaging' # source files diff --git a/raysect/optical/observer/pipeline/meson.build b/raysect/optical/observer/pipeline/meson.build index 7719d89f..86060081 100644 --- a/raysect/optical/observer/pipeline/meson.build +++ b/raysect/optical/observer/pipeline/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/observer/pipeline' # source files diff --git a/raysect/optical/observer/pipeline/mono/meson.build b/raysect/optical/observer/pipeline/mono/meson.build index be8e64db..857d71e5 100644 --- a/raysect/optical/observer/pipeline/mono/meson.build +++ b/raysect/optical/observer/pipeline/mono/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/observer/pipeline/mono' # source files diff --git a/raysect/optical/observer/pipeline/spectral/meson.build b/raysect/optical/observer/pipeline/spectral/meson.build index a2f0b27e..4bdb09f2 100644 --- a/raysect/optical/observer/pipeline/spectral/meson.build +++ b/raysect/optical/observer/pipeline/spectral/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/observer/pipeline/spectral' # source files diff --git a/raysect/optical/scenegraph/meson.build b/raysect/optical/scenegraph/meson.build index 3b0a3415..fb505ffc 100644 --- a/raysect/optical/scenegraph/meson.build +++ b/raysect/optical/scenegraph/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/optical/scenegraph' # source files diff --git a/raysect/primitive/lens/meson.build b/raysect/primitive/lens/meson.build index 6fa8cc2c..a53c2994 100644 --- a/raysect/primitive/lens/meson.build +++ b/raysect/primitive/lens/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/primitive/lens' # source files diff --git a/raysect/primitive/lens/tests/meson.build b/raysect/primitive/lens/tests/meson.build index 1889e9fc..e6b0cea8 100644 --- a/raysect/primitive/lens/tests/meson.build +++ b/raysect/primitive/lens/tests/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/primitive/lens/tests' # source files diff --git a/raysect/primitive/mesh/meson.build b/raysect/primitive/mesh/meson.build index 431b3409..59d33f4c 100644 --- a/raysect/primitive/mesh/meson.build +++ b/raysect/primitive/mesh/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/primitive/mesh' # source files diff --git a/raysect/primitive/meson.build b/raysect/primitive/meson.build index 9e4d80c9..44b24be2 100644 --- a/raysect/primitive/meson.build +++ b/raysect/primitive/meson.build @@ -1,3 +1,6 @@ +# WARNING: This file is automatically generated by dev/generate_meson_files.py. +# The template file used to generate this file is dev/subdir-meson.build. + target_path = 'raysect/primitive' # source files From 546b30bdf2e9953751883e8224c9cc9055af818c Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 22:56:06 +0100 Subject: [PATCH 50/77] Updated copyright. Removed defunct CI badge from readme. --- LICENSE.txt | 2 +- README.md | 1 - raysect/__init__.py | 2 +- raysect/core/__init__.pxd | 2 +- raysect/core/__init__.py | 2 +- raysect/core/acceleration/__init__.pxd | 2 +- raysect/core/acceleration/__init__.py | 2 +- raysect/core/acceleration/accelerator.pxd | 2 +- raysect/core/acceleration/accelerator.pyx | 2 +- raysect/core/acceleration/boundprimitive.pxd | 2 +- raysect/core/acceleration/boundprimitive.pyx | 2 +- raysect/core/acceleration/kdtree.pxd | 2 +- raysect/core/acceleration/kdtree.pyx | 2 +- raysect/core/acceleration/unaccelerated.pxd | 2 +- raysect/core/acceleration/unaccelerated.pyx | 2 +- raysect/core/boundingbox.pxd | 2 +- raysect/core/boundingbox.pyx | 2 +- raysect/core/boundingsphere.pxd | 2 +- raysect/core/boundingsphere.pyx | 2 +- raysect/core/constants.py | 2 +- raysect/core/containers.pxd | 2 +- raysect/core/containers.pyx | 2 +- raysect/core/intersection.pxd | 2 +- raysect/core/intersection.pyx | 2 +- raysect/core/material.pxd | 2 +- raysect/core/material.pyx | 2 +- raysect/core/math/__init__.pxd | 2 +- raysect/core/math/__init__.py | 2 +- raysect/core/math/_mat4.pxd | 2 +- raysect/core/math/_mat4.pyx | 2 +- raysect/core/math/_vec3.pxd | 2 +- raysect/core/math/_vec3.pyx | 2 +- raysect/core/math/affinematrix.pxd | 2 +- raysect/core/math/affinematrix.pyx | 2 +- raysect/core/math/cython/__init__.pxd | 2 +- raysect/core/math/cython/__init__.py | 2 +- raysect/core/math/cython/interpolation/__init__.pxd | 2 +- raysect/core/math/cython/interpolation/cubic.pxd | 2 +- raysect/core/math/cython/interpolation/cubic.pyx | 2 +- raysect/core/math/cython/interpolation/helper/code_generator.py | 2 +- raysect/core/math/cython/interpolation/helper/equation.py | 2 +- raysect/core/math/cython/interpolation/linear.pxd | 2 +- raysect/core/math/cython/interpolation/linear.pyx | 2 +- raysect/core/math/cython/tests/test_tetrahedra.py | 2 +- raysect/core/math/cython/tests/test_triangle.py | 2 +- raysect/core/math/cython/tests/test_utility.py | 2 +- raysect/core/math/cython/tetrahedra.pxd | 2 +- raysect/core/math/cython/tetrahedra.pyx | 2 +- raysect/core/math/cython/transform.pxd | 2 +- raysect/core/math/cython/transform.pyx | 2 +- raysect/core/math/cython/triangle.pxd | 2 +- raysect/core/math/cython/triangle.pyx | 2 +- raysect/core/math/cython/utility.pxd | 2 +- raysect/core/math/cython/utility.pyx | 2 +- raysect/core/math/function/__init__.pxd | 2 +- raysect/core/math/function/__init__.py | 2 +- raysect/core/math/function/base.pxd | 2 +- raysect/core/math/function/base.pyx | 2 +- raysect/core/math/function/float/__init__.pxd | 2 +- raysect/core/math/function/float/__init__.py | 2 +- raysect/core/math/function/float/base.pxd | 2 +- raysect/core/math/function/float/base.pyx | 2 +- raysect/core/math/function/float/function1d/__init__.pxd | 2 +- raysect/core/math/function/float/function1d/__init__.py | 2 +- raysect/core/math/function/float/function1d/arg.pxd | 2 +- raysect/core/math/function/float/function1d/arg.pyx | 2 +- raysect/core/math/function/float/function1d/autowrap.pxd | 2 +- raysect/core/math/function/float/function1d/autowrap.pyx | 2 +- raysect/core/math/function/float/function1d/base.pxd | 2 +- raysect/core/math/function/float/function1d/base.pyx | 2 +- raysect/core/math/function/float/function1d/blend.pxd | 2 +- raysect/core/math/function/float/function1d/blend.pyx | 2 +- raysect/core/math/function/float/function1d/cmath.pxd | 2 +- raysect/core/math/function/float/function1d/cmath.pyx | 2 +- raysect/core/math/function/float/function1d/constant.pxd | 2 +- raysect/core/math/function/float/function1d/constant.pyx | 2 +- raysect/core/math/function/float/function1d/interpolate.pxd | 2 +- raysect/core/math/function/float/function1d/interpolate.pyx | 2 +- raysect/core/math/function/float/function1d/samplers.pxd | 2 +- raysect/core/math/function/float/function1d/samplers.pyx | 2 +- .../float/function1d/tests/scripts/generate_1d_splines.py | 2 +- raysect/core/math/function/float/function1d/tests/test_arg.py | 2 +- .../core/math/function/float/function1d/tests/test_autowrap.py | 2 +- raysect/core/math/function/float/function1d/tests/test_base.py | 2 +- raysect/core/math/function/float/function1d/tests/test_cmath.py | 2 +- .../core/math/function/float/function1d/tests/test_constant.py | 2 +- .../math/function/float/function1d/tests/test_interpolator.py | 2 +- .../core/math/function/float/function1d/tests/test_samplers.py | 2 +- raysect/core/math/function/float/function2d/__init__.pxd | 2 +- raysect/core/math/function/float/function2d/__init__.py | 2 +- raysect/core/math/function/float/function2d/arg.pxd | 2 +- raysect/core/math/function/float/function2d/arg.pyx | 2 +- raysect/core/math/function/float/function2d/autowrap.pxd | 2 +- raysect/core/math/function/float/function2d/autowrap.pyx | 2 +- raysect/core/math/function/float/function2d/base.pxd | 2 +- raysect/core/math/function/float/function2d/base.pyx | 2 +- raysect/core/math/function/float/function2d/blend.pxd | 2 +- raysect/core/math/function/float/function2d/blend.pyx | 2 +- raysect/core/math/function/float/function2d/cmath.pxd | 2 +- raysect/core/math/function/float/function2d/cmath.pyx | 2 +- raysect/core/math/function/float/function2d/constant.pxd | 2 +- raysect/core/math/function/float/function2d/constant.pyx | 2 +- .../math/function/float/function2d/interpolate/__init__.pxd | 2 +- .../core/math/function/float/function2d/interpolate/__init__.py | 2 +- .../core/math/function/float/function2d/interpolate/common.pxd | 2 +- .../core/math/function/float/function2d/interpolate/common.pyx | 2 +- .../function/float/function2d/interpolate/discrete2dmesh.pxd | 2 +- .../function/float/function2d/interpolate/discrete2dmesh.pyx | 2 +- .../float/function2d/interpolate/interpolator2darray.pxd | 2 +- .../float/function2d/interpolate/interpolator2darray.pyx | 2 +- .../float/function2d/interpolate/interpolator2dmesh.pxd | 2 +- .../float/function2d/interpolate/interpolator2dmesh.pyx | 2 +- .../function2d/interpolate/tests/scripts/generate_2d_splines.py | 2 +- .../float/function2d/interpolate/tests/test_interpolator_2d.py | 2 +- raysect/core/math/function/float/function2d/tests/test_arg.py | 2 +- .../core/math/function/float/function2d/tests/test_autowrap.py | 2 +- raysect/core/math/function/float/function2d/tests/test_base.py | 2 +- raysect/core/math/function/float/function2d/tests/test_cmath.py | 2 +- .../core/math/function/float/function2d/tests/test_constant.py | 2 +- raysect/core/math/function/float/function3d/__init__.pxd | 2 +- raysect/core/math/function/float/function3d/__init__.py | 2 +- raysect/core/math/function/float/function3d/arg.pxd | 2 +- raysect/core/math/function/float/function3d/arg.pyx | 2 +- raysect/core/math/function/float/function3d/autowrap.pxd | 2 +- raysect/core/math/function/float/function3d/autowrap.pyx | 2 +- raysect/core/math/function/float/function3d/base.pxd | 2 +- raysect/core/math/function/float/function3d/base.pyx | 2 +- raysect/core/math/function/float/function3d/blend.pxd | 2 +- raysect/core/math/function/float/function3d/blend.pyx | 2 +- raysect/core/math/function/float/function3d/cmath.pxd | 2 +- raysect/core/math/function/float/function3d/cmath.pyx | 2 +- raysect/core/math/function/float/function3d/constant.pxd | 2 +- raysect/core/math/function/float/function3d/constant.pyx | 2 +- .../math/function/float/function3d/interpolate/__init__.pxd | 2 +- .../core/math/function/float/function3d/interpolate/__init__.py | 2 +- .../core/math/function/float/function3d/interpolate/common.pxd | 2 +- .../core/math/function/float/function3d/interpolate/common.pyx | 2 +- .../function/float/function3d/interpolate/discrete3dmesh.pxd | 2 +- .../function/float/function3d/interpolate/discrete3dmesh.pyx | 2 +- .../float/function3d/interpolate/interpolator3darray.pxd | 2 +- .../float/function3d/interpolate/interpolator3darray.pyx | 2 +- .../function3d/interpolate/tests/scripts/generate_3d_splines.py | 2 +- .../float/function3d/interpolate/tests/test_interpolator_3d.py | 2 +- raysect/core/math/function/float/function3d/tests/test_arg.py | 2 +- .../core/math/function/float/function3d/tests/test_autowrap.py | 2 +- raysect/core/math/function/float/function3d/tests/test_base.py | 2 +- raysect/core/math/function/float/function3d/tests/test_cmath.py | 2 +- .../core/math/function/float/function3d/tests/test_constant.py | 2 +- raysect/core/math/function/vector3d/__init__.pxd | 2 +- raysect/core/math/function/vector3d/__init__.py | 2 +- raysect/core/math/function/vector3d/base.pxd | 2 +- raysect/core/math/function/vector3d/base.pyx | 2 +- raysect/core/math/function/vector3d/function1d/__init__.pxd | 2 +- raysect/core/math/function/vector3d/function1d/autowrap.pxd | 2 +- raysect/core/math/function/vector3d/function1d/autowrap.pyx | 2 +- raysect/core/math/function/vector3d/function1d/base.pxd | 2 +- raysect/core/math/function/vector3d/function1d/base.pyx | 2 +- raysect/core/math/function/vector3d/function1d/blend.pxd | 2 +- raysect/core/math/function/vector3d/function1d/blend.pyx | 2 +- raysect/core/math/function/vector3d/function1d/constant.pxd | 2 +- raysect/core/math/function/vector3d/function1d/constant.pyx | 2 +- .../math/function/vector3d/function1d/tests/test_autowrap.py | 2 +- .../core/math/function/vector3d/function1d/tests/test_base.py | 2 +- .../math/function/vector3d/function1d/tests/test_constant.py | 2 +- .../vector3d/function1d/tests/test_float_to_vector3d.py | 2 +- raysect/core/math/function/vector3d/function1d/utility.pxd | 2 +- raysect/core/math/function/vector3d/function1d/utility.pyx | 2 +- raysect/core/math/function/vector3d/function2d/__init__.pxd | 2 +- raysect/core/math/function/vector3d/function2d/autowrap.pxd | 2 +- raysect/core/math/function/vector3d/function2d/autowrap.pyx | 2 +- raysect/core/math/function/vector3d/function2d/base.pxd | 2 +- raysect/core/math/function/vector3d/function2d/base.pyx | 2 +- raysect/core/math/function/vector3d/function2d/blend.pxd | 2 +- raysect/core/math/function/vector3d/function2d/blend.pyx | 2 +- raysect/core/math/function/vector3d/function2d/constant.pxd | 2 +- raysect/core/math/function/vector3d/function2d/constant.pyx | 2 +- .../math/function/vector3d/function2d/tests/test_autowrap.py | 2 +- .../core/math/function/vector3d/function2d/tests/test_base.py | 2 +- .../math/function/vector3d/function2d/tests/test_constant.py | 2 +- .../vector3d/function2d/tests/test_float_to_vector3d.py | 2 +- raysect/core/math/function/vector3d/function2d/utility.pxd | 2 +- raysect/core/math/function/vector3d/function2d/utility.pyx | 2 +- raysect/core/math/function/vector3d/function3d/__init__.pxd | 2 +- raysect/core/math/function/vector3d/function3d/autowrap.pxd | 2 +- raysect/core/math/function/vector3d/function3d/autowrap.pyx | 2 +- raysect/core/math/function/vector3d/function3d/base.pxd | 2 +- raysect/core/math/function/vector3d/function3d/base.pyx | 2 +- raysect/core/math/function/vector3d/function3d/blend.pxd | 2 +- raysect/core/math/function/vector3d/function3d/blend.pyx | 2 +- raysect/core/math/function/vector3d/function3d/constant.pxd | 2 +- raysect/core/math/function/vector3d/function3d/constant.pyx | 2 +- .../math/function/vector3d/function3d/tests/test_autowrap.py | 2 +- .../core/math/function/vector3d/function3d/tests/test_base.py | 2 +- .../math/function/vector3d/function3d/tests/test_constant.py | 2 +- .../vector3d/function3d/tests/test_float_to_vector3d.py | 2 +- raysect/core/math/function/vector3d/function3d/utility.pxd | 2 +- raysect/core/math/function/vector3d/function3d/utility.pyx | 2 +- raysect/core/math/normal.pxd | 2 +- raysect/core/math/normal.pyx | 2 +- raysect/core/math/point.pxd | 2 +- raysect/core/math/point.pyx | 2 +- raysect/core/math/polygon.pxd | 2 +- raysect/core/math/polygon.pyx | 2 +- raysect/core/math/quaternion.pxd | 2 +- raysect/core/math/quaternion.pyx | 2 +- raysect/core/math/random.pxd | 2 +- raysect/core/math/random.pyx | 2 +- raysect/core/math/sampler/__init__.pxd | 2 +- raysect/core/math/sampler/__init__.py | 2 +- raysect/core/math/sampler/solidangle.pxd | 2 +- raysect/core/math/sampler/solidangle.pyx | 2 +- raysect/core/math/sampler/surface3d.pxd | 2 +- raysect/core/math/sampler/surface3d.pyx | 2 +- raysect/core/math/sampler/targetted.pxd | 2 +- raysect/core/math/sampler/targetted.pyx | 2 +- raysect/core/math/spatial/__init__.pxd | 2 +- raysect/core/math/spatial/__init__.py | 2 +- raysect/core/math/spatial/kdtree2d.pxd | 2 +- raysect/core/math/spatial/kdtree2d.pyx | 2 +- raysect/core/math/spatial/kdtree3d.pxd | 2 +- raysect/core/math/spatial/kdtree3d.pyx | 2 +- raysect/core/math/statsarray.pxd | 2 +- raysect/core/math/statsarray.pyx | 2 +- raysect/core/math/tests/test_affinematrix3d.py | 2 +- raysect/core/math/tests/test_interaction3d.py | 2 +- raysect/core/math/tests/test_normal3d.py | 2 +- raysect/core/math/tests/test_point2d.py | 2 +- raysect/core/math/tests/test_point3d.py | 2 +- raysect/core/math/tests/test_quaternion.py | 2 +- raysect/core/math/tests/test_random.py | 2 +- raysect/core/math/tests/test_transform.py | 2 +- raysect/core/math/tests/test_vector2d.py | 2 +- raysect/core/math/tests/test_vector3d.py | 2 +- raysect/core/math/transform.pxd | 2 +- raysect/core/math/transform.pyx | 2 +- raysect/core/math/units.pxd | 2 +- raysect/core/math/units.pyx | 2 +- raysect/core/math/vector.pxd | 2 +- raysect/core/math/vector.pyx | 2 +- raysect/core/ray.pxd | 2 +- raysect/core/ray.pyx | 2 +- raysect/core/scenegraph/__init__.pxd | 2 +- raysect/core/scenegraph/_nodebase.pxd | 2 +- raysect/core/scenegraph/_nodebase.pyx | 2 +- raysect/core/scenegraph/node.pxd | 2 +- raysect/core/scenegraph/node.pyx | 2 +- raysect/core/scenegraph/observer.pxd | 2 +- raysect/core/scenegraph/observer.pyx | 2 +- raysect/core/scenegraph/primitive.pxd | 2 +- raysect/core/scenegraph/primitive.pyx | 2 +- raysect/core/scenegraph/signal.pxd | 2 +- raysect/core/scenegraph/signal.pyx | 2 +- raysect/core/scenegraph/tests/test_node.py | 2 +- raysect/core/scenegraph/tests/test_observer.py | 2 +- raysect/core/scenegraph/tests/test_primitive.py | 2 +- raysect/core/scenegraph/tests/test_world.py | 2 +- raysect/core/scenegraph/utility.pxd | 2 +- raysect/core/scenegraph/utility.pyx | 2 +- raysect/core/scenegraph/world.pxd | 2 +- raysect/core/scenegraph/world.pyx | 2 +- raysect/core/workflow.py | 2 +- raysect/optical/__init__.pxd | 2 +- raysect/optical/__init__.py | 2 +- raysect/optical/colour.pxd | 2 +- raysect/optical/colour.pyx | 2 +- raysect/optical/library/glass/schott.py | 2 +- raysect/optical/library/metal/metal.py | 2 +- raysect/optical/library/metal/roughmetal.py | 2 +- raysect/optical/library/spectra/blackbody.pyx | 2 +- raysect/optical/library/spectra/colours.py | 2 +- raysect/optical/loggingray.pyx | 2 +- raysect/optical/material/__init__.pxd | 2 +- raysect/optical/material/__init__.py | 2 +- raysect/optical/material/absorber.pxd | 2 +- raysect/optical/material/absorber.pyx | 2 +- raysect/optical/material/conductor.pxd | 2 +- raysect/optical/material/conductor.pyx | 2 +- raysect/optical/material/debug.pxd | 2 +- raysect/optical/material/debug.pyx | 2 +- raysect/optical/material/dielectric.pxd | 2 +- raysect/optical/material/dielectric.pyx | 2 +- raysect/optical/material/emitter/__init__.pxd | 2 +- raysect/optical/material/emitter/__init__.py | 2 +- raysect/optical/material/emitter/anisotropic.pxd | 2 +- raysect/optical/material/emitter/anisotropic.pyx | 2 +- raysect/optical/material/emitter/checkerboard.pxd | 2 +- raysect/optical/material/emitter/checkerboard.pyx | 2 +- raysect/optical/material/emitter/homogeneous.pxd | 2 +- raysect/optical/material/emitter/homogeneous.pyx | 2 +- raysect/optical/material/emitter/inhomogeneous.pxd | 2 +- raysect/optical/material/emitter/inhomogeneous.pyx | 2 +- raysect/optical/material/emitter/uniform.pxd | 2 +- raysect/optical/material/emitter/uniform.pyx | 2 +- raysect/optical/material/emitter/unity.pxd | 2 +- raysect/optical/material/emitter/unity.pyx | 2 +- raysect/optical/material/lambert.pyx | 2 +- raysect/optical/material/material.pxd | 2 +- raysect/optical/material/material.pyx | 2 +- raysect/optical/material/modifiers/__init__.py | 2 +- raysect/optical/material/modifiers/add.pyx | 2 +- raysect/optical/material/modifiers/blend.pyx | 2 +- raysect/optical/material/modifiers/roughen.pyx | 2 +- raysect/optical/material/modifiers/transform.pyx | 2 +- raysect/optical/observer/__init__.pxd | 2 +- raysect/optical/observer/__init__.py | 2 +- raysect/optical/observer/base/__init__.pxd | 2 +- raysect/optical/observer/base/__init__.py | 2 +- raysect/optical/observer/base/observer.pxd | 2 +- raysect/optical/observer/base/observer.pyx | 2 +- raysect/optical/observer/base/pipeline.pxd | 2 +- raysect/optical/observer/base/pipeline.pyx | 2 +- raysect/optical/observer/base/processor.pxd | 2 +- raysect/optical/observer/base/processor.pyx | 2 +- raysect/optical/observer/base/sampler.pxd | 2 +- raysect/optical/observer/base/sampler.pyx | 2 +- raysect/optical/observer/base/slice.pxd | 2 +- raysect/optical/observer/base/slice.pyx | 2 +- raysect/optical/observer/imaging/__init__.pxd | 2 +- raysect/optical/observer/imaging/__init__.py | 2 +- raysect/optical/observer/imaging/ccd.pxd | 2 +- raysect/optical/observer/imaging/ccd.pyx | 2 +- raysect/optical/observer/imaging/opencv.pxd | 2 +- raysect/optical/observer/imaging/opencv.pyx | 2 +- raysect/optical/observer/imaging/orthographic.pxd | 2 +- raysect/optical/observer/imaging/orthographic.pyx | 2 +- raysect/optical/observer/imaging/pinhole.pxd | 2 +- raysect/optical/observer/imaging/pinhole.pyx | 2 +- raysect/optical/observer/imaging/targetted_ccd.pxd | 2 +- raysect/optical/observer/imaging/targetted_ccd.pyx | 2 +- raysect/optical/observer/imaging/vector.pxd | 2 +- raysect/optical/observer/imaging/vector.pyx | 2 +- raysect/optical/observer/nonimaging/__init__.pxd | 2 +- raysect/optical/observer/nonimaging/__init__.py | 2 +- raysect/optical/observer/nonimaging/fibreoptic.pxd | 2 +- raysect/optical/observer/nonimaging/fibreoptic.pyx | 2 +- raysect/optical/observer/nonimaging/mesh_camera.pxd | 2 +- raysect/optical/observer/nonimaging/mesh_camera.pyx | 2 +- raysect/optical/observer/nonimaging/mesh_pixel.pxd | 2 +- raysect/optical/observer/nonimaging/mesh_pixel.pyx | 2 +- raysect/optical/observer/nonimaging/pixel.pxd | 2 +- raysect/optical/observer/nonimaging/pixel.pyx | 2 +- raysect/optical/observer/nonimaging/sightline.pxd | 2 +- raysect/optical/observer/nonimaging/sightline.pyx | 2 +- raysect/optical/observer/nonimaging/targetted_pixel.pxd | 2 +- raysect/optical/observer/nonimaging/targetted_pixel.pyx | 2 +- raysect/optical/observer/pipeline/__init__.pxd | 2 +- raysect/optical/observer/pipeline/__init__.py | 2 +- raysect/optical/observer/pipeline/bayer.pxd | 2 +- raysect/optical/observer/pipeline/bayer.pyx | 2 +- raysect/optical/observer/pipeline/mono/__init__.pxd | 2 +- raysect/optical/observer/pipeline/mono/__init__.py | 2 +- raysect/optical/observer/pipeline/mono/power.pxd | 2 +- raysect/optical/observer/pipeline/mono/power.pyx | 2 +- raysect/optical/observer/pipeline/mono/radiance.pxd | 2 +- raysect/optical/observer/pipeline/mono/radiance.pyx | 2 +- raysect/optical/observer/pipeline/rgb.pxd | 2 +- raysect/optical/observer/pipeline/rgb.pyx | 2 +- raysect/optical/observer/pipeline/spectral/__init__.pxd | 2 +- raysect/optical/observer/pipeline/spectral/__init__.py | 2 +- raysect/optical/observer/pipeline/spectral/power.pxd | 2 +- raysect/optical/observer/pipeline/spectral/power.pyx | 2 +- raysect/optical/observer/pipeline/spectral/radiance.pxd | 2 +- raysect/optical/observer/pipeline/spectral/radiance.pyx | 2 +- raysect/optical/observer/sampler1d.pyx | 2 +- raysect/optical/observer/sampler2d.pyx | 2 +- raysect/optical/ray.pxd | 2 +- raysect/optical/ray.pyx | 2 +- raysect/optical/scenegraph/__init__.pxd | 2 +- raysect/optical/scenegraph/__init__.py | 2 +- raysect/optical/scenegraph/world.pxd | 2 +- raysect/optical/scenegraph/world.pyx | 2 +- raysect/optical/spectralfunction.pxd | 2 +- raysect/optical/spectralfunction.pyx | 2 +- raysect/optical/spectrum.pxd | 2 +- raysect/optical/spectrum.pyx | 2 +- raysect/primitive/__init__.pxd | 2 +- raysect/primitive/__init__.py | 2 +- raysect/primitive/box.pxd | 2 +- raysect/primitive/box.pyx | 2 +- raysect/primitive/cone.pxd | 2 +- raysect/primitive/cone.pyx | 2 +- raysect/primitive/csg.pxd | 2 +- raysect/primitive/csg.pyx | 2 +- raysect/primitive/cylinder.pxd | 2 +- raysect/primitive/cylinder.pyx | 2 +- raysect/primitive/lens/spherical.pyx | 2 +- raysect/primitive/lens/tests/test_spherical.py | 2 +- raysect/primitive/mesh/__init__.pxd | 2 +- raysect/primitive/mesh/__init__.py | 2 +- raysect/primitive/mesh/mesh.pxd | 2 +- raysect/primitive/mesh/mesh.pyx | 2 +- raysect/primitive/mesh/obj.py | 2 +- raysect/primitive/mesh/ply.py | 2 +- raysect/primitive/mesh/stl.py | 2 +- raysect/primitive/mesh/vtk.py | 2 +- raysect/primitive/parabola.pxd | 2 +- raysect/primitive/parabola.pyx | 2 +- raysect/primitive/sphere.pxd | 2 +- raysect/primitive/sphere.pyx | 2 +- raysect/primitive/utility.pxd | 2 +- raysect/primitive/utility.pyx | 2 +- 401 files changed, 400 insertions(+), 401 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index b29173f4..efc7d020 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2014-2021, Dr Alex Meakins, Raysect Project +Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 4afa7ea2..cecaaba2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.1341346.svg)](https://doi.org/10.5281/zenodo.1341346) -[![Build Status](https://travis-ci.com/raysect/source.svg?branch=master)](https://travis-ci.com/raysect/source) diff --git a/raysect/__init__.py b/raysect/__init__.py index 55f2d163..0e09c122 100644 --- a/raysect/__init__.py +++ b/raysect/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/__init__.pxd b/raysect/core/__init__.pxd index 0a52110d..ab2ff0f0 100644 --- a/raysect/core/__init__.pxd +++ b/raysect/core/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/__init__.py b/raysect/core/__init__.py index 20ff3a5c..40ce3a2a 100644 --- a/raysect/core/__init__.py +++ b/raysect/core/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/acceleration/__init__.pxd b/raysect/core/acceleration/__init__.pxd index 5c646ab1..d46af06d 100644 --- a/raysect/core/acceleration/__init__.pxd +++ b/raysect/core/acceleration/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/acceleration/__init__.py b/raysect/core/acceleration/__init__.py index e26d57f7..d6e69eb3 100644 --- a/raysect/core/acceleration/__init__.py +++ b/raysect/core/acceleration/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/acceleration/accelerator.pxd b/raysect/core/acceleration/accelerator.pxd index 994b82bb..ca008e6b 100644 --- a/raysect/core/acceleration/accelerator.pxd +++ b/raysect/core/acceleration/accelerator.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/acceleration/accelerator.pyx b/raysect/core/acceleration/accelerator.pyx index 45493f01..3ff5e3f4 100644 --- a/raysect/core/acceleration/accelerator.pyx +++ b/raysect/core/acceleration/accelerator.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/acceleration/boundprimitive.pxd b/raysect/core/acceleration/boundprimitive.pxd index a1bb52a3..8a1e35ed 100644 --- a/raysect/core/acceleration/boundprimitive.pxd +++ b/raysect/core/acceleration/boundprimitive.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/acceleration/boundprimitive.pyx b/raysect/core/acceleration/boundprimitive.pyx index f312aa2a..277ae192 100644 --- a/raysect/core/acceleration/boundprimitive.pyx +++ b/raysect/core/acceleration/boundprimitive.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/acceleration/kdtree.pxd b/raysect/core/acceleration/kdtree.pxd index a6a9290d..6616e2ef 100644 --- a/raysect/core/acceleration/kdtree.pxd +++ b/raysect/core/acceleration/kdtree.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/acceleration/kdtree.pyx b/raysect/core/acceleration/kdtree.pyx index 6631e546..b2900780 100644 --- a/raysect/core/acceleration/kdtree.pyx +++ b/raysect/core/acceleration/kdtree.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/acceleration/unaccelerated.pxd b/raysect/core/acceleration/unaccelerated.pxd index f951e0e6..3d0149a5 100644 --- a/raysect/core/acceleration/unaccelerated.pxd +++ b/raysect/core/acceleration/unaccelerated.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/acceleration/unaccelerated.pyx b/raysect/core/acceleration/unaccelerated.pyx index 293828e7..8695c549 100644 --- a/raysect/core/acceleration/unaccelerated.pyx +++ b/raysect/core/acceleration/unaccelerated.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/boundingbox.pxd b/raysect/core/boundingbox.pxd index 8528a587..cd1e59b4 100644 --- a/raysect/core/boundingbox.pxd +++ b/raysect/core/boundingbox.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/boundingbox.pyx b/raysect/core/boundingbox.pyx index eb616121..a86f7fdb 100644 --- a/raysect/core/boundingbox.pyx +++ b/raysect/core/boundingbox.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/boundingsphere.pxd b/raysect/core/boundingsphere.pxd index 3d8c743a..0026b721 100644 --- a/raysect/core/boundingsphere.pxd +++ b/raysect/core/boundingsphere.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/boundingsphere.pyx b/raysect/core/boundingsphere.pyx index 91d2a0ac..ce65cb2a 100644 --- a/raysect/core/boundingsphere.pyx +++ b/raysect/core/boundingsphere.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/constants.py b/raysect/core/constants.py index 34e03e09..d3b539b1 100644 --- a/raysect/core/constants.py +++ b/raysect/core/constants.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/containers.pxd b/raysect/core/containers.pxd index 1f81042f..add9dded 100644 --- a/raysect/core/containers.pxd +++ b/raysect/core/containers.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/containers.pyx b/raysect/core/containers.pyx index 31880c65..ae93ac83 100644 --- a/raysect/core/containers.pyx +++ b/raysect/core/containers.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/intersection.pxd b/raysect/core/intersection.pxd index e0bd5336..ae11253b 100644 --- a/raysect/core/intersection.pxd +++ b/raysect/core/intersection.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/intersection.pyx b/raysect/core/intersection.pyx index d65373bc..48ec1f7f 100644 --- a/raysect/core/intersection.pyx +++ b/raysect/core/intersection.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/material.pxd b/raysect/core/material.pxd index 79b0c18c..d61f1553 100644 --- a/raysect/core/material.pxd +++ b/raysect/core/material.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/material.pyx b/raysect/core/material.pyx index 8b89e811..5d4342bd 100644 --- a/raysect/core/material.pyx +++ b/raysect/core/material.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/__init__.pxd b/raysect/core/math/__init__.pxd index c6157f8f..6d31a123 100644 --- a/raysect/core/math/__init__.pxd +++ b/raysect/core/math/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/__init__.py b/raysect/core/math/__init__.py index b7f368b2..9482e2b4 100644 --- a/raysect/core/math/__init__.py +++ b/raysect/core/math/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/_mat4.pxd b/raysect/core/math/_mat4.pxd index 96027a05..9993668c 100644 --- a/raysect/core/math/_mat4.pxd +++ b/raysect/core/math/_mat4.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/_mat4.pyx b/raysect/core/math/_mat4.pyx index 3c30c820..f8828744 100644 --- a/raysect/core/math/_mat4.pyx +++ b/raysect/core/math/_mat4.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/_vec3.pxd b/raysect/core/math/_vec3.pxd index 89da3338..70b913c7 100644 --- a/raysect/core/math/_vec3.pxd +++ b/raysect/core/math/_vec3.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/_vec3.pyx b/raysect/core/math/_vec3.pyx index bab25d55..0a1e3d88 100644 --- a/raysect/core/math/_vec3.pyx +++ b/raysect/core/math/_vec3.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/affinematrix.pxd b/raysect/core/math/affinematrix.pxd index de6b6c26..a5de0d98 100644 --- a/raysect/core/math/affinematrix.pxd +++ b/raysect/core/math/affinematrix.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/affinematrix.pyx b/raysect/core/math/affinematrix.pyx index ff729f75..4a67222c 100644 --- a/raysect/core/math/affinematrix.pyx +++ b/raysect/core/math/affinematrix.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/__init__.pxd b/raysect/core/math/cython/__init__.pxd index cab00413..acf269ad 100644 --- a/raysect/core/math/cython/__init__.pxd +++ b/raysect/core/math/cython/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/__init__.py b/raysect/core/math/cython/__init__.py index 72563a6a..09d9347b 100644 --- a/raysect/core/math/cython/__init__.py +++ b/raysect/core/math/cython/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/interpolation/__init__.pxd b/raysect/core/math/cython/interpolation/__init__.pxd index 194028db..88a141b7 100644 --- a/raysect/core/math/cython/interpolation/__init__.pxd +++ b/raysect/core/math/cython/interpolation/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/interpolation/cubic.pxd b/raysect/core/math/cython/interpolation/cubic.pxd index 6a6472ae..d73a25b3 100644 --- a/raysect/core/math/cython/interpolation/cubic.pxd +++ b/raysect/core/math/cython/interpolation/cubic.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/interpolation/cubic.pyx b/raysect/core/math/cython/interpolation/cubic.pyx index af96c5a2..d8c55ea4 100644 --- a/raysect/core/math/cython/interpolation/cubic.pyx +++ b/raysect/core/math/cython/interpolation/cubic.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/interpolation/helper/code_generator.py b/raysect/core/math/cython/interpolation/helper/code_generator.py index be809e48..4b43aecf 100644 --- a/raysect/core/math/cython/interpolation/helper/code_generator.py +++ b/raysect/core/math/cython/interpolation/helper/code_generator.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/interpolation/helper/equation.py b/raysect/core/math/cython/interpolation/helper/equation.py index fa462101..4d7cd31d 100644 --- a/raysect/core/math/cython/interpolation/helper/equation.py +++ b/raysect/core/math/cython/interpolation/helper/equation.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/interpolation/linear.pxd b/raysect/core/math/cython/interpolation/linear.pxd index 2fd510bc..2dcfff26 100644 --- a/raysect/core/math/cython/interpolation/linear.pxd +++ b/raysect/core/math/cython/interpolation/linear.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/interpolation/linear.pyx b/raysect/core/math/cython/interpolation/linear.pyx index 31a3772b..772eed3e 100644 --- a/raysect/core/math/cython/interpolation/linear.pyx +++ b/raysect/core/math/cython/interpolation/linear.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/tests/test_tetrahedra.py b/raysect/core/math/cython/tests/test_tetrahedra.py index 20190be3..cf168060 100644 --- a/raysect/core/math/cython/tests/test_tetrahedra.py +++ b/raysect/core/math/cython/tests/test_tetrahedra.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/tests/test_triangle.py b/raysect/core/math/cython/tests/test_triangle.py index ec694eaf..ed3d40de 100644 --- a/raysect/core/math/cython/tests/test_triangle.py +++ b/raysect/core/math/cython/tests/test_triangle.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/tests/test_utility.py b/raysect/core/math/cython/tests/test_utility.py index 26032987..3d9f5fc0 100644 --- a/raysect/core/math/cython/tests/test_utility.py +++ b/raysect/core/math/cython/tests/test_utility.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/tetrahedra.pxd b/raysect/core/math/cython/tetrahedra.pxd index 2ab8e297..2818fe19 100644 --- a/raysect/core/math/cython/tetrahedra.pxd +++ b/raysect/core/math/cython/tetrahedra.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/tetrahedra.pyx b/raysect/core/math/cython/tetrahedra.pyx index ce1d781e..7ed1af96 100644 --- a/raysect/core/math/cython/tetrahedra.pyx +++ b/raysect/core/math/cython/tetrahedra.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/transform.pxd b/raysect/core/math/cython/transform.pxd index bf0393e7..2a6a08cf 100644 --- a/raysect/core/math/cython/transform.pxd +++ b/raysect/core/math/cython/transform.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/transform.pyx b/raysect/core/math/cython/transform.pyx index a0ad2136..68b27df8 100644 --- a/raysect/core/math/cython/transform.pyx +++ b/raysect/core/math/cython/transform.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/triangle.pxd b/raysect/core/math/cython/triangle.pxd index 0ced0f58..61597ea7 100644 --- a/raysect/core/math/cython/triangle.pxd +++ b/raysect/core/math/cython/triangle.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/triangle.pyx b/raysect/core/math/cython/triangle.pyx index 3ff34bcf..a3e20a6f 100644 --- a/raysect/core/math/cython/triangle.pyx +++ b/raysect/core/math/cython/triangle.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/utility.pxd b/raysect/core/math/cython/utility.pxd index 4574e39c..55251357 100644 --- a/raysect/core/math/cython/utility.pxd +++ b/raysect/core/math/cython/utility.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/cython/utility.pyx b/raysect/core/math/cython/utility.pyx index 38f4d761..9cc8147e 100644 --- a/raysect/core/math/cython/utility.pyx +++ b/raysect/core/math/cython/utility.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/__init__.pxd b/raysect/core/math/function/__init__.pxd index 05892ec9..fc9bb6f8 100644 --- a/raysect/core/math/function/__init__.pxd +++ b/raysect/core/math/function/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/__init__.py b/raysect/core/math/function/__init__.py index 1f86dc02..f1547852 100644 --- a/raysect/core/math/function/__init__.py +++ b/raysect/core/math/function/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/base.pxd b/raysect/core/math/function/base.pxd index 7c5496c9..97114480 100644 --- a/raysect/core/math/function/base.pxd +++ b/raysect/core/math/function/base.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/base.pyx b/raysect/core/math/function/base.pyx index 1651a2b8..eea199ce 100644 --- a/raysect/core/math/function/base.pyx +++ b/raysect/core/math/function/base.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/__init__.pxd b/raysect/core/math/function/float/__init__.pxd index 3ea77d80..d91e9872 100644 --- a/raysect/core/math/function/float/__init__.pxd +++ b/raysect/core/math/function/float/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/__init__.py b/raysect/core/math/function/float/__init__.py index a034c18f..3342550d 100644 --- a/raysect/core/math/function/float/__init__.py +++ b/raysect/core/math/function/float/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/base.pxd b/raysect/core/math/function/float/base.pxd index 9571ba2d..fb029145 100644 --- a/raysect/core/math/function/float/base.pxd +++ b/raysect/core/math/function/float/base.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/base.pyx b/raysect/core/math/function/float/base.pyx index 044ceb05..638cb60b 100644 --- a/raysect/core/math/function/float/base.pyx +++ b/raysect/core/math/function/float/base.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/__init__.pxd b/raysect/core/math/function/float/function1d/__init__.pxd index 4af50056..2ed50c15 100644 --- a/raysect/core/math/function/float/function1d/__init__.pxd +++ b/raysect/core/math/function/float/function1d/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/__init__.py b/raysect/core/math/function/float/function1d/__init__.py index 0f95ba5b..f37b334a 100644 --- a/raysect/core/math/function/float/function1d/__init__.py +++ b/raysect/core/math/function/float/function1d/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/arg.pxd b/raysect/core/math/function/float/function1d/arg.pxd index ded7841d..f5ca173d 100644 --- a/raysect/core/math/function/float/function1d/arg.pxd +++ b/raysect/core/math/function/float/function1d/arg.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/arg.pyx b/raysect/core/math/function/float/function1d/arg.pyx index d45ee931..d01de920 100644 --- a/raysect/core/math/function/float/function1d/arg.pyx +++ b/raysect/core/math/function/float/function1d/arg.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/autowrap.pxd b/raysect/core/math/function/float/function1d/autowrap.pxd index bd874ad1..3b0cee4c 100644 --- a/raysect/core/math/function/float/function1d/autowrap.pxd +++ b/raysect/core/math/function/float/function1d/autowrap.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/autowrap.pyx b/raysect/core/math/function/float/function1d/autowrap.pyx index a00f0f9d..f0df595f 100644 --- a/raysect/core/math/function/float/function1d/autowrap.pyx +++ b/raysect/core/math/function/float/function1d/autowrap.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/base.pxd b/raysect/core/math/function/float/function1d/base.pxd index 4a037f29..c900a3bc 100644 --- a/raysect/core/math/function/float/function1d/base.pxd +++ b/raysect/core/math/function/float/function1d/base.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/base.pyx b/raysect/core/math/function/float/function1d/base.pyx index 2426f3cd..c078e6ee 100644 --- a/raysect/core/math/function/float/function1d/base.pyx +++ b/raysect/core/math/function/float/function1d/base.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/blend.pxd b/raysect/core/math/function/float/function1d/blend.pxd index 430b61db..7face450 100644 --- a/raysect/core/math/function/float/function1d/blend.pxd +++ b/raysect/core/math/function/float/function1d/blend.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/blend.pyx b/raysect/core/math/function/float/function1d/blend.pyx index 85264a15..7c8bb844 100644 --- a/raysect/core/math/function/float/function1d/blend.pyx +++ b/raysect/core/math/function/float/function1d/blend.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/cmath.pxd b/raysect/core/math/function/float/function1d/cmath.pxd index 73b35f7d..95007bc3 100644 --- a/raysect/core/math/function/float/function1d/cmath.pxd +++ b/raysect/core/math/function/float/function1d/cmath.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/cmath.pyx b/raysect/core/math/function/float/function1d/cmath.pyx index c9b4dac2..e557eb98 100644 --- a/raysect/core/math/function/float/function1d/cmath.pyx +++ b/raysect/core/math/function/float/function1d/cmath.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/constant.pxd b/raysect/core/math/function/float/function1d/constant.pxd index 1910e4d5..df5fbbd9 100644 --- a/raysect/core/math/function/float/function1d/constant.pxd +++ b/raysect/core/math/function/float/function1d/constant.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/constant.pyx b/raysect/core/math/function/float/function1d/constant.pyx index 68894b40..715e8d5c 100644 --- a/raysect/core/math/function/float/function1d/constant.pyx +++ b/raysect/core/math/function/float/function1d/constant.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/interpolate.pxd b/raysect/core/math/function/float/function1d/interpolate.pxd index 0e03eaab..e54dbb27 100644 --- a/raysect/core/math/function/float/function1d/interpolate.pxd +++ b/raysect/core/math/function/float/function1d/interpolate.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/interpolate.pyx b/raysect/core/math/function/float/function1d/interpolate.pyx index 30ee5cd8..49a11c36 100644 --- a/raysect/core/math/function/float/function1d/interpolate.pyx +++ b/raysect/core/math/function/float/function1d/interpolate.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/samplers.pxd b/raysect/core/math/function/float/function1d/samplers.pxd index d0cbf99a..9d186b24 100644 --- a/raysect/core/math/function/float/function1d/samplers.pxd +++ b/raysect/core/math/function/float/function1d/samplers.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/samplers.pyx b/raysect/core/math/function/float/function1d/samplers.pyx index 0fbd5c8e..16c6649b 100644 --- a/raysect/core/math/function/float/function1d/samplers.pyx +++ b/raysect/core/math/function/float/function1d/samplers.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/tests/scripts/generate_1d_splines.py b/raysect/core/math/function/float/function1d/tests/scripts/generate_1d_splines.py index b5bccabf..80d96548 100644 --- a/raysect/core/math/function/float/function1d/tests/scripts/generate_1d_splines.py +++ b/raysect/core/math/function/float/function1d/tests/scripts/generate_1d_splines.py @@ -1,5 +1,5 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/tests/test_arg.py b/raysect/core/math/function/float/function1d/tests/test_arg.py index 9bdac4bd..95b1c4e5 100644 --- a/raysect/core/math/function/float/function1d/tests/test_arg.py +++ b/raysect/core/math/function/float/function1d/tests/test_arg.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/tests/test_autowrap.py b/raysect/core/math/function/float/function1d/tests/test_autowrap.py index 37964197..9059fcd4 100644 --- a/raysect/core/math/function/float/function1d/tests/test_autowrap.py +++ b/raysect/core/math/function/float/function1d/tests/test_autowrap.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/tests/test_base.py b/raysect/core/math/function/float/function1d/tests/test_base.py index ead3c2be..9795871a 100644 --- a/raysect/core/math/function/float/function1d/tests/test_base.py +++ b/raysect/core/math/function/float/function1d/tests/test_base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/tests/test_cmath.py b/raysect/core/math/function/float/function1d/tests/test_cmath.py index b81e24cd..eafe1544 100644 --- a/raysect/core/math/function/float/function1d/tests/test_cmath.py +++ b/raysect/core/math/function/float/function1d/tests/test_cmath.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/tests/test_constant.py b/raysect/core/math/function/float/function1d/tests/test_constant.py index 98eee069..deadd77a 100644 --- a/raysect/core/math/function/float/function1d/tests/test_constant.py +++ b/raysect/core/math/function/float/function1d/tests/test_constant.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/tests/test_interpolator.py b/raysect/core/math/function/float/function1d/tests/test_interpolator.py index 670c35ce..1cd3bce6 100644 --- a/raysect/core/math/function/float/function1d/tests/test_interpolator.py +++ b/raysect/core/math/function/float/function1d/tests/test_interpolator.py @@ -1,5 +1,5 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function1d/tests/test_samplers.py b/raysect/core/math/function/float/function1d/tests/test_samplers.py index c2f88c42..6a237a8c 100644 --- a/raysect/core/math/function/float/function1d/tests/test_samplers.py +++ b/raysect/core/math/function/float/function1d/tests/test_samplers.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/__init__.pxd b/raysect/core/math/function/float/function2d/__init__.pxd index 4f7767f9..e4b5cdac 100644 --- a/raysect/core/math/function/float/function2d/__init__.pxd +++ b/raysect/core/math/function/float/function2d/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/__init__.py b/raysect/core/math/function/float/function2d/__init__.py index 75bce885..4b532aa3 100644 --- a/raysect/core/math/function/float/function2d/__init__.py +++ b/raysect/core/math/function/float/function2d/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/arg.pxd b/raysect/core/math/function/float/function2d/arg.pxd index f2ce866d..401edbe3 100644 --- a/raysect/core/math/function/float/function2d/arg.pxd +++ b/raysect/core/math/function/float/function2d/arg.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/arg.pyx b/raysect/core/math/function/float/function2d/arg.pyx index efd6f490..dc5c76fe 100644 --- a/raysect/core/math/function/float/function2d/arg.pyx +++ b/raysect/core/math/function/float/function2d/arg.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/autowrap.pxd b/raysect/core/math/function/float/function2d/autowrap.pxd index f23991c1..32f1cd3c 100644 --- a/raysect/core/math/function/float/function2d/autowrap.pxd +++ b/raysect/core/math/function/float/function2d/autowrap.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/autowrap.pyx b/raysect/core/math/function/float/function2d/autowrap.pyx index e2d0a9af..e9da15da 100644 --- a/raysect/core/math/function/float/function2d/autowrap.pyx +++ b/raysect/core/math/function/float/function2d/autowrap.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/base.pxd b/raysect/core/math/function/float/function2d/base.pxd index 8cc41121..aed5f8fe 100644 --- a/raysect/core/math/function/float/function2d/base.pxd +++ b/raysect/core/math/function/float/function2d/base.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/base.pyx b/raysect/core/math/function/float/function2d/base.pyx index f8d80567..933fc8b4 100644 --- a/raysect/core/math/function/float/function2d/base.pyx +++ b/raysect/core/math/function/float/function2d/base.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/blend.pxd b/raysect/core/math/function/float/function2d/blend.pxd index 2bb1cd40..010d628b 100644 --- a/raysect/core/math/function/float/function2d/blend.pxd +++ b/raysect/core/math/function/float/function2d/blend.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/blend.pyx b/raysect/core/math/function/float/function2d/blend.pyx index 570738b2..e7c31992 100644 --- a/raysect/core/math/function/float/function2d/blend.pyx +++ b/raysect/core/math/function/float/function2d/blend.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/cmath.pxd b/raysect/core/math/function/float/function2d/cmath.pxd index 1a8de05e..aad5ae48 100644 --- a/raysect/core/math/function/float/function2d/cmath.pxd +++ b/raysect/core/math/function/float/function2d/cmath.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/cmath.pyx b/raysect/core/math/function/float/function2d/cmath.pyx index ba93ef36..80ba6716 100644 --- a/raysect/core/math/function/float/function2d/cmath.pyx +++ b/raysect/core/math/function/float/function2d/cmath.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/constant.pxd b/raysect/core/math/function/float/function2d/constant.pxd index 45714c48..e56437e1 100644 --- a/raysect/core/math/function/float/function2d/constant.pxd +++ b/raysect/core/math/function/float/function2d/constant.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/constant.pyx b/raysect/core/math/function/float/function2d/constant.pyx index c95accc7..320b55a7 100644 --- a/raysect/core/math/function/float/function2d/constant.pyx +++ b/raysect/core/math/function/float/function2d/constant.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/interpolate/__init__.pxd b/raysect/core/math/function/float/function2d/interpolate/__init__.pxd index edca0a8a..05130a1d 100644 --- a/raysect/core/math/function/float/function2d/interpolate/__init__.pxd +++ b/raysect/core/math/function/float/function2d/interpolate/__init__.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/interpolate/__init__.py b/raysect/core/math/function/float/function2d/interpolate/__init__.py index bbf8b55f..59c50e45 100644 --- a/raysect/core/math/function/float/function2d/interpolate/__init__.py +++ b/raysect/core/math/function/float/function2d/interpolate/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/interpolate/common.pxd b/raysect/core/math/function/float/function2d/interpolate/common.pxd index febecc1d..700cd1d8 100644 --- a/raysect/core/math/function/float/function2d/interpolate/common.pxd +++ b/raysect/core/math/function/float/function2d/interpolate/common.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/interpolate/common.pyx b/raysect/core/math/function/float/function2d/interpolate/common.pyx index ddfb68f7..4a4ae152 100644 --- a/raysect/core/math/function/float/function2d/interpolate/common.pyx +++ b/raysect/core/math/function/float/function2d/interpolate/common.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/interpolate/discrete2dmesh.pxd b/raysect/core/math/function/float/function2d/interpolate/discrete2dmesh.pxd index 88197139..8fa578cb 100644 --- a/raysect/core/math/function/float/function2d/interpolate/discrete2dmesh.pxd +++ b/raysect/core/math/function/float/function2d/interpolate/discrete2dmesh.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/interpolate/discrete2dmesh.pyx b/raysect/core/math/function/float/function2d/interpolate/discrete2dmesh.pyx index 6acb320b..e7494cf8 100644 --- a/raysect/core/math/function/float/function2d/interpolate/discrete2dmesh.pyx +++ b/raysect/core/math/function/float/function2d/interpolate/discrete2dmesh.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/interpolate/interpolator2darray.pxd b/raysect/core/math/function/float/function2d/interpolate/interpolator2darray.pxd index 742becbc..0f9d3978 100644 --- a/raysect/core/math/function/float/function2d/interpolate/interpolator2darray.pxd +++ b/raysect/core/math/function/float/function2d/interpolate/interpolator2darray.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/interpolate/interpolator2darray.pyx b/raysect/core/math/function/float/function2d/interpolate/interpolator2darray.pyx index 988021f3..0afca494 100644 --- a/raysect/core/math/function/float/function2d/interpolate/interpolator2darray.pyx +++ b/raysect/core/math/function/float/function2d/interpolate/interpolator2darray.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/interpolate/interpolator2dmesh.pxd b/raysect/core/math/function/float/function2d/interpolate/interpolator2dmesh.pxd index d97f3446..8794c150 100644 --- a/raysect/core/math/function/float/function2d/interpolate/interpolator2dmesh.pxd +++ b/raysect/core/math/function/float/function2d/interpolate/interpolator2dmesh.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/interpolate/interpolator2dmesh.pyx b/raysect/core/math/function/float/function2d/interpolate/interpolator2dmesh.pyx index 2bbca3d7..62ddde44 100644 --- a/raysect/core/math/function/float/function2d/interpolate/interpolator2dmesh.pyx +++ b/raysect/core/math/function/float/function2d/interpolate/interpolator2dmesh.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/interpolate/tests/scripts/generate_2d_splines.py b/raysect/core/math/function/float/function2d/interpolate/tests/scripts/generate_2d_splines.py index 8847beab..998a7292 100644 --- a/raysect/core/math/function/float/function2d/interpolate/tests/scripts/generate_2d_splines.py +++ b/raysect/core/math/function/float/function2d/interpolate/tests/scripts/generate_2d_splines.py @@ -1,5 +1,5 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/interpolate/tests/test_interpolator_2d.py b/raysect/core/math/function/float/function2d/interpolate/tests/test_interpolator_2d.py index e538d237..1ed9e602 100644 --- a/raysect/core/math/function/float/function2d/interpolate/tests/test_interpolator_2d.py +++ b/raysect/core/math/function/float/function2d/interpolate/tests/test_interpolator_2d.py @@ -1,5 +1,5 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/tests/test_arg.py b/raysect/core/math/function/float/function2d/tests/test_arg.py index 378713f5..4d588e09 100644 --- a/raysect/core/math/function/float/function2d/tests/test_arg.py +++ b/raysect/core/math/function/float/function2d/tests/test_arg.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/tests/test_autowrap.py b/raysect/core/math/function/float/function2d/tests/test_autowrap.py index a050dfa7..179326bd 100644 --- a/raysect/core/math/function/float/function2d/tests/test_autowrap.py +++ b/raysect/core/math/function/float/function2d/tests/test_autowrap.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/tests/test_base.py b/raysect/core/math/function/float/function2d/tests/test_base.py index 365b3d32..d8acdf78 100644 --- a/raysect/core/math/function/float/function2d/tests/test_base.py +++ b/raysect/core/math/function/float/function2d/tests/test_base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/tests/test_cmath.py b/raysect/core/math/function/float/function2d/tests/test_cmath.py index 0e838492..eaceaccc 100644 --- a/raysect/core/math/function/float/function2d/tests/test_cmath.py +++ b/raysect/core/math/function/float/function2d/tests/test_cmath.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function2d/tests/test_constant.py b/raysect/core/math/function/float/function2d/tests/test_constant.py index 2cf71b3b..96b081a3 100644 --- a/raysect/core/math/function/float/function2d/tests/test_constant.py +++ b/raysect/core/math/function/float/function2d/tests/test_constant.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/__init__.pxd b/raysect/core/math/function/float/function3d/__init__.pxd index abe63a22..5141e672 100644 --- a/raysect/core/math/function/float/function3d/__init__.pxd +++ b/raysect/core/math/function/float/function3d/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/__init__.py b/raysect/core/math/function/float/function3d/__init__.py index ee926ab7..7998e002 100644 --- a/raysect/core/math/function/float/function3d/__init__.py +++ b/raysect/core/math/function/float/function3d/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/arg.pxd b/raysect/core/math/function/float/function3d/arg.pxd index d5a262a2..3c4aa58b 100644 --- a/raysect/core/math/function/float/function3d/arg.pxd +++ b/raysect/core/math/function/float/function3d/arg.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/arg.pyx b/raysect/core/math/function/float/function3d/arg.pyx index 8672e9fb..c8e276fb 100644 --- a/raysect/core/math/function/float/function3d/arg.pyx +++ b/raysect/core/math/function/float/function3d/arg.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/autowrap.pxd b/raysect/core/math/function/float/function3d/autowrap.pxd index 0f9e4d65..6c09fdc4 100644 --- a/raysect/core/math/function/float/function3d/autowrap.pxd +++ b/raysect/core/math/function/float/function3d/autowrap.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/autowrap.pyx b/raysect/core/math/function/float/function3d/autowrap.pyx index e45459a0..de9b8355 100644 --- a/raysect/core/math/function/float/function3d/autowrap.pyx +++ b/raysect/core/math/function/float/function3d/autowrap.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/base.pxd b/raysect/core/math/function/float/function3d/base.pxd index f9b67a28..679fa29b 100644 --- a/raysect/core/math/function/float/function3d/base.pxd +++ b/raysect/core/math/function/float/function3d/base.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/base.pyx b/raysect/core/math/function/float/function3d/base.pyx index b5f1933f..80f80b95 100644 --- a/raysect/core/math/function/float/function3d/base.pyx +++ b/raysect/core/math/function/float/function3d/base.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/blend.pxd b/raysect/core/math/function/float/function3d/blend.pxd index f1dc75e7..3cf06ffe 100644 --- a/raysect/core/math/function/float/function3d/blend.pxd +++ b/raysect/core/math/function/float/function3d/blend.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/blend.pyx b/raysect/core/math/function/float/function3d/blend.pyx index 75672a99..79c54359 100644 --- a/raysect/core/math/function/float/function3d/blend.pyx +++ b/raysect/core/math/function/float/function3d/blend.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/cmath.pxd b/raysect/core/math/function/float/function3d/cmath.pxd index da2b9584..af95f92d 100644 --- a/raysect/core/math/function/float/function3d/cmath.pxd +++ b/raysect/core/math/function/float/function3d/cmath.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/cmath.pyx b/raysect/core/math/function/float/function3d/cmath.pyx index 95cea1ef..f6c9de2d 100644 --- a/raysect/core/math/function/float/function3d/cmath.pyx +++ b/raysect/core/math/function/float/function3d/cmath.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/constant.pxd b/raysect/core/math/function/float/function3d/constant.pxd index 0d3d0ae8..30d38ba0 100644 --- a/raysect/core/math/function/float/function3d/constant.pxd +++ b/raysect/core/math/function/float/function3d/constant.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/constant.pyx b/raysect/core/math/function/float/function3d/constant.pyx index 7a21b9ba..00642ebc 100644 --- a/raysect/core/math/function/float/function3d/constant.pyx +++ b/raysect/core/math/function/float/function3d/constant.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/interpolate/__init__.pxd b/raysect/core/math/function/float/function3d/interpolate/__init__.pxd index 3e110c28..72614b33 100644 --- a/raysect/core/math/function/float/function3d/interpolate/__init__.pxd +++ b/raysect/core/math/function/float/function3d/interpolate/__init__.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/interpolate/__init__.py b/raysect/core/math/function/float/function3d/interpolate/__init__.py index 97efcda4..d4a3cd75 100644 --- a/raysect/core/math/function/float/function3d/interpolate/__init__.py +++ b/raysect/core/math/function/float/function3d/interpolate/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/interpolate/common.pxd b/raysect/core/math/function/float/function3d/interpolate/common.pxd index 2368425a..45c3e385 100644 --- a/raysect/core/math/function/float/function3d/interpolate/common.pxd +++ b/raysect/core/math/function/float/function3d/interpolate/common.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/interpolate/common.pyx b/raysect/core/math/function/float/function3d/interpolate/common.pyx index dee3cc4e..9b65345a 100644 --- a/raysect/core/math/function/float/function3d/interpolate/common.pyx +++ b/raysect/core/math/function/float/function3d/interpolate/common.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/interpolate/discrete3dmesh.pxd b/raysect/core/math/function/float/function3d/interpolate/discrete3dmesh.pxd index 71c07c5c..d3eb7a77 100644 --- a/raysect/core/math/function/float/function3d/interpolate/discrete3dmesh.pxd +++ b/raysect/core/math/function/float/function3d/interpolate/discrete3dmesh.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/interpolate/discrete3dmesh.pyx b/raysect/core/math/function/float/function3d/interpolate/discrete3dmesh.pyx index 79388df2..a61bb236 100644 --- a/raysect/core/math/function/float/function3d/interpolate/discrete3dmesh.pyx +++ b/raysect/core/math/function/float/function3d/interpolate/discrete3dmesh.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/interpolate/interpolator3darray.pxd b/raysect/core/math/function/float/function3d/interpolate/interpolator3darray.pxd index 5b0a17d5..d5ef41ff 100644 --- a/raysect/core/math/function/float/function3d/interpolate/interpolator3darray.pxd +++ b/raysect/core/math/function/float/function3d/interpolate/interpolator3darray.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/interpolate/interpolator3darray.pyx b/raysect/core/math/function/float/function3d/interpolate/interpolator3darray.pyx index 6aa9488b..7c93ea85 100644 --- a/raysect/core/math/function/float/function3d/interpolate/interpolator3darray.pyx +++ b/raysect/core/math/function/float/function3d/interpolate/interpolator3darray.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/interpolate/tests/scripts/generate_3d_splines.py b/raysect/core/math/function/float/function3d/interpolate/tests/scripts/generate_3d_splines.py index 4df2faeb..d1e52a3e 100644 --- a/raysect/core/math/function/float/function3d/interpolate/tests/scripts/generate_3d_splines.py +++ b/raysect/core/math/function/float/function3d/interpolate/tests/scripts/generate_3d_splines.py @@ -1,5 +1,5 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/interpolate/tests/test_interpolator_3d.py b/raysect/core/math/function/float/function3d/interpolate/tests/test_interpolator_3d.py index 33ba56bb..4c6cd5d9 100644 --- a/raysect/core/math/function/float/function3d/interpolate/tests/test_interpolator_3d.py +++ b/raysect/core/math/function/float/function3d/interpolate/tests/test_interpolator_3d.py @@ -1,5 +1,5 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/tests/test_arg.py b/raysect/core/math/function/float/function3d/tests/test_arg.py index 0019c1db..24babb6d 100644 --- a/raysect/core/math/function/float/function3d/tests/test_arg.py +++ b/raysect/core/math/function/float/function3d/tests/test_arg.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/tests/test_autowrap.py b/raysect/core/math/function/float/function3d/tests/test_autowrap.py index e96eeba6..32f24bac 100644 --- a/raysect/core/math/function/float/function3d/tests/test_autowrap.py +++ b/raysect/core/math/function/float/function3d/tests/test_autowrap.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/tests/test_base.py b/raysect/core/math/function/float/function3d/tests/test_base.py index fc5f622c..8984f19f 100644 --- a/raysect/core/math/function/float/function3d/tests/test_base.py +++ b/raysect/core/math/function/float/function3d/tests/test_base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/tests/test_cmath.py b/raysect/core/math/function/float/function3d/tests/test_cmath.py index cd02bfaa..9a3fbede 100644 --- a/raysect/core/math/function/float/function3d/tests/test_cmath.py +++ b/raysect/core/math/function/float/function3d/tests/test_cmath.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/float/function3d/tests/test_constant.py b/raysect/core/math/function/float/function3d/tests/test_constant.py index 22b632c1..ff6d1e57 100644 --- a/raysect/core/math/function/float/function3d/tests/test_constant.py +++ b/raysect/core/math/function/float/function3d/tests/test_constant.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/__init__.pxd b/raysect/core/math/function/vector3d/__init__.pxd index 0b0af398..7d369007 100644 --- a/raysect/core/math/function/vector3d/__init__.pxd +++ b/raysect/core/math/function/vector3d/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/__init__.py b/raysect/core/math/function/vector3d/__init__.py index a034c18f..3342550d 100644 --- a/raysect/core/math/function/vector3d/__init__.py +++ b/raysect/core/math/function/vector3d/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/base.pxd b/raysect/core/math/function/vector3d/base.pxd index 24ea7cab..1db0cba7 100644 --- a/raysect/core/math/function/vector3d/base.pxd +++ b/raysect/core/math/function/vector3d/base.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/base.pyx b/raysect/core/math/function/vector3d/base.pyx index ba2d1afe..4d97b1b1 100644 --- a/raysect/core/math/function/vector3d/base.pyx +++ b/raysect/core/math/function/vector3d/base.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/__init__.pxd b/raysect/core/math/function/vector3d/function1d/__init__.pxd index 3ea8b908..7e421dd1 100644 --- a/raysect/core/math/function/vector3d/function1d/__init__.pxd +++ b/raysect/core/math/function/vector3d/function1d/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/autowrap.pxd b/raysect/core/math/function/vector3d/function1d/autowrap.pxd index f8a85b72..a41f9d41 100644 --- a/raysect/core/math/function/vector3d/function1d/autowrap.pxd +++ b/raysect/core/math/function/vector3d/function1d/autowrap.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/autowrap.pyx b/raysect/core/math/function/vector3d/function1d/autowrap.pyx index 6ac407b6..679e0193 100644 --- a/raysect/core/math/function/vector3d/function1d/autowrap.pyx +++ b/raysect/core/math/function/vector3d/function1d/autowrap.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/base.pxd b/raysect/core/math/function/vector3d/function1d/base.pxd index 39e8cdb1..1e868efd 100644 --- a/raysect/core/math/function/vector3d/function1d/base.pxd +++ b/raysect/core/math/function/vector3d/function1d/base.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/base.pyx b/raysect/core/math/function/vector3d/function1d/base.pyx index 2cc9942b..0c1e371c 100644 --- a/raysect/core/math/function/vector3d/function1d/base.pyx +++ b/raysect/core/math/function/vector3d/function1d/base.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/blend.pxd b/raysect/core/math/function/vector3d/function1d/blend.pxd index 6a68ffca..c1bc5472 100644 --- a/raysect/core/math/function/vector3d/function1d/blend.pxd +++ b/raysect/core/math/function/vector3d/function1d/blend.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/blend.pyx b/raysect/core/math/function/vector3d/function1d/blend.pyx index 1a83d61b..91645a65 100644 --- a/raysect/core/math/function/vector3d/function1d/blend.pyx +++ b/raysect/core/math/function/vector3d/function1d/blend.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/constant.pxd b/raysect/core/math/function/vector3d/function1d/constant.pxd index 297a71ab..d5976d5c 100644 --- a/raysect/core/math/function/vector3d/function1d/constant.pxd +++ b/raysect/core/math/function/vector3d/function1d/constant.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/constant.pyx b/raysect/core/math/function/vector3d/function1d/constant.pyx index bc8423dc..4f59b0ef 100644 --- a/raysect/core/math/function/vector3d/function1d/constant.pyx +++ b/raysect/core/math/function/vector3d/function1d/constant.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/tests/test_autowrap.py b/raysect/core/math/function/vector3d/function1d/tests/test_autowrap.py index 296df2ef..6c0391cd 100644 --- a/raysect/core/math/function/vector3d/function1d/tests/test_autowrap.py +++ b/raysect/core/math/function/vector3d/function1d/tests/test_autowrap.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/tests/test_base.py b/raysect/core/math/function/vector3d/function1d/tests/test_base.py index 6827208b..90ec03d2 100644 --- a/raysect/core/math/function/vector3d/function1d/tests/test_base.py +++ b/raysect/core/math/function/vector3d/function1d/tests/test_base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/tests/test_constant.py b/raysect/core/math/function/vector3d/function1d/tests/test_constant.py index b79a9907..8b34fb7e 100644 --- a/raysect/core/math/function/vector3d/function1d/tests/test_constant.py +++ b/raysect/core/math/function/vector3d/function1d/tests/test_constant.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/tests/test_float_to_vector3d.py b/raysect/core/math/function/vector3d/function1d/tests/test_float_to_vector3d.py index c14f288a..46464486 100644 --- a/raysect/core/math/function/vector3d/function1d/tests/test_float_to_vector3d.py +++ b/raysect/core/math/function/vector3d/function1d/tests/test_float_to_vector3d.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/utility.pxd b/raysect/core/math/function/vector3d/function1d/utility.pxd index a94cf660..de1c1a64 100644 --- a/raysect/core/math/function/vector3d/function1d/utility.pxd +++ b/raysect/core/math/function/vector3d/function1d/utility.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function1d/utility.pyx b/raysect/core/math/function/vector3d/function1d/utility.pyx index 25f64ead..43fe661c 100644 --- a/raysect/core/math/function/vector3d/function1d/utility.pyx +++ b/raysect/core/math/function/vector3d/function1d/utility.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/__init__.pxd b/raysect/core/math/function/vector3d/function2d/__init__.pxd index 18545b5c..8a25facc 100644 --- a/raysect/core/math/function/vector3d/function2d/__init__.pxd +++ b/raysect/core/math/function/vector3d/function2d/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/autowrap.pxd b/raysect/core/math/function/vector3d/function2d/autowrap.pxd index 1bda3174..06c7c52d 100644 --- a/raysect/core/math/function/vector3d/function2d/autowrap.pxd +++ b/raysect/core/math/function/vector3d/function2d/autowrap.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/autowrap.pyx b/raysect/core/math/function/vector3d/function2d/autowrap.pyx index 9c7fb5bb..d6050def 100644 --- a/raysect/core/math/function/vector3d/function2d/autowrap.pyx +++ b/raysect/core/math/function/vector3d/function2d/autowrap.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/base.pxd b/raysect/core/math/function/vector3d/function2d/base.pxd index 78028056..c8159ba9 100644 --- a/raysect/core/math/function/vector3d/function2d/base.pxd +++ b/raysect/core/math/function/vector3d/function2d/base.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/base.pyx b/raysect/core/math/function/vector3d/function2d/base.pyx index 6f3f4ff1..021dbf71 100644 --- a/raysect/core/math/function/vector3d/function2d/base.pyx +++ b/raysect/core/math/function/vector3d/function2d/base.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/blend.pxd b/raysect/core/math/function/vector3d/function2d/blend.pxd index 6177e866..dd55f465 100644 --- a/raysect/core/math/function/vector3d/function2d/blend.pxd +++ b/raysect/core/math/function/vector3d/function2d/blend.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/blend.pyx b/raysect/core/math/function/vector3d/function2d/blend.pyx index b808d1a6..6a69c7e7 100644 --- a/raysect/core/math/function/vector3d/function2d/blend.pyx +++ b/raysect/core/math/function/vector3d/function2d/blend.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/constant.pxd b/raysect/core/math/function/vector3d/function2d/constant.pxd index 828c7de0..9b63f5a0 100644 --- a/raysect/core/math/function/vector3d/function2d/constant.pxd +++ b/raysect/core/math/function/vector3d/function2d/constant.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/constant.pyx b/raysect/core/math/function/vector3d/function2d/constant.pyx index ff4f8b47..4656e368 100644 --- a/raysect/core/math/function/vector3d/function2d/constant.pyx +++ b/raysect/core/math/function/vector3d/function2d/constant.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/tests/test_autowrap.py b/raysect/core/math/function/vector3d/function2d/tests/test_autowrap.py index 9f244dc1..47fda6cc 100644 --- a/raysect/core/math/function/vector3d/function2d/tests/test_autowrap.py +++ b/raysect/core/math/function/vector3d/function2d/tests/test_autowrap.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/tests/test_base.py b/raysect/core/math/function/vector3d/function2d/tests/test_base.py index 0649dc3a..5f4a5380 100644 --- a/raysect/core/math/function/vector3d/function2d/tests/test_base.py +++ b/raysect/core/math/function/vector3d/function2d/tests/test_base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/tests/test_constant.py b/raysect/core/math/function/vector3d/function2d/tests/test_constant.py index 00d7f062..a2e93ebb 100644 --- a/raysect/core/math/function/vector3d/function2d/tests/test_constant.py +++ b/raysect/core/math/function/vector3d/function2d/tests/test_constant.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/tests/test_float_to_vector3d.py b/raysect/core/math/function/vector3d/function2d/tests/test_float_to_vector3d.py index 3112c5a0..a4895c05 100644 --- a/raysect/core/math/function/vector3d/function2d/tests/test_float_to_vector3d.py +++ b/raysect/core/math/function/vector3d/function2d/tests/test_float_to_vector3d.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/utility.pxd b/raysect/core/math/function/vector3d/function2d/utility.pxd index 9647e92d..37ca19c2 100644 --- a/raysect/core/math/function/vector3d/function2d/utility.pxd +++ b/raysect/core/math/function/vector3d/function2d/utility.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function2d/utility.pyx b/raysect/core/math/function/vector3d/function2d/utility.pyx index 7c0446dd..f9850f27 100644 --- a/raysect/core/math/function/vector3d/function2d/utility.pyx +++ b/raysect/core/math/function/vector3d/function2d/utility.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/__init__.pxd b/raysect/core/math/function/vector3d/function3d/__init__.pxd index a554aea0..6ec9a9de 100644 --- a/raysect/core/math/function/vector3d/function3d/__init__.pxd +++ b/raysect/core/math/function/vector3d/function3d/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/autowrap.pxd b/raysect/core/math/function/vector3d/function3d/autowrap.pxd index ef319211..e36d1f89 100644 --- a/raysect/core/math/function/vector3d/function3d/autowrap.pxd +++ b/raysect/core/math/function/vector3d/function3d/autowrap.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/autowrap.pyx b/raysect/core/math/function/vector3d/function3d/autowrap.pyx index 2c1b276a..fa604350 100644 --- a/raysect/core/math/function/vector3d/function3d/autowrap.pyx +++ b/raysect/core/math/function/vector3d/function3d/autowrap.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/base.pxd b/raysect/core/math/function/vector3d/function3d/base.pxd index c22778b1..95f4b0e8 100644 --- a/raysect/core/math/function/vector3d/function3d/base.pxd +++ b/raysect/core/math/function/vector3d/function3d/base.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/base.pyx b/raysect/core/math/function/vector3d/function3d/base.pyx index 79242d5d..453a49bd 100644 --- a/raysect/core/math/function/vector3d/function3d/base.pyx +++ b/raysect/core/math/function/vector3d/function3d/base.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/blend.pxd b/raysect/core/math/function/vector3d/function3d/blend.pxd index 160a96bd..16efa436 100644 --- a/raysect/core/math/function/vector3d/function3d/blend.pxd +++ b/raysect/core/math/function/vector3d/function3d/blend.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/blend.pyx b/raysect/core/math/function/vector3d/function3d/blend.pyx index 5f5a1ae7..a44f3bba 100644 --- a/raysect/core/math/function/vector3d/function3d/blend.pyx +++ b/raysect/core/math/function/vector3d/function3d/blend.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/constant.pxd b/raysect/core/math/function/vector3d/function3d/constant.pxd index 937b3086..9173094e 100644 --- a/raysect/core/math/function/vector3d/function3d/constant.pxd +++ b/raysect/core/math/function/vector3d/function3d/constant.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/constant.pyx b/raysect/core/math/function/vector3d/function3d/constant.pyx index 051bd8bb..b39749f4 100644 --- a/raysect/core/math/function/vector3d/function3d/constant.pyx +++ b/raysect/core/math/function/vector3d/function3d/constant.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/tests/test_autowrap.py b/raysect/core/math/function/vector3d/function3d/tests/test_autowrap.py index 5836be92..9d8f3732 100644 --- a/raysect/core/math/function/vector3d/function3d/tests/test_autowrap.py +++ b/raysect/core/math/function/vector3d/function3d/tests/test_autowrap.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/tests/test_base.py b/raysect/core/math/function/vector3d/function3d/tests/test_base.py index d94177af..c1a981ff 100644 --- a/raysect/core/math/function/vector3d/function3d/tests/test_base.py +++ b/raysect/core/math/function/vector3d/function3d/tests/test_base.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/tests/test_constant.py b/raysect/core/math/function/vector3d/function3d/tests/test_constant.py index 8f945fd8..b2b3ddfb 100644 --- a/raysect/core/math/function/vector3d/function3d/tests/test_constant.py +++ b/raysect/core/math/function/vector3d/function3d/tests/test_constant.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/tests/test_float_to_vector3d.py b/raysect/core/math/function/vector3d/function3d/tests/test_float_to_vector3d.py index b619520a..2b3ba3e9 100644 --- a/raysect/core/math/function/vector3d/function3d/tests/test_float_to_vector3d.py +++ b/raysect/core/math/function/vector3d/function3d/tests/test_float_to_vector3d.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/utility.pxd b/raysect/core/math/function/vector3d/function3d/utility.pxd index 7caa75ca..3ff5813c 100644 --- a/raysect/core/math/function/vector3d/function3d/utility.pxd +++ b/raysect/core/math/function/vector3d/function3d/utility.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/function/vector3d/function3d/utility.pyx b/raysect/core/math/function/vector3d/function3d/utility.pyx index 891d93af..3f01fdf3 100644 --- a/raysect/core/math/function/vector3d/function3d/utility.pyx +++ b/raysect/core/math/function/vector3d/function3d/utility.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/normal.pxd b/raysect/core/math/normal.pxd index b30878f0..ab43f7af 100644 --- a/raysect/core/math/normal.pxd +++ b/raysect/core/math/normal.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/normal.pyx b/raysect/core/math/normal.pyx index c0045a7b..78169bc0 100644 --- a/raysect/core/math/normal.pyx +++ b/raysect/core/math/normal.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/point.pxd b/raysect/core/math/point.pxd index dc7840bf..ced255a4 100644 --- a/raysect/core/math/point.pxd +++ b/raysect/core/math/point.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/point.pyx b/raysect/core/math/point.pyx index 7ebdc2b2..20294b54 100644 --- a/raysect/core/math/point.pyx +++ b/raysect/core/math/point.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/polygon.pxd b/raysect/core/math/polygon.pxd index c21befce..cf7fba87 100644 --- a/raysect/core/math/polygon.pxd +++ b/raysect/core/math/polygon.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/polygon.pyx b/raysect/core/math/polygon.pyx index be2c0610..01c245b3 100644 --- a/raysect/core/math/polygon.pyx +++ b/raysect/core/math/polygon.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/quaternion.pxd b/raysect/core/math/quaternion.pxd index 50972181..bb6d1339 100644 --- a/raysect/core/math/quaternion.pxd +++ b/raysect/core/math/quaternion.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/quaternion.pyx b/raysect/core/math/quaternion.pyx index 76038a78..26d85e06 100644 --- a/raysect/core/math/quaternion.pyx +++ b/raysect/core/math/quaternion.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/random.pxd b/raysect/core/math/random.pxd index 67198a16..5be54b81 100644 --- a/raysect/core/math/random.pxd +++ b/raysect/core/math/random.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/random.pyx b/raysect/core/math/random.pyx index 096acb04..c47c024a 100644 --- a/raysect/core/math/random.pyx +++ b/raysect/core/math/random.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/sampler/__init__.pxd b/raysect/core/math/sampler/__init__.pxd index f7b95ca1..8931c646 100644 --- a/raysect/core/math/sampler/__init__.pxd +++ b/raysect/core/math/sampler/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/sampler/__init__.py b/raysect/core/math/sampler/__init__.py index 23bed925..0181770d 100644 --- a/raysect/core/math/sampler/__init__.py +++ b/raysect/core/math/sampler/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/sampler/solidangle.pxd b/raysect/core/math/sampler/solidangle.pxd index 146b6072..ca9343e9 100644 --- a/raysect/core/math/sampler/solidangle.pxd +++ b/raysect/core/math/sampler/solidangle.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/sampler/solidangle.pyx b/raysect/core/math/sampler/solidangle.pyx index 55a76e33..04068a42 100644 --- a/raysect/core/math/sampler/solidangle.pyx +++ b/raysect/core/math/sampler/solidangle.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/sampler/surface3d.pxd b/raysect/core/math/sampler/surface3d.pxd index fad67d71..72dc91bf 100644 --- a/raysect/core/math/sampler/surface3d.pxd +++ b/raysect/core/math/sampler/surface3d.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/sampler/surface3d.pyx b/raysect/core/math/sampler/surface3d.pyx index 23303a30..110a7f86 100644 --- a/raysect/core/math/sampler/surface3d.pyx +++ b/raysect/core/math/sampler/surface3d.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/sampler/targetted.pxd b/raysect/core/math/sampler/targetted.pxd index e85e0d69..a938a571 100644 --- a/raysect/core/math/sampler/targetted.pxd +++ b/raysect/core/math/sampler/targetted.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/sampler/targetted.pyx b/raysect/core/math/sampler/targetted.pyx index 9faf898e..3aabb070 100644 --- a/raysect/core/math/sampler/targetted.pyx +++ b/raysect/core/math/sampler/targetted.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/spatial/__init__.pxd b/raysect/core/math/spatial/__init__.pxd index c6160567..87ff7989 100644 --- a/raysect/core/math/spatial/__init__.pxd +++ b/raysect/core/math/spatial/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/spatial/__init__.py b/raysect/core/math/spatial/__init__.py index 9c638ece..8b7b50bd 100644 --- a/raysect/core/math/spatial/__init__.py +++ b/raysect/core/math/spatial/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/spatial/kdtree2d.pxd b/raysect/core/math/spatial/kdtree2d.pxd index 560dd1ca..1ebfb847 100644 --- a/raysect/core/math/spatial/kdtree2d.pxd +++ b/raysect/core/math/spatial/kdtree2d.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/spatial/kdtree2d.pyx b/raysect/core/math/spatial/kdtree2d.pyx index 2e26888e..879f4a4d 100644 --- a/raysect/core/math/spatial/kdtree2d.pyx +++ b/raysect/core/math/spatial/kdtree2d.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/spatial/kdtree3d.pxd b/raysect/core/math/spatial/kdtree3d.pxd index f0706b58..fd7d2745 100644 --- a/raysect/core/math/spatial/kdtree3d.pxd +++ b/raysect/core/math/spatial/kdtree3d.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/spatial/kdtree3d.pyx b/raysect/core/math/spatial/kdtree3d.pyx index e82306f4..f8a95850 100644 --- a/raysect/core/math/spatial/kdtree3d.pyx +++ b/raysect/core/math/spatial/kdtree3d.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/statsarray.pxd b/raysect/core/math/statsarray.pxd index 3075155a..6ea7bf7e 100644 --- a/raysect/core/math/statsarray.pxd +++ b/raysect/core/math/statsarray.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/statsarray.pyx b/raysect/core/math/statsarray.pyx index c4f5b38e..491467af 100644 --- a/raysect/core/math/statsarray.pyx +++ b/raysect/core/math/statsarray.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/tests/test_affinematrix3d.py b/raysect/core/math/tests/test_affinematrix3d.py index 43faaa63..23b28f51 100644 --- a/raysect/core/math/tests/test_affinematrix3d.py +++ b/raysect/core/math/tests/test_affinematrix3d.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/tests/test_interaction3d.py b/raysect/core/math/tests/test_interaction3d.py index 9c2105dd..108a1719 100644 --- a/raysect/core/math/tests/test_interaction3d.py +++ b/raysect/core/math/tests/test_interaction3d.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/tests/test_normal3d.py b/raysect/core/math/tests/test_normal3d.py index d791a72e..38c95ecc 100644 --- a/raysect/core/math/tests/test_normal3d.py +++ b/raysect/core/math/tests/test_normal3d.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/tests/test_point2d.py b/raysect/core/math/tests/test_point2d.py index 30d1e360..3e9d1da0 100644 --- a/raysect/core/math/tests/test_point2d.py +++ b/raysect/core/math/tests/test_point2d.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/tests/test_point3d.py b/raysect/core/math/tests/test_point3d.py index ac43f9b8..59ee2316 100644 --- a/raysect/core/math/tests/test_point3d.py +++ b/raysect/core/math/tests/test_point3d.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/tests/test_quaternion.py b/raysect/core/math/tests/test_quaternion.py index b6477a1d..ea5a4a31 100644 --- a/raysect/core/math/tests/test_quaternion.py +++ b/raysect/core/math/tests/test_quaternion.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/tests/test_random.py b/raysect/core/math/tests/test_random.py index 9ca6d7a3..bec3ce4c 100644 --- a/raysect/core/math/tests/test_random.py +++ b/raysect/core/math/tests/test_random.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/tests/test_transform.py b/raysect/core/math/tests/test_transform.py index 5efdb6af..5a9f5026 100644 --- a/raysect/core/math/tests/test_transform.py +++ b/raysect/core/math/tests/test_transform.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/tests/test_vector2d.py b/raysect/core/math/tests/test_vector2d.py index e47eea24..c6a68d9f 100644 --- a/raysect/core/math/tests/test_vector2d.py +++ b/raysect/core/math/tests/test_vector2d.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/tests/test_vector3d.py b/raysect/core/math/tests/test_vector3d.py index d4f2e9f9..d74c071c 100644 --- a/raysect/core/math/tests/test_vector3d.py +++ b/raysect/core/math/tests/test_vector3d.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/transform.pxd b/raysect/core/math/transform.pxd index b489a683..705c5dde 100644 --- a/raysect/core/math/transform.pxd +++ b/raysect/core/math/transform.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/transform.pyx b/raysect/core/math/transform.pyx index 75b5b93c..bf431ea0 100644 --- a/raysect/core/math/transform.pyx +++ b/raysect/core/math/transform.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/units.pxd b/raysect/core/math/units.pxd index 554a4a1f..0b1b906d 100644 --- a/raysect/core/math/units.pxd +++ b/raysect/core/math/units.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/units.pyx b/raysect/core/math/units.pyx index 0b31f713..07b8375d 100644 --- a/raysect/core/math/units.pyx +++ b/raysect/core/math/units.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/vector.pxd b/raysect/core/math/vector.pxd index c65e765c..07a0f8a5 100644 --- a/raysect/core/math/vector.pxd +++ b/raysect/core/math/vector.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/math/vector.pyx b/raysect/core/math/vector.pyx index 08e88a90..054479b0 100644 --- a/raysect/core/math/vector.pyx +++ b/raysect/core/math/vector.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/ray.pxd b/raysect/core/ray.pxd index afdf07a2..db89e3cf 100644 --- a/raysect/core/ray.pxd +++ b/raysect/core/ray.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/ray.pyx b/raysect/core/ray.pyx index 15d97910..8eca9339 100644 --- a/raysect/core/ray.pyx +++ b/raysect/core/ray.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/__init__.pxd b/raysect/core/scenegraph/__init__.pxd index 6a37263c..b6840cda 100644 --- a/raysect/core/scenegraph/__init__.pxd +++ b/raysect/core/scenegraph/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/_nodebase.pxd b/raysect/core/scenegraph/_nodebase.pxd index 76503a55..159c0fe6 100644 --- a/raysect/core/scenegraph/_nodebase.pxd +++ b/raysect/core/scenegraph/_nodebase.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/_nodebase.pyx b/raysect/core/scenegraph/_nodebase.pyx index 0a9032d6..726b4b5e 100644 --- a/raysect/core/scenegraph/_nodebase.pyx +++ b/raysect/core/scenegraph/_nodebase.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/node.pxd b/raysect/core/scenegraph/node.pxd index bdeff42a..2a80246b 100644 --- a/raysect/core/scenegraph/node.pxd +++ b/raysect/core/scenegraph/node.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/node.pyx b/raysect/core/scenegraph/node.pyx index 61ed6e79..70a05cbf 100644 --- a/raysect/core/scenegraph/node.pyx +++ b/raysect/core/scenegraph/node.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/observer.pxd b/raysect/core/scenegraph/observer.pxd index f4ef9e62..a8fccb8e 100644 --- a/raysect/core/scenegraph/observer.pxd +++ b/raysect/core/scenegraph/observer.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/observer.pyx b/raysect/core/scenegraph/observer.pyx index ff16a50b..1d56a45a 100644 --- a/raysect/core/scenegraph/observer.pyx +++ b/raysect/core/scenegraph/observer.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/primitive.pxd b/raysect/core/scenegraph/primitive.pxd index db5e6d3a..ad9d03f4 100644 --- a/raysect/core/scenegraph/primitive.pxd +++ b/raysect/core/scenegraph/primitive.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/primitive.pyx b/raysect/core/scenegraph/primitive.pyx index b4a1addd..22d78f04 100644 --- a/raysect/core/scenegraph/primitive.pyx +++ b/raysect/core/scenegraph/primitive.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/signal.pxd b/raysect/core/scenegraph/signal.pxd index 8a8c7bc0..0c6a43b8 100644 --- a/raysect/core/scenegraph/signal.pxd +++ b/raysect/core/scenegraph/signal.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/signal.pyx b/raysect/core/scenegraph/signal.pyx index 43e0d4ff..a52ca56b 100644 --- a/raysect/core/scenegraph/signal.pyx +++ b/raysect/core/scenegraph/signal.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/tests/test_node.py b/raysect/core/scenegraph/tests/test_node.py index 6e4eaf43..6a036685 100644 --- a/raysect/core/scenegraph/tests/test_node.py +++ b/raysect/core/scenegraph/tests/test_node.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/tests/test_observer.py b/raysect/core/scenegraph/tests/test_observer.py index c6c99f00..ffa1debd 100644 --- a/raysect/core/scenegraph/tests/test_observer.py +++ b/raysect/core/scenegraph/tests/test_observer.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/tests/test_primitive.py b/raysect/core/scenegraph/tests/test_primitive.py index 96619fde..25491b74 100644 --- a/raysect/core/scenegraph/tests/test_primitive.py +++ b/raysect/core/scenegraph/tests/test_primitive.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/tests/test_world.py b/raysect/core/scenegraph/tests/test_world.py index 44005a20..9ddcdda3 100644 --- a/raysect/core/scenegraph/tests/test_world.py +++ b/raysect/core/scenegraph/tests/test_world.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/utility.pxd b/raysect/core/scenegraph/utility.pxd index 9583b803..6b3b452f 100644 --- a/raysect/core/scenegraph/utility.pxd +++ b/raysect/core/scenegraph/utility.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/utility.pyx b/raysect/core/scenegraph/utility.pyx index aa99a2ea..6947beb0 100644 --- a/raysect/core/scenegraph/utility.pyx +++ b/raysect/core/scenegraph/utility.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/world.pxd b/raysect/core/scenegraph/world.pxd index 5c162d1a..a9e697af 100644 --- a/raysect/core/scenegraph/world.pxd +++ b/raysect/core/scenegraph/world.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/scenegraph/world.pyx b/raysect/core/scenegraph/world.pyx index d25db0a5..d44de0a6 100644 --- a/raysect/core/scenegraph/world.pyx +++ b/raysect/core/scenegraph/world.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/core/workflow.py b/raysect/core/workflow.py index beedff2a..859d9123 100644 --- a/raysect/core/workflow.py +++ b/raysect/core/workflow.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/__init__.pxd b/raysect/optical/__init__.pxd index 3ad82713..1937e255 100644 --- a/raysect/optical/__init__.pxd +++ b/raysect/optical/__init__.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/__init__.py b/raysect/optical/__init__.py index 85d5c250..d7d3c988 100644 --- a/raysect/optical/__init__.py +++ b/raysect/optical/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/colour.pxd b/raysect/optical/colour.pxd index 15130a5d..d0211e8a 100644 --- a/raysect/optical/colour.pxd +++ b/raysect/optical/colour.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/colour.pyx b/raysect/optical/colour.pyx index f3afaff7..bedef460 100644 --- a/raysect/optical/colour.pyx +++ b/raysect/optical/colour.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/library/glass/schott.py b/raysect/optical/library/glass/schott.py index 0582101e..e8ebbd8c 100644 --- a/raysect/optical/library/glass/schott.py +++ b/raysect/optical/library/glass/schott.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/library/metal/metal.py b/raysect/optical/library/metal/metal.py index 90e47411..6a07295f 100644 --- a/raysect/optical/library/metal/metal.py +++ b/raysect/optical/library/metal/metal.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/library/metal/roughmetal.py b/raysect/optical/library/metal/roughmetal.py index 6b567a47..37837cdd 100644 --- a/raysect/optical/library/metal/roughmetal.py +++ b/raysect/optical/library/metal/roughmetal.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/library/spectra/blackbody.pyx b/raysect/optical/library/spectra/blackbody.pyx index 94d7ae3b..0e8b2d23 100644 --- a/raysect/optical/library/spectra/blackbody.pyx +++ b/raysect/optical/library/spectra/blackbody.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/library/spectra/colours.py b/raysect/optical/library/spectra/colours.py index 52c73328..dfb96e66 100644 --- a/raysect/optical/library/spectra/colours.py +++ b/raysect/optical/library/spectra/colours.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/loggingray.pyx b/raysect/optical/loggingray.pyx index 5fa38d5b..edc79b01 100644 --- a/raysect/optical/loggingray.pyx +++ b/raysect/optical/loggingray.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/__init__.pxd b/raysect/optical/material/__init__.pxd index 3a117d2e..cd4c5bcd 100644 --- a/raysect/optical/material/__init__.pxd +++ b/raysect/optical/material/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/__init__.py b/raysect/optical/material/__init__.py index c843b58d..20da1c8c 100644 --- a/raysect/optical/material/__init__.py +++ b/raysect/optical/material/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/absorber.pxd b/raysect/optical/material/absorber.pxd index 28688fe6..36abe6b5 100644 --- a/raysect/optical/material/absorber.pxd +++ b/raysect/optical/material/absorber.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/absorber.pyx b/raysect/optical/material/absorber.pyx index 2024a7f1..f9f1be2d 100644 --- a/raysect/optical/material/absorber.pyx +++ b/raysect/optical/material/absorber.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/conductor.pxd b/raysect/optical/material/conductor.pxd index 90814267..83416241 100644 --- a/raysect/optical/material/conductor.pxd +++ b/raysect/optical/material/conductor.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/conductor.pyx b/raysect/optical/material/conductor.pyx index 79fc4365..aa7a6222 100644 --- a/raysect/optical/material/conductor.pyx +++ b/raysect/optical/material/conductor.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/debug.pxd b/raysect/optical/material/debug.pxd index 69b05bea..369603c8 100644 --- a/raysect/optical/material/debug.pxd +++ b/raysect/optical/material/debug.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/debug.pyx b/raysect/optical/material/debug.pyx index 1a4f1610..edeb211f 100644 --- a/raysect/optical/material/debug.pyx +++ b/raysect/optical/material/debug.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/dielectric.pxd b/raysect/optical/material/dielectric.pxd index 8ec2a331..d541c7d3 100644 --- a/raysect/optical/material/dielectric.pxd +++ b/raysect/optical/material/dielectric.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/dielectric.pyx b/raysect/optical/material/dielectric.pyx index 0e1e2cc6..b2c65ac3 100644 --- a/raysect/optical/material/dielectric.pyx +++ b/raysect/optical/material/dielectric.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/__init__.pxd b/raysect/optical/material/emitter/__init__.pxd index ce84be6e..4e670feb 100644 --- a/raysect/optical/material/emitter/__init__.pxd +++ b/raysect/optical/material/emitter/__init__.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/__init__.py b/raysect/optical/material/emitter/__init__.py index 3801bcfd..4711fb61 100644 --- a/raysect/optical/material/emitter/__init__.py +++ b/raysect/optical/material/emitter/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/anisotropic.pxd b/raysect/optical/material/emitter/anisotropic.pxd index a837c9b9..fd4a58c4 100644 --- a/raysect/optical/material/emitter/anisotropic.pxd +++ b/raysect/optical/material/emitter/anisotropic.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/anisotropic.pyx b/raysect/optical/material/emitter/anisotropic.pyx index e6fe6d42..4bd4cacc 100644 --- a/raysect/optical/material/emitter/anisotropic.pyx +++ b/raysect/optical/material/emitter/anisotropic.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/checkerboard.pxd b/raysect/optical/material/emitter/checkerboard.pxd index 40760f70..96357aa3 100644 --- a/raysect/optical/material/emitter/checkerboard.pxd +++ b/raysect/optical/material/emitter/checkerboard.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/checkerboard.pyx b/raysect/optical/material/emitter/checkerboard.pyx index 998bb12d..667b3024 100644 --- a/raysect/optical/material/emitter/checkerboard.pyx +++ b/raysect/optical/material/emitter/checkerboard.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/homogeneous.pxd b/raysect/optical/material/emitter/homogeneous.pxd index 70373bf3..2016ffeb 100644 --- a/raysect/optical/material/emitter/homogeneous.pxd +++ b/raysect/optical/material/emitter/homogeneous.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/homogeneous.pyx b/raysect/optical/material/emitter/homogeneous.pyx index bbf4647a..592037a1 100644 --- a/raysect/optical/material/emitter/homogeneous.pyx +++ b/raysect/optical/material/emitter/homogeneous.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/inhomogeneous.pxd b/raysect/optical/material/emitter/inhomogeneous.pxd index 969906fe..7db353db 100644 --- a/raysect/optical/material/emitter/inhomogeneous.pxd +++ b/raysect/optical/material/emitter/inhomogeneous.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/inhomogeneous.pyx b/raysect/optical/material/emitter/inhomogeneous.pyx index d4fb8fb6..28d9f20a 100644 --- a/raysect/optical/material/emitter/inhomogeneous.pyx +++ b/raysect/optical/material/emitter/inhomogeneous.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/uniform.pxd b/raysect/optical/material/emitter/uniform.pxd index 559188f7..6052ab02 100644 --- a/raysect/optical/material/emitter/uniform.pxd +++ b/raysect/optical/material/emitter/uniform.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/uniform.pyx b/raysect/optical/material/emitter/uniform.pyx index 3d9b918f..bddda8af 100644 --- a/raysect/optical/material/emitter/uniform.pyx +++ b/raysect/optical/material/emitter/uniform.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/unity.pxd b/raysect/optical/material/emitter/unity.pxd index f5259484..1f91e5c0 100644 --- a/raysect/optical/material/emitter/unity.pxd +++ b/raysect/optical/material/emitter/unity.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/emitter/unity.pyx b/raysect/optical/material/emitter/unity.pyx index 0666ab7b..145fa1c9 100644 --- a/raysect/optical/material/emitter/unity.pyx +++ b/raysect/optical/material/emitter/unity.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/lambert.pyx b/raysect/optical/material/lambert.pyx index ba1fe3bb..9e2c1a67 100644 --- a/raysect/optical/material/lambert.pyx +++ b/raysect/optical/material/lambert.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/material.pxd b/raysect/optical/material/material.pxd index 4bb2617b..f7392016 100644 --- a/raysect/optical/material/material.pxd +++ b/raysect/optical/material/material.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/material.pyx b/raysect/optical/material/material.pyx index 01ab33e7..543694ff 100644 --- a/raysect/optical/material/material.pyx +++ b/raysect/optical/material/material.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/modifiers/__init__.py b/raysect/optical/material/modifiers/__init__.py index 17141bce..73485c44 100644 --- a/raysect/optical/material/modifiers/__init__.py +++ b/raysect/optical/material/modifiers/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/modifiers/add.pyx b/raysect/optical/material/modifiers/add.pyx index 450231db..7bcd5fa7 100644 --- a/raysect/optical/material/modifiers/add.pyx +++ b/raysect/optical/material/modifiers/add.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/modifiers/blend.pyx b/raysect/optical/material/modifiers/blend.pyx index 5e03c10d..c059a07b 100644 --- a/raysect/optical/material/modifiers/blend.pyx +++ b/raysect/optical/material/modifiers/blend.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/modifiers/roughen.pyx b/raysect/optical/material/modifiers/roughen.pyx index d705b1f7..024b0de3 100644 --- a/raysect/optical/material/modifiers/roughen.pyx +++ b/raysect/optical/material/modifiers/roughen.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/material/modifiers/transform.pyx b/raysect/optical/material/modifiers/transform.pyx index 3ef77f86..eac58c9d 100644 --- a/raysect/optical/material/modifiers/transform.pyx +++ b/raysect/optical/material/modifiers/transform.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/__init__.pxd b/raysect/optical/observer/__init__.pxd index 54bae438..8e401ed9 100644 --- a/raysect/optical/observer/__init__.pxd +++ b/raysect/optical/observer/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/__init__.py b/raysect/optical/observer/__init__.py index f41a292f..40153755 100644 --- a/raysect/optical/observer/__init__.py +++ b/raysect/optical/observer/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/base/__init__.pxd b/raysect/optical/observer/base/__init__.pxd index fe7b1d76..e49d93a0 100644 --- a/raysect/optical/observer/base/__init__.pxd +++ b/raysect/optical/observer/base/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/base/__init__.py b/raysect/optical/observer/base/__init__.py index c03e71b3..62c41a2f 100644 --- a/raysect/optical/observer/base/__init__.py +++ b/raysect/optical/observer/base/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/base/observer.pxd b/raysect/optical/observer/base/observer.pxd index dddc57fd..b354b5db 100644 --- a/raysect/optical/observer/base/observer.pxd +++ b/raysect/optical/observer/base/observer.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/base/observer.pyx b/raysect/optical/observer/base/observer.pyx index 5afdd7fb..66c7705b 100644 --- a/raysect/optical/observer/base/observer.pyx +++ b/raysect/optical/observer/base/observer.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/base/pipeline.pxd b/raysect/optical/observer/base/pipeline.pxd index a5f96b2b..4b0437f4 100644 --- a/raysect/optical/observer/base/pipeline.pxd +++ b/raysect/optical/observer/base/pipeline.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/base/pipeline.pyx b/raysect/optical/observer/base/pipeline.pyx index 1ae47d67..802b3a8e 100644 --- a/raysect/optical/observer/base/pipeline.pyx +++ b/raysect/optical/observer/base/pipeline.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/base/processor.pxd b/raysect/optical/observer/base/processor.pxd index d3f80fe4..c99cd6b5 100644 --- a/raysect/optical/observer/base/processor.pxd +++ b/raysect/optical/observer/base/processor.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/base/processor.pyx b/raysect/optical/observer/base/processor.pyx index b283b1f5..9d13ff0d 100644 --- a/raysect/optical/observer/base/processor.pyx +++ b/raysect/optical/observer/base/processor.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/base/sampler.pxd b/raysect/optical/observer/base/sampler.pxd index e78a2299..c80a9d09 100644 --- a/raysect/optical/observer/base/sampler.pxd +++ b/raysect/optical/observer/base/sampler.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/base/sampler.pyx b/raysect/optical/observer/base/sampler.pyx index 3ff41d8b..7c595f40 100644 --- a/raysect/optical/observer/base/sampler.pyx +++ b/raysect/optical/observer/base/sampler.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/base/slice.pxd b/raysect/optical/observer/base/slice.pxd index 0a411be8..68b8f5fd 100644 --- a/raysect/optical/observer/base/slice.pxd +++ b/raysect/optical/observer/base/slice.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/base/slice.pyx b/raysect/optical/observer/base/slice.pyx index 6cacc8a0..20848f80 100644 --- a/raysect/optical/observer/base/slice.pyx +++ b/raysect/optical/observer/base/slice.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/__init__.pxd b/raysect/optical/observer/imaging/__init__.pxd index 88b0bcc2..2a62327d 100644 --- a/raysect/optical/observer/imaging/__init__.pxd +++ b/raysect/optical/observer/imaging/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/__init__.py b/raysect/optical/observer/imaging/__init__.py index 4dc11805..b3dfd66e 100644 --- a/raysect/optical/observer/imaging/__init__.py +++ b/raysect/optical/observer/imaging/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/ccd.pxd b/raysect/optical/observer/imaging/ccd.pxd index f9be4281..c49fbcaa 100644 --- a/raysect/optical/observer/imaging/ccd.pxd +++ b/raysect/optical/observer/imaging/ccd.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/ccd.pyx b/raysect/optical/observer/imaging/ccd.pyx index 9af15b09..7483f0a3 100644 --- a/raysect/optical/observer/imaging/ccd.pyx +++ b/raysect/optical/observer/imaging/ccd.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/opencv.pxd b/raysect/optical/observer/imaging/opencv.pxd index 5070dd63..bbb0b6d5 100644 --- a/raysect/optical/observer/imaging/opencv.pxd +++ b/raysect/optical/observer/imaging/opencv.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/opencv.pyx b/raysect/optical/observer/imaging/opencv.pyx index 1c8f3430..b3257dd9 100644 --- a/raysect/optical/observer/imaging/opencv.pyx +++ b/raysect/optical/observer/imaging/opencv.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/orthographic.pxd b/raysect/optical/observer/imaging/orthographic.pxd index c9ca2b92..0b1acb99 100644 --- a/raysect/optical/observer/imaging/orthographic.pxd +++ b/raysect/optical/observer/imaging/orthographic.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/orthographic.pyx b/raysect/optical/observer/imaging/orthographic.pyx index 371b9b06..5d0946d2 100644 --- a/raysect/optical/observer/imaging/orthographic.pyx +++ b/raysect/optical/observer/imaging/orthographic.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/pinhole.pxd b/raysect/optical/observer/imaging/pinhole.pxd index 121a57d6..bb3bbb3b 100644 --- a/raysect/optical/observer/imaging/pinhole.pxd +++ b/raysect/optical/observer/imaging/pinhole.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/pinhole.pyx b/raysect/optical/observer/imaging/pinhole.pyx index ec05847d..b7876db1 100644 --- a/raysect/optical/observer/imaging/pinhole.pyx +++ b/raysect/optical/observer/imaging/pinhole.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/targetted_ccd.pxd b/raysect/optical/observer/imaging/targetted_ccd.pxd index a3de79b8..f8af58b1 100644 --- a/raysect/optical/observer/imaging/targetted_ccd.pxd +++ b/raysect/optical/observer/imaging/targetted_ccd.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/targetted_ccd.pyx b/raysect/optical/observer/imaging/targetted_ccd.pyx index cb355626..9a0defa6 100644 --- a/raysect/optical/observer/imaging/targetted_ccd.pyx +++ b/raysect/optical/observer/imaging/targetted_ccd.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/vector.pxd b/raysect/optical/observer/imaging/vector.pxd index d9d5f4bf..f5c5830d 100644 --- a/raysect/optical/observer/imaging/vector.pxd +++ b/raysect/optical/observer/imaging/vector.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/imaging/vector.pyx b/raysect/optical/observer/imaging/vector.pyx index 25aa6f86..dd73fd5f 100644 --- a/raysect/optical/observer/imaging/vector.pyx +++ b/raysect/optical/observer/imaging/vector.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/__init__.pxd b/raysect/optical/observer/nonimaging/__init__.pxd index 880166a6..4e2f420a 100644 --- a/raysect/optical/observer/nonimaging/__init__.pxd +++ b/raysect/optical/observer/nonimaging/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/__init__.py b/raysect/optical/observer/nonimaging/__init__.py index 0a800447..d1a8ee26 100644 --- a/raysect/optical/observer/nonimaging/__init__.py +++ b/raysect/optical/observer/nonimaging/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/fibreoptic.pxd b/raysect/optical/observer/nonimaging/fibreoptic.pxd index ced7d611..8033ca4b 100644 --- a/raysect/optical/observer/nonimaging/fibreoptic.pxd +++ b/raysect/optical/observer/nonimaging/fibreoptic.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/fibreoptic.pyx b/raysect/optical/observer/nonimaging/fibreoptic.pyx index c3fde275..87969674 100644 --- a/raysect/optical/observer/nonimaging/fibreoptic.pyx +++ b/raysect/optical/observer/nonimaging/fibreoptic.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/mesh_camera.pxd b/raysect/optical/observer/nonimaging/mesh_camera.pxd index 1ebda160..2820ed19 100644 --- a/raysect/optical/observer/nonimaging/mesh_camera.pxd +++ b/raysect/optical/observer/nonimaging/mesh_camera.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/mesh_camera.pyx b/raysect/optical/observer/nonimaging/mesh_camera.pyx index 79b5aabf..f47f5b83 100644 --- a/raysect/optical/observer/nonimaging/mesh_camera.pyx +++ b/raysect/optical/observer/nonimaging/mesh_camera.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/mesh_pixel.pxd b/raysect/optical/observer/nonimaging/mesh_pixel.pxd index ec0a3055..3ff29c43 100644 --- a/raysect/optical/observer/nonimaging/mesh_pixel.pxd +++ b/raysect/optical/observer/nonimaging/mesh_pixel.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/mesh_pixel.pyx b/raysect/optical/observer/nonimaging/mesh_pixel.pyx index 61a7479a..6391e417 100644 --- a/raysect/optical/observer/nonimaging/mesh_pixel.pyx +++ b/raysect/optical/observer/nonimaging/mesh_pixel.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/pixel.pxd b/raysect/optical/observer/nonimaging/pixel.pxd index 0da277db..5dba6ef8 100644 --- a/raysect/optical/observer/nonimaging/pixel.pxd +++ b/raysect/optical/observer/nonimaging/pixel.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/pixel.pyx b/raysect/optical/observer/nonimaging/pixel.pyx index ad9e760c..261749f2 100644 --- a/raysect/optical/observer/nonimaging/pixel.pyx +++ b/raysect/optical/observer/nonimaging/pixel.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/sightline.pxd b/raysect/optical/observer/nonimaging/sightline.pxd index 0ae360d8..a99161ac 100644 --- a/raysect/optical/observer/nonimaging/sightline.pxd +++ b/raysect/optical/observer/nonimaging/sightline.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/sightline.pyx b/raysect/optical/observer/nonimaging/sightline.pyx index 9a354106..f830916e 100644 --- a/raysect/optical/observer/nonimaging/sightline.pyx +++ b/raysect/optical/observer/nonimaging/sightline.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/targetted_pixel.pxd b/raysect/optical/observer/nonimaging/targetted_pixel.pxd index 0f7c8402..dd9b7682 100644 --- a/raysect/optical/observer/nonimaging/targetted_pixel.pxd +++ b/raysect/optical/observer/nonimaging/targetted_pixel.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/nonimaging/targetted_pixel.pyx b/raysect/optical/observer/nonimaging/targetted_pixel.pyx index b83b07a0..d6b5048b 100644 --- a/raysect/optical/observer/nonimaging/targetted_pixel.pyx +++ b/raysect/optical/observer/nonimaging/targetted_pixel.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/__init__.pxd b/raysect/optical/observer/pipeline/__init__.pxd index 8ad13376..ebbd41a4 100644 --- a/raysect/optical/observer/pipeline/__init__.pxd +++ b/raysect/optical/observer/pipeline/__init__.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/__init__.py b/raysect/optical/observer/pipeline/__init__.py index 90cc3043..a9ec73af 100644 --- a/raysect/optical/observer/pipeline/__init__.py +++ b/raysect/optical/observer/pipeline/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/bayer.pxd b/raysect/optical/observer/pipeline/bayer.pxd index 6dde0eb4..c31c915f 100644 --- a/raysect/optical/observer/pipeline/bayer.pxd +++ b/raysect/optical/observer/pipeline/bayer.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/bayer.pyx b/raysect/optical/observer/pipeline/bayer.pyx index 28a2f100..206fb214 100644 --- a/raysect/optical/observer/pipeline/bayer.pyx +++ b/raysect/optical/observer/pipeline/bayer.pyx @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/mono/__init__.pxd b/raysect/optical/observer/pipeline/mono/__init__.pxd index 64ec22d0..c3ac54e7 100644 --- a/raysect/optical/observer/pipeline/mono/__init__.pxd +++ b/raysect/optical/observer/pipeline/mono/__init__.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/mono/__init__.py b/raysect/optical/observer/pipeline/mono/__init__.py index 4c2be074..b424cddf 100644 --- a/raysect/optical/observer/pipeline/mono/__init__.py +++ b/raysect/optical/observer/pipeline/mono/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/mono/power.pxd b/raysect/optical/observer/pipeline/mono/power.pxd index 04f2b6ad..12129362 100644 --- a/raysect/optical/observer/pipeline/mono/power.pxd +++ b/raysect/optical/observer/pipeline/mono/power.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/mono/power.pyx b/raysect/optical/observer/pipeline/mono/power.pyx index a95d2d1e..e74f42a2 100644 --- a/raysect/optical/observer/pipeline/mono/power.pyx +++ b/raysect/optical/observer/pipeline/mono/power.pyx @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/mono/radiance.pxd b/raysect/optical/observer/pipeline/mono/radiance.pxd index f83aa868..c065334c 100644 --- a/raysect/optical/observer/pipeline/mono/radiance.pxd +++ b/raysect/optical/observer/pipeline/mono/radiance.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/mono/radiance.pyx b/raysect/optical/observer/pipeline/mono/radiance.pyx index a48795c2..3d64209f 100644 --- a/raysect/optical/observer/pipeline/mono/radiance.pyx +++ b/raysect/optical/observer/pipeline/mono/radiance.pyx @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/rgb.pxd b/raysect/optical/observer/pipeline/rgb.pxd index bc9ecd2d..8f40c6e0 100644 --- a/raysect/optical/observer/pipeline/rgb.pxd +++ b/raysect/optical/observer/pipeline/rgb.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/rgb.pyx b/raysect/optical/observer/pipeline/rgb.pyx index 1d4afa44..4b8b82f1 100644 --- a/raysect/optical/observer/pipeline/rgb.pyx +++ b/raysect/optical/observer/pipeline/rgb.pyx @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/spectral/__init__.pxd b/raysect/optical/observer/pipeline/spectral/__init__.pxd index 6e047e02..c2a80f6a 100644 --- a/raysect/optical/observer/pipeline/spectral/__init__.pxd +++ b/raysect/optical/observer/pipeline/spectral/__init__.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/spectral/__init__.py b/raysect/optical/observer/pipeline/spectral/__init__.py index 2b1440cb..0d66c593 100644 --- a/raysect/optical/observer/pipeline/spectral/__init__.py +++ b/raysect/optical/observer/pipeline/spectral/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/spectral/power.pxd b/raysect/optical/observer/pipeline/spectral/power.pxd index 6cfacef2..b73cf93d 100644 --- a/raysect/optical/observer/pipeline/spectral/power.pxd +++ b/raysect/optical/observer/pipeline/spectral/power.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/spectral/power.pyx b/raysect/optical/observer/pipeline/spectral/power.pyx index 3a094816..36a726c2 100644 --- a/raysect/optical/observer/pipeline/spectral/power.pyx +++ b/raysect/optical/observer/pipeline/spectral/power.pyx @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/spectral/radiance.pxd b/raysect/optical/observer/pipeline/spectral/radiance.pxd index 6c6034f5..9dfd285b 100644 --- a/raysect/optical/observer/pipeline/spectral/radiance.pxd +++ b/raysect/optical/observer/pipeline/spectral/radiance.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/pipeline/spectral/radiance.pyx b/raysect/optical/observer/pipeline/spectral/radiance.pyx index 6f4d32a7..daf0d41e 100644 --- a/raysect/optical/observer/pipeline/spectral/radiance.pyx +++ b/raysect/optical/observer/pipeline/spectral/radiance.pyx @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/sampler1d.pyx b/raysect/optical/observer/sampler1d.pyx index daac982a..12ea4790 100644 --- a/raysect/optical/observer/sampler1d.pyx +++ b/raysect/optical/observer/sampler1d.pyx @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/observer/sampler2d.pyx b/raysect/optical/observer/sampler2d.pyx index f61d0cce..15876213 100644 --- a/raysect/optical/observer/sampler2d.pyx +++ b/raysect/optical/observer/sampler2d.pyx @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/ray.pxd b/raysect/optical/ray.pxd index 38be8be7..176f1666 100644 --- a/raysect/optical/ray.pxd +++ b/raysect/optical/ray.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/ray.pyx b/raysect/optical/ray.pyx index d45e7588..499289d6 100644 --- a/raysect/optical/ray.pyx +++ b/raysect/optical/ray.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/scenegraph/__init__.pxd b/raysect/optical/scenegraph/__init__.pxd index b37c869c..d7053c07 100644 --- a/raysect/optical/scenegraph/__init__.pxd +++ b/raysect/optical/scenegraph/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/scenegraph/__init__.py b/raysect/optical/scenegraph/__init__.py index 2cf9f357..1456a5fc 100644 --- a/raysect/optical/scenegraph/__init__.py +++ b/raysect/optical/scenegraph/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/scenegraph/world.pxd b/raysect/optical/scenegraph/world.pxd index 3421ca2f..be376b6b 100644 --- a/raysect/optical/scenegraph/world.pxd +++ b/raysect/optical/scenegraph/world.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/scenegraph/world.pyx b/raysect/optical/scenegraph/world.pyx index 4d44cbb0..b7099a5a 100644 --- a/raysect/optical/scenegraph/world.pyx +++ b/raysect/optical/scenegraph/world.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/spectralfunction.pxd b/raysect/optical/spectralfunction.pxd index 82af135a..244ad4fd 100644 --- a/raysect/optical/spectralfunction.pxd +++ b/raysect/optical/spectralfunction.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/spectralfunction.pyx b/raysect/optical/spectralfunction.pyx index ea97de4c..003ecc95 100644 --- a/raysect/optical/spectralfunction.pyx +++ b/raysect/optical/spectralfunction.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/spectrum.pxd b/raysect/optical/spectrum.pxd index a69d0655..c59ce20f 100644 --- a/raysect/optical/spectrum.pxd +++ b/raysect/optical/spectrum.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/optical/spectrum.pyx b/raysect/optical/spectrum.pyx index 4111ab77..3a9ecac6 100644 --- a/raysect/optical/spectrum.pyx +++ b/raysect/optical/spectrum.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/__init__.pxd b/raysect/primitive/__init__.pxd index 66ab91b8..2b236180 100644 --- a/raysect/primitive/__init__.pxd +++ b/raysect/primitive/__init__.pxd @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/__init__.py b/raysect/primitive/__init__.py index 298643fc..fc571c09 100644 --- a/raysect/primitive/__init__.py +++ b/raysect/primitive/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/box.pxd b/raysect/primitive/box.pxd index e09e5f0c..2b44fee6 100644 --- a/raysect/primitive/box.pxd +++ b/raysect/primitive/box.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/box.pyx b/raysect/primitive/box.pyx index 01d95d09..8aa92536 100644 --- a/raysect/primitive/box.pyx +++ b/raysect/primitive/box.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/cone.pxd b/raysect/primitive/cone.pxd index e236e340..648b72fc 100644 --- a/raysect/primitive/cone.pxd +++ b/raysect/primitive/cone.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/cone.pyx b/raysect/primitive/cone.pyx index d1da1c9f..17355c8d 100644 --- a/raysect/primitive/cone.pyx +++ b/raysect/primitive/cone.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/csg.pxd b/raysect/primitive/csg.pxd index a139cc12..794f6aea 100644 --- a/raysect/primitive/csg.pxd +++ b/raysect/primitive/csg.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/csg.pyx b/raysect/primitive/csg.pyx index 64ab3e9a..6b760085 100644 --- a/raysect/primitive/csg.pyx +++ b/raysect/primitive/csg.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/cylinder.pxd b/raysect/primitive/cylinder.pxd index a1aa8f58..b7713a39 100644 --- a/raysect/primitive/cylinder.pxd +++ b/raysect/primitive/cylinder.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/cylinder.pyx b/raysect/primitive/cylinder.pyx index 9030e28d..5840cc9f 100644 --- a/raysect/primitive/cylinder.pyx +++ b/raysect/primitive/cylinder.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/lens/spherical.pyx b/raysect/primitive/lens/spherical.pyx index 31ade37b..545bbf76 100644 --- a/raysect/primitive/lens/spherical.pyx +++ b/raysect/primitive/lens/spherical.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/lens/tests/test_spherical.py b/raysect/primitive/lens/tests/test_spherical.py index f8bd197e..8742ca8f 100644 --- a/raysect/primitive/lens/tests/test_spherical.py +++ b/raysect/primitive/lens/tests/test_spherical.py @@ -1,4 +1,4 @@ -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/mesh/__init__.pxd b/raysect/primitive/mesh/__init__.pxd index 7fa0f6bc..82461723 100644 --- a/raysect/primitive/mesh/__init__.pxd +++ b/raysect/primitive/mesh/__init__.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/mesh/__init__.py b/raysect/primitive/mesh/__init__.py index a0d71a23..75e86b5c 100644 --- a/raysect/primitive/mesh/__init__.py +++ b/raysect/primitive/mesh/__init__.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/mesh/mesh.pxd b/raysect/primitive/mesh/mesh.pxd index 3ef49448..8c209581 100644 --- a/raysect/primitive/mesh/mesh.pxd +++ b/raysect/primitive/mesh/mesh.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/mesh/mesh.pyx b/raysect/primitive/mesh/mesh.pyx index ac27ddf7..fabb03b5 100644 --- a/raysect/primitive/mesh/mesh.pyx +++ b/raysect/primitive/mesh/mesh.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/mesh/obj.py b/raysect/primitive/mesh/obj.py index 1831dd65..e1ac904c 100644 --- a/raysect/primitive/mesh/obj.py +++ b/raysect/primitive/mesh/obj.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/mesh/ply.py b/raysect/primitive/mesh/ply.py index b11f7a7e..f5b8b8a3 100644 --- a/raysect/primitive/mesh/ply.py +++ b/raysect/primitive/mesh/ply.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/mesh/stl.py b/raysect/primitive/mesh/stl.py index 92f90db4..181d1bc0 100644 --- a/raysect/primitive/mesh/stl.py +++ b/raysect/primitive/mesh/stl.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/mesh/vtk.py b/raysect/primitive/mesh/vtk.py index 1b904fb5..2f0ee17b 100644 --- a/raysect/primitive/mesh/vtk.py +++ b/raysect/primitive/mesh/vtk.py @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/parabola.pxd b/raysect/primitive/parabola.pxd index 3eac4ba9..bbed0b57 100644 --- a/raysect/primitive/parabola.pxd +++ b/raysect/primitive/parabola.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/parabola.pyx b/raysect/primitive/parabola.pyx index 74f9a6e7..ac1a2c0e 100644 --- a/raysect/primitive/parabola.pyx +++ b/raysect/primitive/parabola.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/sphere.pxd b/raysect/primitive/sphere.pxd index 18cbce8b..1f067a96 100644 --- a/raysect/primitive/sphere.pxd +++ b/raysect/primitive/sphere.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/sphere.pyx b/raysect/primitive/sphere.pyx index 081ea3e6..d5bbd650 100644 --- a/raysect/primitive/sphere.pyx +++ b/raysect/primitive/sphere.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/utility.pxd b/raysect/primitive/utility.pxd index d4a9b1f3..947645ac 100644 --- a/raysect/primitive/utility.pxd +++ b/raysect/primitive/utility.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/utility.pyx b/raysect/primitive/utility.pyx index b3060ff6..7ca8a1e4 100644 --- a/raysect/primitive/utility.pyx +++ b/raysect/primitive/utility.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2023, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without From 37c7f2cac91941d51872f925bf01b4d49450f1b1 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sat, 19 Jul 2025 23:23:27 +0100 Subject: [PATCH 51/77] Addressed uninitialised variable compilation warnings. --- raysect/optical/observer/pipeline/bayer.pyx | 3 +-- raysect/optical/observer/pipeline/mono/power.pyx | 3 +-- raysect/optical/observer/pipeline/rgb.pyx | 9 +++++---- raysect/optical/observer/sampler2d.pyx | 2 ++ 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/raysect/optical/observer/pipeline/bayer.pyx b/raysect/optical/observer/pipeline/bayer.pyx index 206fb214..152fec0d 100644 --- a/raysect/optical/observer/pipeline/bayer.pyx +++ b/raysect/optical/observer/pipeline/bayer.pyx @@ -554,8 +554,7 @@ cdef class BayerPipeline2D(Pipeline2D): for i in range(pixels): if lmv[i] > 0: break - - if i == pixels: + else: return self._display_black_point # identify luminance at threshold diff --git a/raysect/optical/observer/pipeline/mono/power.pyx b/raysect/optical/observer/pipeline/mono/power.pyx index e74f42a2..f4088daa 100644 --- a/raysect/optical/observer/pipeline/mono/power.pyx +++ b/raysect/optical/observer/pipeline/mono/power.pyx @@ -716,8 +716,7 @@ cdef class PowerPipeline2D(Pipeline2D): for i in range(pixels): if lmv[i] > 0: break - - if i == pixels: + else: return self._display_black_point # identify luminance at threshold diff --git a/raysect/optical/observer/pipeline/rgb.pyx b/raysect/optical/observer/pipeline/rgb.pyx index 4b8b82f1..4d6bc65e 100644 --- a/raysect/optical/observer/pipeline/rgb.pyx +++ b/raysect/optical/observer/pipeline/rgb.pyx @@ -455,13 +455,14 @@ cdef class RGBPipeline2D(Pipeline2D): # sort by luminance luminance.sort() + # + i = 0 + # if all pixels black, return default sensitivity for i in range(pixels): if lmv[i] > 0: break - - # return default sensitivity - if i == pixels: + else: return 1.0 # identify luminance at threshold @@ -471,7 +472,7 @@ cdef class RGBPipeline2D(Pipeline2D): if peak_luminance == 0: return 1.0 - return 1 / peak_luminance + return 1.0 / peak_luminance @cython.boundscheck(False) @cython.wraparound(False) diff --git a/raysect/optical/observer/sampler2d.pyx b/raysect/optical/observer/sampler2d.pyx index 15876213..61f4d198 100644 --- a/raysect/optical/observer/sampler2d.pyx +++ b/raysect/optical/observer/sampler2d.pyx @@ -847,6 +847,8 @@ cdef class RGBAdaptiveSampler2D(FrameSampler2D): for c in range(3): if frame.mean_mv[x, y, c] > 0: pixel_normalised[c] = error[x, y, c] / frame.mean_mv[x, y, c] + else: + pixel_normalised[c] = 0.0 normalised_mv[x, y] = max(pixel_normalised[0], pixel_normalised[1], pixel_normalised[2]) # locate error value corresponding to fraction of frame to process From d91e083ef6635717655ac49d335fcde22ebeaf8f Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sun, 20 Jul 2025 00:17:27 +0100 Subject: [PATCH 52/77] Update cython version. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a66e34b9..b16903e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,5 +27,5 @@ Issues = "https://github.com/raysect/source/issues" Changelog = "https://github.com/raysect/source/blob/master/CHANGELOG.txt" [build-system] -requires = ["meson-python", "setuptools", "wheel", "numpy", "cython<3.0"] +requires = ["meson-python", "setuptools", "wheel", "numpy", "cython>=3.1"] build-backend = "mesonpy" From 1df6f311ac4f77d287dcafc64a26e769602065b3 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sun, 20 Jul 2025 00:26:24 +0100 Subject: [PATCH 53/77] Update cython version. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bbeae168..e1084f7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies - run: python -m pip install --prefer-binary meson-python meson ninja setuptools "cython>=0.28,<3.0" "matplotlib>=3,<4" ${{ matrix.numpy-version }} + run: python -m pip install --prefer-binary meson-python meson ninja setuptools "cython>=3.1" "matplotlib>=3,<4" ${{ matrix.numpy-version }} - name: Build and install Raysect run: dev/install_editable.sh - name: Run tests From 30a764e64759e633c46a8379750f2be09cfea481 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sun, 20 Jul 2025 02:19:39 +0100 Subject: [PATCH 54/77] Update .gitignore to remove randomly added string. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c38bf609..590e1871 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ __pycache__/ *$py.class # C extensions -*.son option to enable profiling +*.so *.c # Distribution / packaging .Python From 1696d1f5c14653c62eae1b6aaec543768d62dce1 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sun, 20 Jul 2025 19:28:35 +0100 Subject: [PATCH 55/77] Revised various segments of the code to both modernise it and support cython 3. --- dev/root-meson.build | 3 +- meson.build | 3 +- raysect/core/acceleration/accelerator.pxd | 3 +- raysect/core/acceleration/accelerator.pyx | 3 - raysect/core/acceleration/boundprimitive.pxd | 9 +- raysect/core/acceleration/kdtree.pxd | 3 + raysect/core/acceleration/kdtree.pyx | 1 - raysect/core/acceleration/unaccelerated.pxd | 5 +- raysect/core/acceleration/unaccelerated.pyx | 13 +- raysect/core/math/_mat4.pxd | 3 - raysect/core/math/_mat4.pyx | 2 - raysect/core/math/_vec3.pxd | 6 - raysect/core/math/_vec3.pyx | 9 +- raysect/core/math/affinematrix.pxd | 1 - raysect/core/math/affinematrix.pyx | 103 +++---- raysect/core/math/cython/triangle.pxd | 8 +- raysect/core/math/cython/triangle.pyx | 7 +- raysect/core/math/cython/utility.pyx | 4 +- raysect/core/math/normal.pxd | 12 +- raysect/core/math/normal.pyx | 146 ++++------ raysect/core/math/point.pxd | 14 - raysect/core/math/point.pyx | 87 ++---- raysect/core/math/quaternion.pxd | 20 +- raysect/core/math/quaternion.pyx | 70 ++--- raysect/core/math/sampler/solidangle.pxd | 9 +- raysect/core/math/sampler/solidangle.pyx | 3 +- raysect/core/math/sampler/surface3d.pxd | 3 - raysect/core/math/sampler/targetted.pxd | 8 +- raysect/core/math/sampler/targetted.pyx | 2 +- raysect/core/math/spatial/kdtree2d.pxd | 26 +- raysect/core/math/spatial/kdtree2d.pyx | 6 +- raysect/core/math/spatial/kdtree3d.pxd | 29 -- raysect/core/math/spatial/kdtree3d.pyx | 5 +- raysect/core/math/statsarray.pxd | 25 -- raysect/core/math/statsarray.pyx | 60 +++- raysect/core/math/transform.pyx | 11 +- raysect/core/math/units.pyx | 11 + raysect/core/math/vector.pxd | 26 +- raysect/core/math/vector.pyx | 277 ++++++++----------- 39 files changed, 396 insertions(+), 640 deletions(-) diff --git a/dev/root-meson.build b/dev/root-meson.build index 526acc1a..98253b94 100644 --- a/dev/root-meson.build +++ b/dev/root-meson.build @@ -4,5 +4,6 @@ py = import('python').find_installation(pure: false) numpy = dependency('numpy', method: 'config-tool') fs = import('fs') -cython_args = ['--annotate'] +# disabling explicit noexcept until pycharm fixes their cython 3 support (causes a large number of build warnings) +cython_args = ['--annotate', '-X legacy_implicit_noexcept=True'] cython_dependencies = [numpy] diff --git a/meson.build b/meson.build index 072eed68..77af6bfb 100644 --- a/meson.build +++ b/meson.build @@ -7,7 +7,8 @@ py = import('python').find_installation(pure: false) numpy = dependency('numpy', method: 'config-tool') fs = import('fs') -cython_args = ['--annotate'] +# disabling explicit noexcept until pycharm fixes their cython 3 support +cython_args = ['--annotate', '-Xlegacy_implicit_noexcept=True'] cython_dependencies = [numpy] subdir('raysect') diff --git a/raysect/core/acceleration/accelerator.pxd b/raysect/core/acceleration/accelerator.pxd index ca008e6b..495d3051 100644 --- a/raysect/core/acceleration/accelerator.pxd +++ b/raysect/core/acceleration/accelerator.pxd @@ -33,10 +33,9 @@ from raysect.core.ray cimport Ray from raysect.core.math cimport Point3D from raysect.core.intersection cimport Intersection + cdef class Accelerator: cpdef build(self, list primitives) - cpdef Intersection hit(self, Ray ray) - cpdef list contains(self, Point3D point) diff --git a/raysect/core/acceleration/accelerator.pyx b/raysect/core/acceleration/accelerator.pyx index 3ff5e3f4..e6279d91 100644 --- a/raysect/core/acceleration/accelerator.pyx +++ b/raysect/core/acceleration/accelerator.pyx @@ -32,13 +32,10 @@ cdef class Accelerator: cpdef build(self, list primitives): - pass cpdef Intersection hit(self, Ray ray): - raise NotImplementedError("Accelerator virtual method hit() has not been implemented.") cpdef list contains(self, Point3D point): - raise NotImplementedError("Accelerator virtual method contains() has not been implemented.") \ No newline at end of file diff --git a/raysect/core/acceleration/boundprimitive.pxd b/raysect/core/acceleration/boundprimitive.pxd index 8a1e35ed..cec01939 100644 --- a/raysect/core/acceleration/boundprimitive.pxd +++ b/raysect/core/acceleration/boundprimitive.pxd @@ -37,12 +37,11 @@ from raysect.core.intersection cimport Intersection cdef class BoundPrimitive: - cdef readonly Primitive primitive - cdef readonly BoundingBox3D box - cdef bint _primitive_tested + cdef: + readonly Primitive primitive + readonly BoundingBox3D box + bint _primitive_tested cdef Intersection hit(self, Ray ray) - cdef Intersection next_intersection(self) - cdef bint contains(self, Point3D point) \ No newline at end of file diff --git a/raysect/core/acceleration/kdtree.pxd b/raysect/core/acceleration/kdtree.pxd index 6616e2ef..16d0612d 100644 --- a/raysect/core/acceleration/kdtree.pxd +++ b/raysect/core/acceleration/kdtree.pxd @@ -33,12 +33,15 @@ from raysect.core.acceleration.accelerator cimport Accelerator as _Accelerator from raysect.core.math.spatial.kdtree3d cimport KDTree3DCore as _KDTreeCore from raysect.core.intersection cimport Intersection + cdef class _PrimitiveKDTree(_KDTreeCore): + cdef: list primitives Intersection hit_intersection cdef class KDTree(_Accelerator): + cdef _PrimitiveKDTree _kdtree diff --git a/raysect/core/acceleration/kdtree.pyx b/raysect/core/acceleration/kdtree.pyx index b2900780..2d7b28df 100644 --- a/raysect/core/acceleration/kdtree.pyx +++ b/raysect/core/acceleration/kdtree.pyx @@ -154,7 +154,6 @@ cdef class _PrimitiveKDTree(_KDTreeCore): # dereference the primitives and check if they contain the point enclosing_primitives = [] for item in range(count): - index = self._nodes[id].items[item] primitive = self.primitives[index] if primitive.contains(point): diff --git a/raysect/core/acceleration/unaccelerated.pxd b/raysect/core/acceleration/unaccelerated.pxd index 3d0149a5..d9ce0979 100644 --- a/raysect/core/acceleration/unaccelerated.pxd +++ b/raysect/core/acceleration/unaccelerated.pxd @@ -35,5 +35,6 @@ from raysect.core.boundingbox cimport BoundingBox3D cdef class Unaccelerated(Accelerator): - cdef list primitives - cdef BoundingBox3D world_box + cdef: + list primitives + BoundingBox3D world_box diff --git a/raysect/core/acceleration/unaccelerated.pyx b/raysect/core/acceleration/unaccelerated.pyx index 8695c549..c07e407f 100644 --- a/raysect/core/acceleration/unaccelerated.pyx +++ b/raysect/core/acceleration/unaccelerated.pyx @@ -37,6 +37,7 @@ from raysect.core.math cimport Point3D from raysect.core.intersection cimport Intersection from raysect.core.acceleration.boundprimitive cimport BoundPrimitive + cdef class Unaccelerated(Accelerator): def __init__(self): @@ -54,7 +55,6 @@ cdef class Unaccelerated(Accelerator): self.world_box = BoundingBox3D() for primitive in primitives: - accel_primitive = BoundPrimitive(primitive) self.primitives.append(accel_primitive) self.world_box.union(accel_primitive.box) @@ -70,23 +70,17 @@ cdef class Unaccelerated(Accelerator): # does the ray intersect the space containing the primitives if not self.world_box.hit(ray): - return None # find the closest primitive-ray intersection closest_intersection = None - # intial search distance is maximum possible ray extent + # initial search distance is maximum possible ray extent distance = ray.max_distance - for primitive in self.primitives: - intersection = primitive.hit(ray) - if intersection is not None: - if intersection.ray_distance < distance: - distance = intersection.ray_distance closest_intersection = intersection @@ -101,15 +95,12 @@ cdef class Unaccelerated(Accelerator): BoundPrimitive primitive if not self.world_box.contains(point): - return [] enclosing_primitives = [] for primitive in self.primitives: - if primitive.contains(point): - enclosing_primitives.append(primitive.primitive) return enclosing_primitives \ No newline at end of file diff --git a/raysect/core/math/_mat4.pxd b/raysect/core/math/_mat4.pxd index 9993668c..40d34ff1 100644 --- a/raysect/core/math/_mat4.pxd +++ b/raysect/core/math/_mat4.pxd @@ -34,10 +34,7 @@ cdef class _Mat4: cdef double m[4][4] cdef double get_element(self, int row, int column) - cdef void set_element(self, int row, int column, double v) - cpdef bint is_identity(self, double tolerance=*) - cpdef bint is_close(self, _Mat4 other, double tolerance=*) diff --git a/raysect/core/math/_mat4.pyx b/raysect/core/math/_mat4.pyx index f8828744..4c5c775b 100644 --- a/raysect/core/math/_mat4.pyx +++ b/raysect/core/math/_mat4.pyx @@ -54,7 +54,6 @@ cdef class _Mat4: # special handling for _Mat4 if isinstance(v, _Mat4): - m = <_Mat4>v for i in range(0, 4): for j in range(0, 4): @@ -95,7 +94,6 @@ cdef class _Mat4: Expects a tuple (row, column) as the index. e.g. v = matrix[1, 2] - """ cdef int row, column diff --git a/raysect/core/math/_vec3.pxd b/raysect/core/math/_vec3.pxd index 70b913c7..df241982 100644 --- a/raysect/core/math/_vec3.pxd +++ b/raysect/core/math/_vec3.pxd @@ -32,16 +32,10 @@ cdef class _Vec3: cdef public double x, y, z - cpdef double dot(self, _Vec3 v) - cpdef double angle(self, _Vec3 v) - cdef double get_length(self) nogil - cdef object set_length(self, double v) - cdef double get_index(self, int index) nogil - cdef void set_index(self, int index, double value) nogil diff --git a/raysect/core/math/_vec3.pyx b/raysect/core/math/_vec3.pyx index 0a1e3d88..02c16bd4 100644 --- a/raysect/core/math/_vec3.pyx +++ b/raysect/core/math/_vec3.pyx @@ -58,8 +58,8 @@ cdef class _Vec3: return self.y elif i == 2: return self.z - else: - raise IndexError("Index out of range [0, 2].") + + raise IndexError("Index out of range [0, 2].") def __setitem__(self, int i, double value): """Sets the vector coordinates by index ([0,1,2] -> [x,y,z]).""" @@ -74,6 +74,7 @@ cdef class _Vec3: raise IndexError("Index out of range [0, 2].") def __iter__(self): + yield self.x yield self.y yield self.z @@ -189,8 +190,8 @@ cdef class _Vec3: return self.y elif index == 2: return self.z - else: - return NAN + + return NAN cdef void set_index(self, int index, double value) nogil: """ diff --git a/raysect/core/math/affinematrix.pxd b/raysect/core/math/affinematrix.pxd index a5de0d98..abbfe35d 100644 --- a/raysect/core/math/affinematrix.pxd +++ b/raysect/core/math/affinematrix.pxd @@ -35,7 +35,6 @@ from raysect.core.math._mat4 cimport _Mat4 cdef class AffineMatrix3D(_Mat4): cpdef AffineMatrix3D inverse(self) - cdef AffineMatrix3D mul(self, AffineMatrix3D m) diff --git a/raysect/core/math/affinematrix.pyx b/raysect/core/math/affinematrix.pyx index bf9da108..f585d199 100644 --- a/raysect/core/math/affinematrix.pyx +++ b/raysect/core/math/affinematrix.pyx @@ -33,7 +33,6 @@ cimport cython from libc.math cimport fabs - cdef class AffineMatrix3D(_Mat4): """A 4x4 affine matrix. @@ -150,22 +149,24 @@ cdef class AffineMatrix3D(_Mat4): mx = self my = y - return new_affinematrix3d(mx.m[0][0] * my.m[0][0] + mx.m[0][1] * my.m[1][0] + mx.m[0][2] * my.m[2][0] + mx.m[0][3] * my.m[3][0], - mx.m[0][0] * my.m[0][1] + mx.m[0][1] * my.m[1][1] + mx.m[0][2] * my.m[2][1] + mx.m[0][3] * my.m[3][1], - mx.m[0][0] * my.m[0][2] + mx.m[0][1] * my.m[1][2] + mx.m[0][2] * my.m[2][2] + mx.m[0][3] * my.m[3][2], - mx.m[0][0] * my.m[0][3] + mx.m[0][1] * my.m[1][3] + mx.m[0][2] * my.m[2][3] + mx.m[0][3] * my.m[3][3], - mx.m[1][0] * my.m[0][0] + mx.m[1][1] * my.m[1][0] + mx.m[1][2] * my.m[2][0] + mx.m[1][3] * my.m[3][0], - mx.m[1][0] * my.m[0][1] + mx.m[1][1] * my.m[1][1] + mx.m[1][2] * my.m[2][1] + mx.m[1][3] * my.m[3][1], - mx.m[1][0] * my.m[0][2] + mx.m[1][1] * my.m[1][2] + mx.m[1][2] * my.m[2][2] + mx.m[1][3] * my.m[3][2], - mx.m[1][0] * my.m[0][3] + mx.m[1][1] * my.m[1][3] + mx.m[1][2] * my.m[2][3] + mx.m[1][3] * my.m[3][3], - mx.m[2][0] * my.m[0][0] + mx.m[2][1] * my.m[1][0] + mx.m[2][2] * my.m[2][0] + mx.m[2][3] * my.m[3][0], - mx.m[2][0] * my.m[0][1] + mx.m[2][1] * my.m[1][1] + mx.m[2][2] * my.m[2][1] + mx.m[2][3] * my.m[3][1], - mx.m[2][0] * my.m[0][2] + mx.m[2][1] * my.m[1][2] + mx.m[2][2] * my.m[2][2] + mx.m[2][3] * my.m[3][2], - mx.m[2][0] * my.m[0][3] + mx.m[2][1] * my.m[1][3] + mx.m[2][2] * my.m[2][3] + mx.m[2][3] * my.m[3][3], - mx.m[3][0] * my.m[0][0] + mx.m[3][1] * my.m[1][0] + mx.m[3][2] * my.m[2][0] + mx.m[3][3] * my.m[3][0], - mx.m[3][0] * my.m[0][1] + mx.m[3][1] * my.m[1][1] + mx.m[3][2] * my.m[2][1] + mx.m[3][3] * my.m[3][1], - mx.m[3][0] * my.m[0][2] + mx.m[3][1] * my.m[1][2] + mx.m[3][2] * my.m[2][2] + mx.m[3][3] * my.m[3][2], - mx.m[3][0] * my.m[0][3] + mx.m[3][1] * my.m[1][3] + mx.m[3][2] * my.m[2][3] + mx.m[3][3] * my.m[3][3]) + return new_affinematrix3d( + mx.m[0][0] * my.m[0][0] + mx.m[0][1] * my.m[1][0] + mx.m[0][2] * my.m[2][0] + mx.m[0][3] * my.m[3][0], + mx.m[0][0] * my.m[0][1] + mx.m[0][1] * my.m[1][1] + mx.m[0][2] * my.m[2][1] + mx.m[0][3] * my.m[3][1], + mx.m[0][0] * my.m[0][2] + mx.m[0][1] * my.m[1][2] + mx.m[0][2] * my.m[2][2] + mx.m[0][3] * my.m[3][2], + mx.m[0][0] * my.m[0][3] + mx.m[0][1] * my.m[1][3] + mx.m[0][2] * my.m[2][3] + mx.m[0][3] * my.m[3][3], + mx.m[1][0] * my.m[0][0] + mx.m[1][1] * my.m[1][0] + mx.m[1][2] * my.m[2][0] + mx.m[1][3] * my.m[3][0], + mx.m[1][0] * my.m[0][1] + mx.m[1][1] * my.m[1][1] + mx.m[1][2] * my.m[2][1] + mx.m[1][3] * my.m[3][1], + mx.m[1][0] * my.m[0][2] + mx.m[1][1] * my.m[1][2] + mx.m[1][2] * my.m[2][2] + mx.m[1][3] * my.m[3][2], + mx.m[1][0] * my.m[0][3] + mx.m[1][1] * my.m[1][3] + mx.m[1][2] * my.m[2][3] + mx.m[1][3] * my.m[3][3], + mx.m[2][0] * my.m[0][0] + mx.m[2][1] * my.m[1][0] + mx.m[2][2] * my.m[2][0] + mx.m[2][3] * my.m[3][0], + mx.m[2][0] * my.m[0][1] + mx.m[2][1] * my.m[1][1] + mx.m[2][2] * my.m[2][1] + mx.m[2][3] * my.m[3][1], + mx.m[2][0] * my.m[0][2] + mx.m[2][1] * my.m[1][2] + mx.m[2][2] * my.m[2][2] + mx.m[2][3] * my.m[3][2], + mx.m[2][0] * my.m[0][3] + mx.m[2][1] * my.m[1][3] + mx.m[2][2] * my.m[2][3] + mx.m[2][3] * my.m[3][3], + mx.m[3][0] * my.m[0][0] + mx.m[3][1] * my.m[1][0] + mx.m[3][2] * my.m[2][0] + mx.m[3][3] * my.m[3][0], + mx.m[3][0] * my.m[0][1] + mx.m[3][1] * my.m[1][1] + mx.m[3][2] * my.m[2][1] + mx.m[3][3] * my.m[3][1], + mx.m[3][0] * my.m[0][2] + mx.m[3][1] * my.m[1][2] + mx.m[3][2] * my.m[2][2] + mx.m[3][3] * my.m[3][2], + mx.m[3][0] * my.m[0][3] + mx.m[3][1] * my.m[1][3] + mx.m[3][2] * my.m[2][3] + mx.m[3][3] * my.m[3][3] + ) return NotImplemented @@ -231,38 +232,42 @@ cdef class AffineMatrix3D(_Mat4): t[16] = self.m[1][1] * self.m[3][3] - self.m[1][3] * self.m[3][1] t[17] = self.m[1][2] * self.m[3][3] - self.m[1][3] * self.m[3][2] - return new_affinematrix3d((self.m[2][2] * t[16] - self.m[2][1] * t[17] - self.m[2][3] * t[15]) * idet, - (self.m[2][1] * t[11] - self.m[2][2] * t[10] + self.m[2][3] * t[ 9]) * idet, - (self.m[3][1] * t[ 5] - self.m[3][2] * t[ 4] + self.m[3][3] * t[ 3]) * idet, - -t[21] * idet, - (self.m[2][0] * t[17] - self.m[2][2] * t[14] + self.m[2][3] * t[13]) * idet, - (self.m[2][2] * t[ 8] - self.m[2][0] * t[11] - self.m[2][3] * t[ 7]) * idet, - (self.m[3][2] * t[ 2] - self.m[3][0] * t[ 5] - self.m[3][3] * t[ 1]) * idet, - t[20] * idet, - (self.m[2][1] * t[14] - self.m[2][0] * t[16] - self.m[2][3] * t[12]) * idet, - (self.m[2][0] * t[10] - self.m[2][1] * t[ 8] + self.m[2][3] * t[ 6]) * idet, - (self.m[3][0] * t[ 4] - self.m[3][1] * t[ 2] + self.m[3][3] * t[ 0]) * idet, - -t[19] * idet, - (self.m[2][0] * t[15] - self.m[2][1] * t[13] + self.m[2][2] * t[12]) * idet, - (self.m[2][1] * t[ 7] - self.m[2][0] * t[ 9] - self.m[2][2] * t[ 6]) * idet, - (self.m[3][1] * t[ 1] - self.m[3][0] * t[ 3] - self.m[3][2] * t[ 0]) * idet, - t[18] * idet) + return new_affinematrix3d( + (self.m[2][2] * t[16] - self.m[2][1] * t[17] - self.m[2][3] * t[15]) * idet, + (self.m[2][1] * t[11] - self.m[2][2] * t[10] + self.m[2][3] * t[ 9]) * idet, + (self.m[3][1] * t[ 5] - self.m[3][2] * t[ 4] + self.m[3][3] * t[ 3]) * idet, + -t[21] * idet, + (self.m[2][0] * t[17] - self.m[2][2] * t[14] + self.m[2][3] * t[13]) * idet, + (self.m[2][2] * t[ 8] - self.m[2][0] * t[11] - self.m[2][3] * t[ 7]) * idet, + (self.m[3][2] * t[ 2] - self.m[3][0] * t[ 5] - self.m[3][3] * t[ 1]) * idet, + t[20] * idet, + (self.m[2][1] * t[14] - self.m[2][0] * t[16] - self.m[2][3] * t[12]) * idet, + (self.m[2][0] * t[10] - self.m[2][1] * t[ 8] + self.m[2][3] * t[ 6]) * idet, + (self.m[3][0] * t[ 4] - self.m[3][1] * t[ 2] + self.m[3][3] * t[ 0]) * idet, + -t[19] * idet, + (self.m[2][0] * t[15] - self.m[2][1] * t[13] + self.m[2][2] * t[12]) * idet, + (self.m[2][1] * t[ 7] - self.m[2][0] * t[ 9] - self.m[2][2] * t[ 6]) * idet, + (self.m[3][1] * t[ 1] - self.m[3][0] * t[ 3] - self.m[3][2] * t[ 0]) * idet, + t[18] * idet + ) cdef AffineMatrix3D mul(self, AffineMatrix3D m): - return new_affinematrix3d(self.m[0][0] * m.m[0][0] + self.m[0][1] * m.m[1][0] + self.m[0][2] * m.m[2][0] + self.m[0][3] * m.m[3][0], - self.m[0][0] * m.m[0][1] + self.m[0][1] * m.m[1][1] + self.m[0][2] * m.m[2][1] + self.m[0][3] * m.m[3][1], - self.m[0][0] * m.m[0][2] + self.m[0][1] * m.m[1][2] + self.m[0][2] * m.m[2][2] + self.m[0][3] * m.m[3][2], - self.m[0][0] * m.m[0][3] + self.m[0][1] * m.m[1][3] + self.m[0][2] * m.m[2][3] + self.m[0][3] * m.m[3][3], - self.m[1][0] * m.m[0][0] + self.m[1][1] * m.m[1][0] + self.m[1][2] * m.m[2][0] + self.m[1][3] * m.m[3][0], - self.m[1][0] * m.m[0][1] + self.m[1][1] * m.m[1][1] + self.m[1][2] * m.m[2][1] + self.m[1][3] * m.m[3][1], - self.m[1][0] * m.m[0][2] + self.m[1][1] * m.m[1][2] + self.m[1][2] * m.m[2][2] + self.m[1][3] * m.m[3][2], - self.m[1][0] * m.m[0][3] + self.m[1][1] * m.m[1][3] + self.m[1][2] * m.m[2][3] + self.m[1][3] * m.m[3][3], - self.m[2][0] * m.m[0][0] + self.m[2][1] * m.m[1][0] + self.m[2][2] * m.m[2][0] + self.m[2][3] * m.m[3][0], - self.m[2][0] * m.m[0][1] + self.m[2][1] * m.m[1][1] + self.m[2][2] * m.m[2][1] + self.m[2][3] * m.m[3][1], - self.m[2][0] * m.m[0][2] + self.m[2][1] * m.m[1][2] + self.m[2][2] * m.m[2][2] + self.m[2][3] * m.m[3][2], - self.m[2][0] * m.m[0][3] + self.m[2][1] * m.m[1][3] + self.m[2][2] * m.m[2][3] + self.m[2][3] * m.m[3][3], - self.m[3][0] * m.m[0][0] + self.m[3][1] * m.m[1][0] + self.m[3][2] * m.m[2][0] + self.m[3][3] * m.m[3][0], - self.m[3][0] * m.m[0][1] + self.m[3][1] * m.m[1][1] + self.m[3][2] * m.m[2][1] + self.m[3][3] * m.m[3][1], - self.m[3][0] * m.m[0][2] + self.m[3][1] * m.m[1][2] + self.m[3][2] * m.m[2][2] + self.m[3][3] * m.m[3][2], - self.m[3][0] * m.m[0][3] + self.m[3][1] * m.m[1][3] + self.m[3][2] * m.m[2][3] + self.m[3][3] * m.m[3][3]) + return new_affinematrix3d( + self.m[0][0] * m.m[0][0] + self.m[0][1] * m.m[1][0] + self.m[0][2] * m.m[2][0] + self.m[0][3] * m.m[3][0], + self.m[0][0] * m.m[0][1] + self.m[0][1] * m.m[1][1] + self.m[0][2] * m.m[2][1] + self.m[0][3] * m.m[3][1], + self.m[0][0] * m.m[0][2] + self.m[0][1] * m.m[1][2] + self.m[0][2] * m.m[2][2] + self.m[0][3] * m.m[3][2], + self.m[0][0] * m.m[0][3] + self.m[0][1] * m.m[1][3] + self.m[0][2] * m.m[2][3] + self.m[0][3] * m.m[3][3], + self.m[1][0] * m.m[0][0] + self.m[1][1] * m.m[1][0] + self.m[1][2] * m.m[2][0] + self.m[1][3] * m.m[3][0], + self.m[1][0] * m.m[0][1] + self.m[1][1] * m.m[1][1] + self.m[1][2] * m.m[2][1] + self.m[1][3] * m.m[3][1], + self.m[1][0] * m.m[0][2] + self.m[1][1] * m.m[1][2] + self.m[1][2] * m.m[2][2] + self.m[1][3] * m.m[3][2], + self.m[1][0] * m.m[0][3] + self.m[1][1] * m.m[1][3] + self.m[1][2] * m.m[2][3] + self.m[1][3] * m.m[3][3], + self.m[2][0] * m.m[0][0] + self.m[2][1] * m.m[1][0] + self.m[2][2] * m.m[2][0] + self.m[2][3] * m.m[3][0], + self.m[2][0] * m.m[0][1] + self.m[2][1] * m.m[1][1] + self.m[2][2] * m.m[2][1] + self.m[2][3] * m.m[3][1], + self.m[2][0] * m.m[0][2] + self.m[2][1] * m.m[1][2] + self.m[2][2] * m.m[2][2] + self.m[2][3] * m.m[3][2], + self.m[2][0] * m.m[0][3] + self.m[2][1] * m.m[1][3] + self.m[2][2] * m.m[2][3] + self.m[2][3] * m.m[3][3], + self.m[3][0] * m.m[0][0] + self.m[3][1] * m.m[1][0] + self.m[3][2] * m.m[2][0] + self.m[3][3] * m.m[3][0], + self.m[3][0] * m.m[0][1] + self.m[3][1] * m.m[1][1] + self.m[3][2] * m.m[2][1] + self.m[3][3] * m.m[3][1], + self.m[3][0] * m.m[0][2] + self.m[3][1] * m.m[1][2] + self.m[3][2] * m.m[2][2] + self.m[3][3] * m.m[3][2], + self.m[3][0] * m.m[0][3] + self.m[3][1] * m.m[1][3] + self.m[3][2] * m.m[2][3] + self.m[3][3] * m.m[3][3] + ) diff --git a/raysect/core/math/cython/triangle.pxd b/raysect/core/math/cython/triangle.pxd index 61597ea7..1530798c 100644 --- a/raysect/core/math/cython/triangle.pxd +++ b/raysect/core/math/cython/triangle.pxd @@ -31,16 +31,16 @@ cdef bint inside_triangle(double v1x, double v1y, double v2x, double v2y, - double v3x, double v3y, double px, double py) nogil + double v3x, double v3y, double px, double py) nogil cdef void barycentric_coords(double v1x, double v1y, double v2x, double v2y, - double v3x, double v3y, double px, double py, - double *alpha, double *beta, double *gamma) nogil + double v3x, double v3y, double px, double py, + double *alpha, double *beta, double *gamma) nogil cdef bint barycentric_inside_triangle(double alpha, double beta, double gamma) nogil cdef double barycentric_interpolation(double alpha, double beta, double gamma, - double va, double vb, double vc) nogil + double va, double vb, double vc) nogil diff --git a/raysect/core/math/cython/triangle.pyx b/raysect/core/math/cython/triangle.pyx index a3e20a6f..8e3c5ecb 100644 --- a/raysect/core/math/cython/triangle.pyx +++ b/raysect/core/math/cython/triangle.pyx @@ -33,7 +33,7 @@ cimport cython cdef bint inside_triangle(double v1x, double v1y, double v2x, double v2y, - double v3x, double v3y, double px, double py) nogil: + double v3x, double v3y, double px, double py) nogil: """ Cython utility for testing if point is inside a triangle. @@ -52,8 +52,7 @@ cdef bint inside_triangle(double v1x, double v1y, double v2x, double v2y, :rtype: bool """ - cdef: - double ux, uy, vx, vy + cdef double ux, uy, vx, vy # calculate vectors ux = v2x - v1x @@ -158,7 +157,7 @@ cdef bint barycentric_inside_triangle(double alpha, double beta, double gamma) n cdef double barycentric_interpolation(double alpha, double beta, double gamma, - double va, double vb, double vc) nogil: + double va, double vb, double vc) nogil: """ Cython utility for interpolation of data at triangle vertices. diff --git a/raysect/core/math/cython/utility.pyx b/raysect/core/math/cython/utility.pyx index 9cc8147e..8f8bdcb9 100644 --- a/raysect/core/math/cython/utility.pyx +++ b/raysect/core/math/cython/utility.pyx @@ -114,8 +114,7 @@ cdef double interpolate(double[::1] x, double[::1] y, double p) nogil: :rtype: double """ - cdef: - int index, top_index + cdef int index, top_index index = find_index(x, p) @@ -202,7 +201,6 @@ cdef double integrate(double[::1] x, double[::1] y, double x0, double x1) nogil: else: integral_sum = 0.0 - if lower_index == 0: # add contribution from point below array diff --git a/raysect/core/math/normal.pxd b/raysect/core/math/normal.pxd index ab43f7af..98ceb5bc 100644 --- a/raysect/core/math/normal.pxd +++ b/raysect/core/math/normal.pxd @@ -33,30 +33,20 @@ from raysect.core.math._vec3 cimport _Vec3 from raysect.core.math.vector cimport Vector3D from raysect.core.math.affinematrix cimport AffineMatrix3D + cdef class Normal3D(_Vec3): cpdef Vector3D cross(self, _Vec3 v) - cpdef Normal3D normalise(self) - cpdef Normal3D transform(self, AffineMatrix3D m) - cpdef Normal3D transform_with_inverse(self, AffineMatrix3D m) - cdef Normal3D neg(self) - cdef Normal3D add(self, _Vec3 v) - cdef Normal3D sub(self, _Vec3 v) - cdef Normal3D mul(self, double m) - cdef Normal3D div(self, double m) - cpdef Normal3D copy(self) - cpdef Vector3D as_vector(self) - cpdef Vector3D orthogonal(self) diff --git a/raysect/core/math/normal.pyx b/raysect/core/math/normal.pyx index 680c06c1..eb680668 100644 --- a/raysect/core/math/normal.pyx +++ b/raysect/core/math/normal.pyx @@ -34,6 +34,7 @@ cimport cython from libc.math cimport sqrt, fabs from raysect.core.math.vector cimport new_vector3d + cdef class Normal3D(_Vec3): """ Represents a normal vector in 3D affine space. @@ -83,15 +84,13 @@ cdef class Normal3D(_Vec3): return self.x == n.x and self.y == n.y and self.z == n.z elif op == 3: # __ne__() return self.x != n.x or self.y != n.y or self.z != n.z - else: - return NotImplemented + + return NotImplemented def __neg__(self): """Returns a normal with the reverse orientation.""" - return new_normal3d(-self.x, - -self.y, - -self.z) + return new_normal3d(-self.x, -self.y, -self.z) def __add__(self, object y): """Addition operator.""" @@ -99,16 +98,10 @@ cdef class Normal3D(_Vec3): cdef _Vec3 v if isinstance(y, _Vec3): - v = <_Vec3>y + return new_normal3d(self.x + v.x, self.y + v.y, self.z + v.z) - return new_normal3d(self.x + v.x, - self.y + v.y, - self.z + v.z) - - else: - - return NotImplemented + return NotImplemented def __radd__(self, object x): """Reverse addition operator.""" @@ -121,16 +114,10 @@ cdef class Normal3D(_Vec3): cdef _Vec3 v if isinstance(y, _Vec3): - v = <_Vec3>y + return new_normal3d(self.x - v.x, self.y - v.y, self.z - v.z) - return new_normal3d(self.x - v.x, - self.y - v.y, - self.z - v.z) - - else: - - return NotImplemented + return NotImplemented def __rsub__(self, object x): """Reverse subtract operator.""" @@ -138,16 +125,10 @@ cdef class Normal3D(_Vec3): cdef _Vec3 v if isinstance(x, _Vec3): - v = <_Vec3>x + return new_normal3d(v.x - self.x, v.y - self.y, v.z - self.z) - return new_normal3d(v.x - self.x, - v.y - self.y, - v.z - self.z) - - else: - - return NotImplemented + return NotImplemented def __mul__(self, object y): """Multiply operator.""" @@ -155,43 +136,32 @@ cdef class Normal3D(_Vec3): cdef double s if isinstance(y, numbers.Real): - s = y + return new_normal3d(s * self.x, s * self.y, s * self.z) - return new_normal3d(s * self.x, - s * self.y, - s * self.z) - - else: - - return NotImplemented + return NotImplemented def __rmul__(self, object x): """Reverse multiply operator.""" - cdef double s - cdef AffineMatrix3D m, minv + cdef: + double s + AffineMatrix3D m, minv if isinstance(x, numbers.Real): - s = x - - return new_normal3d(s * self.x, - s * self.y, - s * self.z) + return new_normal3d(s * self.x, s * self.y, s * self.z) elif isinstance(x, AffineMatrix3D): - m = x - minv = m.inverse() - return new_normal3d(minv.m[0][0] * self.x + minv.m[1][0] * self.y + minv.m[2][0] * self.z, - minv.m[0][1] * self.x + minv.m[1][1] * self.y + minv.m[2][1] * self.z, - minv.m[0][2] * self.x + minv.m[1][2] * self.y + minv.m[2][2] * self.z) + return new_normal3d( + minv.m[0][0] * self.x + minv.m[1][0] * self.y + minv.m[2][0] * self.z, + minv.m[0][1] * self.x + minv.m[1][1] * self.y + minv.m[2][1] * self.z, + minv.m[0][2] * self.x + minv.m[1][2] * self.y + minv.m[2][2] * self.z + ) - else: - - return NotImplemented + return NotImplemented @cython.cdivision(True) def __truediv__(self, object y): @@ -201,22 +171,16 @@ cdef class Normal3D(_Vec3): if isinstance(y, numbers.Real): - d = y + d = y # prevent divide my zero if d == 0.0: - raise ZeroDivisionError("Cannot divide a vector by a zero scalar.") d = 1.0 / d + return new_normal3d(d * self.x, d * self.y, d * self.z) - return new_normal3d(d * self.x, - d * self.y, - d * self.z) - - else: - - return NotImplemented + return NotImplemented cpdef Vector3D cross(self, _Vec3 v): """ @@ -228,9 +192,11 @@ cdef class Normal3D(_Vec3): :rtype: Vector3D """ - return new_vector3d(self.y * v.z - v.y * self.z, - self.z * v.x - v.z * self.x, - self.x * v.y - v.x * self.y) + return new_vector3d( + self.y * v.z - v.y * self.z, + self.z * v.x - v.z * self.x, + self.x * v.y - v.x * self.y + ) @cython.cdivision(True) cpdef Normal3D normalise(self): @@ -247,15 +213,11 @@ cdef class Normal3D(_Vec3): # if current length is zero, problem is ill defined t = self.x * self.x + self.y * self.y + self.z * self.z if t == 0.0: - raise ZeroDivisionError("A zero length vector can not be normalised as the direction of a zero length vector is undefined.") # normalise and rescale vector t = 1.0 / sqrt(t) - - return new_normal3d(self.x * t, - self.y * t, - self.z * t) + return new_normal3d(self.x * t, self.y * t, self.z * t) cpdef Normal3D transform(self, AffineMatrix3D m): """ @@ -279,9 +241,11 @@ cdef class Normal3D(_Vec3): cdef AffineMatrix3D minv minv = m.inverse() - return new_normal3d(minv.m[0][0] * self.x + minv.m[1][0] * self.y + minv.m[2][0] * self.z, - minv.m[0][1] * self.x + minv.m[1][1] * self.y + minv.m[2][1] * self.z, - minv.m[0][2] * self.x + minv.m[1][2] * self.y + minv.m[2][2] * self.z) + return new_normal3d( + minv.m[0][0] * self.x + minv.m[1][0] * self.y + minv.m[2][0] * self.z, + minv.m[0][1] * self.x + minv.m[1][1] * self.y + minv.m[2][1] * self.z, + minv.m[0][2] * self.x + minv.m[1][2] * self.y + minv.m[2][2] * self.z + ) cpdef Normal3D transform_with_inverse(self, AffineMatrix3D m): """ @@ -300,9 +264,11 @@ cdef class Normal3D(_Vec3): :rtype: Normal3D """ - return new_normal3d(m.m[0][0] * self.x + m.m[1][0] * self.y + m.m[2][0] * self.z, - m.m[0][1] * self.x + m.m[1][1] * self.y + m.m[2][1] * self.z, - m.m[0][2] * self.x + m.m[1][2] * self.y + m.m[2][2] * self.z) + return new_normal3d( + m.m[0][0] * self.x + m.m[1][0] * self.y + m.m[2][0] * self.z, + m.m[0][1] * self.x + m.m[1][1] * self.y + m.m[2][1] * self.z, + m.m[0][2] * self.x + m.m[1][2] * self.y + m.m[2][2] * self.z + ) cdef Normal3D neg(self): """ @@ -312,9 +278,7 @@ cdef class Normal3D(_Vec3): to the equivalent python operator. """ - return new_normal3d(-self.x, - -self.y, - -self.z) + return new_normal3d(-self.x, -self.y, -self.z) cdef Normal3D add(self, _Vec3 v): """ @@ -324,9 +288,7 @@ cdef class Normal3D(_Vec3): to the equivalent python operator. """ - return new_normal3d(self.x + v.x, - self.y + v.y, - self.z + v.z) + return new_normal3d(self.x + v.x, self.y + v.y, self.z + v.z) cdef Normal3D sub(self, _Vec3 v): """ @@ -336,9 +298,7 @@ cdef class Normal3D(_Vec3): to the equivalent python operator. """ - return new_normal3d(self.x - v.x, - self.y - v.y, - self.z - v.z) + return new_normal3d(self.x - v.x, self.y - v.y, self.z - v.z) cdef Normal3D mul(self, double m): """ @@ -348,9 +308,7 @@ cdef class Normal3D(_Vec3): to the equivalent python operator. """ - return new_normal3d(self.x * m, - self.y * m, - self.z * m) + return new_normal3d(self.x * m, self.y * m, self.z * m) @cython.cdivision(True) cdef Normal3D div(self, double d): @@ -362,14 +320,10 @@ cdef class Normal3D(_Vec3): """ if d == 0.0: - raise ZeroDivisionError("Cannot divide a vector by a zero scalar.") d = 1.0 / d - - return new_normal3d(self.x * d, - self.y * d, - self.z * d) + return new_normal3d(self.x * d, self.y * d, self.z * d) cpdef Normal3D copy(self): """ @@ -378,9 +332,7 @@ cdef class Normal3D(_Vec3): :rtype: Normal3D """ - return new_normal3d(self.x, - self.y, - self.z) + return new_normal3d(self.x, self.y, self.z) cpdef Vector3D as_vector(self): """ @@ -389,9 +341,7 @@ cdef class Normal3D(_Vec3): :rtype: Vector3D """ - return new_vector3d(self.x, - self.y, - self.z) + return new_vector3d(self.x, self.y, self.z) cpdef Vector3D orthogonal(self): """ diff --git a/raysect/core/math/point.pxd b/raysect/core/math/point.pxd index ced255a4..3665eecc 100644 --- a/raysect/core/math/point.pxd +++ b/raysect/core/math/point.pxd @@ -38,19 +38,12 @@ cdef class Point3D: cdef public double x, y, z cpdef Vector3D vector_to(self, Point3D p) - cpdef double distance_to(self, Point3D p) - cpdef Point3D transform(self, AffineMatrix3D m) - cdef Point3D add(self, _Vec3 v) - cdef Point3D sub(self, _Vec3 v) - cpdef Point3D copy(self) - cdef double get_index(self, int index) nogil - cdef void set_index(self, int index, double value) nogil @@ -75,19 +68,12 @@ cdef class Point2D: cdef public double x, y cpdef Vector2D vector_to(self, Point2D p) - cpdef double distance_to(self, Point2D p) - # cpdef Point3D transform(self, AffineMatrix3D m) - cdef Point2D add(self, Vector2D v) - cdef Point2D sub(self, Vector2D v) - cpdef Point2D copy(self) - cdef double get_index(self, int index) nogil - cdef void set_index(self, int index, double value) nogil diff --git a/raysect/core/math/point.pyx b/raysect/core/math/point.pyx index 17e4ab65..c1089302 100644 --- a/raysect/core/math/point.pyx +++ b/raysect/core/math/point.pyx @@ -59,7 +59,6 @@ cdef class Point3D: >>> from raysect.core import Point3D >>> a = Point3D(0, 1, 2) - """ def __init__(self, double x=0.0, double y=0.0, double z=0.0): @@ -86,8 +85,8 @@ cdef class Point3D: return self.x == p.x and self.y == p.y and self.z == p.z elif op == 3: # __ne__() return self.x != p.x or self.y != p.y or self.z != p.z - else: - return NotImplemented + + return NotImplemented def __getitem__(self, int i): """Returns the point coordinates by index ([0,1,2] -> [x,y,z]). @@ -103,8 +102,8 @@ cdef class Point3D: return self.y elif i == 2: return self.z - else: - raise IndexError("Index out of range [0, 2].") + + raise IndexError("Index out of range [0, 2].") def __setitem__(self, int i, double value): """Sets the point coordinates by index ([0,1,2] -> [x,y,z]). @@ -132,6 +131,7 @@ cdef class Point3D: >>> x, y, z (0.0, 1.0, 2.0) """ + yield self.x yield self.y yield self.z @@ -146,16 +146,10 @@ cdef class Point3D: cdef _Vec3 v if isinstance(y, _Vec3): + v = <_Vec3> y + return new_point3d(self.x + v.x, self.y + v.y, self.z + v.z) - v = <_Vec3>y - - else: - - return NotImplemented - - return new_point3d(self.x + v.x, - self.y + v.y, - self.z + v.z) + return NotImplemented def __sub__(self, object y): """Subtraction operator. @@ -167,16 +161,10 @@ cdef class Point3D: cdef _Vec3 v if isinstance(y, _Vec3): - v = <_Vec3>y + return new_point3d(self.x - v.x, self.y - v.y, self.z - v.z) - return new_point3d(self.x - v.x, - self.y - v.y, - self.z - v.z) - - else: - - return NotImplemented + return NotImplemented @cython.cdivision(True) def __rmul__(self, object x): @@ -197,15 +185,16 @@ cdef class Point3D: # 4th element of homogeneous coordinate w = m.m[3][0] * self.x + m.m[3][1] * self.y + m.m[3][2] * self.z + m.m[3][3] if w == 0.0: - raise ZeroDivisionError("Bad matrix transform, 4th element of homogeneous coordinate is zero.") # pre divide for speed (dividing is much slower than multiplying) w = 1.0 / w - return new_point3d((m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z + m.m[0][3]) * w, - (m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z + m.m[1][3]) * w, - (m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z + m.m[2][3]) * w) + return new_point3d( + (m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z + m.m[0][3]) * w, + (m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z + m.m[1][3]) * w, + (m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z + m.m[2][3]) * w + ) return NotImplemented @@ -237,9 +226,7 @@ cdef class Point3D: """ - return new_vector3d(p.x - self.x, - p.y - self.y, - p.z - self.z) + return new_vector3d(p.x - self.x, p.y - self.y, p.z - self.z) cpdef double distance_to(self, Point3D p): """ @@ -286,15 +273,15 @@ cdef class Point3D: # 4th element of homogeneous coordinate w = m.m[3][0] * self.x + m.m[3][1] * self.y + m.m[3][2] * self.z + m.m[3][3] if w == 0.0: - raise ZeroDivisionError("Bad matrix transform, 4th element of homogeneous coordinate is zero.") # pre divide for speed (dividing is much slower than multiplying) w = 1.0 / w - - return new_point3d((m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z + m.m[0][3]) * w, - (m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z + m.m[1][3]) * w, - (m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z + m.m[2][3]) * w) + return new_point3d( + (m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z + m.m[0][3]) * w, + (m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z + m.m[1][3]) * w, + (m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z + m.m[2][3]) * w + ) cdef Point3D add(self, _Vec3 v): """ @@ -304,9 +291,7 @@ cdef class Point3D: to the equivalent python operator. """ - return new_point3d(self.x + v.x, - self.y + v.y, - self.z + v.z) + return new_point3d(self.x + v.x, self.y + v.y, self.z + v.z) cdef Point3D sub(self, _Vec3 v): """ @@ -316,9 +301,7 @@ cdef class Point3D: to the equivalent python operator. """ - return new_point3d(self.x - v.x, - self.y - v.y, - self.z - v.z) + return new_point3d(self.x - v.x, self.y - v.y, self.z - v.z) cpdef Point3D copy(self): """ @@ -333,9 +316,7 @@ cdef class Point3D: Point3D(0.0, 1.0, 2.0) """ - return new_point3d(self.x, - self.y, - self.z) + return new_point3d(self.x, self.y, self.z) cdef double get_index(self, int index) nogil: """ @@ -397,7 +378,6 @@ cdef class Point2D: """ def __init__(self, double x=0.0, double y=0.0): - self.x = x self.y = y @@ -434,8 +414,8 @@ cdef class Point2D: return self.x elif i == 1: return self.y - else: - raise IndexError("Index out of range [0, 1].") + + raise IndexError("Index out of range [0, 1].") def __setitem__(self, int i, double value): """Sets the point coordinates by index ([0,1] -> [x,y]). @@ -462,6 +442,7 @@ cdef class Point2D: (1.0, 1.0) """ + yield self.x yield self.y @@ -475,14 +456,10 @@ cdef class Point2D: cdef Vector2D v if isinstance(y, Vector2D): - v = y + return new_point2d(self.x + v.x, self.y + v.y) - else: - - return NotImplemented - - return new_point2d(self.x + v.x, self.y + v.y) + return NotImplemented def __sub__(self, object y): """Subtraction operator. @@ -494,14 +471,10 @@ cdef class Point2D: cdef Vector2D v if isinstance(y, Vector2D): - v = y - return new_point2d(self.x - v.x, self.y - v.y) - else: - - return NotImplemented + return NotImplemented @cython.cdivision(True) def __rmul__(self, object x): diff --git a/raysect/core/math/quaternion.pxd b/raysect/core/math/quaternion.pxd index bb6d1339..2c9ec72e 100644 --- a/raysect/core/math/quaternion.pxd +++ b/raysect/core/math/quaternion.pxd @@ -38,41 +38,23 @@ cdef class Quaternion: cdef public double x, y, z, s cpdef Quaternion copy(self) - cpdef Quaternion conjugate(self) - cpdef Quaternion inverse(self) - cpdef Quaternion normalise(self) - cpdef bint is_unit(self, double tolerance=*) - cpdef Quaternion transform(self, AffineMatrix3D m) - cpdef AffineMatrix3D as_matrix(self) - cpdef Quaternion quaternion_to(self, Quaternion q) - cdef Quaternion neg(self) - cdef Quaternion add(self, Quaternion q) - cdef Quaternion sub(self, Quaternion q) - cdef Quaternion mul_quaternion(self, Quaternion q) - cdef Quaternion mul_scalar(self, double d) - cdef Quaternion div_quaternion(self, Quaternion q) - cdef Quaternion div_scalar(self, double d) - cdef Vector3D get_axis(self) - cdef double get_angle(self) - cdef double get_length(self) nogil - cdef object set_length(self, double v) @@ -96,4 +78,4 @@ cdef inline Quaternion new_quaternion(double x, double y, double z, double s): cdef Quaternion new_quaternion_from_matrix(AffineMatrix3D matrix) -cdef Quaternion new_quaternion_from_axis_angle(Vector3D axis, double angle) \ No newline at end of file +cdef Quaternion new_quaternion_from_axis_angle(Vector3D axis, double angle) diff --git a/raysect/core/math/quaternion.pyx b/raysect/core/math/quaternion.pyx index 9011a6b2..dec92228 100644 --- a/raysect/core/math/quaternion.pyx +++ b/raysect/core/math/quaternion.pyx @@ -31,11 +31,12 @@ import numbers cimport cython -from libc.math cimport sqrt, sin, cos, asin, acos, atan2, fabs, M_PI, copysign +from libc.math cimport sqrt, sin, cos, acos, fabs from raysect.core.math.vector cimport new_vector3d from raysect.core.math.affinematrix cimport new_affinematrix3d, AffineMatrix3D + cdef const double RAD2DEG = 57.29577951308232000 # 180 / pi cdef const double DEG2RAD = 0.017453292519943295 # pi / 180 @@ -72,8 +73,8 @@ cdef class Quaternion: return self.z elif i == 3: return self.s - else: - raise IndexError("Index out of range [0, 3].") + + raise IndexError("Index out of range [0, 3].") def __setitem__(self, int i, double value): """Sets the quaternion coordinates by index ([0,1,2,3] -> [x,y,z,s]). @@ -105,6 +106,7 @@ cdef class Quaternion: >>> x, y, z, s (0.0, 1.0, 2.0, 3.0) """ + yield self.x yield self.y yield self.z @@ -140,12 +142,10 @@ cdef class Quaternion: cdef Quaternion q if isinstance(y, Quaternion): - q = y return self.x == q.x and self.y == q.y and self.z == q.z and self.s == q.s - else: - raise TypeError('A quaternion can only be equality tested against another quaternion.') + raise TypeError('A quaternion can only be equality tested against another quaternion.') def __add__(self, object y): """ @@ -160,12 +160,10 @@ cdef class Quaternion: cdef Quaternion q if isinstance(y, Quaternion): - q = y return new_quaternion(self.x + q.x, self.y + q.y, self.z + q.z, self.s + q.s) - else: - return NotImplemented + return NotImplemented def __sub__(self, object y): """Subtraction operator. @@ -179,12 +177,10 @@ cdef class Quaternion: cdef Quaternion q if isinstance(y, Quaternion): - q = y return new_quaternion(self.x - q.x, self.y - q.y, self.z - q.z, self.s - q.s) - else: - return NotImplemented + return NotImplemented def __mul__(self, object y): """Multiplication operator. @@ -201,19 +197,16 @@ cdef class Quaternion: cdef Quaternion q if isinstance(y, numbers.Real): - s = y return self.mul_scalar(s) elif isinstance(y, Quaternion): - q = y return self.mul_quaternion(q) - else: - return NotImplemented() + return NotImplemented - def __rmul__(self, x): + def __rmul__(self, object x): """Reverse multiplication operator. .. code-block:: pycon @@ -222,7 +215,13 @@ cdef class Quaternion: Quaternion(0.0, 0.0, 2.0, 2.0) """ - return self.__mul__(x) + cdef double s + + if isinstance(x, numbers.Real): + s = x + return self.mul_scalar(s) + + return NotImplemented @cython.cdivision(True) def __truediv__(self, object y): @@ -240,17 +239,14 @@ cdef class Quaternion: cdef Quaternion q if isinstance(y, numbers.Real): - d = y return self.div_scalar(d) elif isinstance(y, Quaternion): - q = y return self.div_quaternion(q) - else: - raise TypeError('Unsupported operand type. Expects a real number.') + raise TypeError('Unsupported operand type. Expects a real number.') @property def length(self): @@ -262,27 +258,33 @@ cdef class Quaternion: >>> Quaternion(1, 2, 3, 0).length 3.7416573867739413 """ + return self.get_length() @length.setter def length(self, value): self.set_length(value) - @property def axis(self): """ The axis around which this quaternion rotates. """ + return self.get_axis() @property def angle(self): - """The magnitude of rotation around this quaternion's rotation axis in degrees.""" + """ + The magnitude of rotation around this quaternion's rotation axis in degrees. + """ + return self.get_angle() cpdef Quaternion copy(self): - """Returns a copy of this quaternion.""" + """ + Returns a copy of this quaternion. + """ return new_quaternion(self.x, self.y, self.z, self.s) @@ -343,6 +345,7 @@ cdef class Quaternion: :param float tolerance: The numerical tolerance by which the quaternion norm can differ by 1.0. """ + return fabs(1.0 - self.get_length()) <= tolerance cpdef Quaternion transform(self, AffineMatrix3D m): @@ -407,10 +410,12 @@ cdef class Quaternion: m21 = 2*qy*qz + 2*qx*qs m22 = 1 - 2*qx2 - 2*qy2 - return new_affinematrix3d(m00, m01, m02, 0, - m10, m11, m12, 0, - m20, m21, m22, 0, - 0, 0, 0, 1) + return new_affinematrix3d( + m00, m01, m02, 0, + m10, m11, m12, 0, + m20, m21, m22, 0, + 0, 0, 0, 1 + ) cpdef Quaternion quaternion_to(self, Quaternion q): """ @@ -563,7 +568,9 @@ cdef class Quaternion: @cython.cdivision(True) cdef double get_angle(self): - """The magnitude of rotation around this quaternion's rotation axis in degrees.""" + """ + The magnitude of rotation around this quaternion's rotation axis in degrees. + """ cdef Quaternion q = self.normalise() return 2 * acos(q.s) * RAD2DEG @@ -576,6 +583,7 @@ cdef class Quaternion: Use instead of Python attribute access in cython code. """ + return sqrt(self.x * self.x + self.y * self.y + self.z * self.z + self.s * self.s) @cython.cdivision(True) @@ -684,4 +692,4 @@ cdef Quaternion new_quaternion_from_axis_angle(Vector3D axis, double angle): qz = axis.z * sin(theta_2) qs = cos(theta_2) - return new_quaternion(qx, qy, qz, qs) \ No newline at end of file + return new_quaternion(qx, qy, qz, qs) diff --git a/raysect/core/math/sampler/solidangle.pxd b/raysect/core/math/sampler/solidangle.pxd index 10b396ab..8a3131c3 100644 --- a/raysect/core/math/sampler/solidangle.pxd +++ b/raysect/core/math/sampler/solidangle.pxd @@ -29,22 +29,15 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from libc.math cimport M_PI, M_1_PI, sqrt, sin, cos -from raysect.core.math cimport Point2D, new_point2d, Point3D, new_point3d, Vector3D, new_vector3d -from raysect.core.math.random cimport uniform -from raysect.core.math.cython cimport barycentric_coords, barycentric_interpolation +from raysect.core.math cimport Vector3D cdef class SolidAngleSampler: cpdef double pdf(self, Vector3D sample) - cdef Vector3D sample(self) - cdef tuple sample_with_pdf(self) - cdef list samples(self, int samples) - cdef list samples_with_pdfs(self, int samples) diff --git a/raysect/core/math/sampler/solidangle.pyx b/raysect/core/math/sampler/solidangle.pyx index b84ab540..c6a29719 100644 --- a/raysect/core/math/sampler/solidangle.pyx +++ b/raysect/core/math/sampler/solidangle.pyx @@ -29,10 +29,11 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from libc.math cimport M_PI, M_1_PI, sqrt, sin, cos, asin +from libc.math cimport M_PI, M_1_PI, sqrt, sin, cos from raysect.core.math cimport Vector3D, new_vector3d from raysect.core.math.random cimport uniform + # TODO: add tests - idea: solve the lighting equation with a uniform emitting surface with each sampler and check the mean radiance is unity cdef const double R_2_PI = 0.15915494309189535 # 1 / (2 * pi) cdef const double R_4_PI = 0.07957747154594767 # 1 / (4 * pi) diff --git a/raysect/core/math/sampler/surface3d.pxd b/raysect/core/math/sampler/surface3d.pxd index 72dc91bf..3b007989 100644 --- a/raysect/core/math/sampler/surface3d.pxd +++ b/raysect/core/math/sampler/surface3d.pxd @@ -35,11 +35,8 @@ from raysect.core.math cimport Point3D cdef class SurfaceSampler3D: cdef Point3D sample(self) - cdef tuple sample_with_pdf(self) - cdef list samples(self, int samples) - cdef list samples_with_pdfs(self, int samples) diff --git a/raysect/core/math/sampler/targetted.pxd b/raysect/core/math/sampler/targetted.pxd index a938a571..69104c46 100644 --- a/raysect/core/math/sampler/targetted.pxd +++ b/raysect/core/math/sampler/targetted.pxd @@ -32,6 +32,7 @@ cimport numpy as np from raysect.core.math cimport Point3D, Vector3D + cdef class _TargettedSampler: cdef: @@ -41,19 +42,12 @@ cdef class _TargettedSampler: double[::1] _cdf_mv cdef object _validate_targets(self) - cpdef double pdf(self, Point3D point, Vector3D sample) - cdef Vector3D sample(self, Point3D point) - cdef tuple sample_with_pdf(self, Point3D point) - cdef list samples(self, Point3D point, int samples) - cdef list samples_with_pdfs(self, Point3D point, int samples) - cdef object _calculate_cdf(self) - cdef tuple _pick_sphere(self) diff --git a/raysect/core/math/sampler/targetted.pyx b/raysect/core/math/sampler/targetted.pyx index 3aabb070..a94c5734 100644 --- a/raysect/core/math/sampler/targetted.pyx +++ b/raysect/core/math/sampler/targetted.pyx @@ -34,7 +34,7 @@ cimport numpy as np from raysect.core.math cimport Point3D, Vector3D, AffineMatrix3D from raysect.core.math.random cimport uniform, vector_sphere, vector_cone_uniform, vector_hemisphere_cosine from raysect.core.math.cython cimport find_index, rotate_basis -from libc.math cimport M_PI, M_1_PI, asin, acos, sqrt +from libc.math cimport M_PI, M_1_PI, asin, sqrt cimport cython diff --git a/raysect/core/math/spatial/kdtree2d.pxd b/raysect/core/math/spatial/kdtree2d.pxd index 1ebfb847..c2c7e414 100644 --- a/raysect/core/math/spatial/kdtree2d.pxd +++ b/raysect/core/math/spatial/kdtree2d.pxd @@ -30,10 +30,10 @@ # POSSIBILITY OF SUCH DAMAGE. from raysect.core.boundingbox cimport BoundingBox2D -from raysect.core.ray cimport Ray from raysect.core.math.point cimport Point2D from libc.stdint cimport int32_t + # c-structure that represent a kd-tree node cdef struct kdnode: @@ -69,56 +69,32 @@ cdef class KDTree2DCore: double _empty_bonus cdef int32_t _build(self, list items, BoundingBox2D bounds, int32_t depth=*) - cdef tuple _split(self, list items, BoundingBox2D bounds) - cdef void _get_edges(self, list items, int32_t axis, int32_t *num_edges, edge **edges_ptr) - cdef void _free_edges(self, edge **edges_ptr) - cdef BoundingBox2D _get_lower_bounds(self, BoundingBox2D bounds, double split, int32_t axis) - cdef BoundingBox2D _get_upper_bounds(self, BoundingBox2D bounds, double split, int32_t axis) - cdef int32_t _new_leaf(self, list ids) - cdef int32_t _new_branch(self, tuple split_solution, int32_t depth) - cdef int32_t _new_node(self) - cpdef bint is_contained(self, Point2D point) - cdef bint _is_contained(self, Point2D point) - cdef bint _is_contained_node(self, int32_t id, Point2D point) - cdef bint _is_contained_branch(self, int32_t id, Point2D point) - cdef bint _is_contained_leaf(self, int32_t id, Point2D point) - cpdef list items_containing(self, Point2D point) - cdef list _items_containing(self, Point2D point) - cdef list _items_containing_node(self, int32_t id, Point2D point) - cdef list _items_containing_branch(self, int32_t id, Point2D point) - cdef list _items_containing_leaf(self, int32_t id, Point2D point) - cdef void _reset(self) - cdef double _read_double(self, object file) - cdef int32_t _read_int32(self, object file) cdef class KDTree2D(KDTree2DCore): cdef bint _is_contained_leaf(self, int32_t id, Point2D point) - cpdef bint _is_contained_items(self, list items, Point2D point) - cdef list _items_containing_leaf(self, int32_t id, Point2D point) - cpdef list _items_containing_items(self, list items, Point2D point) \ No newline at end of file diff --git a/raysect/core/math/spatial/kdtree2d.pyx b/raysect/core/math/spatial/kdtree2d.pyx index cd477cda..526eee0d 100644 --- a/raysect/core/math/spatial/kdtree2d.pyx +++ b/raysect/core/math/spatial/kdtree2d.pyx @@ -40,7 +40,10 @@ from libc.stdint cimport int32_t from libc.math cimport log, ceil cimport cython + +# constants cdef enum: + # this number of nodes will be pre-allocated when the kd-tree is initially created INITIAL_NODE_COUNT = 128 @@ -70,12 +73,11 @@ cdef class Item2D: """ def __init__(self, int32_t id, BoundingBox2D box): - self.id = id self.box = box -cdef int _edge_compare(const void *p1, const void *p2) noexcept nogil: +cdef int _edge_compare(const void *p1, const void *p2) nogil: cdef edge e1, e2 diff --git a/raysect/core/math/spatial/kdtree3d.pxd b/raysect/core/math/spatial/kdtree3d.pxd index fd7d2745..cd80f059 100644 --- a/raysect/core/math/spatial/kdtree3d.pxd +++ b/raysect/core/math/spatial/kdtree3d.pxd @@ -69,66 +69,37 @@ cdef class KDTree3DCore: double _empty_bonus cdef int32_t _build(self, list items, BoundingBox3D bounds, int32_t depth=*) - cdef tuple _split(self, list items, BoundingBox3D bounds) - cdef void _get_edges(self, list items, int32_t axis, int32_t *num_edges, edge **edges_ptr) - cdef void _free_edges(self, edge **edges_ptr) - cdef BoundingBox3D _get_lower_bounds(self, BoundingBox3D bounds, double split, int32_t axis) - cdef BoundingBox3D _get_upper_bounds(self, BoundingBox3D bounds, double split, int32_t axis) - cdef int32_t _new_leaf(self, list ids) - cdef int32_t _new_branch(self, tuple split_solution, int32_t depth) - cdef int32_t _new_node(self) - cpdef bint is_contained(self, Point3D point) - cdef bint _is_contained(self, Point3D point) - cdef bint _is_contained_node(self, int32_t id, Point3D point) - cdef bint _is_contained_branch(self, int32_t id, Point3D point) - cdef bint _is_contained_leaf(self, int32_t id, Point3D point) - cpdef bint trace(self, Ray ray) - cdef bint _trace(self, Ray ray) - cdef bint _trace_node(self, int32_t id, Ray ray, double min_range, double max_range) - cdef bint _trace_branch(self, int32_t id, Ray ray, double min_range, double max_range) - cdef bint _trace_leaf(self, int32_t id, Ray ray, double max_range) - cpdef list items_containing(self, Point3D point) - cdef list _items_containing(self, Point3D point) - cdef list _items_containing_node(self, int32_t id, Point3D point) - cdef list _items_containing_branch(self, int32_t id, Point3D point) - cdef list _items_containing_leaf(self, int32_t id, Point3D point) - cdef void _reset(self) - cdef double _read_double(self, object file) - cdef int32_t _read_int32(self, object file) cdef class KDTree3D(KDTree3DCore): cdef bint _trace_leaf(self, int32_t id, Ray ray, double max_range) - cpdef bint _trace_items(self, list items, Ray ray, double max_range) - cdef list _items_containing_leaf(self, int32_t id, Point3D point) - cpdef list _items_containing_items(self, list items, Point3D point) \ No newline at end of file diff --git a/raysect/core/math/spatial/kdtree3d.pyx b/raysect/core/math/spatial/kdtree3d.pyx index ef95ea87..c1a8a195 100644 --- a/raysect/core/math/spatial/kdtree3d.pyx +++ b/raysect/core/math/spatial/kdtree3d.pyx @@ -40,7 +40,10 @@ from libc.stdint cimport int32_t from libc.math cimport log, ceil cimport cython + +# constants cdef enum: + # this number of nodes will be pre-allocated when the kd-tree is initially created INITIAL_NODE_COUNT = 128 @@ -76,7 +79,7 @@ cdef class Item3D: self.box = box -cdef int _edge_compare(const void *p1, const void *p2) noexcept nogil: +cdef int _edge_compare(const void *p1, const void *p2) nogil: cdef edge e1, e2 diff --git a/raysect/core/math/statsarray.pxd b/raysect/core/math/statsarray.pxd index 6ea7bf7e..3352331a 100644 --- a/raysect/core/math/statsarray.pxd +++ b/raysect/core/math/statsarray.pxd @@ -40,13 +40,9 @@ cdef class StatsBin: readonly int samples cpdef object clear(self) - cpdef StatsBin copy(self) - cpdef object add_sample(self, double sample) - cpdef object combine_samples(self, double mean, double variance, int sample_count) - cpdef double error(self) @@ -62,19 +58,12 @@ cdef class StatsArray1D: int[::1] samples_mv cpdef object clear(self) - cpdef StatsArray1D copy(self) - cpdef object add_sample(self, int x, double sample) - cpdef object combine_samples(self, int x, double mean, double variance, int sample_count) - cpdef double error(self, int x) - cpdef ndarray errors(self) - cdef void _new_buffers(self) - cdef object _bounds_check(self, int x) @@ -90,19 +79,12 @@ cdef class StatsArray2D: int[:,::1] samples_mv cpdef object clear(self) - cpdef StatsArray2D copy(self) - cpdef object add_sample(self, int x, int y, double sample) - cpdef object combine_samples(self, int x, int y, double mean, double variance, int sample_count) - cpdef double error(self, int x, int y) - cpdef ndarray errors(self) - cdef void _new_buffers(self) - cdef object _bounds_check(self, int x, int y) @@ -118,17 +100,10 @@ cdef class StatsArray3D: int[:,:,::1] samples_mv cpdef object clear(self) - cpdef StatsArray3D copy(self) - cpdef object add_sample(self, int x, int y, int z, double sample) - cpdef object combine_samples(self, int x, int y, int z, double mean, double variance, int sample_count) - cpdef double error(self, int x, int y, int z) - cpdef ndarray errors(self) - cdef void _new_buffers(self) - cdef object _bounds_check(self, int x, int y, int z) diff --git a/raysect/core/math/statsarray.pyx b/raysect/core/math/statsarray.pyx index 491467af..4b4a60b2 100644 --- a/raysect/core/math/statsarray.pyx +++ b/raysect/core/math/statsarray.pyx @@ -52,13 +52,19 @@ cdef class StatsBin: self.samples = 0 cpdef object clear(self): - """ Erase the current statistics stored in this StatsBin. """ + """ + Erase the current statistics stored in this StatsBin. + """ + self.mean = 0.0 self.variance = 0.0 self.samples = 0 cpdef StatsBin copy(self): - """ Instantiate a new StatsBin object with the same statistical results. """ + """ + Instantiate a new StatsBin object with the same statistical results. + """ + obj = StatsBin() obj.mean = self.mean obj.variance = self.variance @@ -71,6 +77,7 @@ cdef class StatsBin: :param float sample: The sample value to be added. """ + _add_sample(sample, &self.mean, &self.variance, &self.samples) cpdef object combine_samples(self, double mean, double variance, int sample_count): @@ -115,7 +122,10 @@ cdef class StatsBin: self.samples = nt cpdef double error(self): - """ Compute the standard error of this sample distribution. """ + """ + Compute the standard error of this sample distribution. + """ + return _std_error(self.variance, self.samples) @@ -154,17 +164,25 @@ cdef class StatsArray1D: @property def shape(self): - """ The numpy style array shape of the underlying StatsArray. """ + """ + The numpy style array shape of the underlying StatsArray. + """ return (self.length, ) cpdef object clear(self): - """ Erase the current statistics stored in this StatsArray. """ + """ + Erase the current statistics stored in this StatsArray. + """ + self._new_buffers() @cython.initializedcheck(False) cpdef StatsArray1D copy(self): - """ Instantiate a new StatsArray1D object with the same statistical results. """ + """ + Instantiate a new StatsArray1D object with the same statistical results. + """ + obj = StatsArray1D(self.length) obj.mean_mv[:] = self.mean_mv[:] obj.variance_mv[:] = self.variance_mv[:] @@ -335,16 +353,25 @@ cdef class StatsArray2D: @property def shape(self): - """ The numpy style array shape of the underlying StatsArray. """ + """ + The numpy style array shape of the underlying StatsArray. + """ + return self.nx, self.ny cpdef object clear(self): - """ Erase the current statistics stored in this StatsArray. """ + """ + Erase the current statistics stored in this StatsArray. + """ + self._new_buffers() @cython.initializedcheck(False) cpdef StatsArray2D copy(self): - """ Instantiate a new StatsArray2D object with the same statistical results. """ + """ + Instantiate a new StatsArray2D object with the same statistical results. + """ + obj = StatsArray2D(self.nx, self.ny) obj.mean_mv[:] = self.mean_mv[:] obj.variance_mv[:] = self.variance_mv[:] @@ -531,18 +558,27 @@ cdef class StatsArray3D: @property def shape(self): - """ The numpy style array shape of the underlying StatsArray. """ + """ + The numpy style array shape of the underlying StatsArray. + """ + return self.nx, self.ny, self.nz cpdef object clear(self): - """ Erase the current statistics stored in this StatsArray. """ + """ + Erase the current statistics stored in this StatsArray. + """ + self._new_buffers() @cython.boundscheck(False) @cython.wraparound(False) @cython.initializedcheck(False) cpdef StatsArray3D copy(self): - """ Instantiate a new StatsArray3D object with the same statistical results. """ + """ + Instantiate a new StatsArray3D object with the same statistical results. + """ + obj = StatsArray3D(self.nx, self.ny, self.nz) obj.mean_mv[:] = self.mean_mv[:] obj.variance_mv[:] = self.variance_mv[:] diff --git a/raysect/core/math/transform.pyx b/raysect/core/math/transform.pyx index be1b1751..41c1cd0b 100644 --- a/raysect/core/math/transform.pyx +++ b/raysect/core/math/transform.pyx @@ -29,11 +29,12 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from libc.math cimport sin, cos, sqrt, asin, atan2, M_PI as pi +from libc.math cimport sin, cos, sqrt, asin, atan2 from raysect.core.math.affinematrix cimport new_affinematrix3d from raysect.core.math.point cimport new_point3d cimport cython + cdef const double RAD2DEG = 57.29577951308232000 # 180 / pi cdef const double DEG2RAD = 0.017453292519943295 # pi / 180 @@ -103,7 +104,7 @@ cpdef AffineMatrix3D rotate_x(double angle): cdef double r - r = pi * angle / 180.0 + r = DEG2RAD * angle return new_affinematrix3d(1, 0, 0, 0, 0, cos(r), -sin(r), 0, 0, sin(r), cos(r), 0, @@ -131,7 +132,7 @@ cpdef AffineMatrix3D rotate_y(double angle): cdef double r - r = pi * angle / 180.0 + r = DEG2RAD * angle return new_affinematrix3d(cos(r), 0, sin(r), 0, 0, 1, 0, 0, -sin(r), 0, cos(r), 0, @@ -159,7 +160,7 @@ cpdef AffineMatrix3D rotate_z(double angle): cdef double r - r = pi * angle / 180.0 + r = DEG2RAD * angle return new_affinematrix3d(cos(r), -sin(r), 0, 0, sin(r), cos(r), 0, 0, 0, 0, 1, 0, @@ -190,7 +191,7 @@ cpdef AffineMatrix3D rotate_vector(double angle, Vector3D v): cdef double r, s, c, ci vn = v.normalise() - r = pi * angle / 180.0 + r = DEG2RAD * angle s = sin(r) c = cos(r) ci = 1.0 - c diff --git a/raysect/core/math/units.pyx b/raysect/core/math/units.pyx index 07b8375d..8488725d 100644 --- a/raysect/core/math/units.pyx +++ b/raysect/core/math/units.pyx @@ -31,6 +31,7 @@ from libc.math cimport M_1_PI + cpdef double km(double v): """ Converts kilometers to meters. @@ -40,6 +41,7 @@ cpdef double km(double v): """ return v * 1e3 + cpdef double cm(double v): """ Converts centimeters to meters. @@ -49,6 +51,7 @@ cpdef double cm(double v): """ return v * 1e-2 + cpdef double mm(double v): """ Converts millimeters to meters. @@ -58,6 +61,7 @@ cpdef double mm(double v): """ return v * 1e-3 + cpdef double um(double v): """ Converts micrometers to meters. @@ -67,6 +71,7 @@ cpdef double um(double v): """ return v * 1e-6 + cpdef double nm(double v): """ Converts nanometers to meters. @@ -76,6 +81,7 @@ cpdef double nm(double v): """ return v * 1e-9 + cpdef double mile(double v): """ Converts miles to meters. @@ -85,6 +91,7 @@ cpdef double mile(double v): """ return v * 1609.34 + cpdef double yard(double v): """ Converts yards to meters. @@ -94,6 +101,7 @@ cpdef double yard(double v): """ return v * 0.9144 + cpdef double foot(double v): """ Converts feet to meters. @@ -103,6 +111,7 @@ cpdef double foot(double v): """ return v * 0.3048 + cpdef double inch(double v): """ Converts inches to meters. @@ -112,6 +121,7 @@ cpdef double inch(double v): """ return v * 0.0254 + cpdef double mil(double v): """ Converts mils (thousandths of an inch) to meters. @@ -121,6 +131,7 @@ cpdef double mil(double v): """ return v * 2.54e-5 + cpdef radian(double v): """ Converts radians to degrees. diff --git a/raysect/core/math/vector.pxd b/raysect/core/math/vector.pxd index 07a0f8a5..c0843002 100644 --- a/raysect/core/math/vector.pxd +++ b/raysect/core/math/vector.pxd @@ -32,30 +32,20 @@ from raysect.core.math._vec3 cimport _Vec3 from raysect.core.math.affinematrix cimport AffineMatrix3D + cdef class Vector3D(_Vec3): cpdef Vector3D cross(self, _Vec3 v) - cpdef Vector3D normalise(self) - cpdef Vector3D transform(self, AffineMatrix3D m) - cdef Vector3D neg(self) - cdef Vector3D add(self, _Vec3 v) - cdef Vector3D sub(self, _Vec3 v) - cdef Vector3D mul(self, double m) - cdef Vector3D div(self, double m) - cpdef Vector3D copy(self) - cpdef Vector3D orthogonal(self) - cpdef Vector3D lerp(self, Vector3D b, double t) - cpdef Vector3D slerp(self, Vector3D b, double t) @@ -80,33 +70,19 @@ cdef class Vector2D: cdef public double x, y cpdef double dot(self, Vector2D v) - cdef double get_length(self) nogil - cdef object set_length(self, double v) - cdef double get_index(self, int index) nogil - cdef void set_index(self, int index, double value) nogil - cpdef double cross(self, Vector2D v) - cpdef Vector2D normalise(self) - # cpdef Vector2D transform(self, AffineMatrix2D m): - cdef Vector2D neg(self) - cdef Vector2D add(self, Vector2D v) - cdef Vector2D sub(self, Vector2D v) - cdef Vector2D mul(self, double m) - cdef Vector3D div(self, double d) - cpdef Vector2D copy(self) - cpdef Vector2D orthogonal(self) diff --git a/raysect/core/math/vector.pyx b/raysect/core/math/vector.pyx index 9c7a3907..54e294a2 100644 --- a/raysect/core/math/vector.pyx +++ b/raysect/core/math/vector.pyx @@ -33,6 +33,7 @@ import numbers cimport cython from libc.math cimport sqrt, fabs, NAN, acos, cos, sin + cdef const double EPSILON = 1e-12 @@ -68,12 +69,16 @@ cdef class Vector3D(_Vec3): self.z = z def __repr__(self): - """Returns a string representation of the Vector3D object.""" + """ + Returns a string representation of the Vector3D object. + """ return "Vector3D(" + str(self.x) + ", " + str(self.y) + ", " + str(self.z) + ")" def __richcmp__(self, object other, int op): - """Provides basic vector comparison operations.""" + """ + Provides basic vector comparison operations. + """ cdef Vector3D v @@ -85,11 +90,12 @@ cdef class Vector3D(_Vec3): return self.x == v.x and self.y == v.y and self.z == v.z elif op == 3: # __ne__() return self.x != v.x or self.y != v.y or self.z != v.z - else: - return NotImplemented + + return NotImplemented def __getitem__(self, int i): - """Returns the vector coordinates by index ([0,1,2] -> [x,y,z]). + """ + Returns the vector coordinates by index ([0,1,2] -> [x,y,z]). >>> a = Vector3D(1, 0, 0) >>> a[0] @@ -102,11 +108,12 @@ cdef class Vector3D(_Vec3): return self.y elif i == 2: return self.z - else: - raise IndexError("Index out of range [0, 2].") + + raise IndexError("Index out of range [0, 2].") def __setitem__(self, int i, double value): - """Sets the vector coordinates by index ([0,1,2] -> [x,y,z]). + """ + Sets the vector coordinates by index ([0,1,2] -> [x,y,z]). >>> a = Vector3D(1, 0, 0) >>> a[1] = 2 @@ -124,31 +131,33 @@ cdef class Vector3D(_Vec3): raise IndexError("Index out of range [0, 2].") def __iter__(self): - """Iterates over the vector coordinates (x, y, z) + """ + Iterates over the vector coordinates (x, y, z) >>> a = Vector3D(0, 1, 2) >>> x, y, z = a >>> x, y, z (0.0, 1.0, 2.0) """ + yield self.x yield self.y yield self.z def __neg__(self): - """Returns a vector with the reverse orientation (negation operator). + """ + Returns a vector with the reverse orientation (negation operator). >>> a = Vector3D(1, 0, 0) >>> -a Vector3D(-1.0, -0.0, -0.0) """ - return new_vector3d(-self.x, - -self.y, - -self.z) + return new_vector3d(-self.x, -self.y, -self.z) def __add__(self, object y): - """Addition operator. + """ + Addition operator. >>> Vector3D(1, 0, 0) + Vector3D(0, 1, 0) Vector3D(1.0, 1.0, 0.0) @@ -157,19 +166,14 @@ cdef class Vector3D(_Vec3): cdef _Vec3 vy if isinstance(y, _Vec3): + vy = <_Vec3> y + return new_vector3d(self.x + vy.x, self.y + vy.y, self.z + vy.z) - vy = <_Vec3>y - - return new_vector3d(self.x + vy.x, - self.y + vy.y, - self.z + vy.z) - - else: - - return NotImplemented + return NotImplemented def __radd__(self, object x): - """ Reverse addition operator. + """ + Reverse addition operator. >>> Vector3D(1, 0, 0) + Vector3D(0, 1, 0) Vector3D(1.0, 1.0, 0.0) @@ -178,7 +182,8 @@ cdef class Vector3D(_Vec3): return self.__add__(x) def __sub__(self, object y): - """Subtraction operator. + """ + Subtraction operator. >>> Vector3D(1, 0, 0) - Vector3D(0, 1, 0) Vector3D(1.0, -1.0, 0.0) @@ -187,19 +192,14 @@ cdef class Vector3D(_Vec3): cdef _Vec3 vy if isinstance(y, _Vec3): + vy = <_Vec3> y + return new_vector3d(self.x - vy.x, self.y - vy.y, self.z - vy.z) - vy = <_Vec3>y - - return new_vector3d(self.x - vy.x, - self.y - vy.y, - self.z - vy.z) - - else: - - return NotImplemented + return NotImplemented def __rsub__(self, object x): - """ Reverse subtraction operator. + """ + Reverse subtraction operator. >>> Vector3D(1, 0, 0) - Vector3D(0, 1, 0) Vector3D(1.0, -1.0, 0.0) @@ -208,19 +208,14 @@ cdef class Vector3D(_Vec3): cdef _Vec3 vx if isinstance(x, _Vec3): + vx = <_Vec3> x + return new_vector3d(vx.x - self.x, vx.y - self.y, vx.z - self.z) - vx = <_Vec3>x - - return new_vector3d(vx.x - self.x, - vx.y - self.y, - vx.z - self.z) - - else: - - return NotImplemented + return NotImplemented def __mul__(self, object y): - """Multiplication operator. + """ + Multiplication operator. 3D vectors can be multiplied with both scalars and transformation matrices. @@ -233,19 +228,14 @@ cdef class Vector3D(_Vec3): cdef AffineMatrix3D m if isinstance(y, numbers.Real): + s = y + return new_vector3d(s * self.x, s * self.y, s * self.z) - s = y - - return new_vector3d(s * self.x, - s * self.y, - s * self.z) - - else: - - return NotImplemented + return NotImplemented def __rmul__(self, object x): - """ Reverse multiplication operator. + """ + Reverse multiplication operator. 3D vectors can be multiplied with both scalars and transformation matrices. @@ -260,25 +250,18 @@ cdef class Vector3D(_Vec3): cdef AffineMatrix3D m if isinstance(x, numbers.Real): - - s = x - - return new_vector3d(s * self.x, - s * self.y, - s * self.z) + s = x + return new_vector3d(s * self.x, s * self.y, s * self.z) elif isinstance(x, AffineMatrix3D): + m = x + return new_vector3d( + m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z, + m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z, + m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z + ) - m = x - - return new_vector3d(m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z, - m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z, - m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z) - - else: - - return NotImplemented - + return NotImplemented @cython.cdivision(True) def __truediv__(self, object y): @@ -296,18 +279,12 @@ cdef class Vector3D(_Vec3): # prevent divide my zero if d == 0.0: - raise ZeroDivisionError("Cannot divide a vector by a zero scalar.") d = 1.0 / d + return new_vector3d(d * self.x, d * self.y, d * self.z) - return new_vector3d(d * self.x, - d * self.y, - d * self.z) - - else: - - raise TypeError("Unsupported operand type. Expects a real number.") + raise TypeError("Unsupported operand type. Expects a real number.") cpdef Vector3D cross(self, _Vec3 v): """ @@ -326,9 +303,11 @@ cdef class Vector3D(_Vec3): Vector3D(0.0, 0.0, 1.0) """ - return new_vector3d(self.y * v.z - v.y * self.z, - self.z * v.x - v.z * self.x, - self.x * v.y - v.x * self.y) + return new_vector3d( + self.y * v.z - v.y * self.z, + self.z * v.x - v.z * self.x, + self.x * v.y - v.x * self.y + ) @cython.cdivision(True) cpdef Vector3D normalise(self): @@ -351,15 +330,11 @@ cdef class Vector3D(_Vec3): # if current length is zero, problem is ill defined t = self.x * self.x + self.y * self.y + self.z * self.z if t == 0.0: - raise ZeroDivisionError("A zero length vector can not be normalised as the direction of a zero length vector is undefined.") # normalise and rescale vector t = 1.0 / sqrt(t) - - return new_vector3d(self.x * t, - self.y * t, - self.z * t) + return new_vector3d(self.x * t, self.y * t, self.z * t) cpdef Vector3D transform(self, AffineMatrix3D m): """ @@ -387,9 +362,11 @@ cdef class Vector3D(_Vec3): Vector3D(0.0, -1.0, 6.123233995736766e-17) """ - return new_vector3d(m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z, - m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z, - m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z) + return new_vector3d( + m.m[0][0] * self.x + m.m[0][1] * self.y + m.m[0][2] * self.z, + m.m[1][0] * self.x + m.m[1][1] * self.y + m.m[1][2] * self.z, + m.m[2][0] * self.x + m.m[2][1] * self.y + m.m[2][2] * self.z + ) cdef Vector3D neg(self): """ @@ -399,9 +376,7 @@ cdef class Vector3D(_Vec3): to the equivalent python operator. """ - return new_vector3d(-self.x, - -self.y, - -self.z) + return new_vector3d(-self.x, -self.y, -self.z) cdef Vector3D add(self, _Vec3 v): """ @@ -411,9 +386,7 @@ cdef class Vector3D(_Vec3): to the equivalent python operator. """ - return new_vector3d(self.x + v.x, - self.y + v.y, - self.z + v.z) + return new_vector3d(self.x + v.x, self.y + v.y, self.z + v.z) cdef Vector3D sub(self, _Vec3 v): """ @@ -423,9 +396,7 @@ cdef class Vector3D(_Vec3): to the equivalent python operator. """ - return new_vector3d(self.x - v.x, - self.y - v.y, - self.z - v.z) + return new_vector3d(self.x - v.x, self.y - v.y, self.z - v.z) cdef Vector3D mul(self, double m): """ @@ -435,9 +406,7 @@ cdef class Vector3D(_Vec3): to the equivalent python operator. """ - return new_vector3d(self.x * m, - self.y * m, - self.z * m) + return new_vector3d(self.x * m, self.y * m, self.z * m) @cython.cdivision(True) cdef Vector3D div(self, double d): @@ -449,14 +418,10 @@ cdef class Vector3D(_Vec3): """ if d == 0.0: - raise ZeroDivisionError("Cannot divide a vector by a zero scalar.") d = 1.0 / d - - return new_vector3d(self.x * d, - self.y * d, - self.z * d) + return new_vector3d(self.x * d, self.y * d, self.z * d) cpdef Vector3D copy(self): """ @@ -471,9 +436,7 @@ cdef class Vector3D(_Vec3): Vector3D(1.0, 1.0, 1.0) """ - return new_vector3d(self.x, - self.y, - self.z) + return new_vector3d(self.x, self.y, self.z) # todo: this is common code with normal, move into math.cython and call cpdef Vector3D orthogonal(self): @@ -533,7 +496,6 @@ cdef class Vector3D(_Vec3): raise ValueError("Vector lerp parameter t must be in range (0, 1).") t_minus = 1 - t - return new_vector3d(self.x * t_minus + b.x * t, self.y * t_minus + b.y * t, self.z * t_minus + b.z * t) cpdef Vector3D slerp(self, Vector3D b, double t): @@ -671,12 +633,16 @@ cdef class Vector2D: self.y = y def __repr__(self): - """Returns a string representation of the Vector2D object.""" + """ + Returns a string representation of the Vector2D object. + """ return "Vector2D(" + str(self.x) + ", " + str(self.y) + ")" def __richcmp__(self, object other, int op): - """Provides basic vector comparison operations.""" + """ + Provides basic vector comparison operations. + """ cdef Vector2D v @@ -688,11 +654,12 @@ cdef class Vector2D: return self.x == v.x and self.y == v.y elif op == 3: # __ne__() return self.x != v.x or self.y != v.y - else: - return NotImplemented + + return NotImplemented def __getitem__(self, int i): - """Returns the vector coordinates by index ([0,1] -> [x,y]). + """ + Returns the vector coordinates by index ([0,1] -> [x,y]). >>> a = Vector2D(1, 0) >>> a[0] @@ -703,11 +670,12 @@ cdef class Vector2D: return self.x elif i == 1: return self.y - else: - raise IndexError("Index out of range [0, 1].") + + raise IndexError("Index out of range [0, 1].") def __setitem__(self, int i, double value): - """Sets the vector coordinates by index ([0,1] -> [x,y]). + """ + Sets the vector coordinates by index ([0,1] -> [x,y]). >>> a = Vector2D(1, 0) >>> a[1] = 2 @@ -723,7 +691,8 @@ cdef class Vector2D: raise IndexError("Index out of range [0, 1].") def __iter__(self): - """Iterates over the vector coordinates (x, y) + """ + Iterates over the vector coordinates (x, y) >>> a = Vector2D(1, 0) >>> x, y = a @@ -731,11 +700,13 @@ cdef class Vector2D: (1.0, 0.0) """ + yield self.x yield self.y def __neg__(self): - """Returns a vector with the reverse orientation (negation operator). + """ + Returns a vector with the reverse orientation (negation operator). >>> a = Vector2D(1, 0) >>> -a @@ -746,7 +717,8 @@ cdef class Vector2D: return new_vector2d(-self.x, -self.y) def __add__(self, object y): - """Addition operator. + """ + Addition operator. >>> Vector2D(1, 0) + Vector2D(0, 1) Vector2D(1.0, 1.0) @@ -755,17 +727,14 @@ cdef class Vector2D: cdef Vector2D vy if isinstance(y, Vector2D): - vy = y - return new_vector2d(self.x + vy.x, self.y + vy.y) - else: - - return NotImplemented + return NotImplemented def __sub__(self, object y): - """Subtraction operator. + """ + Subtraction operator. >>> Vector2D(1, 0) - Vector2D(0, 1) Vector2D(1.0, -1.0) @@ -774,17 +743,14 @@ cdef class Vector2D: cdef Vector2D vy if isinstance(y, Vector2D): - vy = y - return new_vector2d(self.x - vy.x, self.y - vy.y) - else: - - return NotImplemented + return NotImplemented def __mul__(self, object y): - """Multiplication operator. + """ + Multiplication operator. >>> Vector3D(1, 2) * 2 Vector2D(2.0, 4.0) @@ -793,16 +759,14 @@ cdef class Vector2D: cdef double s if isinstance(y, numbers.Real): - - s = y - + s = y return new_vector2d(s * self.x, s * self.y) - else: - raise TypeError("Unsupported operand type. Expects a real number.") + raise TypeError("Unsupported operand type. Expects a real number.") def __rmul__(self, object x): - """Reverse multiplication operator. + """ + Reverse multiplication operator. >>> 2 * Vector3D(1, 2) Vector2D(2.0, 4.0) @@ -811,19 +775,15 @@ cdef class Vector2D: cdef double s if isinstance(x, numbers.Real): - - s = x - + s = x return new_vector2d(s * self.x, s * self.y) - else: - raise TypeError("Unsupported operand type. Expects a real number.") - - + raise TypeError("Unsupported operand type. Expects a real number.") @cython.cdivision(True) def __truediv__(self, object y): - """Division operator. + """ + Division operator. >>> Vector2D(1, 1) / 2 Vector2D(0.5, 0.5) @@ -837,16 +797,12 @@ cdef class Vector2D: # prevent divide my zero if d == 0.0: - raise ZeroDivisionError("Cannot divide a vector by a zero scalar.") d = 1.0 / d - return new_vector2d(d * self.x, d * self.y) - else: - - raise TypeError("Unsupported operand type. Expects a real number.") + raise TypeError("Unsupported operand type. Expects a real number.") @property def length(self): @@ -862,6 +818,7 @@ cdef class Vector2D: 1.4142135623730951 """ + return self.get_length() @length.setter @@ -915,7 +872,6 @@ cdef class Vector2D: # normalise and rescale vector t = v / sqrt(t) - self.x = self.x * t self.y = self.y * t @@ -932,8 +888,8 @@ cdef class Vector2D: return self.x elif index == 1: return self.y - else: - return NAN + + return NAN cdef void set_index(self, int index, double value) nogil: """ @@ -994,7 +950,6 @@ cdef class Vector2D: # if current length is zero, problem is ill defined t = self.x * self.x + self.y * self.y if t == 0.0: - raise ZeroDivisionError("A zero length vector can not be normalised as the direction of a zero length vector is undefined.") # normalise and rescale vector @@ -1071,11 +1026,9 @@ cdef class Vector2D: """ if d == 0.0: - raise ZeroDivisionError("Cannot divide a vector by a zero scalar.") d = 1.0 / d - return new_vector2d(self.x * d, self.y * d) cpdef Vector2D copy(self): @@ -1108,11 +1061,7 @@ cdef class Vector2D: """ - cdef: - Vector2D n - - n = self.normalise() - + cdef Vector2D n = self.normalise() return new_vector2d(-n.y, n.x) From e4804b0820fdabeafa4706b517f4ef0fe02e9eae Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sun, 20 Jul 2025 19:36:34 +0100 Subject: [PATCH 56/77] Revised various segments of the code to both modernise it and support cython 3. --- raysect/core/boundingbox.pxd | 25 ------------------------- raysect/core/boundingbox.pyx | 3 +++ raysect/core/boundingsphere.pxd | 8 -------- raysect/core/containers.pxd | 6 ------ raysect/core/intersection.pyx | 1 + raysect/core/ray.pxd | 1 - raysect/core/scenegraph/node.pxd | 3 +-- raysect/core/scenegraph/observer.pxd | 1 + raysect/core/scenegraph/primitive.pxd | 10 +--------- raysect/core/scenegraph/utility.pyx | 1 + raysect/core/scenegraph/world.pxd | 13 ++++++------- 11 files changed, 14 insertions(+), 58 deletions(-) diff --git a/raysect/core/boundingbox.pxd b/raysect/core/boundingbox.pxd index cd1e59b4..29167b96 100644 --- a/raysect/core/boundingbox.pxd +++ b/raysect/core/boundingbox.pxd @@ -40,37 +40,21 @@ cdef class BoundingBox3D: cdef Point3D upper cdef Point3D get_centre(self) - cpdef bint hit(self, Ray ray) - cpdef tuple full_intersection(self, Ray ray) - cdef bint intersect(self, Ray ray, double *front_intersection, double *back_intersection) - cdef void _slab(self, double origin, double direction, double lower, double upper, double *front_intersection, double *back_intersection) nogil - cpdef bint contains(self, Point3D point) - cpdef object union(self, BoundingBox3D box) - cpdef object extend(self, Point3D point, double padding=*) - cpdef double surface_area(self) - cpdef double volume(self) - cpdef list vertices(self) - cpdef double extent(self, int axis) except -1 - cpdef int largest_axis(self) - cpdef double largest_extent(self) - cpdef object pad(self, double padding) - cpdef object pad_axis(self, int axis, double padding) - cpdef BoundingSphere3D enclosing_sphere(self) @@ -95,23 +79,14 @@ cdef class BoundingBox2D: cdef Point2D upper cpdef bint contains(self, Point2D point) - cpdef object union(self, BoundingBox2D box) - cpdef object extend(self, Point2D point, double padding=*) - cpdef double surface_area(self) - cpdef list vertices(self) - cpdef double extent(self, int axis) except -1 - cpdef int largest_axis(self) - cpdef double largest_extent(self) - cpdef object pad(self, double padding) - cpdef object pad_axis(self, int axis, double padding) diff --git a/raysect/core/boundingbox.pyx b/raysect/core/boundingbox.pyx index 52f59d21..5c54fc41 100644 --- a/raysect/core/boundingbox.pyx +++ b/raysect/core/boundingbox.pyx @@ -35,12 +35,14 @@ cimport cython from libc.math cimport INFINITY from raysect.core.math cimport new_point3d, new_point2d + # axis defines cdef enum: X_AXIS = 0 Y_AXIS = 1 Z_AXIS = 2 + # defines the padding on the sphere which encloses the BoundingBox3D. cdef const double SPHERE_PADDING = 1.000001 @@ -257,6 +259,7 @@ cdef class BoundingBox3D: return False if (point.z < self.lower.z) or (point.z > self.upper.z): return False + return True cpdef object union(self, BoundingBox3D box): diff --git a/raysect/core/boundingsphere.pxd b/raysect/core/boundingsphere.pxd index 0026b721..821bea48 100644 --- a/raysect/core/boundingsphere.pxd +++ b/raysect/core/boundingsphere.pxd @@ -39,19 +39,11 @@ cdef class BoundingSphere3D: Point3D centre cpdef bint hit(self, Ray ray) - cpdef tuple full_intersection(self, Ray ray) - cdef bint intersect(self, Ray ray, double *front_intersection, double *back_intersection) - cpdef bint contains(self, Point3D point) - cpdef object union(self, BoundingSphere3D sphere) - cpdef object extend(self, Point3D point, double padding=*) - cpdef double surface_area(self) - cpdef double volume(self) - cpdef object pad(self, double padding) diff --git a/raysect/core/containers.pxd b/raysect/core/containers.pxd index add9dded..3de638c5 100644 --- a/raysect/core/containers.pxd +++ b/raysect/core/containers.pxd @@ -46,22 +46,16 @@ cdef class LinkedList: _Item last cpdef bint is_empty(self) - cpdef add(self, object value) - cpdef add_items(self, object iterable) - cpdef object get_index(self, int index) - cpdef insert(self, object value, int index) - cpdef object remove(self, int index) cdef class Stack(LinkedList): cpdef push(self, object value) - cpdef object pop(self) diff --git a/raysect/core/intersection.pyx b/raysect/core/intersection.pyx index 48ec1f7f..70cad491 100644 --- a/raysect/core/intersection.pyx +++ b/raysect/core/intersection.pyx @@ -31,6 +31,7 @@ cimport cython + @cython.freelist(256) cdef class Intersection: """ diff --git a/raysect/core/ray.pxd b/raysect/core/ray.pxd index db89e3cf..4e02656b 100644 --- a/raysect/core/ray.pxd +++ b/raysect/core/ray.pxd @@ -39,7 +39,6 @@ cdef class Ray: cdef public double max_distance cpdef Point3D point_on(self, double t) - cpdef Ray copy(self, Point3D origin=*, Vector3D direction=*) diff --git a/raysect/core/scenegraph/node.pxd b/raysect/core/scenegraph/node.pxd index 2a80246b..e0350249 100644 --- a/raysect/core/scenegraph/node.pxd +++ b/raysect/core/scenegraph/node.pxd @@ -32,10 +32,9 @@ from raysect.core.scenegraph._nodebase cimport _NodeBase from raysect.core.math.affinematrix cimport AffineMatrix3D + cdef class Node(_NodeBase): cpdef AffineMatrix3D to(self, _NodeBase node) - cpdef AffineMatrix3D to_local(self) - cpdef AffineMatrix3D to_root(self) diff --git a/raysect/core/scenegraph/observer.pxd b/raysect/core/scenegraph/observer.pxd index a8fccb8e..0fdde98e 100644 --- a/raysect/core/scenegraph/observer.pxd +++ b/raysect/core/scenegraph/observer.pxd @@ -31,6 +31,7 @@ from raysect.core.scenegraph.node cimport Node + cdef class Observer(Node): cpdef observe(self) \ No newline at end of file diff --git a/raysect/core/scenegraph/primitive.pxd b/raysect/core/scenegraph/primitive.pxd index ad9d03f4..52f46b9a 100644 --- a/raysect/core/scenegraph/primitive.pxd +++ b/raysect/core/scenegraph/primitive.pxd @@ -44,19 +44,11 @@ cdef class Primitive(Node): cdef Material _material cdef Material get_material(self) - cpdef Intersection hit(self, Ray ray) - cpdef Intersection next_intersection(self) - cpdef bint contains(self, Point3D p) except -1 - cpdef BoundingBox3D bounding_box(self) - cpdef BoundingSphere3D bounding_sphere(self) - cpdef object instance(self, object parent=*, AffineMatrix3D transform=*, Material material=*, str name=*) - cpdef object notify_geometry_change(self) - - cpdef object notify_material_change(self) \ No newline at end of file + cpdef object notify_material_change(self) diff --git a/raysect/core/scenegraph/utility.pyx b/raysect/core/scenegraph/utility.pyx index 6947beb0..0870aa3d 100644 --- a/raysect/core/scenegraph/utility.pyx +++ b/raysect/core/scenegraph/utility.pyx @@ -35,6 +35,7 @@ from raysect.core.scenegraph._nodebase cimport _NodeBase from raysect.core.scenegraph.node cimport Node from raysect.core.scenegraph.signal cimport ChangeSignal + cdef class BridgeNode(Node): """ Specialised scene-graph root node that propagates geometry notifications. diff --git a/raysect/core/scenegraph/world.pxd b/raysect/core/scenegraph/world.pxd index a9e697af..57068045 100644 --- a/raysect/core/scenegraph/world.pxd +++ b/raysect/core/scenegraph/world.pxd @@ -35,18 +35,17 @@ from raysect.core.acceleration.accelerator cimport Accelerator from raysect.core.math cimport Point3D, AffineMatrix3D from raysect.core.scenegraph._nodebase cimport _NodeBase + cdef class World(_NodeBase): - cdef bint _rebuild_accelerator - cdef Accelerator _accelerator - cdef list _primitives - cdef list _observers + cdef: + bint _rebuild_accelerator + Accelerator _accelerator + list _primitives + list _observers cpdef AffineMatrix3D to(self, _NodeBase node) - cpdef Intersection hit(self, Ray ray) - cpdef list contains(self, Point3D point) - cpdef build_accelerator(self, bint force=*) From 2359effb2aa123e6fe0c7ef367a091b9bd58b399 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sun, 20 Jul 2025 20:27:23 +0100 Subject: [PATCH 57/77] Revised various segments of the code to both modernise it and support cython 3. --- .../math/function/float/function1d/base.pyx | 38 ++++++++++++++++++- .../function/float/function1d/samplers.pyx | 2 +- .../math/function/float/function2d/base.pyx | 37 ++++++++++++++++++ .../math/function/float/function3d/base.pyx | 37 ++++++++++++++++++ raysect/optical/material/conductor.pxd | 4 -- raysect/optical/material/debug.pxd | 1 - raysect/optical/material/dielectric.pxd | 1 + raysect/optical/material/emitter/unity.pxd | 1 - raysect/optical/material/emitter/unity.pyx | 2 + raysect/optical/material/lambert.pyx | 1 - raysect/optical/observer/base/observer.pxd | 16 -------- raysect/optical/observer/base/pipeline.pxd | 9 ----- raysect/optical/observer/base/processor.pxd | 2 +- raysect/optical/observer/base/processor.pyx | 1 + .../observer/nonimaging/mesh_camera.pyx | 6 ++- .../observer/nonimaging/mesh_pixel.pyx | 6 ++- raysect/optical/observer/pipeline/bayer.pxd | 8 +--- .../optical/observer/pipeline/mono/power.pxd | 7 ---- raysect/optical/observer/pipeline/rgb.pxd | 8 ---- raysect/optical/scenegraph/world.pxd | 11 +----- raysect/optical/spectrum.pxd | 1 + raysect/primitive/box.pyx | 1 + raysect/primitive/cylinder.pyx | 1 + raysect/primitive/mesh/mesh.pxd | 16 -------- raysect/primitive/mesh/mesh.pyx | 8 +++- raysect/primitive/utility.pxd | 3 -- 26 files changed, 138 insertions(+), 90 deletions(-) diff --git a/raysect/core/math/function/float/function1d/base.pyx b/raysect/core/math/function/float/function1d/base.pyx index 3bba6f0c..7695fc99 100644 --- a/raysect/core/math/function/float/function1d/base.pyx +++ b/raysect/core/math/function/float/function1d/base.pyx @@ -66,42 +66,54 @@ cdef class Function1D(FloatFunction): return 'Function1D(x)' def __add__(self, object b): + if is_callable(b): # a() + b() return AddFunction1D(self, b) + elif isinstance(b, numbers.Real): # a() + B -> B + a() return AddScalar1D( b, self) + return NotImplemented def __radd__(self, object a): return self.__add__(a) def __sub__(self, object b): + if is_callable(b): # a() - b() return SubtractFunction1D(self, b) + elif isinstance(b, numbers.Real): # a() - B -> -B + a() return AddScalar1D(-( b), self) + return NotImplemented def __rsub__(self, object a): + if is_callable(a): # a() - b() return SubtractFunction1D(a, self) + elif isinstance(a, numbers.Real): # A - b() return SubtractScalar1D( a, self) + return NotImplemented def __mul__(self, object b): + if is_callable(b): # a() * b() return MultiplyFunction1D(self, b) + elif isinstance(b, numbers.Real): # a() * B -> B * a() return MultiplyScalar1D( b, self) + return NotImplemented def __rmul__(self, object a): @@ -109,54 +121,68 @@ cdef class Function1D(FloatFunction): @cython.cdivision(True) def __truediv__(self, object b): + cdef double v + if is_callable(b): # a() / b() return DivideFunction1D(self, b) + elif isinstance(b, numbers.Real): # a() / B -> 1/B * a() v = b if v == 0.0: raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") - return MultiplyScalar1D(1/v, self) + return MultiplyScalar1D(1 / v, self) + return NotImplemented - @cython.cdivision(True) def __rtruediv__(self, object a): + if is_callable(a): # a() / b() return DivideFunction1D(a, self) + elif isinstance(a, numbers.Real): # A / b() return DivideScalar1D( a, self) + return NotImplemented def __mod__(self, object b): + cdef double v + if is_callable(b): # a() % b() return ModuloFunction1D(self, b) + elif isinstance(b, numbers.Real): # a() % B v = b if v == 0.0: raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") return ModuloFunctionScalar1D(self, v) + return NotImplemented def __rmod__(self, object a): + if is_callable(a): # a() % b() return ModuloFunction1D(a, self) + elif isinstance(a, numbers.Real): # A % b() return ModuloScalarFunction1D( a, self) + return NotImplemented def __neg__(self): return MultiplyScalar1D(-1, self) def __pow__(self, object b, object c): + if c is not None: # Optimised implementation of pow(a, b, c) not available: fall back # to general implementation @@ -165,12 +191,15 @@ cdef class Function1D(FloatFunction): if is_callable(b): # a() ** b() return PowFunction1D(self, b) + elif isinstance(b, numbers.Real): # a() ** b return PowFunctionScalar1D(self, b) + return NotImplemented def __rpow__(self, object a, object c): + if c is not None: # Optimised implementation of pow(a, b, c) not available: fall back # to general implementation @@ -179,15 +208,18 @@ cdef class Function1D(FloatFunction): if is_callable(a): # a() ** b() return PowFunction1D(a, self) + elif isinstance(a, numbers.Real): # A ** b() return PowScalarFunction1D( a, self) + return NotImplemented def __abs__(self): return AbsFunction1D(self) def __richcmp__(self, object other, int op): + if is_callable(other): if op == Py_EQ: return EqualsFunction1D(self, other) @@ -201,6 +233,7 @@ cdef class Function1D(FloatFunction): return LessEqualsFunction1D(self, other) if op == Py_GE: return GreaterEqualsFunction1D(self, other) + if isinstance(other, numbers.Real): if op == Py_EQ: return EqualsScalar1D( other, self) @@ -218,6 +251,7 @@ cdef class Function1D(FloatFunction): if op == Py_GE: # f() >= K -> K <= f return LessEqualsScalar1D( other, self) + return NotImplemented diff --git a/raysect/core/math/function/float/function1d/samplers.pyx b/raysect/core/math/function/float/function1d/samplers.pyx index 16c6649b..e17f5439 100644 --- a/raysect/core/math/function/float/function1d/samplers.pyx +++ b/raysect/core/math/function/float/function1d/samplers.pyx @@ -35,6 +35,7 @@ from .autowrap cimport autowrap_function1d cimport cython cimport numpy as np + @cython.boundscheck(False) @cython.wraparound(False) cpdef tuple sample1d(object function, double x_min, double x_max, int x_samples): @@ -58,7 +59,6 @@ cpdef tuple sample1d(object function, double x_min, double x_max, int x_samples) if x_samples < 1: raise ValueError("The argument x_samples must be >= 1") - # ensures that func is of type Function1D. I.e. if 'function' argument was Python function, it'll get autowrapped # into Function1D object func = autowrap_function1d(function) diff --git a/raysect/core/math/function/float/function2d/base.pyx b/raysect/core/math/function/float/function2d/base.pyx index b6efd557..a689415b 100644 --- a/raysect/core/math/function/float/function2d/base.pyx +++ b/raysect/core/math/function/float/function2d/base.pyx @@ -64,42 +64,54 @@ cdef class Function2D(FloatFunction): return self.evaluate(x, y) def __add__(self, object b): + if is_callable(b): # a() + b() return AddFunction2D(self, b) + elif isinstance(b, numbers.Real): # a() + B -> B + a() return AddScalar2D( b, self) + return NotImplemented def __radd__(self, object a): return self.__add__(a) def __sub__(self, object b): + if is_callable(b): # a() - b() return SubtractFunction2D(self, b) + elif isinstance(b, numbers.Real): # a() - B -> -B + a() return AddScalar2D(-( b), self) + return NotImplemented def __rsub__(self, object a): + if is_callable(a): # a() - b() return SubtractFunction2D(a, self) + elif isinstance(a, numbers.Real): # A - b() return SubtractScalar2D( a, self) + return NotImplemented def __mul__(self, object b): + if is_callable(b): # a() * b() return MultiplyFunction2D(self, b) + elif isinstance(b, numbers.Real): # a() * B -> B * a() return MultiplyScalar2D( b, self) + return NotImplemented def __rmul__(self, object a): @@ -107,83 +119,106 @@ cdef class Function2D(FloatFunction): @cython.cdivision(True) def __truediv__(self, object b): + cdef double v + if is_callable(b): # a() / b() return DivideFunction2D(self, b) + elif isinstance(b, numbers.Real): # a() / B -> 1/B * a() v = b if v == 0.0: raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") return MultiplyScalar2D(1/v, self) + return NotImplemented @cython.cdivision(True) def __rtruediv__(self, object a): + if is_callable(a): # a() / b() return DivideFunction2D(a, self) + elif isinstance(a, numbers.Real): # A / b() return DivideScalar2D( a, self) + return NotImplemented def __mod__(self, object b): + cdef double v + if is_callable(b): # a() % b() return ModuloFunction2D(self, b) + elif isinstance(b, numbers.Real): # a() % B v = b if v == 0.0: raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") return ModuloFunctionScalar2D(self, v) + return NotImplemented def __rmod__(self, object a): + if is_callable(a): # a() % b() return ModuloFunction2D(a, self) + elif isinstance(a, numbers.Real): # A % b() return ModuloScalarFunction2D( a, self) + return NotImplemented def __neg__(self): return MultiplyScalar2D(-1, self) def __pow__(self, object b, object c): + if c is not None: # Optimised implementation of pow(a, b, c) not available: fall back # to general implementation return PowFunction2D(self, b) % c + if is_callable(b): # a() ** b() return PowFunction2D(self, b) + elif isinstance(b, numbers.Real): # a() ** b return PowFunctionScalar2D(self, b) + return NotImplemented def __rpow__(self, object a, object c): + if c is not None: # Optimised implementation of pow(a, b, c) not available: fall back # to general implementation return PowFunction2D(a, self) % c + if is_callable(a): # a() ** b() return PowFunction2D(a, self) + elif isinstance(a, numbers.Real): # A ** b() return PowScalarFunction2D( a, self) + return NotImplemented def __abs__(self): return AbsFunction2D(self) def __richcmp__(self, object other, int op): + if is_callable(other): if op == Py_EQ: return EqualsFunction2D(self, other) @@ -197,6 +232,7 @@ cdef class Function2D(FloatFunction): return LessEqualsFunction2D(self, other) if op == Py_GE: return GreaterEqualsFunction2D(self, other) + if isinstance(other, numbers.Real): if op == Py_EQ: return EqualsScalar2D( other, self) @@ -214,6 +250,7 @@ cdef class Function2D(FloatFunction): if op == Py_GE: # f() >= K -> K <= f return LessEqualsScalar2D( other, self) + return NotImplemented diff --git a/raysect/core/math/function/float/function3d/base.pyx b/raysect/core/math/function/float/function3d/base.pyx index cf64adb9..f5cdf065 100644 --- a/raysect/core/math/function/float/function3d/base.pyx +++ b/raysect/core/math/function/float/function3d/base.pyx @@ -65,42 +65,54 @@ cdef class Function3D(FloatFunction): return self.evaluate(x, y, z) def __add__(self, object b): + if is_callable(b): # a() + b() return AddFunction3D(self, b) + elif isinstance(b, numbers.Real): # a() + B -> B + a() return AddScalar3D( b, self) + return NotImplemented def __radd__(self, object a): return self.__add__(a) def __sub__(self, object b): + if is_callable(b): # a() - b() return SubtractFunction3D(self, b) + elif isinstance(b, numbers.Real): # a() - B -> -B + a() return AddScalar3D(-( b), self) + return NotImplemented def __rsub__(self, object a): + if is_callable(a): # a() - b() return SubtractFunction3D(a, self) + elif isinstance(a, numbers.Real): # A - b() return SubtractScalar3D( a, self) + return NotImplemented def __mul__(self, object b): + if is_callable(b): # a() * b() return MultiplyFunction3D(self, b) + elif isinstance(b, numbers.Real): # a() * B -> B * a() return MultiplyScalar3D( b, self) + return NotImplemented def __rmul__(self, object a): @@ -108,83 +120,106 @@ cdef class Function3D(FloatFunction): @cython.cdivision(True) def __truediv__(self, object b): + cdef double v + if is_callable(b): # a() / b() return DivideFunction3D(self, b) + elif isinstance(b, numbers.Real): # a() / B -> 1/B * a() v = b if v == 0.0: raise ZeroDivisionError("Scalar used as the denominator of the division is zero valued.") return MultiplyScalar3D(1/v, self) + return NotImplemented @cython.cdivision(True) def __rtruediv__(self, object a): + if is_callable(a): # a() / b() return DivideFunction3D(a, self) + elif isinstance(a, numbers.Real): # A / b() return DivideScalar3D( a, self) + return NotImplemented def __mod__(self, object b): + cdef double v + if is_callable(b): # a() % b() return ModuloFunction3D(self, b) + elif isinstance(b, numbers.Real): # a() % B v = b if v == 0.0: raise ZeroDivisionError("Scalar used as the divisor of the division is zero valued.") return ModuloFunctionScalar3D(self, v) + return NotImplemented def __rmod__(self, object a): + if is_callable(a): # a() % b() return ModuloFunction3D(a, self) + elif isinstance(a, numbers.Real): # A % b() return ModuloScalarFunction3D( a, self) + return NotImplemented def __neg__(self): return MultiplyScalar3D(-1, self) def __pow__(self, object b, object c): + if c is not None: # Optimised implementation of pow(a, b, c) not available: fall back # to general implementation return PowFunction3D(self, b) % c + if is_callable(b): # a() ** b() return PowFunction3D(self, b) + elif isinstance(b, numbers.Real): # a() ** b return PowFunctionScalar3D(self, b) + return NotImplemented def __rpow__(self, object a, object c): + if c is not None: # Optimised implementation of pow(a, b, c) not available: fall back # to general implementation return PowFunction3D(a, self) % c + if is_callable(a): # a() ** b() return PowFunction3D(a, self) + elif isinstance(a, numbers.Real): # A ** b() return PowScalarFunction3D( a, self) + return NotImplemented def __abs__(self): return AbsFunction3D(self) def __richcmp__(self, object other, int op): + if is_callable(other): if op == Py_EQ: return EqualsFunction3D(self, other) @@ -198,6 +233,7 @@ cdef class Function3D(FloatFunction): return LessEqualsFunction3D(self, other) if op == Py_GE: return GreaterEqualsFunction3D(self, other) + if isinstance(other, numbers.Real): if op == Py_EQ: return EqualsScalar3D( other, self) @@ -215,6 +251,7 @@ cdef class Function3D(FloatFunction): if op == Py_GE: # f() >= K -> K <= f return LessEqualsScalar3D( other, self) + return NotImplemented diff --git a/raysect/optical/material/conductor.pxd b/raysect/optical/material/conductor.pxd index 83416241..9eb5aecb 100644 --- a/raysect/optical/material/conductor.pxd +++ b/raysect/optical/material/conductor.pxd @@ -51,11 +51,7 @@ cdef class RoughConductor(ContinuousBSDF): double _roughness cdef double _d(self, Vector3D s_half) - cdef double _g(self, Vector3D s_incoming, Vector3D s_outgoing) - cdef double _g1(self, Vector3D v) - cdef Spectrum _f(self, Spectrum spectrum, Vector3D s_outgoing, Vector3D s_normal) - cdef double _fresnel_conductor(self, double ci, double n, double k) nogil diff --git a/raysect/optical/material/debug.pxd b/raysect/optical/material/debug.pxd index 369603c8..0ec21e36 100644 --- a/raysect/optical/material/debug.pxd +++ b/raysect/optical/material/debug.pxd @@ -42,5 +42,4 @@ cdef class Light(NullVolume): cdef class PerfectReflectingSurface(Material): - pass diff --git a/raysect/optical/material/dielectric.pxd b/raysect/optical/material/dielectric.pxd index d541c7d3..969e18b5 100644 --- a/raysect/optical/material/dielectric.pxd +++ b/raysect/optical/material/dielectric.pxd @@ -32,6 +32,7 @@ from raysect.optical cimport SpectralFunction, NumericallyIntegratedSF from raysect.optical.material cimport Material + cdef class Sellmeier(NumericallyIntegratedSF): cdef: diff --git a/raysect/optical/material/emitter/unity.pxd b/raysect/optical/material/emitter/unity.pxd index 1f91e5c0..33e20a9c 100644 --- a/raysect/optical/material/emitter/unity.pxd +++ b/raysect/optical/material/emitter/unity.pxd @@ -29,7 +29,6 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from raysect.optical cimport World, Primitive, Ray, Spectrum, Point3D, Vector3D, Normal3D, AffineMatrix3D, Intersection from raysect.optical.material.emitter.homogeneous cimport HomogeneousVolumeEmitter from raysect.optical.material.material cimport NullVolume diff --git a/raysect/optical/material/emitter/unity.pyx b/raysect/optical/material/emitter/unity.pyx index 145fa1c9..a3fc2bea 100644 --- a/raysect/optical/material/emitter/unity.pyx +++ b/raysect/optical/material/emitter/unity.pyx @@ -29,6 +29,8 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +from raysect.optical cimport World, Primitive, Ray, Spectrum, Point3D, Vector3D, Normal3D, AffineMatrix3D, Intersection + cimport cython diff --git a/raysect/optical/material/lambert.pyx b/raysect/optical/material/lambert.pyx index 9e2c1a67..a3ecd08b 100644 --- a/raysect/optical/material/lambert.pyx +++ b/raysect/optical/material/lambert.pyx @@ -32,7 +32,6 @@ from raysect.core.math.sampler cimport HemisphereCosineSampler from raysect.optical cimport Point3D, Vector3D, AffineMatrix3D, Primitive, World, Ray, Spectrum, SpectralFunction, ConstantSF, Intersection from raysect.optical.material cimport ContinuousBSDF -from numpy cimport ndarray cdef HemisphereCosineSampler hemisphere_sampler = HemisphereCosineSampler() diff --git a/raysect/optical/observer/base/observer.pxd b/raysect/optical/observer/base/observer.pxd index b354b5db..39602353 100644 --- a/raysect/optical/observer/base/observer.pxd +++ b/raysect/optical/observer/base/observer.pxd @@ -55,31 +55,18 @@ cdef class _ObserverBase(Observer): public bint quiet cpdef list _slice_spectrum(self) - cpdef list _generate_templates(self, list slices) - cpdef object _render_pixel(self, tuple task, int slice_id, Ray template) - cpdef object _update_state(self, tuple packed_result, int slice_id) - cpdef list _generate_tasks(self) - cpdef list _obtain_pixel_processors(self, tuple task, int slice_id) - cpdef object _initialise_pipelines(self, double min_wavelength, double max_wavelength, int spectral_bins, list slices, bint quiet) - cpdef object _update_pipelines(self, tuple task, list results, int slice_id) - cpdef object _finalise_pipelines(self) - cpdef object _initialise_statistics(self, list tasks) - cpdef object _update_statistics(self, uint64_t sample_ray_count) - cpdef object _finalise_statistics(self) - cpdef list _obtain_rays(self, tuple task, Ray template) - cpdef double _obtain_sensitivity(self, tuple task) @@ -91,7 +78,6 @@ cdef class Observer0D(_ObserverBase): int _samples_per_task cpdef list _generate_rays(self, Ray template, int ray_count) - cpdef double _pixel_sensitivity(self) @@ -104,7 +90,6 @@ cdef class Observer1D(_ObserverBase): int _pixel_samples cpdef list _generate_rays(self, int pixel, Ray template, int ray_count) - cpdef double _pixel_sensitivity(self, int pixel) @@ -117,7 +102,6 @@ cdef class Observer2D(_ObserverBase): int _pixel_samples cpdef list _generate_rays(self, int x, int y, Ray template, int ray_count) - cpdef double _pixel_sensitivity(self, int x, int y) diff --git a/raysect/optical/observer/base/pipeline.pxd b/raysect/optical/observer/base/pipeline.pxd index 4b0437f4..0b6be0c2 100644 --- a/raysect/optical/observer/base/pipeline.pxd +++ b/raysect/optical/observer/base/pipeline.pxd @@ -35,32 +35,23 @@ from raysect.optical.observer.base.processor cimport PixelProcessor cdef class Pipeline0D: cpdef object initialise(self, double min_wavelength, double max_wavelength, int spectral_bins, list spectral_slices, bint quiet) - cpdef PixelProcessor pixel_processor(self, int slice_id) - cpdef object update(self, int slice_id, tuple packed_result, int samples) - cpdef object finalise(self) cdef class Pipeline1D: cpdef object initialise(self, int pixels, int pixel_samples, double min_wavelength, double max_wavelength, int spectral_bins, list spectral_slices, bint quiet) - cpdef PixelProcessor pixel_processor(self, int pixel, int slice_id) - cpdef object update(self, int pixel, int slice_id, tuple packed_result) - cpdef object finalise(self) cdef class Pipeline2D: cpdef object initialise(self, tuple pixels, int pixel_samples, double min_wavelength, double max_wavelength, int spectral_bins, list spectral_slices, bint quiet) - cpdef PixelProcessor pixel_processor(self, int x, int y, int slice_id) - cpdef object update(self, int x, int y, int slice_id, tuple packed_result) - cpdef object finalise(self) diff --git a/raysect/optical/observer/base/processor.pxd b/raysect/optical/observer/base/processor.pxd index c99cd6b5..10dbc820 100644 --- a/raysect/optical/observer/base/processor.pxd +++ b/raysect/optical/observer/base/processor.pxd @@ -31,9 +31,9 @@ from raysect.optical cimport Spectrum + cdef class PixelProcessor: cpdef object add_sample(self, Spectrum spectrum, double sensitivity) - cpdef tuple pack_results(self) diff --git a/raysect/optical/observer/base/processor.pyx b/raysect/optical/observer/base/processor.pyx index 9d13ff0d..ecf98dd7 100644 --- a/raysect/optical/observer/base/processor.pyx +++ b/raysect/optical/observer/base/processor.pyx @@ -31,6 +31,7 @@ cimport cython + @cython.freelist(64) cdef class PixelProcessor: """ diff --git a/raysect/optical/observer/nonimaging/mesh_camera.pyx b/raysect/optical/observer/nonimaging/mesh_camera.pyx index 0967e6db..60b1af97 100644 --- a/raysect/optical/observer/nonimaging/mesh_camera.pyx +++ b/raysect/optical/observer/nonimaging/mesh_camera.pyx @@ -43,12 +43,16 @@ from raysect.optical.observer.sampler1d import FullFrameSampler1D from raysect.primitive.mesh.mesh cimport Mesh cimport cython -# convenience defines + +# constants cdef enum: + + # axis X = 0 Y = 1 Z = 2 + # vertex V1 = 0 V2 = 1 V3 = 2 diff --git a/raysect/optical/observer/nonimaging/mesh_pixel.pyx b/raysect/optical/observer/nonimaging/mesh_pixel.pyx index 3eb4a792..9ed81706 100644 --- a/raysect/optical/observer/nonimaging/mesh_pixel.pyx +++ b/raysect/optical/observer/nonimaging/mesh_pixel.pyx @@ -43,12 +43,16 @@ from raysect.optical.observer.pipeline.spectral import SpectralPowerPipeline0D from raysect.primitive.mesh.mesh cimport Mesh cimport cython -# convenience defines + +# constants cdef enum: + + # axis X = 0 Y = 1 Z = 2 + # vertex V1 = 0 V2 = 1 V3 = 2 diff --git a/raysect/optical/observer/pipeline/bayer.pxd b/raysect/optical/observer/pipeline/bayer.pxd index c31c915f..41d50d31 100644 --- a/raysect/optical/observer/pipeline/bayer.pxd +++ b/raysect/optical/observer/pipeline/bayer.pxd @@ -32,6 +32,7 @@ from raysect.optical.spectralfunction cimport SpectralFunction from raysect.optical.observer.base cimport PixelProcessor, Pipeline2D from raysect.core.math cimport StatsArray2D + cdef class BayerPipeline2D(Pipeline2D): cdef: @@ -56,17 +57,10 @@ cdef class BayerPipeline2D(Pipeline2D): bint _quiet cpdef object _start_display(self) - cpdef object _update_display(self, int x, int y) - cpdef object _refresh_display(self) - cpdef object _render_display(self, StatsArray2D frame, str status=*) - cpdef np.ndarray _generate_display_image(self, StatsArray2D frame) - cpdef double _calculate_white_point(self, np.ndarray image) - cpdef object display(self) - cpdef object save(self, str filename) diff --git a/raysect/optical/observer/pipeline/mono/power.pxd b/raysect/optical/observer/pipeline/mono/power.pxd index 12129362..347fee1a 100644 --- a/raysect/optical/observer/pipeline/mono/power.pxd +++ b/raysect/optical/observer/pipeline/mono/power.pxd @@ -83,19 +83,12 @@ cdef class PowerPipeline2D(Pipeline2D): bint _quiet cpdef object _start_display(self) - cpdef object _update_display(self, int x, int y) - cpdef object _refresh_display(self) - cpdef object _render_display(self, StatsArray2D frame, str status=*) - cpdef np.ndarray _generate_display_image(self, StatsArray2D frame) - cpdef double _calculate_white_point(self, np.ndarray image) - cpdef object display(self) - cpdef object save(self, str filename) diff --git a/raysect/optical/observer/pipeline/rgb.pxd b/raysect/optical/observer/pipeline/rgb.pxd index 8f40c6e0..35326988 100644 --- a/raysect/optical/observer/pipeline/rgb.pxd +++ b/raysect/optical/observer/pipeline/rgb.pxd @@ -54,21 +54,13 @@ cdef class RGBPipeline2D(Pipeline2D): bint _quiet cpdef object _start_display(self) - cpdef object _update_display(self, int x, int y) - cpdef object _refresh_display(self) - cpdef object _render_display(self, StatsArray3D frame, str status=*) - cpdef np.ndarray _generate_display_image(self, StatsArray3D frame) - cpdef double _calculate_sensitivity(self, np.ndarray image) - cpdef np.ndarray _generate_srgb_image(self, double[:,:,::1] xyz_image_mv) - cpdef object display(self) - cpdef object save(self, str filename) diff --git a/raysect/optical/scenegraph/world.pxd b/raysect/optical/scenegraph/world.pxd index be376b6b..0249c64d 100644 --- a/raysect/optical/scenegraph/world.pxd +++ b/raysect/optical/scenegraph/world.pxd @@ -42,27 +42,18 @@ cdef class ImportanceManager: list _spheres cdef object _process_primitives(self, list primitives) - cdef object _calculate_cdf(self) - cdef tuple _pick_sphere(self) - cpdef Vector3D sample(self, Point3D origin) - cpdef double pdf(self, Point3D origin, Vector3D direction) - cpdef bint has_primitives(self) cdef class World(CoreWorld): - cdef: - ImportanceManager _importance + cdef ImportanceManager _importance cpdef build_importance(self, bint force=*) - cpdef Vector3D important_direction_sample(self, Point3D origin) - cpdef double important_direction_pdf(self, Point3D origin, Vector3D direction) - cpdef bint has_important_primitives(self) diff --git a/raysect/optical/spectrum.pxd b/raysect/optical/spectrum.pxd index c59ce20f..7c465e56 100644 --- a/raysect/optical/spectrum.pxd +++ b/raysect/optical/spectrum.pxd @@ -32,6 +32,7 @@ from raysect.optical.spectralfunction cimport SpectralFunction from numpy cimport ndarray + cdef class Spectrum(SpectralFunction): cdef: diff --git a/raysect/primitive/box.pyx b/raysect/primitive/box.pyx index 6f3cbec8..3f585b1b 100644 --- a/raysect/primitive/box.pyx +++ b/raysect/primitive/box.pyx @@ -40,6 +40,7 @@ cdef const double BOX_PADDING = 1e-9 cdef const double EPSILON = 1e-9 cdef enum: + # slab face enumeration NO_FACE = -1 LOWER_FACE = 0 diff --git a/raysect/primitive/cylinder.pyx b/raysect/primitive/cylinder.pyx index 07454b9c..f7ddd8d7 100644 --- a/raysect/primitive/cylinder.pyx +++ b/raysect/primitive/cylinder.pyx @@ -41,6 +41,7 @@ cdef const double BOX_PADDING = 1e-9 cdef const double EPSILON = 1e-9 cdef enum: + # object type enumeration NO_TYPE = -1 CYLINDER = 0 diff --git a/raysect/primitive/mesh/mesh.pxd b/raysect/primitive/mesh/mesh.pxd index 8c209581..7deb725a 100644 --- a/raysect/primitive/mesh/mesh.pxd +++ b/raysect/primitive/mesh/mesh.pxd @@ -60,37 +60,21 @@ cdef class MeshData(KDTree3DCore): int32_t _i cpdef Point3D vertex(self, int index) - cpdef ndarray triangle(self, int index) - cpdef Normal3D vertex_normal(self, int index) - cpdef Normal3D face_normal(self, int index) - cdef object _filter_triangles(self) - cdef object _flip_normals(self) - cdef object _generate_face_normals(self) - cdef BoundingBox3D _generate_bounding_box(self, int32_t i) - cdef void _calc_rayspace_transform(self, Ray ray) - cdef bint _hit_triangle(self, int32_t i, Ray ray, float[4] hit_data) - cpdef Intersection calc_intersection(self, Ray ray) - cdef Normal3D _intersection_normal(self) - cpdef bint contains(self, Point3D p) - cpdef BoundingBox3D bounding_box(self, AffineMatrix3D to_world) - cdef uint8_t _read_uint8(self, object file) - cdef bint _read_bool(self, object file) - cdef double _read_float(self, object file) diff --git a/raysect/primitive/mesh/mesh.pyx b/raysect/primitive/mesh/mesh.pyx index 6f7d117b..048e21c2 100644 --- a/raysect/primitive/mesh/mesh.pyx +++ b/raysect/primitive/mesh/mesh.pyx @@ -51,20 +51,26 @@ cdef const double BOX_PADDING = 1e-6 # additional ray distance to avoid re-hitting the same surface point cdef const double EPSILON = 1e-6 -# convenience defines +# constants cdef enum: + + # axis index X = 0 Y = 1 Z = 2 + # intersection data U = 0 V = 1 W = 2 T = 3 + # vertex index V1 = 0 V2 = 1 V3 = 2 + + # normal index N1 = 3 N2 = 4 N3 = 5 diff --git a/raysect/primitive/utility.pxd b/raysect/primitive/utility.pxd index 947645ac..cce6cc05 100644 --- a/raysect/primitive/utility.pxd +++ b/raysect/primitive/utility.pxd @@ -38,9 +38,6 @@ cdef class EncapsulatedPrimitive(Primitive): Primitive _primitive cpdef Intersection hit(self, Ray ray) - cpdef Intersection next_intersection(self) - cpdef bint contains(self, Point3D p) except -1 - cpdef BoundingBox3D bounding_box(self) From e6a3c029a58a0f38db35dd03fd9f2e32a04ad0d1 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sun, 20 Jul 2025 20:28:06 +0100 Subject: [PATCH 58/77] Add python 3.13 to the test matrix. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1084f7e..af832884 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: fail-fast: false matrix: numpy-version: ["numpy"] - python-version: ["3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - name: Checkout code uses: actions/checkout@v4 From 3bc16d210fa8f992fbaffeff2a987eb45013d1c3 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sun, 20 Jul 2025 20:34:13 +0100 Subject: [PATCH 59/77] Removed random text that was accidentally added to .gitignore. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c38bf609..590e1871 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ __pycache__/ *$py.class # C extensions -*.son option to enable profiling +*.so *.c # Distribution / packaging .Python From ce4bff9cea6b00a7c81f5ea7e90a1796d64b3442 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Sun, 20 Jul 2025 20:53:31 +0100 Subject: [PATCH 60/77] Renamed all instances of the word "targetted" with "targeted" to fix a longstanding typo. --- CHANGELOG.txt | 3 ++ ...ed_sampler.py => plot_targeted_sampler.py} | 6 +-- demos/observers/cornell_box_cooke_triplet.py | 8 +-- demos/observers/cornell_box_real_pinhole.py | 4 +- demos/observers/metal_with_lens.py | 4 +- .../{targetted_pixel.py => targeted_pixel.py} | 12 ++--- demos/optics/etendue_of_pinhole.py | 10 ++-- meson.build | 4 +- raysect/core/math/sampler/__init__.pxd | 2 +- raysect/core/math/sampler/__init__.py | 2 +- raysect/core/math/sampler/meson.build | 4 +- .../sampler/{targetted.pxd => targeted.pxd} | 6 +-- .../sampler/{targetted.pyx => targeted.pyx} | 14 ++--- raysect/optical/observer/imaging/__init__.pxd | 2 +- raysect/optical/observer/imaging/__init__.py | 2 +- raysect/optical/observer/imaging/meson.build | 4 +- .../{targetted_ccd.pxd => targeted_ccd.pxd} | 8 +-- .../{targetted_ccd.pyx => targeted_ccd.pyx} | 42 +++++++-------- .../optical/observer/nonimaging/__init__.pxd | 2 +- .../optical/observer/nonimaging/__init__.py | 2 +- .../optical/observer/nonimaging/meson.build | 4 +- ...targetted_pixel.pxd => targeted_pixel.pxd} | 8 +-- ...targetted_pixel.pyx => targeted_pixel.pyx} | 52 +++++++++---------- 23 files changed, 104 insertions(+), 101 deletions(-) rename demos/maths/{plot_targetted_sampler.py => plot_targeted_sampler.py} (86%) rename demos/observers/{targetted_pixel.py => targeted_pixel.py} (76%) rename raysect/core/math/sampler/{targetted.pxd => targeted.pxd} (94%) rename raysect/core/math/sampler/{targetted.pyx => targeted.pyx} (97%) rename raysect/optical/observer/imaging/{targetted_ccd.pxd => targeted_ccd.pxd} (91%) rename raysect/optical/observer/imaging/{targetted_ccd.pyx => targeted_ccd.pyx} (87%) rename raysect/optical/observer/nonimaging/{targetted_pixel.pxd => targeted_pixel.pxd} (92%) rename raysect/optical/observer/nonimaging/{targetted_pixel.pyx => targeted_pixel.pyx} (86%) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 7dd3faf6..afb6b336 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -13,6 +13,9 @@ Build changes: - Meson-python performs the build out of the project folder, so the project source folders will no longer be polluted with build artefacts. - Cython build annotations are now always enabled, the annotation files can be found in the build folder under the build artefacts folder associated with each so file (*.so.p folder). +API changes: +* Corrected spelling of all classes, methods and functions where "targeted" was incorrectly spelt "targetted". + Release 0.8.1 (12 Feb 2023) --------------------------- diff --git a/demos/maths/plot_targetted_sampler.py b/demos/maths/plot_targeted_sampler.py similarity index 86% rename from demos/maths/plot_targetted_sampler.py rename to demos/maths/plot_targeted_sampler.py index 0978a647..d46f2a7b 100644 --- a/demos/maths/plot_targetted_sampler.py +++ b/demos/maths/plot_targeted_sampler.py @@ -2,7 +2,7 @@ import matplotlib.pyplot as plt from math import atan2, sqrt, degrees -from raysect.core.math import Point3D, TargettedHemisphereSampler, TargettedSphereSampler +from raysect.core.math import Point3D, TargetedHemisphereSampler, TargetedSphereSampler def display_samples(samples, title): @@ -34,8 +34,8 @@ def display_samples(samples, title): observation_point = Point3D(0, 0, 0) # generate samplers -hemisphere = TargettedHemisphereSampler(targets) -sphere = TargettedSphereSampler(targets) +hemisphere = TargetedHemisphereSampler(targets) +sphere = TargetedSphereSampler(targets) # sample for origin point and point at (0, 0, -10) h_samples = hemisphere(observation_point, samples=samples) diff --git a/demos/observers/cornell_box_cooke_triplet.py b/demos/observers/cornell_box_cooke_triplet.py index 2f7ee055..653632f2 100644 --- a/demos/observers/cornell_box_cooke_triplet.py +++ b/demos/observers/cornell_box_cooke_triplet.py @@ -4,7 +4,7 @@ from raysect.optical import World, Node, translate, rotate, Point3D from raysect.optical.material import Lambert, UniformSurfaceEmitter, AbsorbingSurface, Checkerboard from raysect.optical.library import * -from raysect.optical.observer import TargettedCCDArray +from raysect.optical.observer import TargetedCCDArray from raysect.optical.observer import RGBPipeline2D, BayerPipeline2D, PowerPipeline2D from raysect.optical.observer import RGBAdaptiveSampler2D from raysect.core.math import mm @@ -184,9 +184,9 @@ rgb = RGBPipeline2D(display_unsaturated_fraction=0.96, name="sRGB") sampler = RGBAdaptiveSampler2D(rgb, ratio=10, fraction=0.2, min_samples=1000, cutoff=0.01) -# CCD targetting all rays at last lens element for speed -ccd = TargettedCCDArray( - targetted_path_prob=1.0, targets=[l3], +# CCD targeting all rays at last lens element for speed +ccd = TargetedCCDArray( + targeted_path_prob=1.0, targets=[l3], width=mm(35), pixels=(512, 512), parent=image_plane, transform=translate(0, 0, 0)*rotate(0, 0, 180), pipelines=[rgb] diff --git a/demos/observers/cornell_box_real_pinhole.py b/demos/observers/cornell_box_real_pinhole.py index 64e43e38..d456cc13 100644 --- a/demos/observers/cornell_box_real_pinhole.py +++ b/demos/observers/cornell_box_real_pinhole.py @@ -5,7 +5,7 @@ from raysect.optical import World, Node, translate, rotate, Point3D from raysect.optical.material import Lambert, UniformSurfaceEmitter, NullMaterial from raysect.optical.library import * -from raysect.optical.observer import RGBPipeline2D, BayerPipeline2D, PowerPipeline2D, TargettedCCDArray +from raysect.optical.observer import RGBPipeline2D, BayerPipeline2D, PowerPipeline2D, TargetedCCDArray from raysect.optical.observer import RGBAdaptiveSampler2D @@ -144,7 +144,7 @@ camera = Node(parent=world, transform=translate(0, 0, -3.3)) pinhole = Sphere(0.0005, camera, transform=translate(0, 0, 0), material=NullMaterial()) -film = TargettedCCDArray(targetted_path_prob=1.0, targets=[pinhole], width=0.1, pixels=(512, 512), parent=camera, +film = TargetedCCDArray(targeted_path_prob=1.0, targets=[pinhole], width=0.1, pixels=(512, 512), parent=camera, transform=translate(0, 0, -0.1207), pipelines=pipelines) film.frame_sampler = sampler film.pixel_samples = 250 diff --git a/demos/observers/metal_with_lens.py b/demos/observers/metal_with_lens.py index e1bbaa8c..a5bcc6ce 100644 --- a/demos/observers/metal_with_lens.py +++ b/demos/observers/metal_with_lens.py @@ -7,7 +7,7 @@ from raysect.optical.library.metal import Gold, Silver, Copper, Titanium, Aluminium, Beryllium from raysect.optical.material import Lambert, UniformSurfaceEmitter, AbsorbingSurface, NullMaterial from raysect.optical.library import schott -from raysect.optical.observer import RGBPipeline2D, BayerPipeline2D, TargettedCCDArray, CCDArray, RGBAdaptiveSampler2D +from raysect.optical.observer import RGBPipeline2D, BayerPipeline2D, TargetedCCDArray, CCDArray, RGBAdaptiveSampler2D from raysect.optical.colour import ciexyz_x, ciexyz_y, ciexyz_z @@ -49,7 +49,7 @@ pipelines = [rgb, bayer] # ccd = CCDArray(parent=camera, pipelines=pipelines) -ccd = TargettedCCDArray(targets=[aperture], parent=camera, pipelines=pipelines) +ccd = TargetedCCDArray(targets=[aperture], parent=camera, pipelines=pipelines) ccd.frame_sampler = sampler ccd.pixels = (180*2, 120*2) # (360, 240) ccd.pixel_samples = 250 diff --git a/demos/observers/targetted_pixel.py b/demos/observers/targeted_pixel.py similarity index 76% rename from demos/observers/targetted_pixel.py rename to demos/observers/targeted_pixel.py index ff272219..8ca58ed1 100644 --- a/demos/observers/targetted_pixel.py +++ b/demos/observers/targeted_pixel.py @@ -3,7 +3,7 @@ from raysect.primitive import Box from raysect.optical import World, translate, Point3D, Node -from raysect.optical.observer import Pixel, TargettedPixel, PowerPipeline0D +from raysect.optical.observer import Pixel, TargetedPixel, PowerPipeline0D from raysect.optical.material import UnitySurfaceEmitter @@ -28,16 +28,16 @@ basic_pipeline = PowerPipeline0D(name="Basic Pixel Observer") basic_pixel = Pixel(parent=world, pixel_samples=SAMPLES, pipelines=[basic_pipeline]) -# setup targetted pixel -targetted_pipeline = PowerPipeline0D(name="Targeted Pixel Observer") -targetted_pixel = TargettedPixel(parent=world, targets=targets, pixel_samples=SAMPLES, pipelines=[targetted_pipeline]) -targetted_pixel.targetted_path_prob = 1 +# setup targeted pixel +targeted_pipeline = PowerPipeline0D(name="Targeted Pixel Observer") +targeted_pixel = TargetedPixel(parent=world, targets=targets, pixel_samples=SAMPLES, pipelines=[targeted_pipeline]) +targeted_pixel.targeted_path_prob = 1 # render ion() basic_pixel.observe() print() -targetted_pixel.observe() +targeted_pixel.observe() ioff() show() diff --git a/demos/optics/etendue_of_pinhole.py b/demos/optics/etendue_of_pinhole.py index 38a85b9b..e1daac54 100644 --- a/demos/optics/etendue_of_pinhole.py +++ b/demos/optics/etendue_of_pinhole.py @@ -3,7 +3,7 @@ import matplotlib.pyplot as plt from raysect.core import Point3D, Vector3D, rotate_basis, translate, Ray as CoreRay -from raysect.core.math.sampler import DiskSampler3D, RectangleSampler3D, TargettedHemisphereSampler +from raysect.core.math.sampler import DiskSampler3D, RectangleSampler3D, TargetedHemisphereSampler from raysect.optical import World from raysect.primitive import Box, Cylinder, Subtract from raysect.optical.material import AbsorbingSurface, NullMaterial @@ -35,8 +35,8 @@ def raytraced_etendue(distance, detector_radius=0.001, ray_count=100000, batches sphere = target.bounding_sphere() spheres = [(sphere.centre.transform(detector_transform), sphere.radius, 1.0)] - # instance targetted pixel sampler - targetted_sampler = TargettedHemisphereSampler(spheres) + # instance targeted pixel sampler + targeted_sampler = TargetedHemisphereSampler(spheres) point_sampler = DiskSampler3D(detector_radius) @@ -53,8 +53,8 @@ def raytraced_etendue(distance, detector_radius=0.001, ray_count=100000, batches passed = 0.0 for origin in origins: - # obtain targetted vector sample - direction, pdf = targetted_sampler(origin, pdf=True) + # obtain targeted vector sample + direction, pdf = targeted_sampler(origin, pdf=True) path_weight = R_2_PI * direction.z/pdf origin = origin.transform(detector_transform) diff --git a/meson.build b/meson.build index 77af6bfb..3a2b2cf0 100644 --- a/meson.build +++ b/meson.build @@ -7,8 +7,8 @@ py = import('python').find_installation(pure: false) numpy = dependency('numpy', method: 'config-tool') fs = import('fs') -# disabling explicit noexcept until pycharm fixes their cython 3 support -cython_args = ['--annotate', '-Xlegacy_implicit_noexcept=True'] +# disabling explicit noexcept until pycharm fixes their cython 3 support (causes a large number of build warnings) +cython_args = ['--annotate', '-X legacy_implicit_noexcept=True'] cython_dependencies = [numpy] subdir('raysect') diff --git a/raysect/core/math/sampler/__init__.pxd b/raysect/core/math/sampler/__init__.pxd index 8931c646..98840aff 100644 --- a/raysect/core/math/sampler/__init__.pxd +++ b/raysect/core/math/sampler/__init__.pxd @@ -31,5 +31,5 @@ from raysect.core.math.sampler.solidangle cimport * from raysect.core.math.sampler.surface3d cimport * -from raysect.core.math.sampler.targetted cimport * +from raysect.core.math.sampler.targeted cimport * diff --git a/raysect/core/math/sampler/__init__.py b/raysect/core/math/sampler/__init__.py index 0181770d..39b18ae2 100644 --- a/raysect/core/math/sampler/__init__.py +++ b/raysect/core/math/sampler/__init__.py @@ -31,4 +31,4 @@ from .solidangle import * from .surface3d import * -from .targetted import * +from .targeted import * diff --git a/raysect/core/math/sampler/meson.build b/raysect/core/math/sampler/meson.build index 06378d7f..1e980d02 100644 --- a/raysect/core/math/sampler/meson.build +++ b/raysect/core/math/sampler/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/math/sampler' # source files py_files = ['__init__.py'] -pyx_files = ['solidangle.pyx', 'targetted.pyx', 'surface3d.pyx'] -pxd_files = ['targetted.pxd', 'surface3d.pxd', 'solidangle.pxd', '__init__.pxd'] +pyx_files = ['targeted.pyx', 'solidangle.pyx', 'surface3d.pyx'] +pxd_files = ['targeted.pxd', 'surface3d.pxd', 'solidangle.pxd', '__init__.pxd'] data_files = [] # compile cython diff --git a/raysect/core/math/sampler/targetted.pxd b/raysect/core/math/sampler/targeted.pxd similarity index 94% rename from raysect/core/math/sampler/targetted.pxd rename to raysect/core/math/sampler/targeted.pxd index 69104c46..b5633b4d 100644 --- a/raysect/core/math/sampler/targetted.pxd +++ b/raysect/core/math/sampler/targeted.pxd @@ -33,7 +33,7 @@ cimport numpy as np from raysect.core.math cimport Point3D, Vector3D -cdef class _TargettedSampler: +cdef class _TargetedSampler: cdef: double _total_weight @@ -51,8 +51,8 @@ cdef class _TargettedSampler: cdef tuple _pick_sphere(self) -cdef class TargettedHemisphereSampler(_TargettedSampler): +cdef class TargetedHemisphereSampler(_TargetedSampler): pass -cdef class TargettedSphereSampler(_TargettedSampler): +cdef class TargetedSphereSampler(_TargetedSampler): pass \ No newline at end of file diff --git a/raysect/core/math/sampler/targetted.pyx b/raysect/core/math/sampler/targeted.pyx similarity index 97% rename from raysect/core/math/sampler/targetted.pyx rename to raysect/core/math/sampler/targeted.pyx index a94c5734..9dffa327 100644 --- a/raysect/core/math/sampler/targetted.pyx +++ b/raysect/core/math/sampler/targeted.pyx @@ -38,7 +38,7 @@ from libc.math cimport M_PI, M_1_PI, asin, sqrt cimport cython -cdef class _TargettedSampler: +cdef class _TargetedSampler: def __init__(self, object targets): """ @@ -46,7 +46,7 @@ cdef class _TargettedSampler: Each target tuple consists of (Point3D sphere_centre, double sphere_radius, double weight). - :param list targets: A list of tuples describing spheres for targetted sampling. + :param list targets: A list of tuples describing spheres for targeted sampling. """ self._targets = tuple(targets) @@ -248,16 +248,16 @@ cdef class _TargettedSampler: return self._targets[index] -cdef class TargettedHemisphereSampler(_TargettedSampler): +cdef class TargetedHemisphereSampler(_TargetedSampler): """ - Generates vectors on a hemisphere targetting a set of target spheres. + Generates vectors on a hemisphere targeting a set of target spheres. This sampler takes a list of spheres and corresponding weighting factors. To generate a sample a sphere is randomly selected according the distribution of the sphere weights and launches a ray at the solid angle subtended by the target sphere. - If the targetted sphere intersects with the hemisphere's base plane, lies + If the targeted sphere intersects with the hemisphere's base plane, lies behind the plane, or the origin point from which samples are generated lies inside the target sphere, a sample is randomly selected from the full hemisphere using a cosine weighted distribution. @@ -389,9 +389,9 @@ cdef class TargettedHemisphereSampler(_TargettedSampler): return sample.transform(rotation) -cdef class TargettedSphereSampler(_TargettedSampler): +cdef class TargetedSphereSampler(_TargetedSampler): """ - Generates vectors targetting a set of target spheres. + Generates vectors targeting a set of target spheres. This sampler takes a list of spheres and corresponding weighting factors. To generate a sample a sphere is randomly selected according the diff --git a/raysect/optical/observer/imaging/__init__.pxd b/raysect/optical/observer/imaging/__init__.pxd index 2a62327d..946850b4 100644 --- a/raysect/optical/observer/imaging/__init__.pxd +++ b/raysect/optical/observer/imaging/__init__.pxd @@ -30,7 +30,7 @@ # POSSIBILITY OF SUCH DAMAGE. from raysect.optical.observer.imaging.ccd cimport CCDArray -from raysect.optical.observer.imaging.targetted_ccd cimport TargettedCCDArray +from raysect.optical.observer.imaging.targeted_ccd cimport TargetedCCDArray from raysect.optical.observer.imaging.orthographic cimport OrthographicCamera from raysect.optical.observer.imaging.pinhole cimport PinholeCamera from raysect.optical.observer.imaging.vector cimport VectorCamera diff --git a/raysect/optical/observer/imaging/__init__.py b/raysect/optical/observer/imaging/__init__.py index b3dfd66e..0028eedc 100644 --- a/raysect/optical/observer/imaging/__init__.py +++ b/raysect/optical/observer/imaging/__init__.py @@ -30,7 +30,7 @@ # POSSIBILITY OF SUCH DAMAGE. from .ccd import CCDArray -from .targetted_ccd import TargettedCCDArray +from .targeted_ccd import TargetedCCDArray from .orthographic import OrthographicCamera from .pinhole import PinholeCamera from .vector import VectorCamera diff --git a/raysect/optical/observer/imaging/meson.build b/raysect/optical/observer/imaging/meson.build index 200bb394..2a157322 100644 --- a/raysect/optical/observer/imaging/meson.build +++ b/raysect/optical/observer/imaging/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/optical/observer/imaging' # source files py_files = ['__init__.py'] -pyx_files = ['targetted_ccd.pyx', 'ccd.pyx', 'vector.pyx', 'pinhole.pyx', 'opencv.pyx', 'orthographic.pyx'] -pxd_files = ['targetted_ccd.pxd', 'ccd.pxd', 'orthographic.pxd', 'vector.pxd', 'pinhole.pxd', 'opencv.pxd', '__init__.pxd'] +pyx_files = ['ccd.pyx', 'vector.pyx', 'targeted_ccd.pyx', 'pinhole.pyx', 'opencv.pyx', 'orthographic.pyx'] +pxd_files = ['targeted_ccd.pxd', 'ccd.pxd', 'orthographic.pxd', 'vector.pxd', 'pinhole.pxd', 'opencv.pxd', '__init__.pxd'] data_files = [] # compile cython diff --git a/raysect/optical/observer/imaging/targetted_ccd.pxd b/raysect/optical/observer/imaging/targeted_ccd.pxd similarity index 91% rename from raysect/optical/observer/imaging/targetted_ccd.pxd rename to raysect/optical/observer/imaging/targeted_ccd.pxd index f8af58b1..4e7fb777 100644 --- a/raysect/optical/observer/imaging/targetted_ccd.pxd +++ b/raysect/optical/observer/imaging/targeted_ccd.pxd @@ -29,17 +29,17 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from raysect.core.math.sampler cimport RectangleSampler3D, HemisphereCosineSampler, TargettedHemisphereSampler +from raysect.core.math.sampler cimport RectangleSampler3D, HemisphereCosineSampler, TargetedHemisphereSampler from raysect.optical.observer.base cimport Observer2D -cdef class TargettedCCDArray(Observer2D): +cdef class TargetedCCDArray(Observer2D): cdef: - double _width, _pixel_area, _image_delta, _image_start_x, _image_start_y, _targetted_path_prob + double _width, _pixel_area, _image_delta, _image_start_x, _image_start_y, _targeted_path_prob tuple _targets RectangleSampler3D _point_sampler HemisphereCosineSampler _cosine_sampler - TargettedHemisphereSampler _targetted_sampler + TargetedHemisphereSampler _targeted_sampler cdef object _update_image_geometry(self) diff --git a/raysect/optical/observer/imaging/targetted_ccd.pyx b/raysect/optical/observer/imaging/targeted_ccd.pyx similarity index 87% rename from raysect/optical/observer/imaging/targetted_ccd.pyx rename to raysect/optical/observer/imaging/targeted_ccd.pyx index 0fdad56b..5daa7468 100644 --- a/raysect/optical/observer/imaging/targetted_ccd.pyx +++ b/raysect/optical/observer/imaging/targeted_ccd.pyx @@ -33,7 +33,7 @@ from raysect.core.math.random cimport probability from raysect.optical.observer.sampler2d import FullFrameSampler2D from raysect.optical.observer.pipeline import RGBPipeline2D -from raysect.core.math.sampler cimport RectangleSampler3D, HemisphereCosineSampler, TargettedHemisphereSampler +from raysect.core.math.sampler cimport RectangleSampler3D, HemisphereCosineSampler, TargetedHemisphereSampler from raysect.optical cimport Primitive, BoundingSphere3D, Ray, AffineMatrix3D, Point3D, Vector3D, translate from libc.math cimport M_PI from raysect.optical.observer.base cimport Observer2D @@ -43,24 +43,24 @@ cimport cython cdef const double R_2_PI = 0.15915494309189535 # 1 / (2 * pi) -cdef class TargettedCCDArray(Observer2D): +cdef class TargetedCCDArray(Observer2D): """ An ideal CCD-like imaging sensor that preferentially targets a given list of primitives. - The targetted CCD is a regular array of square pixels. Each pixel samples red, green + The targeted CCD is a regular array of square pixels. Each pixel samples red, green and blue channels (behaves like a Foveon imaging sensor). The CCD sensor width is specified with the width parameter. The CCD height is calculated from the width and the number of vertical and horizontal pixels. The default width and sensor ratio approximates a 35mm camera sensor. - The targetted CCD takes a list of target primitives. Each pixel will target the + The targeted CCD takes a list of target primitives. Each pixel will target the bounding spheres that encompass each target primitive. Therefore, for best performance, the target primitives should be split up such that their surfaces are closely wrapped by the bounding sphere. The sampling algorithm fires a proportion of rays at the targets, and a portion sampled from the full hemisphere. The proportion that is fired towards the targets is controlled - with the targetted_path_prob attribute. By default this attribute is set to 0.9, i.e. + with the targeted_path_prob attribute. By default this attribute is set to 0.9, i.e. 90% of the rays are fired towards the targets. .. Warning.. @@ -68,7 +68,7 @@ cdef class TargettedCCDArray(Observer2D): targets. The user must ensure there are no sources of radiance outside of the targeted directions, otherwise they will not be sampled and the result will be biased. - :param list targets: The list of primitives for targetted sampling. + :param list targets: The list of primitives for targeted sampling. :param tuple pixels: A tuple of pixel dimensions for the camera (default=(720, 480)). :param float width: The CCD sensor x-width in metres (default=35mm). :param list pipelines: The list of pipelines that will process the spectrum measured @@ -76,7 +76,7 @@ cdef class TargettedCCDArray(Observer2D): :param kwargs: **kwargs and properties from Observer2D and _ObserverBase. """ - def __init__(self, targets, pixels=(720, 480), width=0.035, targetted_path_prob=None, parent=None, transform=None, name=None, pipelines=None): + def __init__(self, targets, pixels=(720, 480), width=0.035, targeted_path_prob=None, parent=None, transform=None, name=None, pipelines=None): # initial values to prevent undefined behaviour when setting via self.width self._width = 0.035 @@ -88,12 +88,12 @@ cdef class TargettedCCDArray(Observer2D): parent=parent, transform=transform, name=name) self._cosine_sampler = HemisphereCosineSampler() - self._targetted_sampler = None + self._targeted_sampler = None # setting width triggers calculation of image geometry calculations self.width = width self.targets = targets - self.targetted_path_prob = targetted_path_prob or 0.9 + self.targeted_path_prob = targeted_path_prob or 0.9 @property def pixels(self): @@ -158,7 +158,7 @@ cdef class TargettedCCDArray(Observer2D): self._targets = value @property - def targetted_path_prob(self): + def targeted_path_prob(self): """ The probability that an individual sample will be fired at a target instead of a sample from the whole hemisphere. @@ -169,14 +169,14 @@ cdef class TargettedCCDArray(Observer2D): :rtype: float """ - return self._targetted_path_prob + return self._targeted_path_prob - @targetted_path_prob.setter - def targetted_path_prob(self, double value): + @targeted_path_prob.setter + def targeted_path_prob(self, double value): if value < 0 or value > 1: raise ValueError("Targeted path probability must lie in the range [0, 1].") - self._targetted_path_prob = value + self._targeted_path_prob = value cdef object _update_image_geometry(self): @@ -212,8 +212,8 @@ cdef class TargettedCCDArray(Observer2D): sphere = target.bounding_sphere() spheres.append((sphere.centre.transform(self.to_local()), sphere.radius, 1.0)) - # instance targetted pixel sampler - self._targetted_sampler = TargettedHemisphereSampler(spheres) + # instance targeted pixel sampler + self._targeted_sampler = TargetedHemisphereSampler(spheres) # generate pixel transform pixel_x = self._image_start_x - self._image_delta * (ix + 0.5) @@ -230,17 +230,17 @@ cdef class TargettedCCDArray(Observer2D): # transform to local space from pixel space origin = origin.transform(pixel_to_local) - if probability(self._targetted_path_prob): - # obtain targetted vector sample - direction = self._targetted_sampler.sample(origin) + if probability(self._targeted_path_prob): + # obtain targeted vector sample + direction = self._targeted_sampler.sample(origin) else: # obtain cosine weighted hemisphere sample direction = self._cosine_sampler.sample() # calculate combined pdf and ray weight - pdf = self._targetted_path_prob * self._targetted_sampler.pdf(origin, direction) + \ - (1-self._targetted_path_prob) * self._cosine_sampler.pdf(direction) + pdf = self._targeted_path_prob * self._targeted_sampler.pdf(origin, direction) + \ + (1-self._targeted_path_prob) * self._cosine_sampler.pdf(direction) if pdf <= 0: raise ValueError('Ray direction probability is zero. The target object extends beyond the pixel horizon.') diff --git a/raysect/optical/observer/nonimaging/__init__.pxd b/raysect/optical/observer/nonimaging/__init__.pxd index 4e2f420a..f550d902 100644 --- a/raysect/optical/observer/nonimaging/__init__.pxd +++ b/raysect/optical/observer/nonimaging/__init__.pxd @@ -32,7 +32,7 @@ from raysect.optical.observer.nonimaging.sightline import SightLine from raysect.optical.observer.nonimaging.fibreoptic import FibreOptic from raysect.optical.observer.nonimaging.pixel import Pixel -from raysect.optical.observer.nonimaging.targetted_pixel import TargettedPixel +from raysect.optical.observer.nonimaging.targeted_pixel import TargetedPixel from raysect.optical.observer.nonimaging.mesh_pixel import MeshPixel from raysect.optical.observer.nonimaging.mesh_camera import MeshCamera diff --git a/raysect/optical/observer/nonimaging/__init__.py b/raysect/optical/observer/nonimaging/__init__.py index d1a8ee26..2b04ab5f 100644 --- a/raysect/optical/observer/nonimaging/__init__.py +++ b/raysect/optical/observer/nonimaging/__init__.py @@ -32,7 +32,7 @@ from .sightline import SightLine from .fibreoptic import FibreOptic from .pixel import Pixel -from .targetted_pixel import TargettedPixel +from .targeted_pixel import TargetedPixel from .mesh_pixel import MeshPixel from .mesh_camera import MeshCamera diff --git a/raysect/optical/observer/nonimaging/meson.build b/raysect/optical/observer/nonimaging/meson.build index 6cea95cf..9ae92de4 100644 --- a/raysect/optical/observer/nonimaging/meson.build +++ b/raysect/optical/observer/nonimaging/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/optical/observer/nonimaging' # source files py_files = ['__init__.py'] -pyx_files = ['pixel.pyx', 'mesh_camera.pyx', 'fibreoptic.pyx', 'mesh_pixel.pyx', 'sightline.pyx', 'targetted_pixel.pyx'] -pxd_files = ['pixel.pxd', 'mesh_camera.pxd', 'sightline.pxd', 'mesh_pixel.pxd', 'fibreoptic.pxd', '__init__.pxd', 'targetted_pixel.pxd'] +pyx_files = ['pixel.pyx', 'mesh_camera.pyx', 'fibreoptic.pyx', 'mesh_pixel.pyx', 'sightline.pyx', 'targeted_pixel.pyx'] +pxd_files = ['pixel.pxd', 'mesh_camera.pxd', 'sightline.pxd', 'targeted_pixel.pxd', 'mesh_pixel.pxd', 'fibreoptic.pxd', '__init__.pxd'] data_files = [] # compile cython diff --git a/raysect/optical/observer/nonimaging/targetted_pixel.pxd b/raysect/optical/observer/nonimaging/targeted_pixel.pxd similarity index 92% rename from raysect/optical/observer/nonimaging/targetted_pixel.pxd rename to raysect/optical/observer/nonimaging/targeted_pixel.pxd index dd9b7682..378a6a12 100644 --- a/raysect/optical/observer/nonimaging/targetted_pixel.pxd +++ b/raysect/optical/observer/nonimaging/targeted_pixel.pxd @@ -29,15 +29,15 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from raysect.core.math.sampler cimport RectangleSampler3D, HemisphereCosineSampler, TargettedHemisphereSampler +from raysect.core.math.sampler cimport RectangleSampler3D, HemisphereCosineSampler, TargetedHemisphereSampler from raysect.optical.observer.base cimport Observer0D -cdef class TargettedPixel(Observer0D): +cdef class TargetedPixel(Observer0D): cdef: - double _x_width, _y_width, _solid_angle, _collection_area, _targetted_path_prob + double _x_width, _y_width, _solid_angle, _collection_area, _targeted_path_prob tuple _targets RectangleSampler3D _point_sampler HemisphereCosineSampler _cosine_sampler - TargettedHemisphereSampler _targetted_sampler + TargetedHemisphereSampler _targeted_sampler diff --git a/raysect/optical/observer/nonimaging/targetted_pixel.pyx b/raysect/optical/observer/nonimaging/targeted_pixel.pyx similarity index 86% rename from raysect/optical/observer/nonimaging/targetted_pixel.pyx rename to raysect/optical/observer/nonimaging/targeted_pixel.pyx index 4d4cdb07..4a8d2c07 100644 --- a/raysect/optical/observer/nonimaging/targetted_pixel.pyx +++ b/raysect/optical/observer/nonimaging/targeted_pixel.pyx @@ -32,7 +32,7 @@ from libc.math cimport M_PI as PI from raysect.core.math.random cimport probability -from raysect.core.math.sampler cimport RectangleSampler3D, HemisphereCosineSampler, TargettedHemisphereSampler +from raysect.core.math.sampler cimport RectangleSampler3D, HemisphereCosineSampler, TargetedHemisphereSampler from raysect.optical cimport Ray, Primitive, Point3D, Vector3D, BoundingSphere3D from raysect.optical.observer.base cimport Observer0D from raysect.optical.observer.pipeline.spectral import SpectralPowerPipeline0D @@ -42,18 +42,18 @@ cimport cython cdef const double R_2_PI = 0.15915494309189535 # 1 / (2 * pi) -cdef class TargettedPixel(Observer0D): +cdef class TargetedPixel(Observer0D): """ A pixel observer that preferentially targets rays towards a given list of primitives. - The targetted pixel takes a list of target primitives. The observer targets the + The targeted pixel takes a list of target primitives. The observer targets the bounding sphere that encompasses a target primitive. Therefore, for best performance, the target primitives should be split up such that their surfaces are closely wrapped by the bounding sphere. The sampling algorithm fires a proportion of rays at the targets, and a portion sampled from the full hemisphere. The proportion that is fired towards the targets is controlled - with the targetted_path_prob attribute. By default this attribute is set to 0.9, i.e. + with the targeted_path_prob attribute. By default this attribute is set to 0.9, i.e. 90% of the rays are fired towards the targets. .. Warning.. @@ -62,7 +62,7 @@ cdef class TargettedPixel(Observer0D): targeted directions, otherwise they will not be sampled and the result will be biased. :param list targets: The list of primitives for targeted sampling. - :param float targetted_path_prob: The probability of sampling a targeted primitive VS sampling over the whole hemisphere. + :param float targeted_path_prob: The probability of sampling a targeted primitive VS sampling over the whole hemisphere. :param list pipelines: The list of pipelines that will process the spectrum measured by this pixel (default=SpectralPipeline0D()). :param float x_width: The rectangular collection area's width along the @@ -73,21 +73,21 @@ cdef class TargettedPixel(Observer0D): .. code-block:: pycon - >>> from raysect.optical.observer import TargettedPixel, PowerPipeline0D + >>> from raysect.optical.observer import TargetedPixel, PowerPipeline0D >>> >>> # set-up scenegraph >>> world = World() >>> emitter = Sphere(radius=sphere_radius, parent=world) >>> emitter.material = UnityVolumeEmitter() >>> - >>> # setup targetted pixel observer - >>> targetted_pipeline = PowerPipeline0D(name="Targeted Pixel Observer") - >>> targetted_pixel = TargettedPixel(parent=world, targets=[emitter], - >>> pixel_samples=250, pipelines=[targetted_pipeline]) - >>> targetted_pixel.observe() + >>> # setup targeted pixel observer + >>> targeted_pipeline = PowerPipeline0D(name="Targeted Pixel Observer") + >>> targeted_pixel = TargetedPixel(parent=world, targets=[emitter], + >>> pixel_samples=250, pipelines=[targeted_pipeline]) + >>> targeted_pixel.observe() """ - def __init__(self, targets, targetted_path_prob=None, + def __init__(self, targets, targeted_path_prob=None, pipelines=None, x_width=None, y_width=None, parent=None, transform=None, name=None, render_engine=None, pixel_samples=None, samples_per_task=None, spectral_rays=None, spectral_bins=None, min_wavelength=None, max_wavelength=None, ray_extinction_prob=None, ray_extinction_min_depth=None, @@ -105,14 +105,14 @@ cdef class TargettedPixel(Observer0D): self._x_width = 0.01 self._y_width = 0.01 self._cosine_sampler = HemisphereCosineSampler() - self._targetted_sampler = None + self._targeted_sampler = None self._solid_angle = 2 * PI self.x_width = x_width or 0.01 self.y_width = y_width or 0.01 self.targets = targets - self.targetted_path_prob = targetted_path_prob or 0.9 + self.targeted_path_prob = targeted_path_prob or 0.9 @property def x_width(self): @@ -205,7 +205,7 @@ cdef class TargettedPixel(Observer0D): self._targets = value @property - def targetted_path_prob(self): + def targeted_path_prob(self): """ The probability that an individual sample will be fired at a target instead of a sample from the whole hemisphere. @@ -216,14 +216,14 @@ cdef class TargettedPixel(Observer0D): :rtype: float """ - return self._targetted_path_prob + return self._targeted_path_prob - @targetted_path_prob.setter - def targetted_path_prob(self, double value): + @targeted_path_prob.setter + def targeted_path_prob(self, double value): if value < 0 or value > 1: raise ValueError("Targeted path probability must lie in the range [0, 1].") - self._targetted_path_prob = value + self._targeted_path_prob = value @cython.boundscheck(False) @cython.wraparound(False) @@ -249,8 +249,8 @@ cdef class TargettedPixel(Observer0D): sphere = target.bounding_sphere() spheres.append((sphere.centre.transform(self.to_local()), sphere.radius, 1.0)) - # instance targetted pixel sampler - self._targetted_sampler = TargettedHemisphereSampler(spheres) + # instance targeted pixel sampler + self._targeted_sampler = TargetedHemisphereSampler(spheres) # sample pixel origins origins = self._point_sampler.samples(ray_count) @@ -258,17 +258,17 @@ cdef class TargettedPixel(Observer0D): rays = [] for origin in origins: - if probability(self._targetted_path_prob): - # obtain targetted vector sample - direction = self._targetted_sampler.sample(origin) + if probability(self._targeted_path_prob): + # obtain targeted vector sample + direction = self._targeted_sampler.sample(origin) else: # obtain cosine weighted hemisphere sample direction = self._cosine_sampler.sample() # calculate combined pdf and ray weight - pdf = self._targetted_path_prob * self._targetted_sampler.pdf(origin, direction) + \ - (1-self._targetted_path_prob) * self._cosine_sampler.pdf(direction) + pdf = self._targeted_path_prob * self._targeted_sampler.pdf(origin, direction) + \ + (1-self._targeted_path_prob) * self._cosine_sampler.pdf(direction) if pdf <= 0: raise ValueError('Ray direction probability is zero. The target object extends beyond the pixel horizon.') From 45f63d5191e4b32fd8ae5cb1d596c0c48da8573c Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Mon, 21 Jul 2025 13:57:14 -0400 Subject: [PATCH 61/77] =?UTF-8?q?=F0=9F=90=9B=20Fix=20intersection=20cachi?= =?UTF-8?q?ng=20and=20indexing=20in=20Torus=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- raysect/primitive/torus.pxd | 15 +++++++++------ raysect/primitive/torus.pyx | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/raysect/primitive/torus.pxd b/raysect/primitive/torus.pxd index 5b2c7d48..9643552b 100644 --- a/raysect/primitive/torus.pxd +++ b/raysect/primitive/torus.pxd @@ -33,11 +33,14 @@ from raysect.core cimport Primitive, Point3D, Vector3D, Ray, Intersection cdef class Torus(Primitive): - cdef double _major_radius, _minor_radius - cdef bint _further_intersection - cdef double _next_t - cdef Point3D _cached_origin - cdef Vector3D _cached_direction - cdef Ray _cached_ray + cdef: + double _major_radius, _minor_radius + bint _further_intersection + int _next_t_index + int _num_t + double[4] _cached_t + Point3D _cached_origin + Vector3D _cached_direction + Ray _cached_ray cdef Intersection _generate_intersection(self, Ray ray, Point3D origin, Vector3D direction, double ray_distance) diff --git a/raysect/primitive/torus.pyx b/raysect/primitive/torus.pyx index b284752b..0b9818aa 100644 --- a/raysect/primitive/torus.pyx +++ b/raysect/primitive/torus.pyx @@ -91,7 +91,8 @@ cdef class Torus(Primitive): # initialise next intersection caching and control attributes self._further_intersection = False - self._next_t = 0.0 + self._next_t_index = 0 + self._num_t = 0 self._cached_origin = None self._cached_direction = None self._cached_ray = None @@ -233,15 +234,19 @@ cdef class Torus(Primitive): if t[0] > ray.max_distance or t[3] < 0.0: return None + # cache the all intersection points + self._num_t = num + self._cached_t = t + for i in range(num - 1): if t[i] >= 0.0: t_closest = t[i] if t[i + 1] <= ray.max_distance: self._further_intersection = True + self._next_t_index = i + 1 self._cached_ray = ray self._cached_origin = origin self._cached_direction = direction - self._next_t = t[i + 1] return self._generate_intersection(ray, origin, direction, t_closest) @@ -254,12 +259,33 @@ cdef class Torus(Primitive): cpdef Intersection next_intersection(self): + cdef: + double next_t + Intersection new_intersection + if not self._further_intersection: return None - # this is the 2nd intersection - self._further_intersection = False - return self._generate_intersection(self._cached_ray, self._cached_origin, self._cached_direction, self._next_t) + next_t = self._cached_t[self._next_t_index] + + # generate the next intersection. + new_intersection = self._generate_intersection( + self._cached_ray, + self._cached_origin, + self._cached_direction, + next_t, + ) + + # update the next intersection index + # if there are no more intersections, disable further intersection state + # and reset the next_t_index to 0 + if self._next_t_index < self._num_t - 1: + self._next_t_index += 1 + else: + self._further_intersection = False + self._next_t_index = 0 + + return new_intersection cdef Intersection _generate_intersection(self, Ray ray, Point3D origin, Vector3D direction, double ray_distance): From 052718ccc3dfdea03c7475800448a69018f76766 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Mon, 21 Jul 2025 20:37:56 -0400 Subject: [PATCH 62/77] Update documentation to include Parabola and Torus in geometric primitives --- .../api_reference/primitives/geometric_primitives.rst | 4 ++++ docs/source/how_it_works.rst | 2 +- docs/source/primitives.rst | 11 ++++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/source/api_reference/primitives/geometric_primitives.rst b/docs/source/api_reference/primitives/geometric_primitives.rst index d0b833cb..a21d2b87 100644 --- a/docs/source/api_reference/primitives/geometric_primitives.rst +++ b/docs/source/api_reference/primitives/geometric_primitives.rst @@ -21,3 +21,7 @@ Geometric Primitives .. autoclass:: raysect.primitive.Parabola :members: radius, height :show-inheritance: + +.. autoclass:: raysect.primitive.Torus + :members: major_radius, minor_radius + :show-inheritance: diff --git a/docs/source/how_it_works.rst b/docs/source/how_it_works.rst index d64f5916..ef6994ff 100644 --- a/docs/source/how_it_works.rst +++ b/docs/source/how_it_works.rst @@ -58,7 +58,7 @@ Primitives Scenes in Raysect consist of primitives, which are the basic objects making up the scene. These are objects that rays can interact with, e.g light sources, lenses, mirrors, diffuse surfaces. Types of primitives: -* Mathematically defined surfaces and solids (i.e. sphere, box, cylinder, cone). +* Mathematically defined surfaces and solids (i.e. sphere, box, cylinder, cone, parabola, torus). * Constructive Solid Geometry Operators (union, intersect, subtract). * Tri-poly meshes optimised for millions of polygons, support instancing. Importers for STL and OBJ. diff --git a/docs/source/primitives.rst b/docs/source/primitives.rst index ac997661..1a94b7ae 100644 --- a/docs/source/primitives.rst +++ b/docs/source/primitives.rst @@ -3,7 +3,7 @@ Primitives ********** -The raysect primitives: sphere; box; cylinder; and cone. +The raysect primitives: sphere; box; cylinder; cone; parabola; and torus. .. image:: images/raysect_primitives.png :align: center @@ -30,6 +30,15 @@ Cone .. autoclass:: raysect.primitive.Cone +Parabola +~~~~~~~~ + +.. autoclass:: raysect.primitive.Parabola + +Torus +~~~~~ + +.. autoclass:: raysect.primitive.Torus ============== CSG Operations From 28e605da5702c5fc76d413e8fcb533cf4eb9a192 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Tue, 22 Jul 2025 08:26:59 -0400 Subject: [PATCH 63/77] Add demo script for raysect geometric primitives. --- demos/raysect_primitives.py | 126 ++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 demos/raysect_primitives.py diff --git a/demos/raysect_primitives.py b/demos/raysect_primitives.py new file mode 100644 index 00000000..9e6825fd --- /dev/null +++ b/demos/raysect_primitives.py @@ -0,0 +1,126 @@ +# External imports +from time import strftime + +from matplotlib import pyplot as plt + +# Raysect imports +from raysect.optical import Point3D, World, d65_white, rotate, translate +from raysect.optical.library import schott +from raysect.optical.material import Checkerboard, Lambert +from raysect.optical.observer import PinholeCamera, RGBAdaptiveSampler2D, RGBPipeline2D +from raysect.primitive import Box, Cone, Cylinder, Parabola, Sphere, Torus + +# 1. Create Primitives +# -------------------- + +# Box defining the ground plane +ground = Box( + lower=Point3D(-50, -0.01, -50), upper=Point3D(50, 0.0, 50), material=Lambert() +) + +# checker board wall that acts as emitter +emitter = Box( + lower=Point3D(-100, -100, 10), + upper=Point3D(100, 100, 10.1), + material=Checkerboard(4, d65_white, d65_white, 0.1, 2.0), +) + +# Primitive showcasing all geometric features +# Note that the primitives must be displaced slightly above the ground plane to prevent numerically issues that could +# cause a light leak at the intersection between the objects and the ground. +cylinder = Cylinder( + radius=1.5, + height=3.0, + transform=translate(1.5 * 3 + 1.0, 0.0001, 0) * rotate(0, 90, 0), + material=schott("N-BK7"), +) +cone = Cone( + radius=1.5, + height=3.0, + transform=translate(1.5 + 0.2, 0.0001, 0) * rotate(0, 90, 0), + material=schott("N-BK7"), +) +sphere = Sphere( + radius=1.5, + transform=translate(-1.5 - 0.2, 1.5 + 0.0001, 0), + material=schott("N-BK7"), +) +box = Box( + lower=Point3D(-1.5, 0.0, -1.5), + upper=Point3D(1.5, 3.0, 1.5), + transform=translate(-1.5 * 3 - 1.0, 0.0001, 0), + material=schott("N-BK7"), +) +parabola = Parabola( + radius=2.0, + height=1.0, + transform=translate(2.5, 1.0 + 0.0001, -5.0) * rotate(0, -90, 0), + material=schott("N-BK7"), +) +torus = Torus( + major_radius=1.0, + minor_radius=0.5, + transform=translate(-2.5, 0.5 + 0.0001, -5.0) * rotate(0, 90, 0), + material=schott("N-BK7"), +) + + +# 2. Add Observer +# --------------- + +# Process the ray-traced spectra with the RGB pipeline. +rgb = RGBPipeline2D(display_unsaturated_fraction=0.96) +sampler = RGBAdaptiveSampler2D( + rgb, ratio=10, fraction=0.2, min_samples=2000, cutoff=0.01 +) + +# camera +camera = PinholeCamera( + (512, 512), pipelines=[rgb], transform=translate(-7, 12, -15) * rotate(-25, -40, 0) +) + +# camera - pixel sampling settings +camera.fov = 45 +camera.pixel_samples = 250 + +# camera - ray sampling settings +camera.spectral_rays = 15 +camera.spectral_bins = 15 +camera.ray_max_depth = 100 +camera.ray_extinction_prob = 0.1 +camera.min_wavelength = 375.0 +camera.max_wavelength = 740.0 + + +# 3. Build Scenegraph +# ------------------- + +world = World() + +ground.parent = world +emitter.parent = world +camera.parent = world +cylinder.parent = world +cone.parent = world +sphere.parent = world +box.parent = world +parabola.parent = world +torus.parent = world + +# 4. Observe() +# ------------ +name = "raysect_primitives" +timestamp = strftime("%Y-%m-%d_%H-%M-%S") +render_pass = 1 +plt.ion() +while not camera.render_complete: + print(f"Rendering pass {render_pass}...") + camera.observe() + rgb.save(f"{name}_{timestamp}_pass_{render_pass}.png") + render_pass += 1 + print() + +# display final result +plt.ioff() +rgb.display() +plt.show() From 4c5c4810e3759be1f64de18445bc2caca384c8da Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Tue, 22 Jul 2025 08:30:06 -0400 Subject: [PATCH 64/77] Update raysect_primitives.png image for including all geometric primitives --- docs/source/images/raysect_primitives.png | Bin 290618 -> 214353 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/source/images/raysect_primitives.png b/docs/source/images/raysect_primitives.png index 0439ab9320057d44eaa53432c78721e92f100ddd..cd2301f3ef492e9c132fe14b9154da0923edb0be 100644 GIT binary patch literal 214353 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 !l>)RXw@0b#_ zu@8D2Ogy0}1ewQX0Y-x&Me|XWR>rVoRFUg{O;{X`3JLO&ZNI?`N^<&ic&i3+Sh?1AvcBRVzW!y8$-+>2<@&5zGXoqfn(B z{pVj_)AmG+_E}WfMlE0MvYUv=P2r|6IlG0T*l+R4fI8h6nkg<)y=98m5h|h({qIn} zCb{~R0)F6t+)(FQ?#rOenENYxK-yt!!C!qSRoFw~diCY|BS34dNBwPD$9s$^Jc3nGsU*2YAf;{+sO8TuY`VN@^59_jx*3BFC03=)-g*?YWyQGKmiCo$b)mK$n6MQ!zJFqAEuGY-=o?!u;MPRd-5cWq7 zQ=|se%XnZ)>?i|sRt8?$>+n&%9yLDqkRO;DvJG!O)C4#O9n9e_s}*ZG-@rzwxQJIR zs(f*uTdu#Fw_%wmxcshrgM`)EKs#(57a{Bv`vM+g`AcC?QT7T@qF!kkIZ0qq=~KL{ zjpTr1qYiOIc-bIzGCab_dk{(HtfY7u=+>b~O^<0=|B5pfTe0zh?fFJuK~Wq@g&~)| zags03sZKedRg9RH^WXqD=+&0M+ZEQ0?TTa8acM3i=77|4x+-D^H-ZLncTtfHI&!jI ze_;6S$@Jp5py6@+b!1ff3<(e!=uzbUN|~md_LgH#=GQ(_Ghb^Dxut9yrg~Ecs`h9P zKs1+J`R)k@CZkB0DbdZm)Z~;YD0!Gvkzj|sZUi5Abcb&rkHO%C72EXY6gXeybVWKRu}M*-5B^`9mSN_(_Gs@pcdX$|i4ht;6^d z16%yO(yS2bLz9xMLY2Nl{Q);x;!_;d(i?RHd)mZ#$Isy~PF9(UkfSe|lnO8OY8tSj^|PhbSxZ7+m9l%naHC z)_QI`gmrK337=!mr~lnA*I;#X=8p^dj3_>FLLL5nN5m1rkW9LG%V8bUsNDOaFSRCD z^>5VftGW8>qsss5b;9it1GQej@Uk`oa7wz9c#o2{agXEm-kzt|YMZHVOd35>!lu-Z zkV0CZm8=CbY@>l2c=+7erL zJWt_{x)V5W7B2p1;&BND^>M(5Q*{ur{^(`Ff!4$-?mU9@Id#lA*bOsb1o*6iFGUlJ zAjaRna9^b9hWcWmbnpkV%!o!ZfS>;A5jZ3Glv5Q@^g9r~CPDzVUB|Z^~%G*ftdFdm-?=w}N%`#95#1Mm~ zW`H0$oqDJ}pO#1bz$YEB&LdCuI%L|5wpui-WTk{>UVI3Oo@AhFOg;$c2!_a6a8xN}QPPlZ*3sznoV+{Nu1TctQ z$by;}yZkWm@QSIy1lPw~Wp8>a`C`&z9T=J9fLt;rTS{uV3h12pEmgWN{Qbuf0pFC9 z9v>Sumf(H5tRga!?m+YQK|?Vlg=qy7puGXT0S5%7CoiNN<;VeaV~#s&x%LX3JThg5 z`y|uVq42L##BUja$3b>O7 z{H=c-q*v_ zp8%QFw+3(P-zvYUW;^FygPCaSd{Mm+AUXNtYGI-Y~rE&jC@d%kO=vk0KlDXqobg zco7lU;}B4JmQUMX5M{GSo7nIgRt5Z8$z@;La(JB__TZHG?5|W>Y#Po_^Uh@`5oSqB z6*$%!stQe+dOx0k;LeGcJ#wR8a15@XgIEjDDP6`jyaUXpTYtm9i!$OH7-6|1_B)vImWOb>fsyE9zyHB7b6vi_@mC*1G>zX!eBPBJ z%oQv0jN)^W#bX*n;2=Px8+BsqA`z-Pa?(#w)L_1LQxglFD_@V~B8Ov2OmU98y(^7J z9*Y!DUVefVu8nKsIHh0TJV)T6pgT`k0@wfo;fCLFlh)+{G7aK1;04DdVCKtlPSZau$@i+fltQT>pn!{MsrTRSc?epqn0I|* zbxS{Jgkpmv-@W_P`6J+U$BM)$RlkSh1SJHQ>&yCi^YpnG@B4CqDm9LMB)%l-0~3#Z z5mC*j1JYkhnE4kg?6%F;0LUcMIjNp56TM21*7Oj-gwH`97ZL{z_^9lX&Fe_&$;v>G z`h}boeN3{rkXLzQJ_b*16ot893;J~ zJFtjabc^ae`PZB}x+L<3@^h`4_#I#-y*garb!&2PD|lzY7TiJ4eV!7bN(|P0O5e|e zBYiAqpYEG&oR%%VQJj(F!P>hi&P=E&O}kOW)^G1cMe-3(5B8D_s?KoE+EZ0FF_D%; zOMfCUO-_CI1{Cb0EV$N9ZH$PVO*k|f_*E8N} z=RAi8B|aaYOllXuT>osb?~!55WFoLTa|F| z&hHfF4@?6otRx5Fp83w zJ8SK!Cb*FNoP-jLZ2W$f(Cf^J{{#ZlHYBzKKrG_|gK@jP6nbRu#JuZ|Ro@-%+F4P$ z23G@xlQW~{ zWTM-kZ6f)$Kn-Q^X1Y{L6c4-mITj?AkRFCDZXS(+2ZLv8WXvOmJE4#k>p-$;(V-P8 zb|1J7jctpcG%?}2Kc(;pm`8Pa9PWg<>gl=uz(N*45|bh?KF$=rv3t>>zpaEupO9@2 zBm!6C9%4RE#uo?L9Rnmk0%)1u8~aG8GQ3!Jf(AZ zbTHmsLXEv1q6Rxs;=cbqwmz+Bw?u?^RHQ#f4jI0 zBesF@t_^4E5OnJ?2i{vWJ|(d^gc+u$B!|*xCh3#LnfOB2ssqWL=rj{kBpnPkiTuIR z4EeavSWP;*$`EzHqSAF@QvIhN9C1#CfhE-vnhjuZ6)CTS5~!Et58Tq;$>EIGjZwx! zNnivs9rZM?v9?%$xbxrV-m7cm(ggem?R*+vg3cgH6`%-{a%k|bBC$--ctD(uC5DtC zE?V*a!;*?85$}UA%2HQ!L?9e!8j9Wbdej5G7c8`7Q0&Fv?;!?HeMKi_%DpBqG@37^ zdIEEOKps{2zVM4b--t-aK5Db0s_#wyAAG{E+miSGJA~o32=iBNLzq-WHZ251E-yr(rTYCz45^| z)$v;fXH6IF8|p)b?tq*R2yFZPF(E1HjR{SQ0XVyo9FN*O%F&Z&@;E+;;NP0O;4>6P zUIsn+48R5h{6V5z4unmnni>${#EzSN{g(}6Egb!(>H(Lwe86BuwhAzoS}~NX1uiHy z)U&TEX?s`A$V;-@w&3@%Oc8SXa*d|1sYq;)MCSdX&13CcPjYj~_ za6C%-1Lhp45Z=N0%`dkp4yx||Ovqq#T_E5#4bxV0VpyR7FpdLF4{h+Ub9v?BZR$Ef z0qUcc4_#6nd@%@Xod>`c8}=xQwbQQtgFcANbSF6Jpo1c}WLanGeSpx@NkZkBlbM4! z*Zho)W063-R)g%BZU9Y}&#=z!E9ALohrTW9j6RkPeog>{JGD(E#Qe#0?zAdz;#5Bv(`&J32 zp69k#Skq5K29soR>rzDa;MTUTjNJm9G9Ud-;<-UM_X=bL>i*vXkNj;Su{S>0;X#>$ zuL4-FjkcDyY~$pf9S942T|e}o8x!kHSad9I!PTwhGyYO|bDP1m|9Z+IC2KdW{)jE~ zllv)>N0x{L9*##z{_1#JTa==YcwhcXHS_upv&|yVj$v1?;?Cru>ks3*h>j)b#S!l@^~~GF@HCzdp!sV^h5?(vF(jJtB|AkTZp@KnDm0BP%x@cC$Kj&7g$RPcXBB^3z+ zSh)sb*e)k70rQ;w5{ktl{XYK^$2Jb8D^AYgR?8+-TX8i9y%vwB7z%0Vr zz%o3*NO1jthe1C42S;ayJz=0`T`hE~1lc!-v|fChzMpr#b8u465lyHs2pXV*PMmiw z7S1dpx&OxpN8zjx(1p#DD`85nHM?c;VX8+-+O4IfRHcrL8T~q3cfYGL>`&8?LbS95 zHR2~~W8lg#Q@6FX?U^Y}8<$RlfY#T(!@yWu0`UDVTB3cn!~RCC;oW&$RLL2DK7{ou zzOm6+Oj^N3tlgtLagP6ysaQtrG~Pc@;Uu-)e8>-9CaaHDZbd-+0QL~|^wDXxwzuKl z+b1oo9~%}5ieKP!WhiSgM+#cJldRdl`>Ox&6n2~lRlw3UVV1z_cGxyHh1rDdYWy1- zsYQciPmg-5>m0C6H?PjO?ROPAm}w40GF(+Mn`dmMs@8OUdt_MiS_*@)k^2Mu&Qu}k z$4TTK+xWsu+_~%sHi_#d94HF^ro{FSK5CB8^#XCYf(-fhbV6>AdtJTA>!;Fl6nJ1t zW$ZTHs%JLdw8g;v##js)W`-sP>v>J^1Czm=h#>HJOUz8m-r>Pafrl)cJ#`q6KjM> zEfDw?G_)A&Y3_H}K>^-(mQkgj{2_1Tzq{MdX zj$+WA*O)dZJm7%`o^!v`QSw(xjm`u%VCvUAw!o%-%d~T!I*_W{U_%9_*YmDp1`9sh z__e#z1hfY-DP41*+&$}-XP3K0FvSnyn*59$Pc@707a};eS%WT2{@HtQbX1oK4^oU| zXjn8J0~jNwYbSE@-1C7T>9o>Wt=84TlY?vKE~4AC-`+J~d9%6`9kwyz!2*+WDJNet zly}3ntNOG0#K1oF90M|h=oK8DSqn_n*fXH9*6+V$DDh;2tby8A6`QPVd-s_TPx4WX z43j7YzUp!--C=;QgU(B+&7d77sJ4sm zHL+CR$Tl2cunxDgDEUIPRFIwZe%^m^T^3w=;48#n%^7&RR5TsBhBkq51)~CBh#i9p zE)U)_L+B|!Lqk{F_uvwB?XtxZg$pj_Kz*~e%jJe0E$Qw~W3$!%G<0;|Bn2}ciu^^N zIF*jG3d7->-HwO|9$Sopo?X3u=%{>yb!<$cWqeRQ>H5G3hSp&_7^+&v4W;P_L1Xb5 za>b)CW)^)~OSf;TV>s#tbQ!Y`$O8<}DCp7@F)INK`<*#YkL$jhb`Fs7?00J_R>f20><}ymDP8qQU?eA`j_y`z4yH*dx7~ z)#Kf!Q!LTmk4XPb;F6kJF?PGxW!C2k_U8;@A2n^Kdi-9 zwiG4VGYozwtsBU9jPlEY5{SZ=Ai1w*nb2cdL4{ncmRTyI_hmn*V8>pR{Aip8+JkVy-azvC&#HIenekQVJy;|IJLzxS&ck0F<^ zPHvn+>y(TY66-uRl$3+=Ogxez;g|@P5vYZsCP)h|taN_z_H9Rwk+hWIRjMDlL9jA- z#*_yEd_XU7Ej0~ootir_SaTXY^pQe9_^E+32P{1Dl)S@qb;?!VlW^gC>n;+({<1WN zJ7*`iogixZrl3cjP@hfVn}()}UN{01E7LGY93IlsSlS|)6OwI2zzJ@L_$|i zz-Kx=kbIyWn-WoPjxA?g4|&FS znZ|giDVxhp+%=Q8L)s}aoenK^9He&7n!0FVzXLI+vKTniNyQ&=ZJW?sf{goJ@+j|2 zcQ=pmtjO$**a2tz2d4*?biCt=M}`4NOe8k!1V|M*E~kG0KS03054?Hf>$LyiJ2b1) z?s7?&Ndy)XuuKWD(mm)Sa?l<$1(0SewEKoZH0{MwJ$vHtMDwz4>+i}zdu%0vDe?P7 zALu3g<8sW`9i8jgf1uagPlMl})!Gg|5T{=D+x&6almy_cHw*iu%6!gh^n|&k+&W=Lc#SNW-x;_3uv$cY9l9+GUx%G zNH!hH3XjD7WZltms?61PHG0`8r#{!L*Dw^b{G8Zx4o44*z*K>S+`?0#>WD=3&Y=-N z>V9ZNODDz#9|N&SuGZAWq3gSac5cIGoY5jds>j>I)AZ^%;u% z{{ndU>Rm7OM5~!}@P&CE0)8iKBDwSerfT-|FL4cSZ3Y~Ua|QjFr+R@k_2fDJ?K_-o ziVs}JMc8_B--R$5l-5XbAq4GS=w`xKvA$0LlD5;XGNRw3uDW1CjdZ2oDLNEBo`i> zRO+-5^~WELrU$2EGTIIkBKZ_>5tM-S?2CsxllxfvmJXA$RGw?}oM6w!uS=G`28;t8 zdejIiMestehP3It@?NB!M&L7{@?G19+fK*&C?#K=I6#o7DJK%UE3CA*i(6Alzufo;DisLp}+S#g5?Aw4Y+jg0#!B1SIy48bV8N49MsM*{saI+f`>?`CcJbE*s%OHF>HO;n03sT(ZEN=pc2|N zjqZ31fL$|#L7|G!GQZTe~i`#$|!{SA}AG`(`nQOxsnoDh$C+2n`E04s5xV#B(9 zO*lcHlg_?|%{B1?Zyv3k)N5}|>eU>R0xKjJz&cRIvTT)7kB^RZwUz`5)wW{#exJvW zW{$}xc*9P;eA4ISh@c#Ks1zCyY;wt|JJ#` zC<*uAVDL5PM|uceofo`V&=6ynPz;R*bl)}zD78pX%(~VBC|^zkjU$8=5~8dnS5dm; zxiPxfV2k9cKb~aL-ADE}i>fNLCobKX2PE_}=DXrrlE_3$3zE1TBCzD)yq8&qEHC@J zbPqgXb5#vNalQGNV^0gjIN4`bOejqGrHF11l~}e3W32PQV>cQ_-}Lgd>3csWipz=2jj(1MQ+WS}}5I^v?pwmS5$iT#4|R{b>Dh1OnITWrYo zdAx#M2Yq;cZtMKUEQZVDn60PQ=*6|)Vrp$FS&&gS;4x4Q3L^?v5@{*xZB7(wkG1Z! z2SlZ7LvKBdGe{4QLwdc zIl})e)j~Xa%x;j4x6yeume-fUC!DD86m5j#mX`R8r9~T<%UH2L%xx4{m|sHQF3-jV z+J6CqOdmkFV)BV93tbZO`!#_*Cm!!UQ{%ENMqp+H@O>8qXIrK^ITrFmzN|QG@fW>r z%4q&Gep^s3ac8q9>!$?_+atEq{Ja9-(_6Qqbfj1-u{$Zei z->7#re84bW4qyNxZ~?xDkINvR(#LqSGB>fNbLh2vKT+ue9K4>bvfgQkdPCbQ0Ui7HDk2) zRilLEy3%1fIX;7K-eC~jkFt4v7=8FUxDyFXDXyK~4ofY;Ne%`mG3(nU!4!lmSNeciH>ye&o|iEQ!Wo$3m|HH)=csk@;$CRE>0pd=O8& z?}(fVlm;hZFxLxf|1GcM^8<5I=wRp)8^q56^YJMC3Ru}n$9(4>4iX-chMz=>JFS5$ z=ElXVVv>tiP8x;lUeI#;UDHL@>{TP#VNis;+PO&z7b(~-t|2XLCByln!$X7R_RpGN ztBw2)`SjEw#laTJM0jNFU?)QR2AZB?H*@hz(rg_Z=;8Vhp+yD-ejTV{3fs>I4Y7~a zLFH^4=d!9XYFp~s6T!^(YH<@0{w)FDyo9>I>v&W|?mX8)&Lq#NEH`E6hFfa$M)zG( zVcd(z-udp+y@5j_aR;3MZed><$OHY#N`>lPio7hNVKil(buAOB`?jRdk$WaLt4==7 z8Z)cz3vj!wVPlj>rT&tk`*@8G(g*ZYZxLD@(6d3NjKm6Z5k73%xS2d+PE)()dqOa1 zwoY_s_|OFmxGECltC9EI1MGv~y%hXLz|0{`u=AlTY|- ztet6Ut(n(Aekk)wCg~p~RE7g(x1kK{`DY7zS@y#eTR|M9g^tdw; z$+gYsw{*l@0H1kH%Qfh@O@!X02MJci`Dku@lv;*s zbDc5>Kx=w}5*68ZvHpyIncOB@BiXcXqDCx6wrY=PyVu?Y^a-I4-4^nZXfq)3~s=}+yiSFKPb3! zjqAa>8;0XpBt##GvX=jj!%}A<26PQi0TjTm^YkJY;HBw|!8HCFRQBRQSs&%VmW6V= zHtjv&8rn+c@(ML*p&9@=%|vu@D#NFxU1m)U#?*h!7?nw2g@dt6!^cFVLh>U>s|;m* zwLy~wI79@O9fJ}kMW5PlN!+UT1*W_+IB_j){V>OV8-siHDAD9q^&Mv_odY}|d0a2r>#@?W=4t#Nri`wWy@vg_U9V8sF5W7eul$mkd+*^%KT^|uDPakCB1 z6Vj59bZa#^0aWs-1m%i8+bDgR#LvlI-Y?=WLf^nsz1DafPubV%zN+QxnoA+ZY_o1G zD**4Y2NR5Ipky2`1f&^EZ^xf1wWZ%{l{?qDhwV^=*0{`)bX6#^5(s2I@mv{x6@!Y@ z2xw;eG0TBZ2$&~)j9J1jWra0i5*cj!d|l@DLAG~_`wI9CH2&jC69!#*;NdXf55)jd zw*t{D{X}p$G_GOd@VkoSy{-v6gIb#(x=e?Akg=^(x!Z;OhH(~uCKA86XFL7*H1XCuzLhWe0FFEXiQpKa3B9^adl~sDd!*N&9PSiN zAYp!oFLepRlYJ6ZXDV`v$2Hw-19ov@m8@5x0I^r$6PBwXUjs`8z*u=~6@zOBG%uUc22&2>ICG)e-2g%QdTuzLDsgO4*gyn zdhxB6PP<zyfjrX{^YzLzg^T*(VOlBU%`YvnPl7{mPY?zi#oVQ zT~>XG$7T`S@H+N`SHMUy(%HaTG_BO!q6hQ4TkM8UWQqDDOs2)Z`eL!&B zfE1A@Qk}nv=Y9keuPgTlcGj}YXj6y5qzuv3wNHuO8t9{6|d z)y>CK{p({y!Cn$zv3ggLy~fW^jMVtpxnW;(fi^BV$YfLUv#S5<^B7x=-JSe!c_?MG zJO@1^UB&A}I+G@1Wz_dRz5k%O6tgClEE>8x!e=e9!Q&56z1n&h3q-Bl{gjnr!UdHg z%c8iSEv~zMR3sh^k2gvJ;_R6o+d>CFG@3*HQhSeY5QgjSX9a`%3$loaQSH3bpF#$9 zT3)f{YfY{jbXqV@9jVO6%tvxFrUfXk8kssfJc!}^*B&5nC?GVy$2w~nB(O-n6BRF< z6mjhk-D+1l8QQnx;1LUUs=^Z>M1J!Wx1~yHRVE*|Azp$}1zBiyeQ!0ghD6ZvmWP&uc zIEW2UI~&zJi1<+Zm|&+x^5EiV^{LHcs+1m&)zX(&Fu26DNqIX#yn2>QYa2V3G8D3< z`0M>c~q|dsn8;~T3I`$cmpdYcCGcSpOMgzR{8#yQh5RXbVwnyz<98L1XTLX$p zzXjkOA`$e$wASVHdu(nfSHjTlz*r^?@F}y*ICgnx<9kkSUH%S_sLly+h$nZF?aU@q zJxzzN9F*>-eU~Q)>eoD!))kx->x!=2!FLmCDL@rT1xQE@XWTEsK?J1}xD zO@gK4OZqS;d3+R*uv3wh{(Kb97^G+Q7Z%|_0{07)?r4^?;|KWqt`Nx{)g``(mSfsy z7JR@Q%Mx4u+(VJT5RdsepI|jWvP}x`4!q=OMVcnX$1ylDgX%kk~adAXRigQMG~~Euorzo z;5aBaQ33_yep{Xde)y;xA5lk>Pb8I=INlm2jPvYID)~qf?OccUDv%w72GnjhCHOAS zpf`rQFP(+q<*HLHyl=!=40Y6oM-lynKmH7M1?bAzqvMMEF3nqE)N6Algza z_<-92cFBW*bI`!1WljyCPG{jHcdf~Dh@QUdQ7&Yz4zZ?he1wB5KRTrN2`tsL+PDTk zkfNWkAY!O$jPMZeH@U1Z<*i*5c`q=JmOQ4;if<%ECN(E+k| zfmS)BxJbMh{Ar7I2dPDik_IFtEC*Jg68Kp9pLXWBH4Mq^dWvnZ+w7^?qb^0$&6&4_ zWDHt?nBTv0sH|VY3->cfiuEZmimWhJf=0x?H`A=b&-;Ya6W!pJzzo;;yZz0Tggk9U z;k9fE^u*n~Ec3=^wM>oU{H#&Sgrr?1=umnQ5Oz2bBsVGK&mEFH0!GA^b|^<@5H@0m zSuDj7VT0A7T}@H7EK9B=O~dZ*Fuj4V+XSCTZQ}VG0MAA{aSt}iRqJ)9%HdNE638|e zz-)LODO;A~;K;AxV}l+P(GEq3pxlK!*l{C~VAW_mBr0=hXK!JPag6Ywa{!s5ffg~% zd<+nk2gE{xf6$NCHhVd3Y2 zwj8PG2@A!tbFbunryZZsTm-GfNOCP2_y3zo`U8Qan*n449>M{y8e zK(RI7Fc){Tj%3g|<&6CVWh+Sra??Z{LP=sCi@ZANV>&%(D$7DA$a2ZRIktXw#ZPHRjq9T zES=WYGi>D)lwX^0OMeY;-^#M`?2P7Bi~s*x$D#>WkcR0VS!x`fW_s~ zloOUe>CP9(Vtffj!|^!D;T&73$@;;Uk7CQ&>R{@D15Dyx7lT9vK)?Y3+{dqnbu0%9 zvGFLT+Op%ktL7x;{20(vFe1YH`i^?2XsD%CNPN;%(Tp7GjIuml#UTfO&Sg3Te&hhPTg5wUE;P3bnqWsiG*L8t7LNkRXv0M_c zq5gEx_8Qk;a0!D$1AC$YBs-+`S}p@imUY6Ci>CmPUHo@Wl5Hes zTmuad*Gyo5(V$ zW~8y{&}O(KDo;> z%JN;ywtG;PGmF||46?508C(VmfC2T8rcC2Wh16UoB8)u|_YKAXV#B{X##byjxPcZw z^|~%&sBGf`J`vz|>}aYyFcEo){bkVcz$Z(>ODLsjMGuj2&d4S4Ioj{-~tJ7CnHz<56r4l zn+n=YZHdBj22Q3W6^Jf;#1jLOdS#4=1{GAbmGYh3<1~T{Vr%x6$pt-FD8WPGplLy2 zsBJk5FL+x|?2>KFBF(k5P4wy=UFw{JkdVrQ*fRtat<97^B-{zW zu2#>=qLfa4}; zN=wpsPqpv+uUNTe=0wt>BzbgoU zt8u!$#p5V)ASMA65H%MaB9~(yQwdTDKXBV9zje2&Liq71v{D7SMfnQaBAkY-=XWO+ zq-v9C-FZkU!}XMCzC3|;wna0N<7*kKX-O(b2Fh`i9o*MsRF1c#gnYkPzrtYHZFt^8 zS_d1c6KY{#ACynCx4JnTWv@QIF*oa3FAtdCAcEjSAu?qr>1DVt!ON@d$A<0eMJHpb ztOpMSj^2Ic_Jw@47Xq<}GHfiB?05Us;StZ8OiVpFIeN-tN8ny2hcYOi^x|v2 z14v-9C>aKmJKmwABPg^*eTKejph*HlolT*(#>F(n2!UUdqCg#&C%fspOo|jxTp`q; zCfh*a%E@;*(eZ@|tm`p>!HI?^2Q3YzI*)s9o_weQ*g~M-P1$i=kGaB+z^ous(pc0j zGvV-Tw}`|BFDWgf*d(WNBiO;W;CilSfj;r>(vyTa84do`<=R-1YC-wB%MBsJ4HB<>KmbMF!^C6ek(iS6^Aop| zB}8a^e2^76YGHn@)U}}{Lee?8>_o+!w!|aQ}%igy0|$o zbhwjXB)?FNW&2%EcLnme!N3hIfG3}ZFT$*A$QPIpF`>U7pkO4>h&Eus@P=>|jT{q@ zstWGb^S2AsJ@0 zmM8Ui=6jGWs6(vdhNQ;)!(#%OsjMb{s0)TFm~w21&h2(bd2!uqgKN;v z8=@bC&cH@ZHo;A=#zMeZ?oq4#b{Y69C>bAjY6ls0xK?B7#6Y&-@5B2(H>n#|>*O8< zxPLN8DZ2&J2EdL)XYg--ul|k+Ly*?-u<{vJJV;aPojU4HXTS&KL|RL*I8D?b@yWi#DSdR$A)B@NpZ@369AS{35I~>J#)!&oGzI zU{M*6LJs9Rm(g87rGe7^zSzU9S*YV0%#*#QcC~Hx_oz%SzFm3z`aaX2m&tlI(fBOa z3`(~e6fQ1%ScwJPq=g1u*Vv~wB~9N6o44s-$X20pqgknBA?w*@F5bmp*srxDt1#si zPiKUN(&yVum)E7B(2Oxp(!N2h20?~zd(&oAfxdB;0lN#nNy8m62vG{lSaJ4jn5Kt| zk+?}m`Dm&?r|(FZcM*_yDpn~G_Z|wiO`z}GI0vkO;DNBySbmbcB!d{NHnf$leWt|U zVFnXnObM1Zc5-ROVvI#>(NKMB;TfeIvG#ubh(3=d{VU!(_fztTd}9 z;=A`jSNJ%e_9^$62@|1#^77k!8IZr$mpx=TwBKyj$IL=K7#r2q{1i=!>Tx}YfS?v1 zL<-`|Rf{~KbefQsMF#tjdtfN^`c2gC8tzBBtUpoxy%)D_Acx@W$)Z~(Nd%ZyvWm#J*wvRs$%ZhQ1N77&VobUh)6VAb4*k4#oqVdP4X6uzojDzpIbtdl) zmjpzFN8-A}sZ9k=olZ)v@3=c19hj|N)n1igxq!-~_DEiZPF^4r&!HFLe|1x-a8)yfE~-Kj76>*3A=2f%3mxtrNyNh zG@~fi&XTLte?oB*>H9;kY}DYW^}<~BQV%>#b*(O_wALdK^r`^F0Ti0`LfQh*c-_Ba zT?`ImaUl*+veZUs9hPKpnh<`hxakNFwi#36cZ6ddv^D;lspDbo8%JAgE-N&%-7cp^ zYRd##yY|Gf*~h_F01|wn+xcC7BxGdOjP3&csU7vT{rzH_1U%61UMKD>mta&0Id#`B zH-M=I|Gq2JQs87B15rIm?=LtAz@>orDeTadk-l)rflrof*^evDB2~FO5gQeRL01@q zxOaypdVz-P8etoKBJ8Y^%N-uq3YEbYF~j1UE&&+vef+J%mexY?$x!s+LgQjYp5>B3 z&k@{>mMfSuZ75?3)~@w~Qql$F;8S?H2LO?fI-CS})O}aRqgFI?;vkK{4E58AvQeES zs2ov=)w0TaJ#;vO`kr(kKyZ{#ylsb4tP}p3BUMaN*zbz>66B3HE2SryMzogsUjJlI zPlt1Lid`v#0?Hp%P@!d%n)tn8tl0wtaVGC;%( zmVI^;Bk}sEUZ2Lz@6H1|uI+vl)TvdD8uy-0;0OzaAD6cSDq2KgFG}J^yn7z`2!8Q# z$gHNIOA#e3U3!6eoe(`=NE9CJvASi8nD%`K!hMnsAzW#7 zM*G2c4%j9?uTl#>uB9Tm_G=jgAxuy;5(=S^`)Zy)$kd&n9!>qY_G>f6X=9vUQ$bM6 zov*xpdElaf%#|~xzIw7?S(WOQJ{yf?(_5`+NWseY()%`cOtTKM2>=;wLUdJtzR+K) z7Sy!aBY;bw-RSNF!ILq^C z666~bqyf-aB*gG*5!iU8RoosMp17b3t(AaA7Q^&0`p|BV)A_iU1nCeoTefwTV8VgT z_Y{hhh=Aqw)4mp(UR5;Y4IL`;l6w+8czj@hZ?%`WCYG<6MVdH65l>nvnQ!|rzwacl z2+uY3lF^^n0~O+33nyJMhT&ixaZj^B9;koGKBKL`vo{9Orts8<p)>@LHjp$rP~A%XVT10{RU(GLM3aDP-IJ3-1HOE zyF-WJ#Qq(=coIzmfYDkeo|Jl4w6(uN8;8hG4+sC8HD zb%Lm!V7*KX>Dnyh#OA3ib}jdqYROqdVuKTIJo4`aS6Wg*=O+_s;%HR|`j=0gI>^FC z;z9pT5PK^i0DbjoL`265K!<}eP}TgA%Yo+9N6PLqQMp?kVqCKh4cbs#e++J)lHO4# zs4k$7?wh;obB$PZ`3c^A%EpvPkedeHv7XlFtJbP^T zf2+~7861DbP(v7NIBHt~G1#JT108OA#FGSKu)(y)sX#lNuEI=^18Q{a;<%Sb-GehE zgmN`nw!D$7qQ8j1n*bBYI|MO3-OQbU1Su+&h)8P{wf1m#@xh}o;;Y!AKPPWlZuMtz z>iLz#UD-NFQ1viT!~_-z$encpJ{{b|E?ivoD#4MR)|zRnan#FxebUS85t)K zK_spH4m@C%LgffdiVjT>E4T;iAK>dTqbCyaE}(Ck^#bwQqp&8FxV*kUvX}LgX!?zY z6rVubk<3gg`2qQ=x+5VL<4 z&b%Uk?U{x(Gn>=oh>$pY>CEK-4LeJoFwP8Q^c#WICkTk7&m!`%EDEUvWrmr4cmr7Hdwzyy3Umvzz=#%lu24N;!3>hqmM z!SYiKjP8yPfdn|hd{>MUq0ui(hF)JKuktXkBVbTp&}Rfosh5wcctAo=rR%0`>k=GJ zkkfKh6rI{un6Geu?WpMXsL7r@nCj%vWCc$G`#M<;g1v$xS{Xl4|01jzD_J3M!wMBe zxnSM6$+p3i+E0f;-~MwkcqAnAzL3;%uH#o2^Y=Nx5Ef=DIHrsY7N;$#X%4hJOjLM7 zo22e=Iz4(aP>a*pw#K;ea+=Z>p#fY|y<%gL{7G+3K2d$CP};cb#g`kc@DqaopvKB2 zWHnwOBqIK^y@GWqM2&AqfxmT!4ZGkazlRP*+hdR3Z|cEfIYGe|JI8`eHTY0f%CWo& z6Z(Sq;*FsYLz?*@ej|bOm$c(|`WmAt|7uYq0+a;E>d`>Y>^Iw=oq_qYi}H#ESJ-fU zp!+HolpbU0mXgl}!TK$gMi5+DW16xw;y~MtACIN%3OFdJf$hJajb#;EyLzX@hP5zN zVHq$bbSiZk9J^`H4HmLv}$rz`(1g(2A9qDw6fU4WKwn8cX4L-d;1im6>NMM}of?fek1tQoM8{y`pepHQMRw}O} zrU6b;-Ov|;l}qZ3*;2QzUNEqTDeF=+Xu= z(tmkG%(8#nC`KDg%NkPEKFV9v6SAWYqHO_N_cQljt+E$-i`VSb`HtTkKpl%nZgeR9 z!rVL0Y-!L$IZcf2uz{Iu1N~MkrkgHcsqK%!c3@ZC!A|=YLKxOD?qugp84?2*eD6QK z>t#CzM$qngm(NP*F&n6xg7j?G>YvZ6v(+9atbK4myw!XtCUT1@A94@@ZM%?_sj5D1 zsBr!^M!~r;A?~sO8R#X!AVJq@RCCNKMo_Gu7%nwUvB5dbHNnUa5EC`JD_PBi?=F-+ zCPAW*t@TFTABk%DJk_20;h;g=jI1Aes=7VibwLuzCJ>r_0>GkB1RKZOdL+?Rphv`= z4p?A}_bxd`5^tcz>{RsK^pO5KL5@GOEFr5S=um0!ApOPhR-8W9s~IXjHDe$lWa~_Q6Ci5U8wU`k z_hP{JE?GhNKKd-3mEpBi+=;TC_vzRAx>n~VIhx;8KGi>d-I1-;u<8i z)#V0b_hpa=br8A$R%0Wcu?hrfnZTo1DGxvDm!eS|xPDC}p8NL%80spsMu=m<8!mx3 zk1&(wv1Tb#)hqg}4)S_AEg=|R5806}2&|TgQABgF6b;}N-O!iQ3H-#~fLDC0qAM-B zhrLv|p%0fE7!mTl0vxBwfI(4yah~9@PdaA|SbXd#0%hbN$%2>h$uwPh8=tY)c2e4( zzSo108)%-|8P&Wb3kSxHf5&S}wb{Sdzg3__0$M24KK{q?pN5rSi?0>6HgjSyJMqgw+$qk3L1pqjjyTvcXqiT<_3Ex`_f71&vPp>u{1p>L;EI2D|89g3u%zj` z;gX8v^Zn-L*q4=P;?%o;r=+N)O0p%Dp&le{N%C&`2peG=O{^V}26P!a@YQIY-ups% zOj5TMk{yexUEMZhLt+L60iXq2Sm7i3K-*VaLj&|Np_DHYF$YhLP~c+V!`0wHfh0;I*KtSL!Z*Lcv6xI`cr-dyd3rCk0L7kBMfregF`MGc|h#v>y0BAJ6~l`A#*2Hj=nR&WXx3M zl{m|9epBil6|N%ST!)yzEq$R)rvPm_blovauX}yGB_EQ-$CeZF9xJ%-!Zq|W*U}BA zWZkkcZ3jK!`Wzo<=}9S581}@1=d_ZoX?nVIM)Kgu4Ge_=Y3BcQZmpF0%&S`3V7Unu zty&6!R1yGALE~r+{kcj|f#v#056Rb)Od;JRM`;_q61gh@P1^H5J-IpJY6GB|dli4t z$iyur5Em7!RYAQ1U`w>4!7mDJu3N@nI@<72?x`xiI5=#J1CuCNk^?%$1NlZmQfLfY5iMEF>z29N(W#o z_Re0FKb@8gsmP+90#{o8Azv;_H7=I1(XCLR&dYapYDv$xG%N;_f*us;%=W?J407jE zseVD@rV9X(Dg@}GvC`-|l4+sPjo5qVKq9T9-v+2FgEin>^b5dUfuFDh`)S$X5OBuN znzvZ;Jpp0oEhS`))EM}mmOGVvGYc`kd_8p#FKRKNfsX4Y_=an~2ZwwPb!gJ`U!9~z z50!_lz~4&<=0-j6f5ZR-+tppeZeIjPR~g8Pp@;fG-v|X%e~|wE#IUOZVPoV7pvI7} zAj#@fr=QQxqu)g`fTrB#tB%>2+Dj)1{{8D;$h zUsWjvG(5pu*5*q5u8v%sE)Ls)jc71eGMrbfS2}MyPBpb$3zwQOZR=Py)xB)7zTJrR zU6F!pcS#ARMsH)b!}XO21g^ln)+P{9&L4tN&@l${Ro2&*}z&_A?HgWkIgzh zgq7z2GA$yfp553w>uJ#LLF)#8uN<61X91m+YdW0QqiUbtAy+s)m7R}j4NOj079gev zE^A?No|J-PL$NM3-`-X212GmKy6!$!lFG_K79Rn0KR|Ojj~@KYa9pb!G6Rb%|CNp> z7mP6ic;E$qoD$-`?sOE}kzTs@5njjv_`1lgZ$-VasEyec4OYH;-L1&z+gQJ`h^9#+ z?JDh{O^r??^bV2ZdjvrE?Mitb3gXD+FdiOivlUbEt_fRc7CSF2la1>RpPKOqj?IX> z&}HF`3#eC40;^W@B4EIEq7w6q7r82CKj|55jK_*^2ZD5TIo^(5ozjT{*~fhYS>EMt zKSFe{&w3MJ0SSdhm4Wu0C?(H{*;5z~34*vRKju(^#Yd$WOf`@Q6iZNc38teapRY+n zc#d-CL12UO6{klK6-ilX15+`wLfxADCQj8WzsbR?9S2*s-pY$KqRcHlamQt}V>_Ip z_uJkvp(ielL`E)7zGNhRpXI?<6M?M0KJ>gl^N6N}Pml$=P3RXq` zr~`v*TLuJ@sH06RqV4tCZg{K@`hXkdNYqy7Ad$*bOJd5A*d7ZcCIC2*S3AdSBr_=I z*vh^=F{50h4@jg^uHnrY`K>=CvI%NwXyUk$LX0;LsCWOf>&M1xWE9m`WZ{5Y!lI8E zTnoL_05KAEK+?fz;(TseG-b1{5<(!4QeRP59pq{mU<&Z_@z!iYT$Dx>2N;L0w<)hc zn_GY&IB&}X5Cyfr8TM_cQO)ROvl{%3Qd9$j$nB~rY^;a6A~eC08N`D6$_6Ki1?!{mD5RzDjixdb(MD%Zw-U8 zg)>lSC(9uL21f);__EJL&}`1(-@w#nH6Evd zz4~~cno{DPEascS0{9U-gT4`wU|*9?hz%g0YxhQjj#xJan_+@;{zC`OJjA{9uXHmI z*FY)L6Et3cUh7Hnc!2s7gPd1>F;VVC;eTOUqy;pop9y>+;Fqp7+Yh|Q&k1imn6NUs zOKy5wki4nN$4H5B5!f_XFY>FIDJez0Z81w^MqPEatoqs&BHTC~ddXIANIFgzuYytx z3dR=XY(7$^EA*Sn14jm~Fb7B}6UmsYesLqqI{;`=B@jmNbKtlVl+V#f(>b)Tt{?&! zyr#}0rxbeBN|0WSr2Lx-t&UOKO>`!+0E4yn$>(<88K8WqNP9U6VCv$+3I`EvMaM_? zL*^=@)gn_Co37QBHh&}#!#uC{^D8?zMG;?X$LeTXO=SRgR;f@Qs+$TPqPViOWxav^AeuDp&34Gg%$5Py7fAQIVjt!0;zFtbX0!R3MA zztpXp6V-_-@UPa}7RA!CjG&s8&MIyMUzwK^Pb$3aGDD+ztU-QhArbMT#8F#{5;hvn zzd_^;2QX?d@TY6W^_atfUJA{#EzU#oF2ZcM7%4q88a6R;-P^GDl!c9@YRFB^$hA_}Xaudg{ zkBLBvZ8jDVa3&yKAJtkV-5lg$y>Mv_^)m;#5%Uy)wXL=@jpPJKQ+W3tI(3cYmsbYQ z83IovlTjK9HBY(YSKiUIe7j-Adgv`>?+$61o zLF$e;M2F`HSalFwC$0i#@ZAkP3j{7fSLkDYokSF_hrx?|C+|>KrP%BoM3I)Fj)9ffPk)d`4) z??m49Rzj$@B`9^%PZ#5>6P64Izcy`X0ry?M=qGJn)m!ej#-j^&GW#iu|J6saz;*o+^og! zws7u7z3z&krh>^4ghfv$pYi0t3B`tUrLsxY;67CoV zux5E@r>tQyU-w`5Dicx;>czlL%F1^U2Hu} zVPLk@0qxQGYOoga(Oth@u|8}7$u1=i<+xUVmlTHhLqIodr7MCB~mYDQu?~<{;qZ;gkPFr@+ufKUMk`;3 z?;P#Okw(9D)*-0ix(-BF+TdhKMCRIl!Xc+t>oTpkFO}vopBP zx~)E%->?}oZuo^NlVS5B&BhFMx;jp;PsAkq2qG%TS|&ruFL9}l?VPZg7dJ+SNg^&dSz&DjPCZw-p+RndKtH>jzzdT2S3Z73W)~QWLeRnKs zqL$z>rQ++4NABF|5sMZ8I{pOsPXeE530WR;%G`ar>L)yN`&>au9e}2S9HN zKp31^v6oejxB6J7<>#0ng?qy z_ejQ#yPgzjv)Dk>zffLr->ybMxi@yH*8_K?B{d__*^Qv%nobmC9rKUN3;J;WV~{oE zt60`7^4kWqhAajiRxj=j%@rR0ynPx#nmkiUBfDsOxx$_QmP*CiG6!kGa#sRB{+TFP z9{BDGXh#hWP+)1-Be)vwgf)w9%|rc1g|4w`^5>wwoiuV3P{i-zaah`WImWOV*TZ(k ztwlt00MPGV3imbP7!WUxtE8qW2~@*YD49~nL~&^#H-PQZ6L>;Elb%dyilr)>c}E#I z$@RUsR_axB!Mc#Rrk=?=l*rK9zZW~WX$S4LNmq(K9&*Ix+FI5voUEtG{_LE!ZnKzV zDE6Q~9)l~c;L&3>ltrlqFL#7_s4-}!t|g4TXd1|5KhxcRzd}XE>AV_Uv<|)h;S&JS zW*d8TU8Nt`#YO?&F0N0Fzsb=Y`-rpcq25EAiHC{1ier!H6ebZkp!bDw|Fv!HCSR%#D7-khdY6xci4u5dn*qO0GduF}_ zUXW1ky#@qxadxI8jp#AEd3WOBn09eNm(EvPu7Td&2i%c7d7!g3*;{`n8Kz@$xJgEG zb>M&xZGLj<>1{kt@N!zDQtG!1JmE^xym+ON>F5m$D@*_3zq^IMolWuK)B+xsWGEJu z6JG-nk;1d`-Km%BP-dN8d-B2gf@Dc+zraA2E4N1`gHMi1uUow#_S8U``*g!KxV(~^ zahYcPIDZXJOxl1uXFMcl820y|C(G9@MluDg!iEZWdDT#`GzBNF8xhu}4>J6f!b>Ln z6}|NH-~pIfk1GS#0se>1$T=807kf2|5Rp)s(@jM^t!Wg5{i@@H%TqqOrgC+(88|8a z1kPM1H9g%0hcHUA+f&IdYpq_V@c6OGMCQ6vUh zz1t03M5Q;t1EGpK&PQr+shz*GEDT8+ZAxEt@o`{8!2q!#(wn1Pn>adBeMD=~gYKhG zM2{lMTaF3sTZQhNndZ(ND(U=0f9^fcb%Q5IBEwC$MYrbl1mUV{?LxkrpxDdX85EWZ zl*BL=u^gC;jb}CSp?Z|jPCJIV>Xvi`dW4h+EKb<+MuQ%X%2kAkzg%nQ^*FUTU%RS>$EnxR4-ePS zuP|-U(!94*w?svu6Em~~P#o4j!AZq~TQXSjUj~yBopE^qI&dfIwTSxAcGt0?ie*A1 zQvRXRZ?L0&M!n%R5ot+TW(SxZ)(8M%laQ#?D?md>(D-+Fe7jIoGGlX$zay7FX+9{_@Bk@!+HM4`R4d{`r9ZRLu zB}1-4oGN$INRptn{~(Px4@nkfX(3{rKA%9Rp4dSg@5iq>AI`h;1LBiTNI&4ap%SR$ zFVw=M9M_7IBabz14C{l8p)1Y1a>UBOpxvQ?-l!tjc-nV?d#|e3yT0g7Fp|X~@YH7w z`KF`No?hD&3C4C@QPQ}`WIJOSS)up9W~tXFY(NjId|3IMj!N@{NhccT-#kSw8`kdc zs6Mi@LE9qU3GXRBs%!xqt9RXnx8_V2$2rh|nK4?X?@3L^I`yc@AO*qArOidaKWO?9 zKo5f%lRj}20NRmY=HI9epHI*VOwD1Q6f8d@Bo70c09ywq0Qyw`)7BN#iz24bU?-g9 ztjB3IfN&2Pq+Rczbfmj8OVuXVw^GE>7NKpm2YhJ{zq>*$eFAlP#d&P%8(5)S0cAKC zk7@Q|UyRKPIzhJtO(YTnpKu>QTHXhSCiq(O8<(X5l}a0224BGs^XY!vtX|6s2BI9( z3LEOo_l9b@9>lo7DDr1KAN|;N5j1Cua6~=Spd$G-w z9gZmyn-4=sH_AtYBgBP0JZ8Alsd1ucYw60elT39E9$}No2u&t6p?hpuV` zbsYf^Luk+xEZSn?HTc{zBc||L#_}(BJK9Ze((a=YXZgbTy5xP<&@u!w@E62f=M_qE z$_C%sXZTrn(o_po%jNfZ^K?DJ(nZRvsJcp&|9}Jh19>?0c^Yu~HM%hiV&#nn#^>=3 z6IIiRGs_9uM0g3n3>Ex9AYhg@wm_p4IMK&}H;q+Ylwl4F*p){5s|e8q@CIq&)-osh?~yZ5DKxq#!+TdDYjX5%Lrh8qc(wsX}1kywVza zfCrkyB=3f`lWf*d{w|X7e9}Ss{Mv=Hk!pe&m?vFmDtrSLfftk!jXL*f z1L?`qwa(=ghNwm9oS4Gt&}j-yoQkA(9n>V?ryIa%Rzz9WOnC=+2>ed=tR*ybk?zRr zJ5E|EQK(KomA?%RI)~M_CcnIxE6}uPeawy@`-1XqtoFRS2xX?=Y`|lB!yK|Ib6HK; z?#@1l2n&_i4;})v`ga^)VuHu>n&68#*&gx*Sr*&}UtNp|Jzg^8v91Mt;X5Yj=%WU{ z3JgS8c7hBa7=U1P&kdOh87Ml5*@_YY!5c*t)~U(9S{%Uw%dyrRL+96lHI#&MY%<0F z>0q3ET<=kWKjHA#wXkv#r}e{E9b3^?y?s@Qq)!Ij2z><;)f*m;8l^IptqcGZIvCUk zj!u43NE75XP378YWs%QOn3{h2(RPE^z)|KCpOYVmH90~tASA0(0c~rTRTQ7(?d?{{ zOtViB&jQ7uMiTrmVbnW6TK5D8WqD-6nk*}h<1bR4tKqYuN;?gH*TDiN4`{eP>VE?d4u{;E&2p`A|-8JRv!QkMHM(uh(R>ETy@#fj>@gn zHgu?P z818whnoHkVhf;@&3@Ic1i*u*@(&Qr^FDy~N5jnq$S9lX(J*Q>`Xljt44MSKh0U+)i z6WIId1m4qkP7atDs_RgYc`HbA@%mAuY%Ir+TMPGA3tA0!Zri-p#8jR15Y)I9=K^6( z<(|AAt;Jf(N@!hJr*{ZQ>H-H3Q8w_ZW~Bf5UU~XqA(E)ouggVU2-c%gy^~)h{munv zteLC1kT4kPnLmE0b6ZSIv|LkkuYd&MtD1>7e0_g+RasUgf1q9SnAn^z2Q1*VLN|_w zTl^Tvg{pysT?edDx8e-;he_O$X2`q!)}S2qBT(*irsa`{C3gv+jiS=51_mNjLS9-Y z!sTH{%M#qEIudZZ6Xu{znkJjTJUBQdQGcm+P1yT04CCBPSdSht$a3s|!pw<=wUW0S zC$3sv#3G2-kPOv;^|ZJ}^&|g_;u|jEO!=cmSC`<%mAO0mHi1C7rXn=ehkaKC&g{rw zyQx-nuqt%<(9SdVA0sUQ_9{DpSz)QMe9R2k`{IE2&K|Wg9J7<7P1>%B5<4g_PV>ix z6u+)r+x~^PwRS?us8*b}5Kf>l9he1Aj?PhL)E0Zyp?vki4^$9! z2mD7JYxy3sWypp6VrdtOe<;U3>i!ui{UJW*j75)yXj_v&`BGy|cT6ufb{)+q@6n|3 zxZjeGA6U}>v-mX(Sy9RHqtKt%75IjI_I+A|!5Rur@d|>s?=?g^2hW)tBxwY32B0c(zE}5}Bc?+_5#b#(5Cd(PHF<9ZFfN3GTlVL2q7Qk!A=mn=r-q|^E&Ho;DEN(;E0OfK! z06CBwa8Ur?>sNN_i%CvlZ0!WO!|##`rqrDuOpOE zqbInr3+HSygja+F?3;qHQ@qQiJL$o{aiHgcBaAK z49p0$HL^RPv(x7%^FMT1dc*7EAi)C~fUMrtAW(F^uDCckay4jk#|MWc;CgfDF62_U z?#>2XIv^5=^AI4N_Nz?;!A>6{u+iNaY;tFzuFNb@T;)a>VvfT_~2J~RE4l}5e3 z2d0x$@w+$eHW8sa3G@0UxsEun9*Ir4l%}S=eQoT%fjnY-KqvS}Q?n4pQRcA#Jx`WI zyAX-=HiSor;`EKn@rQWo05;y=Q0mj*WH+LO;IlYY=i#o_%J99F)wU@&zegfYVI6~- zDSF5xmz0fFpp0TyN*3@T>;l~eA_lTNec*+Bhj|p8*^;p`Tx9Cw=ILCxt3B-)q zz`lv(Y*iZpXB`ba4i&+UH05XlUS?oi=A-R^u;3~v{&Ooc)r&((o1 zs7-+;sXu%wz$>UoOv#XgK>gt8t)4bARX>qSH0~-xt0=fg&lq-o@kx>x*DKL!QC50W z+y?v>`-b`-eyoDv=oHiw2-HLAAJ?j&RD-1I^ZF>T9qsckM2uFtwgY^6X$tpI-)Aw^ z#iZ%_cYDRuxe2|fC!`IqQnDFs7#$>av$Z$rp(=Of@Mh;{_Ze|iTso4vUlWi+n13%c zD8~&|?vpjuWk*D&rK@Qmv}9MP#BJg!g4W;cSt(Nrvi?h7?RE#@C<>RM!`tfSt7u&f zg>);j_qy?00m#cSS#w^Q9|g?5$HexgqvtQ=VxoAAy}>Uc9{v0DZfoR1GmfG!12-_` zGV*b7+F{mk_6e_x`W?Mms^qtmp;y%znsEoJ2cBWYLaw>V5!f)okNe-D#Hh5&GN~^E zO~RFF@PeRG!3}`5sycc(3^3kypj=ZAO6M3*bvVGzQ{VW7SNPM7N$*a8wlSS-N{NP8 z-`&?;*<}BQ=x^2342tD-8Q5E)UBL;#c1#zoC(f7^wqQNg@S7`U;D7R&X}Yh(4Lu$t znPl-g4a`tn_kBRQ<|A5?s%Cp12U))C0fKT$u-u}8F9tD(c25-eaGq!Apj-z&n$gCJ141N0zEH%N`FlPMYY6lps_wZ}`0ZG*<4L928) zi1q>r0U2&}&lXQdo&e``05H;b)M*bc9rdp)1o?r|C$`DyJLt2`a|?_o9{UO=Hm4Ah zLb60LDx?rAo_J08P8`L?HX<|wQSaLE1wp~DO3prL<}8dUYEzOq$-jBRXZq>A|J!Fn zKOukq6fn-T=$%FW$=38S2uT5`tG^^fX$AYNnbQ_BhUT7okh#?S=B+9Tx#p4&I(|^ z=%xYcML#8v499$h-yp$0S|9#Ay?3B<>y*0`*+bUL7_C7!({~@kIj30P??ZmLxj2EF zwMSOtF-=IqxQPgp><)=^dCx&2G_FB=qmS1YL@^`4Y>DkU2#A3I0-}JY?-Lp@?hyr9 zw&+4!-u+pZ^p-w2SyDP1Hb8{W0o@60FvKe!suheh^f-?1yfVN3Jloetz}&}jplPW1 z@u&dCQqS)Rtp9Mf;vChL_KYCj>o{VGin{4%bgTWxdHMrwr_MwbZF-F{hK@ zb&$mY)q{On>lp!L@_D36m8y$?_cQ{Sa#>5KBXbM`VWl_)26BShjcYYId4j<}G9i6e zNzblTfAaUJ)B`|FKC#M;>g1(5ioC+z7odg9jd zIbG|psl(Y@kFmw1dTZvQBp%ljCSy>5xj?W^R5TK*#u&9JoUNF0g5&n#}3m#Qy zmH|b=c;!dob~*Zl2n+;r`&d{A=s2UK&7u;rs0z(4IbGba>bv&O7_z%n0i7LEpktMS z1&073dl{omAPy0z&)uWY5I{!|*V3*sb;6B_&YX~}Twn1pQ{U<2e0ykn4?xI?`mtEi3vA4WHC(dbQ#KF>Op zp{yq2*;hJ+8Pu+A#`I^q)%Se4;-D|+0uyVcj@<-J1k4ntaf@z0=mDhqH_(O3MxI!}#t4uT0jinB+9unqM9X@D~eEiWXgp z%IhjL%YpVT8>!~0;VCgp2s{}M%h@_OfLY&r-}5Hi>6l`oOTpiV{-&^c3%yf4w6_k$ z(BJf=46f#lukVO>@W*ad>cV5!e=U22NC)9;TH;&>3;>p4c;qo9&nx3)H%EIE-)!!1 zz()r_=W$dL&L0)LLT)mnusIF*+{1nd&5n^dwXhAi99x#a2c) zNQ=abMwJ5-47c3pz!ts9YX8HX+muL*X=(hKkG6|Djv6Rf`AV-}2TJpmDPC&Kpzqc` z5ky^%J}7`p(^^lTBhRR4m9_S*`IFj2!;@}C3DW=`34#Jzt_ec(d+(+xEB9xcoSyiq{=4gycG=Qo*8C~4PuC_NZH&dg zt9a`P>aAGzC<2+4!;_U{uO<dw#@o8<)(R&)Dp-%Jn zRtCjrwOQsk7y!5-Ct$%2uZ`u{RPVh5vw-bLUjsf)n}a;yn1jigS*BJmb{4hJ=Guky zVRJeO6Y_h)RFK!W$(+Dk-_yl9ioniEqfploo*Q=ib$!(Lm)LgJ`h0a`2ie-^u_Oj! zA)p7h)@eNg2DbPR&21+lw(OFe2f+xI2SLYlh`K$MBmFF=(8cK&9gKi=n?Zn#gW2}e z@I`vegSo0Iaa*?i978U0`gF&H*9P-UoBNOkEwLJFw|BrasjkjekoG-m@Z0wggn{G2 zNXQAqlWy;k@2IB)uchtP?}nabLz;8@Ge%C=?+>nD9c>VHqHr~)X!=bEpqDy%9q?y? zvMGwAo=%WrQ6Klr;L|JxU3ZmLDWC8-VHLLPInI~Yu73E(n`3B{8Gz~BU8_e_AN8TT z*O#t>Uw~lHYB( zSd#%>L768+Whtjat6@rW-4TGGUwY5ioh@$>LRe01TBj%rmn2L57ntkeYETv0g?JmF1RvCcZDU#1fat zeouW8izfiE4~Pu{@4@|JqaNPT1U-D)f|2TiMh%I<*U%5SnG-{ak!e(r0Fm`2VjA?* zH|kBafw8ep#=FsXp=ZejSZns@K3zWp3UBA_Ib|-Bh=IxdFC!0>3_j z0r~=^Tm!I7;ijg`C~Y&x;Rn-_1MG1-fPoRIv0tvDS$ci``I%s)nZ z0)yFsf{%&GL;v(hBs6te{*1OeUw!!n5~s)Fv3D3CdMfv}{_>H=cT_D7r|#d_BCl8O`FmF4am8|a@^|qs#m++v`!y30 zv-*pX67kd8ou&tnuL)DAX3@3tS5TBBjdn~VzX})%R9rh!N+S}DVAuS64{$SW(URimjyllLvaoQK)VbhKkUd3l0#XeMt_phgi9E zHEcNi*wMqlFUs7BUrgu)BJ|!J18of|t%>i{y$)xmM8#n;$#SwSR!`dX3H-(vCKAh* zkSC%7d5(2^i$ea=(k_Ck_*hLagEG^PrckKAqBUfY2vlZvwoB=E)0-YR-ulzanZbwN=!mcV2yawkST)P?+Ac%z9g3q_0Zjp~~ud2au<>Vb^^l+WR ztvbl=))gK<&VsxfuntOT8-SaGop`Mwoo}9F8`XbrJ23Ti@GN&wa4pxpEWqDTu9BnT zeCsVt4DGNUne%}-;h7N$MIM_-uy6C#lOiH= z>rYc1L*P&csZXO7FX^w+6cJh%rnf3VPTQ81_5#}fZ zlv(-Sx&WPArD{w`Ndk#D1E`DBd8gdJb?>g85LyJa;-;w;5D+YuiNfIf7KVVozVjVT zR`Xmv#leJMAl)sTs~AloFTJPl)Q8#jUqh{03EEvR zmJVnAGK(jy%rcWYi-7Sg!?~YL@qgl|_Io}Evl?%BfL|6n6Bw2*r--Q1H%2blc7J2O z_ouV5K?QS=t1O;U3ct@A9dEnb;QjId%*$f*RY>y0OKR_lOs>ZU?@mv)zMi?kz%q}gpGbAku7jXX7ZFh5quQfIF!blWu%kt~Bhae+DD>3Qk)W+t zjeMx6?is9!gP?26g(iUs?cD+W}xsppo4Bq1Cw2cWJO%r z#NEc^dQp#m$01^|I4a9uFy$B^=`#~Ix3RADOdC06?+hKnX-eQ94c?c5pNE_C(xjj>w zO6G`gN>`Knr{feYJrPWibU~ATSHNwpds8dYU|*qEXA$7d9y4(!A~cArJhv!nzP!Hh zCM&RP+w^CG1C^^Z*I=^b$vLv?hQ8`h*fZLz+F;Y7m;Y2|5FJ+?BYK#AurC|ha9K|U zZ#kT&q{7DxR&#_vtt%r9c@KR>r#hvm;RMjAzIn>OCc-xoD&39>#b$T#LBm0zEq zcS`ZE0{Pifskl36<)nto1P#+8g7a`Gn$*FFk=@qRR>3a_>BCi94~xI8 z#)$>XwNkVB2OY`ZtfvB%+CYS+-Y<{pJUu<~`c&xlPzlLXwPjwp0^m~0@ho&Y42Fto{1*6#qL7eQXOV2t$?NvLmJ*~Ctamm-G zgn;MF;Q0a5x&B1?Ef?GH0|!nJoAJo%DGUaVmj4Llf+$CRoHWMy(MdIIyJ!!yl#Z>W zs_oIrT0Gh7`ei&d{;PjXErx3k62KNO>XNzv>Th0w)&_vBys4PPo|8fAr6iY=ZQ{B~ zf8V$ZgdnaG(`)rjOY^x#dj{6}IRR}Z$_A~~>!Ck?l%(DZwJu8z#(@v`?J86LMYpl* za;Xn%9MG%$7@^G@&YfIb(-1<~Woqz^9F|_TE`l4Aw&oYQ0l#*Dq8Go^z8WBTLOyac zlub3Oj;Ho1MB3_{yk|R2T+|<%$?JIkJ5f>V#tL0@=cgm4LgU3P7~q<98U;p|5f&A= z83KJuLhR`$Th{pig+5OBRRR~}Vku7XG1>4@QmExuel@kY^4mk7=lmqDQA!ms`0}5> zZtqYkq$xoW-eo+;r&?ft8*XhJz!`_E;{dzA(I8Q1_BHXs2L%-miEB6}BHo$T0U@i?wDW^hE-M61PBsfYJ;z<0$u>XyW;ozA+VU8f_*E_$ zU=OI)B2694SJ#~m+m=)6)H5dH ztlgiasjOt2^g+v6_Gy5_XC{sz>DR`WFB2rRsV^rO;#r!8Ch)ey_tIaB1Ef<^)jTk; z;jHtc<=?r*MF(Gd@$&GbJ0&NC-LOazziQ6uDHcRWnjP&v!z~Iy8Vp3a#bbfkj9%zt z7z1AGr2Fb$Q~4yw8!@85Q{=`iTSIdN>SZ7xlB&D47{^3B=j!pOKU2sqIVBrDKZTTN z8Y%o`cSEdGq5x^fq0%H+bG;*HyrHndrj$FUo=&4XKZPV;2RE&ED|#s8mM*mMz}2hc zP4=0G4SY%k6F z8$N0roZ@5%Q0lk)Vn076x`{Ki!~>v$=lTrl&p)V}HK%9Qc{erq5IKX#HG=5X6X~8L zW*zcCqXmFeOJfu?-Nwwi()7_4tL|+a{7Li5bLm|mrr+2 zr2olRgjE|mvLTUqckL0o7CiPq?#_GXf`O48^$hTDJGuokrNSH1PwrH{)9Yg0KOn(} zK0ySeKf&=Mf6e6KTpy0{!WH#LAOVJt4W?y{{U|}U?Ma{eIZFiEssS2T3ZFyk9#{1d z)#>Q5n7SW=A-=8{p~EeOhOF@T&-aC}tcq>&S_>#UvvSL`39J#i>f+p!I8}!parG7+ulh@VdCx-y`HaV@PldlD0FK(Nvex=j>N*{d20>V><7_=1 z*I=TLqFJZ<%AUFs%$%{Ky$NiPmcVbqGfKvQQGm`Yuj90adzkinqqwZh)Osu!KM3adgGQU1>8m2t{Ae zA2k0FuSR0@cQOPy452AZ!0l;A;$-OB7SAi-!FE97HqMI|yOUS<(ut}G-3b$S5Nw^# zs-CZzt*L{6=8|w;^)>kdKzDgFUs?r&qU_?`UDlU{h$c>Ck+vV1ay;q!0zOVTeLUU* z_UZi%ltvV*?Q0vY@W5iTi zJ!zNiY!`gwwf$(ZTh7I0yJ%wY zxzmQ%jcy~!DXG~+V~4+YR`D(0+UN<_vuRJj`AVi!TwsgpgLrN7Kc+TiqoGoKj=xjj z*tJ=S6m!6>TNDB@+-)B$Pp`jh$LhH${Hn=#F%=PN1D@&&fTE1}t=((sznZnm2cUsU z8Ub`_e}WozKI}uw_m*si{26*Fbt&N~G$}UdCaIk~N(gTQ>OQbwIBBrh?8p49VU%Xl zM{ca7HZYrCr`OqfJ=YV&bCqsN3y+S+V1E@`SFnxAt@>oOkCyK}adOWU`Y+Q!<9Dus zJXgM^IuqZ_OHX}xU1ZkZ^&o(0O)}K$Ri{XIlJBj_xN7mVFf%~U?!YFxh&gQV}ZXi z^mVYTCY=LY#5XhHCKHR2BiN&XT=#YjGxRtdUOV{kG#hf|k|tV$?&`-#YQn*Ky4ylK zopsvyY1?bUEf%lh?EU zZEAVdt`6I(7#<@c%9ET?4rNHBW;_|U9-ovqn8BaY|LbYxk0QJB>g?!@h`E?SCCkmpQ zv=MisVOMTg`>|%S{z}qwYwC28a{$S{TOfU6g3S@wARtH&UM}g=)0-zz#DZl<5yQs^ zmApmQJz@9O);y?&boIL{4wQ5|^DqP9%;Sm9v^U1vTmdQ8>FqnFoDLl_!=x)4%1^dP zdWi*7f~kiUDZF%+@uBA&FCTar660}5qO29CM=#ropn`TgkW@IPe93J?8`Wt{QKveZ zm@~*wL6O|-XA{onbMaQ{UA_3bTU5*Uw;XNbvVWQIZ6a-KGs=qxE^gGBSE>3kA>#$% zairBddfRBdo=@3}ox#B$b4gDr$lDBr{g__PIe9AZO66}EB$VrIe?-b!T^u)Q;s|;& zIGGU!4D~H~U9NH+e}467PyC!@ukLwDdRcmv#wJQ6vvuqA;IZ(d+WF*HlSCoGp%O_~ zgE?wc&t=ZL-fO*yMm<0E)3`h{9A!1(w^T9kF(tZ3e7t4QphMY21N%r{s{bb<#=FR`D9)Ex*o-Zw#2h$+KeI1gxC5#K|Ae<2`|57 zmbDiz&DnQDSMRdmk~&!RGM~ws{<-V|uR7=ZAOoq^E_uHyd&@F-;>~ zjvQ^!nt_3Z-?TuyZkfX#ZbRjS7dkPa$0nJd9s(@G1oTqMq?`|t-wqD4N{L#7k7COS zW9zs|x6L!iGy)0VAc&uF6Jl^}ep{9-gO`uk_=85u1rK+{C5y_=qMhwD> z8!O0fi|^N$_3&@xWirB~-rn$6`?X0!(*cnLnC@@!Q?hl%uL-y6F~tjCJ@Yk}wq6Sn zp33JT2!8rgdo@}xZ(T+9_$I(GxOBpiV0CEMzSl&-F9A8L2N=0LwBIY%Fh&+KCG5dr=*Wcd0(WTpWZ>#-c05?t}HhH5eBi~a}Wd6c}mY`1fPy6QJ%h6 zy-&8ChM998v#&hjuR1E0SI@5lf|?ememrg`kjLYsn|l3`PLaZ?udI}c~ersRD))PP3ng#&DOv!ETUYGAtFW;?QsU=t7 z^Y);_I>nE}al_F-HZp&yUy~OH%rx@3R`RtTPDi!&89L+4XW!}l7-CUTyd^lJr`$4!FDL!Ank({WBgHTg_& z;+Jc>xYAtp$qCr--zh1cY%#(2q^w%E%25XdlfO*#YT4UU$TT>&Bb~R=oO;Q?Eya(E z1`g|>=b^`3zC+$AVcmGP9NlGOg}WaP2Eru^QN-r#W9h;$2sInk8xkL2&4Y=ml^%S( z9>ClhTyEc{sP^q_NJ~_Z}yLiQ=h0Dl6uA%djO9sIW$k8bchhxsGFFwOZtzaO~}guo)om zwYnxSjBt^^5?Pp!_W6^~)&INH%<*G?$MVX+RpLKc#SS$(Hfd$WikHD)%1Pkw74Xv; z)#CU&l!K8631R?H!?!9R5+~RMr=Ky&@ZILg{UP8 z2MjambiKblfl@dWS3;mNNnMG-a0J@KixXf!;zF4W8 zdOxL)QyVNxyW$G=92M0EOv*^J^@)Dak!ebqR_X*=jFPi2W8df6c{cfG_gsvzD(-w| z&a$^&tXRe9gQ?&kRiVzt2U?o2-j_kSn(jSB1?4?P1`Eelx&kyj7-7A-Zs#~TCH3hj ziR*HRbw#rp?NQBw(Sw_q52n02hJpz##XBZ>entKFu?AW$iDI4*G5P-kti2?FGH+n? P00000NkvXXu0mjf^%y*@ literal 290618 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

9~BmG`<(QI$+;FF*D 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%0DG6(`53Y1<6%_Ih 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%0DH>O-&c8~=>^x~w0azG=nk0x)Fa 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 From b46559a09a5aba215f1bec99cf723d9753fdef20 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Tue, 22 Jul 2025 08:49:43 -0400 Subject: [PATCH 65/77] move raysect_primitives.py to demos/primitives --- demos/{ => primitives}/raysect_primitives.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename demos/{ => primitives}/raysect_primitives.py (100%) diff --git a/demos/raysect_primitives.py b/demos/primitives/raysect_primitives.py similarity index 100% rename from demos/raysect_primitives.py rename to demos/primitives/raysect_primitives.py From 40bab61f6629079030a92aaceb9abbc47913e833 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Tue, 22 Jul 2025 18:58:52 +0100 Subject: [PATCH 66/77] Sort file and subdirectory lists to guarantee ordering when generating meson.build files. Fixes #448. --- dev/generate_meson_files.py | 12 ++++++++++++ raysect/core/acceleration/meson.build | 4 ++-- raysect/core/math/cython/interpolation/meson.build | 2 +- raysect/core/math/cython/meson.build | 6 +++--- raysect/core/math/cython/tests/meson.build | 2 +- .../core/math/function/float/function1d/meson.build | 4 ++-- .../math/function/float/function1d/tests/meson.build | 2 +- .../float/function2d/interpolate/meson.build | 4 ++-- .../float/function2d/interpolate/tests/meson.build | 2 +- .../core/math/function/float/function2d/meson.build | 6 +++--- .../math/function/float/function2d/tests/meson.build | 2 +- .../float/function3d/interpolate/meson.build | 2 +- .../float/function3d/interpolate/tests/meson.build | 2 +- .../core/math/function/float/function3d/meson.build | 6 +++--- .../math/function/float/function3d/tests/meson.build | 2 +- raysect/core/math/function/float/meson.build | 4 ++-- raysect/core/math/function/meson.build | 2 +- .../math/function/vector3d/function1d/meson.build | 4 ++-- .../function/vector3d/function1d/tests/meson.build | 2 +- .../math/function/vector3d/function2d/meson.build | 4 ++-- .../function/vector3d/function2d/tests/meson.build | 2 +- .../math/function/vector3d/function3d/meson.build | 4 ++-- .../function/vector3d/function3d/tests/meson.build | 2 +- raysect/core/math/function/vector3d/meson.build | 4 ++-- raysect/core/math/meson.build | 10 +++++----- raysect/core/math/sampler/meson.build | 4 ++-- raysect/core/math/spatial/meson.build | 2 +- raysect/core/math/tests/meson.build | 2 +- raysect/core/meson.build | 12 ++++++------ raysect/core/scenegraph/meson.build | 4 ++-- raysect/core/scenegraph/tests/meson.build | 2 +- raysect/meson.build | 4 ++-- raysect/optical/library/glass/meson.build | 2 +- raysect/optical/library/meson.build | 2 +- raysect/optical/library/metal/data/meson.build | 2 +- raysect/optical/library/metal/meson.build | 2 +- raysect/optical/material/emitter/meson.build | 4 ++-- raysect/optical/material/meson.build | 4 ++-- raysect/optical/material/modifiers/meson.build | 2 +- raysect/optical/meson.build | 8 ++++---- raysect/optical/observer/base/meson.build | 4 ++-- raysect/optical/observer/imaging/meson.build | 4 ++-- raysect/optical/observer/nonimaging/meson.build | 4 ++-- raysect/optical/observer/pipeline/meson.build | 6 +++--- raysect/optical/observer/pipeline/mono/meson.build | 4 ++-- .../optical/observer/pipeline/spectral/meson.build | 4 ++-- raysect/optical/scenegraph/meson.build | 2 +- raysect/primitive/lens/tests/meson.build | 2 +- raysect/primitive/mesh/meson.build | 4 ++-- raysect/primitive/meson.build | 4 ++-- 50 files changed, 101 insertions(+), 89 deletions(-) diff --git a/dev/generate_meson_files.py b/dev/generate_meson_files.py index ee4b4569..94936664 100755 --- a/dev/generate_meson_files.py +++ b/dev/generate_meson_files.py @@ -124,6 +124,9 @@ def _install_root_meson_file(path, subdirs): if any([not subdir.is_dir() for subdir in subdirs]): raise ValueError('The list of sub-directories must only contain paths to valid directories.') + # guarantee ordering + subdirs.sort() + # write meson file _write_meson_file(path, _generate_root_meson_file(subdirs)) @@ -137,6 +140,9 @@ def _install_subdir_meson_files(path): # generate a list of subdirectories, filtering excluded subdirs = [child for child in path.iterdir() if child.is_dir() and not _excluded_dir(child)] + # guarantee ordering + subdirs.sort() + # write meson file _write_meson_file(path, _generate_subdir_meson_file(path, subdirs)) @@ -211,6 +217,12 @@ def _generate_subdir_meson_file(path, subdirs): else: data.append(child.name) + # guarantee ordering + pyx.sort() + pxd.sort() + py.sort() + data.sort() + # start contents with a warning contents = ( "# WARNING: This file is automatically generated by dev/generate_meson_files.py.\n" diff --git a/raysect/core/acceleration/meson.build b/raysect/core/acceleration/meson.build index 4b45982b..ccd50d7c 100644 --- a/raysect/core/acceleration/meson.build +++ b/raysect/core/acceleration/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/acceleration' # source files py_files = ['__init__.py'] -pyx_files = ['boundprimitive.pyx', 'unaccelerated.pyx', 'kdtree.pyx', 'accelerator.pyx'] -pxd_files = ['accelerator.pxd', 'unaccelerated.pxd', 'kdtree.pxd', 'boundprimitive.pxd', '__init__.pxd'] +pyx_files = ['accelerator.pyx', 'boundprimitive.pyx', 'kdtree.pyx', 'unaccelerated.pyx'] +pxd_files = ['__init__.pxd', 'accelerator.pxd', 'boundprimitive.pxd', 'kdtree.pxd', 'unaccelerated.pxd'] data_files = [] # compile cython diff --git a/raysect/core/math/cython/interpolation/meson.build b/raysect/core/math/cython/interpolation/meson.build index 473a025d..aa0d8393 100644 --- a/raysect/core/math/cython/interpolation/meson.build +++ b/raysect/core/math/cython/interpolation/meson.build @@ -6,7 +6,7 @@ target_path = 'raysect/core/math/cython/interpolation' # source files py_files = [] pyx_files = ['cubic.pyx', 'linear.pyx'] -pxd_files = ['cubic.pxd', 'linear.pxd', '__init__.pxd'] +pxd_files = ['__init__.pxd', 'cubic.pxd', 'linear.pxd'] data_files = [] # compile cython diff --git a/raysect/core/math/cython/meson.build b/raysect/core/math/cython/meson.build index aac5c9dc..4efc6066 100644 --- a/raysect/core/math/cython/meson.build +++ b/raysect/core/math/cython/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/math/cython' # source files py_files = ['__init__.py'] -pyx_files = ['triangle.pyx', 'utility.pyx', 'transform.pyx', 'tetrahedra.pyx'] -pxd_files = ['tetrahedra.pxd', 'triangle.pxd', 'utility.pxd', '__init__.pxd', 'transform.pxd'] +pyx_files = ['tetrahedra.pyx', 'transform.pyx', 'triangle.pyx', 'utility.pyx'] +pxd_files = ['__init__.pxd', 'tetrahedra.pxd', 'transform.pxd', 'triangle.pxd', 'utility.pxd'] data_files = [] # compile cython @@ -27,5 +27,5 @@ py.install_sources( subdir: target_path ) -subdir('tests') subdir('interpolation') +subdir('tests') diff --git a/raysect/core/math/cython/tests/meson.build b/raysect/core/math/cython/tests/meson.build index e462f440..126d42a0 100644 --- a/raysect/core/math/cython/tests/meson.build +++ b/raysect/core/math/cython/tests/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/core/math/cython/tests' # source files -py_files = ['test_utility.py', '__init__.py', 'test_tetrahedra.py', 'test_triangle.py'] +py_files = ['__init__.py', 'test_tetrahedra.py', 'test_triangle.py', 'test_utility.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/core/math/function/float/function1d/meson.build b/raysect/core/math/function/float/function1d/meson.build index 1ab0aad8..ab3dc6d2 100644 --- a/raysect/core/math/function/float/function1d/meson.build +++ b/raysect/core/math/function/float/function1d/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/math/function/float/function1d' # source files py_files = ['__init__.py'] -pyx_files = ['arg.pyx', 'autowrap.pyx', 'samplers.pyx', 'base.pyx', 'blend.pyx', 'constant.pyx', 'cmath.pyx', 'interpolate.pyx'] -pxd_files = ['cmath.pxd', 'constant.pxd', 'autowrap.pxd', 'blend.pxd', 'samplers.pxd', 'base.pxd', 'interpolate.pxd', '__init__.pxd', 'arg.pxd'] +pyx_files = ['arg.pyx', 'autowrap.pyx', 'base.pyx', 'blend.pyx', 'cmath.pyx', 'constant.pyx', 'interpolate.pyx', 'samplers.pyx'] +pxd_files = ['__init__.pxd', 'arg.pxd', 'autowrap.pxd', 'base.pxd', 'blend.pxd', 'cmath.pxd', 'constant.pxd', 'interpolate.pxd', 'samplers.pxd'] data_files = [] # compile cython diff --git a/raysect/core/math/function/float/function1d/tests/meson.build b/raysect/core/math/function/float/function1d/tests/meson.build index 5e2f0eba..d6cc6d57 100644 --- a/raysect/core/math/function/float/function1d/tests/meson.build +++ b/raysect/core/math/function/float/function1d/tests/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/core/math/function/float/function1d/tests' # source files -py_files = ['test_autowrap.py', 'test_cmath.py', 'test_arg.py', 'test_samplers.py', 'test_constant.py', 'test_base.py', 'test_interpolator.py', '__init__.py'] +py_files = ['__init__.py', 'test_arg.py', 'test_autowrap.py', 'test_base.py', 'test_cmath.py', 'test_constant.py', 'test_interpolator.py', 'test_samplers.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/core/math/function/float/function2d/interpolate/meson.build b/raysect/core/math/function/float/function2d/interpolate/meson.build index 595a8598..80c28a76 100644 --- a/raysect/core/math/function/float/function2d/interpolate/meson.build +++ b/raysect/core/math/function/float/function2d/interpolate/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/math/function/float/function2d/interpolate' # source files py_files = ['__init__.py'] -pyx_files = ['interpolator2darray.pyx', 'common.pyx', 'interpolator2dmesh.pyx', 'discrete2dmesh.pyx'] -pxd_files = ['interpolator2darray.pxd', 'interpolator2dmesh.pxd', 'common.pxd', 'discrete2dmesh.pxd', '__init__.pxd'] +pyx_files = ['common.pyx', 'discrete2dmesh.pyx', 'interpolator2darray.pyx', 'interpolator2dmesh.pyx'] +pxd_files = ['__init__.pxd', 'common.pxd', 'discrete2dmesh.pxd', 'interpolator2darray.pxd', 'interpolator2dmesh.pxd'] data_files = [] # compile cython diff --git a/raysect/core/math/function/float/function2d/interpolate/tests/meson.build b/raysect/core/math/function/float/function2d/interpolate/tests/meson.build index 930888be..75b34a0c 100644 --- a/raysect/core/math/function/float/function2d/interpolate/tests/meson.build +++ b/raysect/core/math/function/float/function2d/interpolate/tests/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/core/math/function/float/function2d/interpolate/tests' # source files -py_files = ['test_interpolator_2d.py', '__init__.py'] +py_files = ['__init__.py', 'test_interpolator_2d.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/core/math/function/float/function2d/meson.build b/raysect/core/math/function/float/function2d/meson.build index 291e2a1b..60016468 100644 --- a/raysect/core/math/function/float/function2d/meson.build +++ b/raysect/core/math/function/float/function2d/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/math/function/float/function2d' # source files py_files = ['__init__.py'] -pyx_files = ['arg.pyx', 'autowrap.pyx', 'base.pyx', 'blend.pyx', 'constant.pyx', 'cmath.pyx'] -pxd_files = ['cmath.pxd', 'constant.pxd', 'autowrap.pxd', 'blend.pxd', 'base.pxd', '__init__.pxd', 'arg.pxd'] +pyx_files = ['arg.pyx', 'autowrap.pyx', 'base.pyx', 'blend.pyx', 'cmath.pyx', 'constant.pyx'] +pxd_files = ['__init__.pxd', 'arg.pxd', 'autowrap.pxd', 'base.pxd', 'blend.pxd', 'cmath.pxd', 'constant.pxd'] data_files = [] # compile cython @@ -27,5 +27,5 @@ py.install_sources( subdir: target_path ) -subdir('tests') subdir('interpolate') +subdir('tests') diff --git a/raysect/core/math/function/float/function2d/tests/meson.build b/raysect/core/math/function/float/function2d/tests/meson.build index 883f6155..44b5ea1a 100644 --- a/raysect/core/math/function/float/function2d/tests/meson.build +++ b/raysect/core/math/function/float/function2d/tests/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/core/math/function/float/function2d/tests' # source files -py_files = ['test_autowrap.py', 'test_cmath.py', 'test_arg.py', 'test_constant.py', 'test_base.py', '__init__.py'] +py_files = ['__init__.py', 'test_arg.py', 'test_autowrap.py', 'test_base.py', 'test_cmath.py', 'test_constant.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/core/math/function/float/function3d/interpolate/meson.build b/raysect/core/math/function/float/function3d/interpolate/meson.build index 55828ab8..7d4c2c6b 100644 --- a/raysect/core/math/function/float/function3d/interpolate/meson.build +++ b/raysect/core/math/function/float/function3d/interpolate/meson.build @@ -6,7 +6,7 @@ target_path = 'raysect/core/math/function/float/function3d/interpolate' # source files py_files = ['__init__.py'] pyx_files = ['common.pyx', 'discrete3dmesh.pyx', 'interpolator3darray.pyx'] -pxd_files = ['interpolator3darray.pxd', 'discrete3dmesh.pxd', 'common.pxd', '__init__.pxd'] +pxd_files = ['__init__.pxd', 'common.pxd', 'discrete3dmesh.pxd', 'interpolator3darray.pxd'] data_files = [] # compile cython diff --git a/raysect/core/math/function/float/function3d/interpolate/tests/meson.build b/raysect/core/math/function/float/function3d/interpolate/tests/meson.build index b8244427..66468dc2 100644 --- a/raysect/core/math/function/float/function3d/interpolate/tests/meson.build +++ b/raysect/core/math/function/float/function3d/interpolate/tests/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/core/math/function/float/function3d/interpolate/tests' # source files -py_files = ['test_interpolator_3d.py', '__init__.py'] +py_files = ['__init__.py', 'test_interpolator_3d.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/core/math/function/float/function3d/meson.build b/raysect/core/math/function/float/function3d/meson.build index e4610299..0f1d48f5 100644 --- a/raysect/core/math/function/float/function3d/meson.build +++ b/raysect/core/math/function/float/function3d/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/math/function/float/function3d' # source files py_files = ['__init__.py'] -pyx_files = ['arg.pyx', 'autowrap.pyx', 'base.pyx', 'blend.pyx', 'constant.pyx', 'cmath.pyx'] -pxd_files = ['cmath.pxd', 'constant.pxd', 'autowrap.pxd', 'blend.pxd', 'base.pxd', '__init__.pxd', 'arg.pxd'] +pyx_files = ['arg.pyx', 'autowrap.pyx', 'base.pyx', 'blend.pyx', 'cmath.pyx', 'constant.pyx'] +pxd_files = ['__init__.pxd', 'arg.pxd', 'autowrap.pxd', 'base.pxd', 'blend.pxd', 'cmath.pxd', 'constant.pxd'] data_files = [] # compile cython @@ -27,5 +27,5 @@ py.install_sources( subdir: target_path ) -subdir('tests') subdir('interpolate') +subdir('tests') diff --git a/raysect/core/math/function/float/function3d/tests/meson.build b/raysect/core/math/function/float/function3d/tests/meson.build index 1e5ef2c8..ce4b4c74 100644 --- a/raysect/core/math/function/float/function3d/tests/meson.build +++ b/raysect/core/math/function/float/function3d/tests/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/core/math/function/float/function3d/tests' # source files -py_files = ['test_autowrap.py', 'test_cmath.py', 'test_arg.py', 'test_constant.py', 'test_base.py', '__init__.py'] +py_files = ['__init__.py', 'test_arg.py', 'test_autowrap.py', 'test_base.py', 'test_cmath.py', 'test_constant.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/core/math/function/float/meson.build b/raysect/core/math/function/float/meson.build index 94fc7e45..481844b1 100644 --- a/raysect/core/math/function/float/meson.build +++ b/raysect/core/math/function/float/meson.build @@ -6,7 +6,7 @@ target_path = 'raysect/core/math/function/float' # source files py_files = ['__init__.py'] pyx_files = ['base.pyx'] -pxd_files = ['base.pxd', '__init__.pxd'] +pxd_files = ['__init__.pxd', 'base.pxd'] data_files = [] # compile cython @@ -28,5 +28,5 @@ py.install_sources( ) subdir('function1d') -subdir('function3d') subdir('function2d') +subdir('function3d') diff --git a/raysect/core/math/function/meson.build b/raysect/core/math/function/meson.build index fbd96960..52344203 100644 --- a/raysect/core/math/function/meson.build +++ b/raysect/core/math/function/meson.build @@ -6,7 +6,7 @@ target_path = 'raysect/core/math/function' # source files py_files = ['__init__.py'] pyx_files = ['base.pyx'] -pxd_files = ['base.pxd', '__init__.pxd'] +pxd_files = ['__init__.pxd', 'base.pxd'] data_files = [] # compile cython diff --git a/raysect/core/math/function/vector3d/function1d/meson.build b/raysect/core/math/function/vector3d/function1d/meson.build index 8e4b1432..cad4de14 100644 --- a/raysect/core/math/function/vector3d/function1d/meson.build +++ b/raysect/core/math/function/vector3d/function1d/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/math/function/vector3d/function1d' # source files py_files = ['__init__.py'] -pyx_files = ['autowrap.pyx', 'base.pyx', 'utility.pyx', 'blend.pyx', 'constant.pyx'] -pxd_files = ['constant.pxd', 'autowrap.pxd', 'utility.pxd', 'blend.pxd', 'base.pxd', '__init__.pxd'] +pyx_files = ['autowrap.pyx', 'base.pyx', 'blend.pyx', 'constant.pyx', 'utility.pyx'] +pxd_files = ['__init__.pxd', 'autowrap.pxd', 'base.pxd', 'blend.pxd', 'constant.pxd', 'utility.pxd'] data_files = [] # compile cython diff --git a/raysect/core/math/function/vector3d/function1d/tests/meson.build b/raysect/core/math/function/vector3d/function1d/tests/meson.build index aed59801..52dd0712 100644 --- a/raysect/core/math/function/vector3d/function1d/tests/meson.build +++ b/raysect/core/math/function/vector3d/function1d/tests/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/core/math/function/vector3d/function1d/tests' # source files -py_files = ['test_autowrap.py', 'test_float_to_vector3d.py', 'test_constant.py', 'test_base.py', '__init__.py'] +py_files = ['__init__.py', 'test_autowrap.py', 'test_base.py', 'test_constant.py', 'test_float_to_vector3d.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/core/math/function/vector3d/function2d/meson.build b/raysect/core/math/function/vector3d/function2d/meson.build index 90c77b9f..786a0bbd 100644 --- a/raysect/core/math/function/vector3d/function2d/meson.build +++ b/raysect/core/math/function/vector3d/function2d/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/math/function/vector3d/function2d' # source files py_files = ['__init__.py'] -pyx_files = ['autowrap.pyx', 'base.pyx', 'utility.pyx', 'blend.pyx', 'constant.pyx'] -pxd_files = ['constant.pxd', 'autowrap.pxd', 'utility.pxd', 'blend.pxd', 'base.pxd', '__init__.pxd'] +pyx_files = ['autowrap.pyx', 'base.pyx', 'blend.pyx', 'constant.pyx', 'utility.pyx'] +pxd_files = ['__init__.pxd', 'autowrap.pxd', 'base.pxd', 'blend.pxd', 'constant.pxd', 'utility.pxd'] data_files = [] # compile cython diff --git a/raysect/core/math/function/vector3d/function2d/tests/meson.build b/raysect/core/math/function/vector3d/function2d/tests/meson.build index 552f3c87..4a92b327 100644 --- a/raysect/core/math/function/vector3d/function2d/tests/meson.build +++ b/raysect/core/math/function/vector3d/function2d/tests/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/core/math/function/vector3d/function2d/tests' # source files -py_files = ['test_autowrap.py', 'test_float_to_vector3d.py', 'test_constant.py', 'test_base.py', '__init__.py'] +py_files = ['__init__.py', 'test_autowrap.py', 'test_base.py', 'test_constant.py', 'test_float_to_vector3d.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/core/math/function/vector3d/function3d/meson.build b/raysect/core/math/function/vector3d/function3d/meson.build index 9de64727..d04d5389 100644 --- a/raysect/core/math/function/vector3d/function3d/meson.build +++ b/raysect/core/math/function/vector3d/function3d/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/math/function/vector3d/function3d' # source files py_files = ['__init__.py'] -pyx_files = ['autowrap.pyx', 'base.pyx', 'utility.pyx', 'blend.pyx', 'constant.pyx'] -pxd_files = ['constant.pxd', 'autowrap.pxd', 'utility.pxd', 'blend.pxd', 'base.pxd', '__init__.pxd'] +pyx_files = ['autowrap.pyx', 'base.pyx', 'blend.pyx', 'constant.pyx', 'utility.pyx'] +pxd_files = ['__init__.pxd', 'autowrap.pxd', 'base.pxd', 'blend.pxd', 'constant.pxd', 'utility.pxd'] data_files = [] # compile cython diff --git a/raysect/core/math/function/vector3d/function3d/tests/meson.build b/raysect/core/math/function/vector3d/function3d/tests/meson.build index 05026568..6aac64aa 100644 --- a/raysect/core/math/function/vector3d/function3d/tests/meson.build +++ b/raysect/core/math/function/vector3d/function3d/tests/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/core/math/function/vector3d/function3d/tests' # source files -py_files = ['test_autowrap.py', 'test_float_to_vector3d.py', 'test_constant.py', 'test_base.py', '__init__.py'] +py_files = ['__init__.py', 'test_autowrap.py', 'test_base.py', 'test_constant.py', 'test_float_to_vector3d.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/core/math/function/vector3d/meson.build b/raysect/core/math/function/vector3d/meson.build index e52a738e..a1c9fd33 100644 --- a/raysect/core/math/function/vector3d/meson.build +++ b/raysect/core/math/function/vector3d/meson.build @@ -6,7 +6,7 @@ target_path = 'raysect/core/math/function/vector3d' # source files py_files = ['__init__.py'] pyx_files = ['base.pyx'] -pxd_files = ['base.pxd', '__init__.pxd'] +pxd_files = ['__init__.pxd', 'base.pxd'] data_files = [] # compile cython @@ -28,5 +28,5 @@ py.install_sources( ) subdir('function1d') -subdir('function3d') subdir('function2d') +subdir('function3d') diff --git a/raysect/core/math/meson.build b/raysect/core/math/meson.build index c9a62c88..98bc930a 100644 --- a/raysect/core/math/meson.build +++ b/raysect/core/math/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/math' # source files py_files = ['__init__.py'] -pyx_files = ['units.pyx', 'normal.pyx', 'point.pyx', 'polygon.pyx', 'quaternion.pyx', 'vector.pyx', '_mat4.pyx', 'random.pyx', '_vec3.pyx', 'transform.pyx', 'statsarray.pyx', 'affinematrix.pyx'] -pxd_files = ['units.pxd', 'random.pxd', 'point.pxd', '_vec3.pxd', 'normal.pxd', 'polygon.pxd', 'vector.pxd', 'statsarray.pxd', 'quaternion.pxd', 'affinematrix.pxd', '_mat4.pxd', '__init__.pxd', 'transform.pxd'] +pyx_files = ['_mat4.pyx', '_vec3.pyx', 'affinematrix.pyx', 'normal.pyx', 'point.pyx', 'polygon.pyx', 'quaternion.pyx', 'random.pyx', 'statsarray.pyx', 'transform.pyx', 'units.pyx', 'vector.pyx'] +pxd_files = ['__init__.pxd', '_mat4.pxd', '_vec3.pxd', 'affinematrix.pxd', 'normal.pxd', 'point.pxd', 'polygon.pxd', 'quaternion.pxd', 'random.pxd', 'statsarray.pxd', 'transform.pxd', 'units.pxd', 'vector.pxd'] data_files = [] # compile cython @@ -27,8 +27,8 @@ py.install_sources( subdir: target_path ) -subdir('function') -subdir('tests') subdir('cython') -subdir('spatial') +subdir('function') subdir('sampler') +subdir('spatial') +subdir('tests') diff --git a/raysect/core/math/sampler/meson.build b/raysect/core/math/sampler/meson.build index 06378d7f..c6b49aff 100644 --- a/raysect/core/math/sampler/meson.build +++ b/raysect/core/math/sampler/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/math/sampler' # source files py_files = ['__init__.py'] -pyx_files = ['solidangle.pyx', 'targetted.pyx', 'surface3d.pyx'] -pxd_files = ['targetted.pxd', 'surface3d.pxd', 'solidangle.pxd', '__init__.pxd'] +pyx_files = ['solidangle.pyx', 'surface3d.pyx', 'targetted.pyx'] +pxd_files = ['__init__.pxd', 'solidangle.pxd', 'surface3d.pxd', 'targetted.pxd'] data_files = [] # compile cython diff --git a/raysect/core/math/spatial/meson.build b/raysect/core/math/spatial/meson.build index 853ca0c1..9a65917d 100644 --- a/raysect/core/math/spatial/meson.build +++ b/raysect/core/math/spatial/meson.build @@ -6,7 +6,7 @@ target_path = 'raysect/core/math/spatial' # source files py_files = ['__init__.py'] pyx_files = ['kdtree2d.pyx', 'kdtree3d.pyx'] -pxd_files = ['kdtree3d.pxd', 'kdtree2d.pxd', '__init__.pxd'] +pxd_files = ['__init__.pxd', 'kdtree2d.pxd', 'kdtree3d.pxd'] data_files = [] # compile cython diff --git a/raysect/core/math/tests/meson.build b/raysect/core/math/tests/meson.build index 672ce76e..909aee06 100644 --- a/raysect/core/math/tests/meson.build +++ b/raysect/core/math/tests/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/core/math/tests' # source files -py_files = ['test_transform.py', 'test_random.py', 'test_normal3d.py', 'test_point3d.py', 'test_interaction3d.py', 'test_vector3d.py', 'test_affinematrix3d.py', 'test_quaternion.py', 'test_point2d.py', '__init__.py', 'test_vector2d.py'] +py_files = ['__init__.py', 'test_affinematrix3d.py', 'test_interaction3d.py', 'test_normal3d.py', 'test_point2d.py', 'test_point3d.py', 'test_quaternion.py', 'test_random.py', 'test_transform.py', 'test_vector2d.py', 'test_vector3d.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/core/meson.build b/raysect/core/meson.build index cecc18d2..ab291f7a 100644 --- a/raysect/core/meson.build +++ b/raysect/core/meson.build @@ -4,9 +4,9 @@ target_path = 'raysect/core' # source files -py_files = ['workflow.py', '__init__.py', 'constants.py'] -pyx_files = ['boundingsphere.pyx', 'material.pyx', 'containers.pyx', 'intersection.pyx', 'boundingbox.pyx', 'ray.pyx'] -pxd_files = ['material.pxd', 'containers.pxd', 'intersection.pxd', 'ray.pxd', 'boundingsphere.pxd', 'boundingbox.pxd', '__init__.pxd'] +py_files = ['__init__.py', 'constants.py', 'workflow.py'] +pyx_files = ['boundingbox.pyx', 'boundingsphere.pyx', 'containers.pyx', 'intersection.pyx', 'material.pyx', 'ray.pyx'] +pxd_files = ['__init__.pxd', 'boundingbox.pxd', 'boundingsphere.pxd', 'containers.pxd', 'intersection.pxd', 'material.pxd', 'ray.pxd'] data_files = [] # compile cython @@ -27,7 +27,7 @@ py.install_sources( subdir: target_path ) -subdir('tests') -subdir('scenegraph') -subdir('math') subdir('acceleration') +subdir('math') +subdir('scenegraph') +subdir('tests') diff --git a/raysect/core/scenegraph/meson.build b/raysect/core/scenegraph/meson.build index 178ba563..9e6c2398 100644 --- a/raysect/core/scenegraph/meson.build +++ b/raysect/core/scenegraph/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/scenegraph' # source files py_files = ['__init__.py'] -pyx_files = ['_nodebase.pyx', 'world.pyx', 'node.pyx', 'primitive.pyx', 'observer.pyx', 'utility.pyx', 'signal.pyx'] -pxd_files = ['_nodebase.pxd', 'observer.pxd', 'utility.pxd', 'world.pxd', 'primitive.pxd', 'signal.pxd', 'node.pxd', '__init__.pxd'] +pyx_files = ['_nodebase.pyx', 'node.pyx', 'observer.pyx', 'primitive.pyx', 'signal.pyx', 'utility.pyx', 'world.pyx'] +pxd_files = ['__init__.pxd', '_nodebase.pxd', 'node.pxd', 'observer.pxd', 'primitive.pxd', 'signal.pxd', 'utility.pxd', 'world.pxd'] data_files = [] # compile cython diff --git a/raysect/core/scenegraph/tests/meson.build b/raysect/core/scenegraph/tests/meson.build index 01753d53..1841bdc7 100644 --- a/raysect/core/scenegraph/tests/meson.build +++ b/raysect/core/scenegraph/tests/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/core/scenegraph/tests' # source files -py_files = ['test_node.py', 'test_world.py', 'test_observer.py', '__init__.py', 'test_primitive.py'] +py_files = ['__init__.py', 'test_node.py', 'test_observer.py', 'test_primitive.py', 'test_world.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/meson.build b/raysect/meson.build index d9802a7d..4f55056a 100644 --- a/raysect/meson.build +++ b/raysect/meson.build @@ -27,6 +27,6 @@ py.install_sources( subdir: target_path ) -subdir('primitive') -subdir('optical') subdir('core') +subdir('optical') +subdir('primitive') diff --git a/raysect/optical/library/glass/meson.build b/raysect/optical/library/glass/meson.build index 358c6dad..112e09c0 100644 --- a/raysect/optical/library/glass/meson.build +++ b/raysect/optical/library/glass/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/optical/library/glass' # source files -py_files = ['schott.py', '__init__.py'] +py_files = ['__init__.py', 'schott.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/optical/library/meson.build b/raysect/optical/library/meson.build index 7b9112b7..8f1ad274 100644 --- a/raysect/optical/library/meson.build +++ b/raysect/optical/library/meson.build @@ -27,7 +27,7 @@ py.install_sources( subdir: target_path ) -subdir('spectra') subdir('components') subdir('glass') subdir('metal') +subdir('spectra') diff --git a/raysect/optical/library/metal/data/meson.build b/raysect/optical/library/metal/data/meson.build index 47237562..76d54742 100644 --- a/raysect/optical/library/metal/data/meson.build +++ b/raysect/optical/library/metal/data/meson.build @@ -7,7 +7,7 @@ target_path = 'raysect/optical/library/metal/data' py_files = ['convert_data.py'] pyx_files = [] pxd_files = [] -data_files = ['cobolt.json', 'tungsten.json', 'magnesium.json', 'sodium.json', 'silver.json', 'silicon.json', 'iron.json', 'palladium.json', 'gold.json', 'beryllium.json', 'lithium.json', 'copper.json', 'platinum.json', 'mercury.json', 'manganese.json', 'aluminium.json', 'titanium.json', 'nickel.json'] +data_files = ['aluminium.json', 'beryllium.json', 'cobolt.json', 'copper.json', 'gold.json', 'iron.json', 'lithium.json', 'magnesium.json', 'manganese.json', 'mercury.json', 'nickel.json', 'palladium.json', 'platinum.json', 'silicon.json', 'silver.json', 'sodium.json', 'titanium.json', 'tungsten.json'] # compile cython foreach pyx_file: pyx_files diff --git a/raysect/optical/library/metal/meson.build b/raysect/optical/library/metal/meson.build index f483ab87..fc4f84dc 100644 --- a/raysect/optical/library/metal/meson.build +++ b/raysect/optical/library/metal/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/optical/library/metal' # source files -py_files = ['metal.py', '__init__.py', 'roughmetal.py'] +py_files = ['__init__.py', 'metal.py', 'roughmetal.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/optical/material/emitter/meson.build b/raysect/optical/material/emitter/meson.build index d168f854..eef6b74e 100644 --- a/raysect/optical/material/emitter/meson.build +++ b/raysect/optical/material/emitter/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/optical/material/emitter' # source files py_files = ['__init__.py'] -pyx_files = ['checkerboard.pyx', 'uniform.pyx', 'anisotropic.pyx', 'unity.pyx', 'inhomogeneous.pyx', 'homogeneous.pyx'] -pxd_files = ['uniform.pxd', 'checkerboard.pxd', 'inhomogeneous.pxd', 'anisotropic.pxd', 'homogeneous.pxd', 'unity.pxd', '__init__.pxd'] +pyx_files = ['anisotropic.pyx', 'checkerboard.pyx', 'homogeneous.pyx', 'inhomogeneous.pyx', 'uniform.pyx', 'unity.pyx'] +pxd_files = ['__init__.pxd', 'anisotropic.pxd', 'checkerboard.pxd', 'homogeneous.pxd', 'inhomogeneous.pxd', 'uniform.pxd', 'unity.pxd'] data_files = [] # compile cython diff --git a/raysect/optical/material/meson.build b/raysect/optical/material/meson.build index 014874d6..472d4a2a 100644 --- a/raysect/optical/material/meson.build +++ b/raysect/optical/material/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/optical/material' # source files py_files = ['__init__.py'] -pyx_files = ['material.pyx', 'conductor.pyx', 'absorber.pyx', 'lambert.pyx', 'dielectric.pyx', 'debug.pyx'] -pxd_files = ['material.pxd', 'absorber.pxd', 'conductor.pxd', 'dielectric.pxd', 'debug.pxd', '__init__.pxd'] +pyx_files = ['absorber.pyx', 'conductor.pyx', 'debug.pyx', 'dielectric.pyx', 'lambert.pyx', 'material.pyx'] +pxd_files = ['__init__.pxd', 'absorber.pxd', 'conductor.pxd', 'debug.pxd', 'dielectric.pxd', 'material.pxd'] data_files = [] # compile cython diff --git a/raysect/optical/material/modifiers/meson.build b/raysect/optical/material/modifiers/meson.build index 74223ce0..cba3666c 100644 --- a/raysect/optical/material/modifiers/meson.build +++ b/raysect/optical/material/modifiers/meson.build @@ -5,7 +5,7 @@ target_path = 'raysect/optical/material/modifiers' # source files py_files = ['__init__.py'] -pyx_files = ['roughen.pyx', 'add.pyx', 'blend.pyx', 'transform.pyx'] +pyx_files = ['add.pyx', 'blend.pyx', 'roughen.pyx', 'transform.pyx'] pxd_files = [] data_files = [] diff --git a/raysect/optical/meson.build b/raysect/optical/meson.build index e756f95f..6d763bbd 100644 --- a/raysect/optical/meson.build +++ b/raysect/optical/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/optical' # source files py_files = ['__init__.py'] -pyx_files = ['loggingray.pyx', 'spectralfunction.pyx', 'colour.pyx', 'spectrum.pyx', 'ray.pyx'] -pxd_files = ['colour.pxd', 'ray.pxd', 'spectralfunction.pxd', 'spectrum.pxd', '__init__.pxd'] +pyx_files = ['colour.pyx', 'loggingray.pyx', 'ray.pyx', 'spectralfunction.pyx', 'spectrum.pyx'] +pxd_files = ['__init__.pxd', 'colour.pxd', 'ray.pxd', 'spectralfunction.pxd', 'spectrum.pxd'] data_files = [] # compile cython @@ -27,7 +27,7 @@ py.install_sources( subdir: target_path ) +subdir('library') subdir('material') -subdir('scenegraph') subdir('observer') -subdir('library') +subdir('scenegraph') diff --git a/raysect/optical/observer/base/meson.build b/raysect/optical/observer/base/meson.build index 78700e06..d8495643 100644 --- a/raysect/optical/observer/base/meson.build +++ b/raysect/optical/observer/base/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/optical/observer/base' # source files py_files = ['__init__.py'] -pyx_files = ['processor.pyx', 'slice.pyx', 'observer.pyx', 'sampler.pyx', 'pipeline.pyx'] -pxd_files = ['pipeline.pxd', 'observer.pxd', 'slice.pxd', 'processor.pxd', '__init__.pxd', 'sampler.pxd'] +pyx_files = ['observer.pyx', 'pipeline.pyx', 'processor.pyx', 'sampler.pyx', 'slice.pyx'] +pxd_files = ['__init__.pxd', 'observer.pxd', 'pipeline.pxd', 'processor.pxd', 'sampler.pxd', 'slice.pxd'] data_files = [] # compile cython diff --git a/raysect/optical/observer/imaging/meson.build b/raysect/optical/observer/imaging/meson.build index 200bb394..f20ce2f6 100644 --- a/raysect/optical/observer/imaging/meson.build +++ b/raysect/optical/observer/imaging/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/optical/observer/imaging' # source files py_files = ['__init__.py'] -pyx_files = ['targetted_ccd.pyx', 'ccd.pyx', 'vector.pyx', 'pinhole.pyx', 'opencv.pyx', 'orthographic.pyx'] -pxd_files = ['targetted_ccd.pxd', 'ccd.pxd', 'orthographic.pxd', 'vector.pxd', 'pinhole.pxd', 'opencv.pxd', '__init__.pxd'] +pyx_files = ['ccd.pyx', 'opencv.pyx', 'orthographic.pyx', 'pinhole.pyx', 'targetted_ccd.pyx', 'vector.pyx'] +pxd_files = ['__init__.pxd', 'ccd.pxd', 'opencv.pxd', 'orthographic.pxd', 'pinhole.pxd', 'targetted_ccd.pxd', 'vector.pxd'] data_files = [] # compile cython diff --git a/raysect/optical/observer/nonimaging/meson.build b/raysect/optical/observer/nonimaging/meson.build index 6cea95cf..8df56834 100644 --- a/raysect/optical/observer/nonimaging/meson.build +++ b/raysect/optical/observer/nonimaging/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/optical/observer/nonimaging' # source files py_files = ['__init__.py'] -pyx_files = ['pixel.pyx', 'mesh_camera.pyx', 'fibreoptic.pyx', 'mesh_pixel.pyx', 'sightline.pyx', 'targetted_pixel.pyx'] -pxd_files = ['pixel.pxd', 'mesh_camera.pxd', 'sightline.pxd', 'mesh_pixel.pxd', 'fibreoptic.pxd', '__init__.pxd', 'targetted_pixel.pxd'] +pyx_files = ['fibreoptic.pyx', 'mesh_camera.pyx', 'mesh_pixel.pyx', 'pixel.pyx', 'sightline.pyx', 'targetted_pixel.pyx'] +pxd_files = ['__init__.pxd', 'fibreoptic.pxd', 'mesh_camera.pxd', 'mesh_pixel.pxd', 'pixel.pxd', 'sightline.pxd', 'targetted_pixel.pxd'] data_files = [] # compile cython diff --git a/raysect/optical/observer/pipeline/meson.build b/raysect/optical/observer/pipeline/meson.build index 86060081..c5d6f780 100644 --- a/raysect/optical/observer/pipeline/meson.build +++ b/raysect/optical/observer/pipeline/meson.build @@ -4,9 +4,9 @@ target_path = 'raysect/optical/observer/pipeline' # source files -py_files = ['colormaps.py', '__init__.py'] -pyx_files = ['rgb.pyx', 'bayer.pyx'] -pxd_files = ['bayer.pxd', 'rgb.pxd', '__init__.pxd'] +py_files = ['__init__.py', 'colormaps.py'] +pyx_files = ['bayer.pyx', 'rgb.pyx'] +pxd_files = ['__init__.pxd', 'bayer.pxd', 'rgb.pxd'] data_files = [] # compile cython diff --git a/raysect/optical/observer/pipeline/mono/meson.build b/raysect/optical/observer/pipeline/mono/meson.build index 857d71e5..93907700 100644 --- a/raysect/optical/observer/pipeline/mono/meson.build +++ b/raysect/optical/observer/pipeline/mono/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/optical/observer/pipeline/mono' # source files py_files = ['__init__.py'] -pyx_files = ['radiance.pyx', 'power.pyx'] -pxd_files = ['power.pxd', 'radiance.pxd', '__init__.pxd'] +pyx_files = ['power.pyx', 'radiance.pyx'] +pxd_files = ['__init__.pxd', 'power.pxd', 'radiance.pxd'] data_files = [] # compile cython diff --git a/raysect/optical/observer/pipeline/spectral/meson.build b/raysect/optical/observer/pipeline/spectral/meson.build index 4bdb09f2..b24d7a74 100644 --- a/raysect/optical/observer/pipeline/spectral/meson.build +++ b/raysect/optical/observer/pipeline/spectral/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/optical/observer/pipeline/spectral' # source files py_files = ['__init__.py'] -pyx_files = ['radiance.pyx', 'power.pyx'] -pxd_files = ['power.pxd', 'radiance.pxd', '__init__.pxd'] +pyx_files = ['power.pyx', 'radiance.pyx'] +pxd_files = ['__init__.pxd', 'power.pxd', 'radiance.pxd'] data_files = [] # compile cython diff --git a/raysect/optical/scenegraph/meson.build b/raysect/optical/scenegraph/meson.build index fb505ffc..b0f90099 100644 --- a/raysect/optical/scenegraph/meson.build +++ b/raysect/optical/scenegraph/meson.build @@ -6,7 +6,7 @@ target_path = 'raysect/optical/scenegraph' # source files py_files = ['__init__.py'] pyx_files = ['world.pyx'] -pxd_files = ['world.pxd', '__init__.pxd'] +pxd_files = ['__init__.pxd', 'world.pxd'] data_files = [] # compile cython diff --git a/raysect/primitive/lens/tests/meson.build b/raysect/primitive/lens/tests/meson.build index e6b0cea8..04c5ead0 100644 --- a/raysect/primitive/lens/tests/meson.build +++ b/raysect/primitive/lens/tests/meson.build @@ -4,7 +4,7 @@ target_path = 'raysect/primitive/lens/tests' # source files -py_files = ['test_spherical.py', '__init__.py'] +py_files = ['__init__.py', 'test_spherical.py'] pyx_files = [] pxd_files = [] data_files = [] diff --git a/raysect/primitive/mesh/meson.build b/raysect/primitive/mesh/meson.build index 59d33f4c..67ec049a 100644 --- a/raysect/primitive/mesh/meson.build +++ b/raysect/primitive/mesh/meson.build @@ -4,9 +4,9 @@ target_path = 'raysect/primitive/mesh' # source files -py_files = ['vtk.py', 'ply.py', 'obj.py', '__init__.py', 'stl.py'] +py_files = ['__init__.py', 'obj.py', 'ply.py', 'stl.py', 'vtk.py'] pyx_files = ['mesh.pyx'] -pxd_files = ['mesh.pxd', '__init__.pxd'] +pxd_files = ['__init__.pxd', 'mesh.pxd'] data_files = [] # compile cython diff --git a/raysect/primitive/meson.build b/raysect/primitive/meson.build index 44b24be2..6dab2462 100644 --- a/raysect/primitive/meson.build +++ b/raysect/primitive/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/primitive' # source files py_files = ['__init__.py'] -pyx_files = ['sphere.pyx', 'csg.pyx', 'box.pyx', 'cylinder.pyx', 'cone.pyx', 'utility.pyx', 'parabola.pyx'] -pxd_files = ['parabola.pxd', 'cone.pxd', 'csg.pxd', 'box.pxd', 'utility.pxd', 'sphere.pxd', '__init__.pxd', 'cylinder.pxd'] +pyx_files = ['box.pyx', 'cone.pyx', 'csg.pyx', 'cylinder.pyx', 'parabola.pyx', 'sphere.pyx', 'utility.pyx'] +pxd_files = ['__init__.pxd', 'box.pxd', 'cone.pxd', 'csg.pxd', 'cylinder.pxd', 'parabola.pxd', 'sphere.pxd', 'utility.pxd'] data_files = [] # compile cython From 3d1ee34972b04d58230897cf52bd09b29675a5e9 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Tue, 22 Jul 2025 19:03:48 +0100 Subject: [PATCH 67/77] Regenerated meson.build files using new generator. --- raysect/core/math/sampler/meson.build | 4 ++-- raysect/optical/observer/imaging/meson.build | 4 ++-- raysect/optical/observer/nonimaging/meson.build | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/raysect/core/math/sampler/meson.build b/raysect/core/math/sampler/meson.build index c6b49aff..6fea0bca 100644 --- a/raysect/core/math/sampler/meson.build +++ b/raysect/core/math/sampler/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/core/math/sampler' # source files py_files = ['__init__.py'] -pyx_files = ['solidangle.pyx', 'surface3d.pyx', 'targetted.pyx'] -pxd_files = ['__init__.pxd', 'solidangle.pxd', 'surface3d.pxd', 'targetted.pxd'] +pyx_files = ['solidangle.pyx', 'surface3d.pyx', 'targeted.pyx'] +pxd_files = ['__init__.pxd', 'solidangle.pxd', 'surface3d.pxd', 'targeted.pxd'] data_files = [] # compile cython diff --git a/raysect/optical/observer/imaging/meson.build b/raysect/optical/observer/imaging/meson.build index f20ce2f6..85a7aaa6 100644 --- a/raysect/optical/observer/imaging/meson.build +++ b/raysect/optical/observer/imaging/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/optical/observer/imaging' # source files py_files = ['__init__.py'] -pyx_files = ['ccd.pyx', 'opencv.pyx', 'orthographic.pyx', 'pinhole.pyx', 'targetted_ccd.pyx', 'vector.pyx'] -pxd_files = ['__init__.pxd', 'ccd.pxd', 'opencv.pxd', 'orthographic.pxd', 'pinhole.pxd', 'targetted_ccd.pxd', 'vector.pxd'] +pyx_files = ['ccd.pyx', 'opencv.pyx', 'orthographic.pyx', 'pinhole.pyx', 'targeted_ccd.pyx', 'vector.pyx'] +pxd_files = ['__init__.pxd', 'ccd.pxd', 'opencv.pxd', 'orthographic.pxd', 'pinhole.pxd', 'targeted_ccd.pxd', 'vector.pxd'] data_files = [] # compile cython diff --git a/raysect/optical/observer/nonimaging/meson.build b/raysect/optical/observer/nonimaging/meson.build index 8df56834..6ba3a60d 100644 --- a/raysect/optical/observer/nonimaging/meson.build +++ b/raysect/optical/observer/nonimaging/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/optical/observer/nonimaging' # source files py_files = ['__init__.py'] -pyx_files = ['fibreoptic.pyx', 'mesh_camera.pyx', 'mesh_pixel.pyx', 'pixel.pyx', 'sightline.pyx', 'targetted_pixel.pyx'] -pxd_files = ['__init__.pxd', 'fibreoptic.pxd', 'mesh_camera.pxd', 'mesh_pixel.pxd', 'pixel.pxd', 'sightline.pxd', 'targetted_pixel.pxd'] +pyx_files = ['fibreoptic.pyx', 'mesh_camera.pyx', 'mesh_pixel.pyx', 'pixel.pyx', 'sightline.pyx', 'targeted_pixel.pyx'] +pxd_files = ['__init__.pxd', 'fibreoptic.pxd', 'mesh_camera.pxd', 'mesh_pixel.pxd', 'pixel.pxd', 'sightline.pxd', 'targeted_pixel.pxd'] data_files = [] # compile cython From 22aa40c83e13e694ec0e62ff43068521229c23e4 Mon Sep 17 00:00:00 2001 From: munechika-koyo Date: Tue, 22 Jul 2025 14:25:18 -0400 Subject: [PATCH 68/77] Add torus.pyx and torus.pxd to source files in meson.build --- raysect/primitive/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raysect/primitive/meson.build b/raysect/primitive/meson.build index 6dab2462..1f14a347 100644 --- a/raysect/primitive/meson.build +++ b/raysect/primitive/meson.build @@ -5,8 +5,8 @@ target_path = 'raysect/primitive' # source files py_files = ['__init__.py'] -pyx_files = ['box.pyx', 'cone.pyx', 'csg.pyx', 'cylinder.pyx', 'parabola.pyx', 'sphere.pyx', 'utility.pyx'] -pxd_files = ['__init__.pxd', 'box.pxd', 'cone.pxd', 'csg.pxd', 'cylinder.pxd', 'parabola.pxd', 'sphere.pxd', 'utility.pxd'] +pyx_files = ['box.pyx', 'cone.pyx', 'csg.pyx', 'cylinder.pyx', 'parabola.pyx', 'sphere.pyx', 'torus.pyx', 'utility.pyx'] +pxd_files = ['__init__.pxd', 'box.pxd', 'cone.pxd', 'csg.pxd', 'cylinder.pxd', 'parabola.pxd', 'sphere.pxd', 'torus.pxd', 'utility.pxd'] data_files = [] # compile cython From 462e055ae38059315ce85bd78de20f4f905dcd8c Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Tue, 22 Jul 2025 19:32:39 +0100 Subject: [PATCH 69/77] Updated changelog, adding thanks to @munechika-koyo for their cython 3 contributions. --- CHANGELOG.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index afb6b336..5f24eca2 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -12,6 +12,14 @@ Build changes: - A dev/install_editable.sh script is provided to simplify the installation of the package in editable mode with verbose build output enabled. - Meson-python performs the build out of the project folder, so the project source folders will no longer be polluted with build artefacts. - Cython build annotations are now always enabled, the annotation files can be found in the build folder under the build artefacts folder associated with each so file (*.so.p folder). +* The codebase has been migrated to Cython 3. + - Thanks to the contributions of @munechika-koyo Raysect is now moved to Cython 3. + - Raysect can now benefit from the improvements added to cython since 0.29.x. + +Python support: +* Raysect now requires Python v3.9 and above. + - From version v0.9.0 Raysect is dropping support for Python versions older than v3.9 + - Users of older versions of Python should remain using Raysect v0.8.1 and plan for a migration to v0.9.0+. API changes: * Corrected spelling of all classes, methods and functions where "targeted" was incorrectly spelt "targetted". From 10f93808a46894e07a4d14b6a4542e3bc639088b Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Tue, 22 Jul 2025 19:40:48 +0100 Subject: [PATCH 70/77] Updated changelog with torus primitive. Small code tidy / copyright update. --- CHANGELOG.txt | 5 ++++- raysect/core/math/cython/utility.pyx | 2 ++ raysect/primitive/torus.pxd | 2 +- raysect/primitive/torus.pyx | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 5f24eca2..f7d8d144 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -13,7 +13,7 @@ Build changes: - Meson-python performs the build out of the project folder, so the project source folders will no longer be polluted with build artefacts. - Cython build annotations are now always enabled, the annotation files can be found in the build folder under the build artefacts folder associated with each so file (*.so.p folder). * The codebase has been migrated to Cython 3. - - Thanks to the contributions of @munechika-koyo Raysect is now moved to Cython 3. + - Thanks to the contributions of Koyo Munechika (@munechika-koyo) Raysect is now moved to Cython 3. - Raysect can now benefit from the improvements added to cython since 0.29.x. Python support: @@ -24,6 +24,9 @@ Python support: API changes: * Corrected spelling of all classes, methods and functions where "targeted" was incorrectly spelt "targetted". +New: +* Added a torus primitive (contribute by Koyo Munechika) + Release 0.8.1 (12 Feb 2023) --------------------------- diff --git a/raysect/core/math/cython/utility.pyx b/raysect/core/math/cython/utility.pyx index 693ea8c7..a0cd8b32 100644 --- a/raysect/core/math/cython/utility.pyx +++ b/raysect/core/math/cython/utility.pyx @@ -867,6 +867,7 @@ def _point_inside_polygon(vertices, ptx, pty): """Expose cython function for testing.""" return point_inside_polygon(vertices, ptx, pty) + def _solve_cubic(a, b, c, d): """Expose cython function for testing.""" t0 = 0.0 @@ -877,6 +878,7 @@ def _solve_cubic(a, b, c, d): return (t0, t1, t2, num) + def _solve_quartic(a, b, c, d, e): """Expose cython function for testing.""" t0 = 0.0 diff --git a/raysect/primitive/torus.pxd b/raysect/primitive/torus.pxd index 9643552b..80de4d7c 100644 --- a/raysect/primitive/torus.pxd +++ b/raysect/primitive/torus.pxd @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2021, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/raysect/primitive/torus.pyx b/raysect/primitive/torus.pyx index 0b9818aa..d839e05d 100644 --- a/raysect/primitive/torus.pyx +++ b/raysect/primitive/torus.pyx @@ -1,6 +1,6 @@ # cython: language_level=3 -# Copyright (c) 2014-2021, Dr Alex Meakins, Raysect Project +# Copyright (c) 2014-2025, Dr Alex Meakins, Raysect Project # All rights reserved. # # Redistribution and use in source and binary forms, with or without From 66b7df361ae41353caf28810c02cb4e0d4768e37 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Tue, 22 Jul 2025 23:12:06 +0100 Subject: [PATCH 71/77] Moved to setuptools-scm for automatic versioning. --- .gitignore | 4 +++- dev/root-meson.build | 5 ++++- dev/update_version.sh | 2 ++ meson.build | 5 ++++- pyproject.toml | 7 +++++-- raysect/VERSION | 1 - raysect/__init__.py | 6 +----- raysect/meson.build | 4 ++-- 8 files changed, 21 insertions(+), 13 deletions(-) create mode 100755 dev/update_version.sh delete mode 100644 raysect/VERSION diff --git a/.gitignore b/.gitignore index 590e1871..9da6040c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ __pycache__/ # C extensions *.so *.c + # Distribution / packaging .Python env/ @@ -59,4 +60,5 @@ docs/build/ # PyBuilder target/ - +# setuptools-scm +raysect/_version.py diff --git a/dev/root-meson.build b/dev/root-meson.build index 98253b94..f77fa44a 100644 --- a/dev/root-meson.build +++ b/dev/root-meson.build @@ -1,4 +1,7 @@ -project('raysect', 'cython', default_options: ['python.install-env=auto']) +project('raysect', 'cython', + default_options: ['python.install-env=auto'], + version: run_command(['dev/update_version.sh'], check: true).stdout().strip() +) py = import('python').find_installation(pure: false) numpy = dependency('numpy', method: 'config-tool') diff --git a/dev/update_version.sh b/dev/update_version.sh new file mode 100755 index 00000000..f167ea3a --- /dev/null +++ b/dev/update_version.sh @@ -0,0 +1,2 @@ +#!/bin/bash +python -m setuptools_scm --force-write-version-files diff --git a/meson.build b/meson.build index 3a2b2cf0..7c309e19 100644 --- a/meson.build +++ b/meson.build @@ -1,7 +1,10 @@ # WARNING: This file is automatically generated by dev/generate_meson_files.py. # The template file used to generate this file is dev/root-meson.build. -project('raysect', 'cython', default_options: ['python.install-env=auto']) +project('raysect', 'cython', + default_options: ['python.install-env=auto'], + version: run_command(['dev/update_version.sh'], check: true).stdout().strip() +) py = import('python').find_installation(pure: false) numpy = dependency('numpy', method: 'config-tool') diff --git a/pyproject.toml b/pyproject.toml index b16903e1..4998820c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,5 @@ [project] name = "raysect" -version = "0.9.0" requires-python = ">=3.9" authors = [{name = "Dr Alex Meakins et al.", email = "developers@raysect.org"}] description = "A Ray-tracing Framework for Science and Engineering" @@ -19,6 +18,7 @@ classifiers = [ "Topic :: Multimedia :: Graphics :: 3D Rendering", "Topic :: Scientific/Engineering :: Physics" ] +dynamic = ["version"] [project.urls] Homepage = "https://www.raysect.org" @@ -27,5 +27,8 @@ Issues = "https://github.com/raysect/source/issues" Changelog = "https://github.com/raysect/source/blob/master/CHANGELOG.txt" [build-system] -requires = ["meson-python", "setuptools", "wheel", "numpy", "cython>=3.1"] +requires = ["meson-python", "setuptools", "wheel", "numpy", "cython>=3.1", "setuptools-scm"] build-backend = "mesonpy" + +[tool.setuptools_scm] +version_file = "raysect/_version.py" \ No newline at end of file diff --git a/raysect/VERSION b/raysect/VERSION deleted file mode 100644 index 899f24fc..00000000 --- a/raysect/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.9.0 \ No newline at end of file diff --git a/raysect/__init__.py b/raysect/__init__.py index 0e09c122..27213b8b 100644 --- a/raysect/__init__.py +++ b/raysect/__init__.py @@ -27,8 +27,4 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from os import path as _path - -# parse the package version number -with open(_path.join(_path.dirname(__file__), 'VERSION')) as _f: - __version__ = _f.read().strip() +from ._version import * diff --git a/raysect/meson.build b/raysect/meson.build index 4f55056a..eb654f3a 100644 --- a/raysect/meson.build +++ b/raysect/meson.build @@ -4,10 +4,10 @@ target_path = 'raysect' # source files -py_files = ['__init__.py'] +py_files = ['__init__.py', '_version.py'] pyx_files = [] pxd_files = [] -data_files = ['VERSION'] +data_files = [] # compile cython foreach pyx_file: pyx_files From 027aca822cff2653a34b82050c35b58813bdc0ad Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Tue, 22 Jul 2025 23:16:10 +0100 Subject: [PATCH 72/77] Add setuptools-scm to dependencies. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af832884..9734d021 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install Python dependencies - run: python -m pip install --prefer-binary meson-python meson ninja setuptools "cython>=3.1" "matplotlib>=3,<4" ${{ matrix.numpy-version }} + run: python -m pip install --prefer-binary meson-python meson ninja setuptools setuptools-scm "cython>=3.1" "matplotlib>=3,<4" ${{ matrix.numpy-version }} - name: Build and install Raysect run: dev/install_editable.sh - name: Run tests From 24e6c5efae75709a3dc00a6c5ef77e7048c2d8c9 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Tue, 22 Jul 2025 23:28:17 +0100 Subject: [PATCH 73/77] Update copyright date. --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index d99fdd5f..206b8bf6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -52,7 +52,7 @@ # General information about the project. project = 'Raysect' -copyright = '2014-2023, Dr Alex Meakins' +copyright = '2014-2025, Dr Alex Meakins' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the From 2effc00e89bcbf976847a17b6e62eed4241b4013 Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Tue, 22 Jul 2025 23:30:49 +0100 Subject: [PATCH 74/77] Added missing full stops. --- CHANGELOG.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index f7d8d144..92dcb6ad 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -18,14 +18,14 @@ Build changes: Python support: * Raysect now requires Python v3.9 and above. - - From version v0.9.0 Raysect is dropping support for Python versions older than v3.9 + - From version v0.9.0 Raysect is dropping support for Python versions older than v3.9. - Users of older versions of Python should remain using Raysect v0.8.1 and plan for a migration to v0.9.0+. API changes: * Corrected spelling of all classes, methods and functions where "targeted" was incorrectly spelt "targetted". New: -* Added a torus primitive (contribute by Koyo Munechika) +* Added a torus primitive (contribute by Koyo Munechika). Release 0.8.1 (12 Feb 2023) From 17c48e291d3059ba7c066b470fc1969ba64b9c7a Mon Sep 17 00:00:00 2001 From: Dr Alex Meakins Date: Tue, 22 Jul 2025 23:38:38 +0100 Subject: [PATCH 75/77] Refined notes on cython 3 migration. --- CHANGELOG.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 92dcb6ad..6ceb55db 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -13,8 +13,7 @@ Build changes: - Meson-python performs the build out of the project folder, so the project source folders will no longer be polluted with build artefacts. - Cython build annotations are now always enabled, the annotation files can be found in the build folder under the build artefacts folder associated with each so file (*.so.p folder). * The codebase has been migrated to Cython 3. - - Thanks to the contributions of Koyo Munechika (@munechika-koyo) Raysect is now moved to Cython 3. - - Raysect can now benefit from the improvements added to cython since 0.29.x. + - This migration was made possible due to the contributions of Koyo Munechika (@munechika-koyo). Python support: * Raysect now requires Python v3.9 and above. From 8e275b3f2285d8507222a1c03612c2e6aa416fe2 Mon Sep 17 00:00:00 2001 From: CnlPepper Date: Sun, 27 Jul 2025 22:43:25 +0100 Subject: [PATCH 76/77] Added meson dist script to copy the dynamically generated _version.py into the release distribution. Moved build related scripts (run automatically as part of the build process) out of the root of dev/. --- dev/abi.py | 50 ------------------------------------------- dev/build.sh | 2 +- dev/build_docs.sh | 3 --- dev/clean.sh | 2 +- dev/root-meson.build | 5 ++++- dev/update_version.sh | 2 -- meson.build | 5 ++++- 7 files changed, 10 insertions(+), 59 deletions(-) delete mode 100755 dev/abi.py delete mode 100755 dev/update_version.sh diff --git a/dev/abi.py b/dev/abi.py deleted file mode 100755 index ba72d14a..00000000 --- a/dev/abi.py +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/env python - -import sys -import sysconfig -from typing import Union - -""" -Derived from the meson-python codebase. Original license terms: - -Copyright © 2022 the meson-python contributors -Copyright © 2021 Quansight Labs and Filipe Laíns - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice (including the next -paragraph) shall be included in all copies or substantial portions of the -Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. -""" - - -def get_cpython_abi() -> str: - version = sys.version_info - debug = pymalloc = '' - if version < (3, 8) and _get_config_var('WITH_PYMALLOC', True): - pymalloc = 'm' - return f'cp{version[0]}{version[1]}{debug}{pymalloc}' - - -def _get_config_var(name: str, default: Union[str, int, None] = None) -> Union[str, int, None]: - value: Union[str, int, None] = sysconfig.get_config_var(name) - if value is None: - return default - return value - - -if __name__ == '__main__': - print(get_cpython_abi()) diff --git a/dev/build.sh b/dev/build.sh index 0cbd4336..c5bff15a 100755 --- a/dev/build.sh +++ b/dev/build.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e # exit if an error occurs -BUILD_PATH="build/`dev/abi.py`" +BUILD_PATH="build/`dev/build/abi.py`" echo Rebuilding $BUILD_PATH... meson compile -C $BUILD_PATH diff --git a/dev/build_docs.sh b/dev/build_docs.sh index a104fba8..115dd101 100755 --- a/dev/build_docs.sh +++ b/dev/build_docs.sh @@ -1,13 +1,10 @@ #!/bin/bash echo Building code... - dev/build.sh echo Building docs... - export PYTHONPATH=../:$PYTHONPATH cd docs - make html diff --git a/dev/clean.sh b/dev/clean.sh index a6a5ca57..10e86408 100755 --- a/dev/clean.sh +++ b/dev/clean.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e # exit if an error occurs -BUILD_PATH="build/`dev/abi.py`" +BUILD_PATH="build/`dev/build/abi.py`" echo Cleaning $BUILD_PATH... meson compile -C $BUILD_PATH --clean diff --git a/dev/root-meson.build b/dev/root-meson.build index f77fa44a..a3034a21 100644 --- a/dev/root-meson.build +++ b/dev/root-meson.build @@ -1,8 +1,11 @@ project('raysect', 'cython', default_options: ['python.install-env=auto'], - version: run_command(['dev/update_version.sh'], check: true).stdout().strip() + version: run_command(['dev/build/update_version.sh'], check: true).stdout().strip() ) +# when building a distribution, copy the dynamically generated _version.py to the distribution folder +meson.add_dist_script('dev/build/add_version_file_to_dist.sh') + py = import('python').find_installation(pure: false) numpy = dependency('numpy', method: 'config-tool') fs = import('fs') diff --git a/dev/update_version.sh b/dev/update_version.sh deleted file mode 100755 index f167ea3a..00000000 --- a/dev/update_version.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -python -m setuptools_scm --force-write-version-files diff --git a/meson.build b/meson.build index 7c309e19..62bcf36f 100644 --- a/meson.build +++ b/meson.build @@ -3,9 +3,12 @@ project('raysect', 'cython', default_options: ['python.install-env=auto'], - version: run_command(['dev/update_version.sh'], check: true).stdout().strip() + version: run_command(['dev/build/update_version.sh'], check: true).stdout().strip() ) +# when building a distribution, copy the dynamically generated _version.py to the distribution folder +meson.add_dist_script('dev/build/add_version_file_to_dist.sh') + py = import('python').find_installation(pure: false) numpy = dependency('numpy', method: 'config-tool') fs = import('fs') From 8b9181d9dd745f8594b24723af0e2ee810093c0f Mon Sep 17 00:00:00 2001 From: CnlPepper Date: Sun, 27 Jul 2025 22:57:57 +0100 Subject: [PATCH 77/77] Added files accidentally excluded by .gitignore. Updated .gitignore (!). --- .gitignore | 2 +- dev/build/abi.py | 50 +++++++++++++++++++++++++++ dev/build/add_version_file_to_dist.sh | 19 ++++++++++ dev/build/update_version.sh | 2 ++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100755 dev/build/abi.py create mode 100755 dev/build/add_version_file_to_dist.sh create mode 100755 dev/build/update_version.sh diff --git a/.gitignore b/.gitignore index 9da6040c..5438adae 100644 --- a/.gitignore +++ b/.gitignore @@ -8,10 +8,10 @@ __pycache__/ *.c # Distribution / packaging +/build/ .Python env/ venv/ -build/ develop-eggs/ dist/ downloads/ diff --git a/dev/build/abi.py b/dev/build/abi.py new file mode 100755 index 00000000..ba72d14a --- /dev/null +++ b/dev/build/abi.py @@ -0,0 +1,50 @@ +#!/bin/env python + +import sys +import sysconfig +from typing import Union + +""" +Derived from the meson-python codebase. Original license terms: + +Copyright © 2022 the meson-python contributors +Copyright © 2021 Quansight Labs and Filipe Laíns + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + + +def get_cpython_abi() -> str: + version = sys.version_info + debug = pymalloc = '' + if version < (3, 8) and _get_config_var('WITH_PYMALLOC', True): + pymalloc = 'm' + return f'cp{version[0]}{version[1]}{debug}{pymalloc}' + + +def _get_config_var(name: str, default: Union[str, int, None] = None) -> Union[str, int, None]: + value: Union[str, int, None] = sysconfig.get_config_var(name) + if value is None: + return default + return value + + +if __name__ == '__main__': + print(get_cpython_abi()) diff --git a/dev/build/add_version_file_to_dist.sh b/dev/build/add_version_file_to_dist.sh new file mode 100755 index 00000000..dd95ae3b --- /dev/null +++ b/dev/build/add_version_file_to_dist.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e # exit if an error occurs + +if [[ -z "$MESON_SOURCE_ROOT" ]]; then + echo "ERROR: Must be run inside a meson build environment where the variable MESON_SOURCE_ROOT is set." 1>&2 + exit 1 +fi + +if [[ -z "$MESON_DIST_ROOT" ]]; then + echo "ERROR: Must be run inside a meson build environment where the variable MESON_DIST_ROOT is set." 1>&2 + exit 1 +fi + +VERSION_PATH="raysect/_version.py" +SRC_PATH="$MESON_SOURCE_ROOT/$VERSION_PATH" +DIST_PATH="$MESON_DIST_ROOT/$VERSION_PATH" + +echo "Copying $VERSION_PATH to distribution path $DIST_PATH" +cp $SRC_PATH $DIST_PATH diff --git a/dev/build/update_version.sh b/dev/build/update_version.sh new file mode 100755 index 00000000..f167ea3a --- /dev/null +++ b/dev/build/update_version.sh @@ -0,0 +1,2 @@ +#!/bin/bash +python -m setuptools_scm --force-write-version-files