diff --git a/codegen/stub_generator.py b/codegen/stub_generator.py index dee9d82..267f747 100644 --- a/codegen/stub_generator.py +++ b/codegen/stub_generator.py @@ -111,7 +111,7 @@ def indent(src: list[str]) -> list[str]: return src stub = [ - "# noinspection SpellCheckingInspection", + "# noinspection SpellCheckingInspection,GrazieInspection", f"class {self.name}:" ] if self.docstring: @@ -278,6 +278,7 @@ def add_docstring_line(doc_line: str): classes.append(current_class) out_lines = [ + "# noinspection PyUnresolvedReferences", "from typing import overload, Self, Any, Union", "" ] diff --git a/codegen/templates/_gdmath.pyx b/codegen/templates/_gdmath.pyx index 2f260a1..b908a09 100644 --- a/codegen/templates/_gdmath.pyx +++ b/codegen/templates/_gdmath.pyx @@ -7,6 +7,8 @@ #cython: cdivision=True #cython: always_allow_keywords=True #cython: optimize.use_switch=True +#cython: embedsignature=True +#cython: embedsignature.format=c cimport cython diff --git a/codegen/templates/common_binary_and_inplace_op.pyx b/codegen/templates/common_binary_and_inplace_op.pyx index 9543735..135e97a 100644 --- a/codegen/templates/common_binary_and_inplace_op.pyx +++ b/codegen/templates/common_binary_and_inplace_op.pyx @@ -6,12 +6,14 @@ ctypedef _vTypeC_ # # cdef _VecClassName_ ___OpName___(self, _VecClassName_ other): + """Element-wise _OpReadableName_.""" cdef _VecClassName_ vec = _VecClassName_.__new__(_VecClassName_) #: gen_for_each_dim("vec.{dim} = self.{dim} _Op_ other.{dim}", _Dims_) return vec # cdef _VecClassName_ ___OpName___(self, _vTypeC_ other): + """Element-wise _OpReadableName_ with the same number for all elements.""" cdef _VecClassName_ vec = _VecClassName_.__new__(_VecClassName_) #: gen_for_each_dim("vec.{dim} = self.{dim} _Op_ other", _Dims_) return vec @@ -21,12 +23,14 @@ cdef _VecClassName_ ___OpName___(self, _vTypeC_ other): # cdef _VecClassName_ __i_OpName___(self, _VecClassName_ other): # + """Element-wise inplace _OpReadableName_.""" #: gen_for_each_dim("self.{dim} _Op_= other.{dim}", _Dims_) return self # cdef _VecClassName_ __i_OpName___(self, _vTypeC_ other): # + """Element-wise inplace _OpReadableName_ with the same number for all elements.""" #: gen_for_each_dim("self.{dim} _Op_= other", _Dims_) return self diff --git a/codegen/templates/transform_2d.pyx b/codegen/templates/transform_2d.pyx index a7dcf37..8343315 100644 --- a/codegen/templates/transform_2d.pyx +++ b/codegen/templates/transform_2d.pyx @@ -2,6 +2,7 @@ cimport cython # Dummy types for the IDE ctypedef py_float +ctypedef py_int cdef class Vec2: cdef py_float x, y @@ -15,6 +16,8 @@ from libc.math cimport atan2l, sinl, cosl, sqrtl, NAN @cython.no_gc @cython.final cdef class Transform2D: + """2D linear transformation (2*3 matrix).""" + cdef py_float xx, xy cdef py_float yx, yy cdef py_float ox, oy @@ -27,10 +30,12 @@ cdef class Transform2D: # cdef void __init__(self) noexcept: + """Create an identity transform.""" self.identity() # cdef void __init__(self, py_float xx, py_float xy, py_float yx, py_float yy, py_float ox, py_float oy) noexcept: + """Create a transform from all the matrix elements.""" self.xx = xx self.xy = xy self.yx = yx @@ -40,12 +45,14 @@ cdef class Transform2D: # cdef void __init__(self, Vec2 x, Vec2 y, Vec2 origin) noexcept: + """Create a transform using two base vectors and the origin vector.""" self.xx, self.xy = x.x, x.y self.yx, self.yy = y.x, y.y self.ox, self.oy = origin.x, origin.y # cdef void __init__(self, Transform2D transform) noexcept: + """Create a copy.""" self.xx = transform.xx self.xy = transform.xy self.yx = transform.yx @@ -57,6 +64,7 @@ cdef class Transform2D: @staticmethod def translating(Vec2 translation, /) -> Transform2D: + """Create a translation transform.""" cdef Transform2D t = Transform2D.__new__(Transform2D) t.identity() t.ox, t.oy = translation.x, translation.y @@ -64,6 +72,7 @@ cdef class Transform2D: @staticmethod def rotating(py_float rotation, /, Vec2 origin = None) -> Transform2D: + """Create a rotation transform.""" cdef py_float c = cosl(rotation) cdef py_float s = sinl(rotation) cdef Transform2D t = Transform2D.__new__(Transform2D) @@ -77,6 +86,7 @@ cdef class Transform2D: @staticmethod def scaling(Vec2 scale, /, Vec2 origin = None) -> Transform2D: + """Create a scale transform.""" cdef Transform2D t = Transform2D.__new__(Transform2D) t.xx = scale.x t.yy = scale.y @@ -98,6 +108,10 @@ cdef class Transform2D: return f"⎡X: ({self.xx}, {self.xy})\n⎢Y: ({self.yx}, {self.yy})\n⎣O: ({self.ox}, {self.oy})" def __eq__(self, object other) -> bool: + """Perform exact comparison. + + See Also: `Transform2D.is_close()` + """ if not isinstance(other, Transform2D): return False return self.xx == ( other).xx and self.xy == ( other).xy and\ @@ -105,6 +119,10 @@ cdef class Transform2D: self.ox == ( other).ox and self.oy == ( other).oy def __ne__(self, object other) -> bool: + """Perform exact comparison. + + See Also: `Transform2D.is_close()` + """ if not isinstance(other, Transform2D): return True return self.xx != ( other).xx or self.xy != ( other).xy or \ @@ -112,6 +130,10 @@ cdef class Transform2D: self.ox != ( other).ox or self.oy != ( other).oy def is_close(self, Transform2D other, /, py_float rel_tol = DEFAULT_RELATIVE_TOLERANCE, py_float abs_tol = DEFAULT_ABSOLUTE_TOLERANCE) -> bool: + """Determine if the two transforms are close enough. + + See Also: `math.is_close()` + """ return is_close(self.xx, other.xx, rel_tol, abs_tol) and \ is_close(self.xy, other.xy, rel_tol, abs_tol) and \ is_close(self.yx, other.yx, rel_tol, abs_tol) and \ @@ -132,6 +154,7 @@ cdef class Transform2D: @property def x(self) -> Vec2: + """Base vector X.""" cdef Vec2 vec = Vec2.__new__(Vec2) vec.x = self.xx vec.y = self.xy @@ -139,6 +162,7 @@ cdef class Transform2D: @property def y(self) -> Vec2: + """Base vector Y.""" cdef Vec2 vec = Vec2.__new__(Vec2) vec.x = self.yx vec.y = self.yy @@ -146,6 +170,7 @@ cdef class Transform2D: @property def origin(self) -> Vec2: + """Origin vector.""" cdef Vec2 vec = Vec2.__new__(Vec2) vec.x = self.ox vec.y = self.oy @@ -153,20 +178,29 @@ cdef class Transform2D: @x.setter def x(self, Vec2 value) -> None: + """Set the X base of the matrix.""" self.xx = value.x self.xy = value.y @y.setter def y(self, Vec2 value) -> None: + """Set the Y base of the matrix.""" self.yx = value.x self.yy = value.y @origin.setter def origin(self, Vec2 value) -> None: + """Set the origin.""" self.ox = value.x self.oy = value.y def __getitem__(self, py_int item) -> Vec2: + """Get a column of the transformation matrix. + + 0: Base vector X + 1: Base vector Y + 2: Origin vector + """ cdef Vec2 vec = Vec2.__new__(Vec2) if item == 0: @@ -184,6 +218,13 @@ cdef class Transform2D: return vec def __setitem__(self, py_int key, Vec2 value) -> None: + """Set a column of the transformation matrix. + + 0: Base vector X + 1: Base vector Y + 2: Origin vector + """ + if key == 0: self.xx = value.x self.xy = value.y @@ -197,6 +238,7 @@ cdef class Transform2D: raise IndexError(key) def __len__(self) -> py_int: + """The amount of columns. Is always 3 for `Transform2D`s.""" return 3 cdef inline py_float tdotx(self, py_float x, py_float y) noexcept: @@ -212,6 +254,7 @@ cdef class Transform2D: return self.tdoty(x, y) + self.oy def __mul__(self, Vec2 other) -> Vec2: + """Transform a copy of the vector.""" cdef Vec2 vec = Vec2.__new__(Vec2) vec.x = self.mulx(other.x, other.y) vec.y = self.muly(other.x, other.y) @@ -219,6 +262,7 @@ cdef class Transform2D: # cdef Vec2 __call__(self, Vec2 other): + """Transform a copy of the vector.""" cdef Vec2 vec = Vec2.__new__(Vec2) vec.x = self.mulx(other.x, other.y) vec.y = self.muly(other.x, other.y) @@ -226,6 +270,7 @@ cdef class Transform2D: # cdef Transform2D __call__(self, Transform2D other): + """Transform a copy of the passed in `Transform2D`.""" cdef Transform2D t = Transform2D.__new__(Transform2D) t.xx = self.tdotx(other.xx, other.xy) t.xy = self.tdoty(other.xx, other.xy) @@ -238,6 +283,7 @@ cdef class Transform2D: #:__call__ def __matmul__(self, Transform2D other) -> Transform2D: + """Transform a copy of the `Transform2D` on the right.""" cdef Transform2D t = Transform2D.__new__(Transform2D) t.xx = self.tdotx(other.xx, other.xy) t.xy = self.tdoty(other.xx, other.xy) @@ -249,6 +295,7 @@ cdef class Transform2D: def __imatmul__(self, Transform2D other) -> Transform2D: # + """Transform this `Transform2D` inplace with the other `Transform2D`.""" cdef py_float xx = other.tdotx(self.xx, self.xy) cdef py_float xy = other.tdoty(self.xx, self.xy) cdef py_float yx = other.tdotx(self.yx, self.yy) @@ -265,9 +312,11 @@ cdef class Transform2D: @property def determinant(self) -> py_float: + """Compute the determinant of the matrix.""" return self._determinant() def __invert__(self) -> Transform2D: + """Get the invert transform.""" cdef py_float i_det = 1.0 / self._determinant() cdef Transform2D t = Transform2D.__new__(Transform2D) t.xx = self.yy * +i_det @@ -314,10 +363,15 @@ cdef class Transform2D: @property def rotation(self) -> py_float: + """Get the rotation angle this transform represents.""" return self.get_rotation() @rotation.setter def rotation(self, py_float value) -> None: + """Set the rotation angle this transform represents. + + See Also: `Transform2D.rotating()` + """ self.set_rotation(value) # @property @@ -327,6 +381,7 @@ cdef class Transform2D: # return proxy @property def scale(self) -> Vec2: + """Get the scaling transformation this transform represents.""" cdef Vec2 vec = Vec2.__new__(Vec2) vec.x = self.get_scale_x() vec.y = self.get_scale_y() @@ -334,33 +389,42 @@ cdef class Transform2D: @scale.setter def scale(self, Vec2 value) -> None: + """Set the scaling transformation this transform represents. + + See Also: `Transform2D.scaling()` + """ self.set_scale_x(value.x) self.set_scale_y(value.y) def translate_ip(self, Vec2 translation, /) -> Transform2D: # + """Apply translation to this transform inplace.""" self.ox = translation.x self.oy = translation.y return self def translated(self, Vec2 translation, /) -> Transform2D: + """Apply translation to a copy of this transform.""" cdef Transform2D t = self.copy() t.translate_ip(translation) return t def rotate_ip(self, py_float rotation, /) -> Transform2D: # - self.__imatmul__(Transform2D.rotation(rotation)) + """Apply rotation to this transform inplace.""" + self.__imatmul__(Transform2D.rotating(rotation)) return self def rotated(self, py_float rotation, /) -> Transform2D: + """Apply rotation to a copy of this transform.""" cdef Transform2D t = self.copy() t.rotate_ip(rotation) return t def scale_ip(self, Vec2 scale, /) -> Transform2D: # + """Apply scaling to this transform inplace.""" self.xx *= scale.x self.xy *= scale.y self.yx *= scale.x @@ -370,6 +434,7 @@ cdef class Transform2D: return self def scaled(self, Vec2 scale, /) -> Transform2D: + """Apply scaling to a copy of this transform.""" cdef Transform2D t = self.copy() t.scale_ip(scale) return t diff --git a/codegen/templates/transform_3d.pyx b/codegen/templates/transform_3d.pyx index 5011541..2bf54eb 100644 --- a/codegen/templates/transform_3d.pyx +++ b/codegen/templates/transform_3d.pyx @@ -2,6 +2,7 @@ cimport cython # Dummy types for the IDE ctypedef py_float +ctypedef py_int cdef class Vec3: cdef py_float x, y, z @@ -15,11 +16,14 @@ from libc.math cimport sinl, cosl @cython.no_gc @cython.final cdef class Transform3D: + """3D linear transformation (3*4 matrix).""" + cdef py_float xx, xy, xz cdef py_float yx, yy, yz cdef py_float zx, zy, zz cdef py_float ox, oy, oz + cdef inline void identity(self) noexcept: self.xx, self.xy, self.xz = 1.0, 0.0, 0.0 self.yx, self.yy, self.yz = 0.0, 1.0, 0.0 @@ -28,10 +32,12 @@ cdef class Transform3D: # cdef void __init__(self) noexcept: + """Create an identity transform.""" self.identity() # cdef void __init__(self, py_float xx, py_float xy, py_float xz, py_float yx, py_float yy, py_float yz, py_float zx, py_float zy, py_float zz, py_float ox, py_float oy, py_float oz) noexcept: + """Create a transform from all the matrix elements.""" self.xx = xx self.xy = xy self.xz = xz @@ -47,6 +53,7 @@ cdef class Transform3D: # cdef void __init__(self, Vec3 x, Vec3 y, Vec3 z, Vec3 origin) noexcept: + """Create a transform using three base vectors and the origin vector.""" self.xx, self.xy, self.xz = x.x, x.y, x.z self.yx, self.yy, self.yz = y.x, y.y, y.z self.zx, self.zy, self.zz = z.x, z.y, z.z @@ -54,6 +61,7 @@ cdef class Transform3D: # cdef void __init__(self, Transform3D transform) noexcept: + """Create a copy.""" self.xx = transform.xx self.xy = transform.xy self.xz = transform.xz @@ -71,6 +79,7 @@ cdef class Transform3D: @staticmethod def translating(Vec3 translation, /) -> Transform3D: + """Create a translation transform.""" cdef Transform3D t = Transform3D.__new__(Transform3D) t.identity() t.ox += translation.x @@ -80,6 +89,7 @@ cdef class Transform3D: @staticmethod def rotating(Vec3 axis, py_float angle, /, Vec3 origin = None) -> Transform3D: + """Create a rotation transform.""" cdef Transform3D trans = Transform3D.__new__(Transform3D) cdef py_float axis_x_sq = axis.x * axis.x @@ -118,6 +128,7 @@ cdef class Transform3D: @staticmethod def scaling(Vec3 scale, /, Vec3 origin = None) -> Transform3D: + """Create a scale transform.""" cdef Transform3D t = Transform3D.__new__(Transform3D) t.identity() t.xx = scale.x @@ -151,6 +162,10 @@ cdef class Transform3D: return f"⎡X: ({self.xx}, {self.xy}, {self.xz})\n⎢Y: ({self.yx}, {self.yy}, {self.yz})\n⎢Z: ({self.zx}, {self.zy}, {self.zz})\n⎣O: ({self.ox}, {self.oy}, {self.oz})" def __eq__(self, object other) -> bool: + """Perform exact comparison. + + See Also: `Transform3D.is_close()` + """ if not isinstance(other, Transform3D): return False return self.xx == ( other).xx and self.xy == ( other).xy and self.xz == ( other).xz and\ @@ -159,6 +174,10 @@ cdef class Transform3D: self.ox == ( other).ox and self.oy == ( other).oy and self.oz == ( other).oz def __ne__(self, object other) -> bool: + """Perform exact comparison. + + See Also: `Transform3D.is_close()` + """ if not isinstance(other, Transform3D): return True return self.xx != ( other).xx or self.xy != ( other).xy or self.xz != ( other).xz or\ @@ -167,6 +186,10 @@ cdef class Transform3D: self.ox != ( other).ox or self.oy != ( other).oy or self.oz != ( other).oz def is_close(self, Transform3D other, /, py_float rel_tol = DEFAULT_RELATIVE_TOLERANCE, py_float abs_tol = DEFAULT_ABSOLUTE_TOLERANCE) -> bool: + """Determine if the two transforms are close enough. + + See Also: `math.is_close()` + """ return is_close(self.xx, other.xx, rel_tol, abs_tol) and \ is_close(self.xy, other.xy, rel_tol, abs_tol) and \ is_close(self.xz, other.xz, rel_tol, abs_tol) and \ @@ -199,6 +222,7 @@ cdef class Transform3D: @property def x(self) -> Vec3: + """Base vector X.""" cdef Vec3 vec = Vec3.__new__(Vec3) vec.x = self.xx vec.y = self.xy @@ -207,6 +231,7 @@ cdef class Transform3D: @property def y(self) -> Vec3: + """Base vector Y.""" cdef Vec3 vec = Vec3.__new__(Vec3) vec.x = self.yx vec.y = self.yy @@ -215,6 +240,7 @@ cdef class Transform3D: @property def z(self) -> Vec3: + """Base vector Z.""" cdef Vec3 vec = Vec3.__new__(Vec3) vec.x = self.zx vec.y = self.zy @@ -223,6 +249,7 @@ cdef class Transform3D: @property def origin(self) -> Vec3: + """Origin vector.""" cdef Vec3 vec = Vec3.__new__(Vec3) vec.x = self.ox vec.y = self.oy @@ -231,29 +258,40 @@ cdef class Transform3D: @x.setter def x(self, Vec3 value) -> None: + """Set the X base of the matrix.""" self.xx = value.x self.xy = value.y self.xz = value.z @y.setter def y(self, Vec3 value) -> None: + """Set the Y base of the matrix.""" self.yx = value.x self.yy = value.y self.yz = value.z @z.setter def z(self, Vec3 value) -> None: + """Set the Z base of the matrix.""" self.zx = value.x self.zy = value.y self.zz = value.z @origin.setter def origin(self, Vec3 value) -> None: + """Set the origin.""" self.ox = value.x self.oy = value.y self.oz = value.z def __getitem__(self, py_int item) -> Vec3: + """Get a column of the transformation matrix. + + 0: Base vector X + 1: Base vector Y + 2: Base vector Z + 3: Origin vector + """ cdef vec = Vec3.__new__(Vec3) if item == 0: @@ -278,6 +316,13 @@ cdef class Transform3D: return vec def __setitem__(self, py_int key, Vec3 value) -> None: + """Set a column of the transformation matrix. + + 0: Base vector X + 1: Base vector Y + 2: Base vector Z + 3: Origin vector + """ if key == 0: self.xx = value.x self.xy = value.y @@ -298,6 +343,7 @@ cdef class Transform3D: raise IndexError(key) def __len__(self) -> py_int: + """The amount of columns. Is always 4 for `Transform3D`s.""" return 4 cdef inline py_float tdotx(self, py_float x, py_float y, py_float z) noexcept: @@ -319,6 +365,7 @@ cdef class Transform3D: return self.tdotz(x, y, z) + self.oz def __mul__(self, Vec3 other) -> Vec3: + """Transform a copy of the vector.""" cdef Vec3 vec = Vec3.__new__(Vec3) vec.x = self.mulx(other.x, other.y, other.z) vec.y = self.muly(other.x, other.y, other.z) @@ -327,6 +374,7 @@ cdef class Transform3D: # cdef Vec3 __call__(self, Vec3 other): + """Transform a copy of the vector.""" cdef Vec3 vec = Vec3.__new__(Vec3) vec.x = self.mulx(other.x, other.y, other.z) vec.y = self.muly(other.x, other.y, other.z) @@ -335,6 +383,7 @@ cdef class Transform3D: # cdef Transform3D __call__(self, Transform3D other): + """Transform a copy of the passed in `Transform3D`.""" cdef Transform3D t = Transform3D.__new__(Transform3D) t.xx = self.tdotx(other.xx, other.xy, other.xz) t.xy = self.tdoty(other.xx, other.xy, other.xz) @@ -353,6 +402,7 @@ cdef class Transform3D: #:__call__ def __matmul__(self, Transform3D other) -> Transform3D: + """Transform a copy of the `Transform3D` on the right.""" cdef Transform3D t = Transform3D.__new__(Transform3D) t.xx = self.tdotx(other.xx, other.xy, other.xz) t.xy = self.tdoty(other.xx, other.xy, other.xz) @@ -370,6 +420,7 @@ cdef class Transform3D: def __imatmul__(self, Transform3D other) -> Transform3D: # + """Transform this `Transform3D` inplace with the other `Transform2D`.""" cdef py_float xx = other.tdotx(self.xx, self.xy, self.xz) cdef py_float xy = other.tdoty(self.xx, self.xy, self.xz) cdef py_float xz = other.tdotz(self.xx, self.xy, self.xz) @@ -395,9 +446,11 @@ cdef class Transform3D: @property def determinant(self) -> py_float: + """Compute the determinant of the matrix.""" return self._determinant() def __invert__(self) -> Transform3D: + """Get the invert transform.""" cdef py_float cox = self.yy * self.zz - self.yz * self.zy cdef py_float coy = self.xz * self.zy - self.xy * self.zz cdef py_float coz = self.xy * self.yz - self.yy * self.xz @@ -421,28 +474,33 @@ cdef class Transform3D: def translate_ip(self, Vec3 translation, /) -> Transform3D: # + """Apply translation to this transform inplace.""" self.ox += translation.x self.oy += translation.y self.oz += translation.z return self def translated(self, Vec3 translation, /) -> Transform3D: + """Apply translation to a copy of this transform.""" cdef Transform3D t = self.copy() t.translate_ip(translation) return t def rotate_ip(self, Vec3 axis, py_float angle, /) -> Transform3D: # + """Apply rotation to this transform inplace.""" self.__imatmul__(Transform3D.rotating(axis, angle)) return self def rotated(self, Vec3 axis, py_float angle, /) -> Transform3D: + """Apply rotation to a copy of this transform.""" cdef Transform3D t = self.copy() t.rotate_ip(axis, angle) return t def scale_ip(self, Vec3 scale, /) -> Transform3D: # + """Apply scaling to this transform inplace.""" self.xx *= scale.x self.yx *= scale.x self.zx *= scale.x @@ -455,6 +513,7 @@ cdef class Transform3D: return self def scaled(self, Vec3 scale, /) -> Transform3D: + """Apply scaling to a copy of this transform.""" cdef Transform3D t = self.copy() t.scale_ip(scale) return t diff --git a/codegen/templates/vec_class.pyx b/codegen/templates/vec_class.pyx index af6d9ee..9950d1d 100644 --- a/codegen/templates/vec_class.pyx +++ b/codegen/templates/vec_class.pyx @@ -26,16 +26,25 @@ DEF DEFAULT_ABSOLUTE_TOLERANCE = 0 # Dummy Value @cython.no_gc @cython.final cdef class _VecClassName_: + #: _vType_ is float + """_Dims_D vector.""" + # + #: _vType_ is int + """_Dims_D integer vector.""" + # + #: gen_var_decls(_Dims_, "_vTypeC_") # cdef void __init__(self) noexcept: + """Create a zero vector.""" #: gen_single_value_constructor(_Dims_, _vType_(0)) pass # # cdef void __init__(self, _vTypeC_ value) noexcept: + """Create a vector with the same value for all elements.""" #: gen_single_value_constructor(_Dims_, "value") pass # @@ -50,19 +59,32 @@ cdef class _VecClassName_: return #: gen_repr(_Dims_, _vType_) def __eq__(self, object other) -> bool: + """Perform exact comparison. + + See Also: `_VecClassName_.is_close()` + """ if not isinstance(other, _VecClassName_): return False return #: gen_for_each_dim("self.{dim} == other.{dim}", _Dims_, join=" and ") def __ne__(self, object other) -> bool: + """Perform exact comparison. + + See Also: `_VecClassName_.is_close()` + """ if not isinstance(other, _VecClassName_): return True return #: gen_for_each_dim("self.{dim} != other.{dim}", _Dims_, join=" or ") def is_close(self, _VecClassName_ other, /, py_float rel_tol = DEFAULT_RELATIVE_TOLERANCE, py_float abs_tol = DEFAULT_ABSOLUTE_TOLERANCE) -> bool: + """Determine if the two vectors are close enough. + + See Also: `math.is_close()` + """ return #: gen_for_each_dim("is_close( self.{dim}, other.{dim}, rel_tol, abs_tol)", _Dims_, join=" and ") def __bool__(self) -> bool: + """If the vector is a zero vector.""" return #: gen_for_each_dim("self.{dim} == 0", _Dims_, join=" and ") #TODO: Better hashing @@ -80,23 +102,26 @@ cdef class _VecClassName_: # def __pos__(self) -> _VecClassName_: + """Return a copy of this vector.""" cdef _VecClassName_ vec = _VecClassName_.__new__(_VecClassName_) #: gen_for_each_dim("vec.{dim} = self.{dim}", _Dims_) return vec def __neg__(self) -> _VecClassName_: + """Return the inverse vector.""" cdef _VecClassName_ vec = _VecClassName_.__new__(_VecClassName_) #: gen_for_each_dim("vec.{dim} = -self.{dim}", _Dims_) return vec - #: gen_common_binary_and_inplace_op("+", "add") + #: gen_common_binary_and_inplace_op("+", "add", "addition") - #: gen_common_binary_and_inplace_op("-", "sub") + #: gen_common_binary_and_inplace_op("-", "sub", "subtraction") - #: gen_common_binary_and_inplace_op("*", "mul") + #: gen_common_binary_and_inplace_op("*", "mul", "multiplication") #: _Dims_ == 2 # cdef Vec2 __mul__(self, Transform2D t): + """Transform a copy of this vector using the `Transform2D`.""" cdef Vec2 vec = Vec2.__new__(Vec2) cdef py_float x = self.x - t.ox cdef py_float y = self.y - t.oy @@ -107,6 +132,7 @@ cdef class _VecClassName_: #: _Dims_ == 3 # cdef Vec3 __mul__(self, Transform3D t): + """Transform a copy of this vector using the `Transform3D`.""" cdef Vec3 vec = Vec3.__new__(Vec3) cdef py_float x = self.x - t.ox cdef py_float y = self.y - t.oy @@ -117,16 +143,16 @@ cdef class _VecClassName_: return vec # - #: gen_common_binary_and_inplace_op("/", "truediv") + #: gen_common_binary_and_inplace_op("/", "truediv", "division") def __matmul__(self, _VecClassName_ other) -> _vTypeC_: - """Vector dot product""" + """Dot product.""" return #: gen_for_each_dim("self.{dim} * other.{dim}", _Dims_, join=" + ") #: _Dims_ == 3 def __xor__(self, _VecClassName_ other) -> _VecClassName_: - """Vector cross product""" + """Cross product.""" cdef _VecClassName_ vec = _VecClassName_.__new__(_VecClassName_) vec.x = self.y * other.z - self.z * other.y vec.y = self.z * other.x - self.x * other.z @@ -136,18 +162,26 @@ cdef class _VecClassName_: def __len__(self) -> int: + """The number of dimensions (elements) of this vector.""" _Dims_ = 0 # return _Dims_ def __getitem__(self, int key) -> _vTypeC_: + """Get the n-th element of this vector.""" #: gen_item_op(_Dims_, "return self.{dim}") pass # def __setitem__(self, int key, _vTypeC_ value) -> None: + """Set the n-th element of this vector.""" #: gen_item_op(_Dims_, "self.{dim} = value") pass # def __iter__(self) -> __VecClassName_iterator: + """Create an iterator that yields all the elements of the vector in sequence. + + This creates a copy of all the elements of this vector, + so modifying the vector won't affect already created iterators. + """ cdef __VecClassName_iterator iterator = __VecClassName_iterator.__new__(__VecClassName_iterator) #: gen_for_each_dim("iterator.{dim} = self.{dim}", _Dims_) # iterator.vec = self @@ -158,36 +192,41 @@ cdef class _VecClassName_: # noinspection PyTypeChecker @property def length(self) -> py_float: + """The (Euclidean) length of this vector.""" return #: f"sqrtl({gen_for_each_dim(' (self.{dim} * self.{dim})', _Dims_, join=' + ')})" # # noinspection PyTypeChecker @property def length_sqr(self) -> py_float: + """The squared Euclidean length of this vector.""" return #: gen_for_each_dim("self.{dim} * self.{dim}", _Dims_, join=" + ") # # noinspection PyTypeChecker def __or__(self, _VecClassName_ other) -> py_float: - """Equivalent to distance_to()""" + """The (Euclidean) distance between two vectors.""" #: gen_for_each_dim("cdef _vTypeC_ d{dim} = self.{dim} - other.{dim}", _Dims_) return #: f"sqrtl( ({gen_for_each_dim('d{dim} * d{dim}', _Dims_, join=' + ')}))" # # noinspection PyTypeChecker def distance_to(self, _VecClassName_ other, /) -> py_float: + """The (Euclidean) distance between two vectors.""" #: gen_for_each_dim("cdef _vTypeC_ d{dim} = self.{dim} - other.{dim}", _Dims_) return #: f"sqrtl( ({gen_for_each_dim('d{dim} * d{dim}', _Dims_, join=' + ')}))" # # noinspection PyTypeChecker def distance_sqr_to(self, _VecClassName_ other, /) -> py_float: + """The squared Euclidean distance between two vectors.""" #: gen_for_each_dim("cdef _vTypeC_ d{dim} = self.{dim} - other.{dim}", _Dims_) return #: f" ({gen_for_each_dim('d{dim} * d{dim}', _Dims_, join=' + ')})" #: _vType_ is float @property def normalized(self) -> _VecClassName_: + """Get a normalized copy of this vector.""" cdef py_float l = #: f"sqrtl({gen_for_each_dim('self.{dim} * self.{dim}', _Dims_, join=' + ')})" cdef _VecClassName_ vec = _VecClassName_.__new__(_VecClassName_) #: gen_for_each_dim("vec.{dim} = self.{dim} / l", _Dims_) @@ -202,6 +241,8 @@ cdef class _VecClassName_: @cython.final @cython.freelist(8) cdef class __VecClassName_iterator: + """Internal iterator class for _VecClassName_.""" + #: gen_var_decls(_Dims_, "_vTypeC_") cdef short index diff --git a/codegen/vector_codegen.py b/codegen/vector_codegen.py index 9b617c5..3c73d67 100644 --- a/codegen/vector_codegen.py +++ b/codegen/vector_codegen.py @@ -39,8 +39,10 @@ def gen_single_value_constructor(dims: int, value: Any) -> str: def gen_type_conversion_constructor(dims: int, vtype: Type) -> str: assert vtype is int or vtype is float from_type = float if vtype is int else int + type_str = {int: "integer", float: "floating-point"} out = "#\n" out += f"cdef void __init__(self, {get_vec_class_name(dims, from_type)} vec) noexcept:\n" + out += f' """Convert a {type_str[from_type]} vector to a {type_str[vtype]} vector."""\n' for dim in DIMS[:dims]: out += f" self.{dim} = <{get_c_type(vtype)}>vec.{dim}\n" return out @@ -83,6 +85,19 @@ def gen_combination_constructors(dims: int, vtype: Type) -> str: func = "#\n" func += (f"cdef void __init__(self, " f"{', '.join(f'{t} {n}' for t, n in zip(param_types, param_names))}) noexcept:\n") + + docstring = f"Create a {dims}D vector from " + for i, param_dims in enumerate(combination): + if i == 0: + docstring += "a " + elif i == len(combination) - 1: + docstring += " and a " + else: + docstring += ", a " + docstring += f"{param_dims}D vector" if param_dims > 1 else "number" + docstring += "." + func += f' """{docstring}"""\n' + for assign in assigns: func += f" {assign}\n" func = func[:-1] # remove last \n @@ -104,6 +119,27 @@ def _gen_swizzle_get(swizzle: str, vtype: Type) -> str: vec_cls = get_vec_class_name(len(swizzle), vtype) out += f"def {swizzle}(self) -> {vec_cls}:\n" out += f" cdef {vec_cls} vec = {vec_cls}.__new__({vec_cls})\n" + + docstring = f"Create a `{vec_cls}` with its elements being " + for i, swiz in enumerate(swizzle): + if i == 0: + pass + elif i == len(swizzle) - 1: + docstring += ", and " + else: + docstring += ", " + + if swiz in DIMS: + docstring += f"the {swiz.upper()} element of this vector" + elif swiz == "o": + docstring += f"a zero" + elif swiz == "l": + docstring += f"a one" + else: + assert False + docstring += "." + out += f' """{docstring}"""\n' + for i, swiz in enumerate(swizzle): if swiz in DIMS: val = f"self.{swiz}" @@ -121,6 +157,12 @@ def _gen_swizzle_get(swizzle: str, vtype: Type) -> str: def _gen_swizzle_set(swizzle: str, vtype: Type): out = f"@{swizzle}.setter\n" out += f"def {swizzle}(self, {get_vec_class_name(len(swizzle), vtype)} vec) -> None:\n" + + docstring = f"Set the value of the {', '.join(swizzle.upper())}" + docstring = docstring[:-3] + " and " + docstring[-1] + docstring += " elements of this vector to the value of the elements of the other vector, respectively." + out += f' """{docstring}"""\n' + for i, swiz in enumerate(swizzle): assert swiz in DIMS out += f" self.{swiz} = vec.{DIMS[i]}\n" @@ -174,8 +216,8 @@ def gen_repr(dims: int, vtype: Type) -> str: out += ')"' return out -def gen_common_binary_and_inplace_op(op: str, name: str) -> str: - return from_template(open("templates/common_binary_and_inplace_op.pyx").read(), {"Op": op, "OpName": name}) +def gen_common_binary_and_inplace_op(op: str, name: str, readable_name: str) -> str: + return from_template(open("templates/common_binary_and_inplace_op.pyx").read(), {"Op": op, "OpName": name, "OpReadableName": readable_name}) def gen_item_op(dims: int, op: str) -> str: out = ""