Skip to content

kumiki.rule

Flat import path

kumiki/__init__.py re-exports everything on this page via from kumiki import *, so every name below is also available directly as kumiki.scalar -- you do not need to import from the submodule path shown in the heading above.

kumiki.rule

The library uses RHS coordinates with Z facing up, Y facing north, and X facing east. The main class is the Orientation class which stores rotation in 2 components.

Coordinate System (RHS):

(up) Z ^ ^ Y (north) | / |/ +-----> X (east) / / v -Y (south)

RHS = Right Hand System - X-axis: points east - Y-axis: points north - Z-axis: points up - Thumb = X, Index = Y, Middle = Z

All numeric values in this library are plain Python floats. Users declare measurements with the helpers below (scalar, inches, mm, degrees, ...) for convenience and unit-conversion, but the values themselves are ordinary floats -- there is no lazy/symbolic expression tree, and no separate "numeric mode" to switch between.

Expr module-attribute

Expr = float

Float module-attribute

Float = float

Integer module-attribute

Integer = int

S module-attribute

S = float

sympify module-attribute

sympify = float

oo module-attribute

oo = math.inf

Abs module-attribute

Abs = abs

Min module-attribute

Min = min

Max module-attribute

Max = max

pi module-attribute

pi = math.pi

Numeric module-attribute

Numeric = Union[float, int]

V2 module-attribute

V2 = Matrix

V3 module-attribute

V3 = Matrix

Direction3D module-attribute

Direction3D = Matrix

EPSILON_GENERIC module-attribute

EPSILON_GENERIC = scalar('1e-8')

safe_prune module-attribute

safe_prune = prune

numeric_prune module-attribute

numeric_prune = prune

safe_norm module-attribute

safe_norm = giraffe_norm

numeric_norm module-attribute

numeric_norm = giraffe_norm

safe_det module-attribute

safe_det = giraffe_det

numeric_det module-attribute

numeric_det = giraffe_det

safe_simplify module-attribute

safe_simplify = giraffe_simplify

safe_compare module-attribute

safe_compare = giraffe_compare

numeric_compare module-attribute

numeric_compare = giraffe_compare

safe_dot_product module-attribute

safe_dot_product = giraffe_dot_product

numeric_dot_product module-attribute

numeric_dot_product = giraffe_dot_product

safe_transform_vector module-attribute

safe_transform_vector = giraffe_transform_vector

numeric_transform_vector module-attribute

numeric_transform_vector = giraffe_transform_vector

safe_normalize_vector module-attribute

safe_normalize_vector = giraffe_normalize_vector

numeric_normalize_vector module-attribute

numeric_normalize_vector = giraffe_normalize_vector

safe_magnitude module-attribute

safe_magnitude = giraffe_magnitude

numeric_magnitude module-attribute

numeric_magnitude = giraffe_magnitude

INCH_TO_METER module-attribute

INCH_TO_METER = scalar(254, 10000)

FOOT_TO_METER module-attribute

FOOT_TO_METER = scalar(3048, 10000)

SHAKU_TO_METER module-attribute

SHAKU_TO_METER = scalar(10, 33)

Matrix

Matrix(data)

Immutable: _data is set once at construction and never written to again (enforced both by omitting __setitem__ and by marking the underlying numpy buffer read-only), matching the frozen dataclasses (Transform/Orientation/Axis) that hold Matrix-typed fields elsewhere in this module -- without this, some_frozen_transform.position[0] = 5 would silently succeed despite the dataclass being frozen.

Source code in kumiki/rule.py
def __init__(self, data):
    if isinstance(data, Matrix):
        arr = np.array(data._data, dtype=float, copy=True)
    elif isinstance(data, np.ndarray):
        arr = np.array(data, dtype=float)
        arr = arr.reshape(-1, 1) if arr.ndim == 1 else arr
    else:
        data = list(data)
        if len(data) > 0 and isinstance(data[0], (list, tuple)):
            arr = np.array([[float(v) for v in row] for row in data], dtype=float)
        else:
            arr = np.array([float(v) for v in data], dtype=float).reshape(-1, 1)
    arr.setflags(write=False)
    self._data = arr

__slots__ class-attribute instance-attribute

__slots__ = ('_data',)

shape property

shape: Tuple[int, int]

rows property

rows: int

cols property

cols: int

T property

T: Matrix

eye classmethod

eye(n: int) -> Matrix
Source code in kumiki/rule.py
@classmethod
def eye(cls, n: int) -> 'Matrix':
    return cls._wrap(np.eye(n, dtype=float))

zeros classmethod

zeros(rows: int, cols: Optional[int] = None) -> Matrix
Source code in kumiki/rule.py
@classmethod
def zeros(cls, rows: int, cols: Optional[int] = None) -> 'Matrix':
    return cls._wrap(np.zeros((rows, cols if cols is not None else rows), dtype=float))

det

det() -> float
Source code in kumiki/rule.py
def det(self) -> float:
    return float(np.linalg.det(self._data))

dot

dot(other: Matrix) -> float
Source code in kumiki/rule.py
def dot(self, other: 'Matrix') -> float:
    other_data = other._data if isinstance(other, Matrix) else np.asarray(other, dtype=float)
    return float(np.dot(self._data.flatten(), other_data.flatten()))

cross

cross(other: Matrix) -> Matrix
Source code in kumiki/rule.py
def cross(self, other: 'Matrix') -> 'Matrix':
    other_data = other._data if isinstance(other, Matrix) else np.asarray(other, dtype=float)
    result = np.cross(self._data.flatten(), other_data.flatten())
    return Matrix._wrap(result.reshape(-1, 1))

equals

equals(other: Matrix, tolerance: Optional[float] = None) -> bool

Elementwise approximate equality (tolerates float noise from trig/sqrt).

Source code in kumiki/rule.py
def equals(self, other: 'Matrix', tolerance: Optional[float] = None) -> bool:
    """Elementwise approximate equality (tolerates float noise from trig/sqrt)."""
    if not isinstance(other, Matrix) or self._data.shape != other._data.shape:
        return False
    tol = EPSILON_GENERIC if tolerance is None else tolerance
    return bool(np.all(np.abs(self._data - other._data) < tol))

norm

norm() -> float
Source code in kumiki/rule.py
def norm(self) -> float:
    return float(np.linalg.norm(self._data))

tolist

tolist() -> list
Source code in kumiki/rule.py
def tolist(self) -> list:
    return self._data.tolist()

__getitem__

__getitem__(key)
Source code in kumiki/rule.py
def __getitem__(self, key):
    if isinstance(key, tuple):
        result = self._data[key]
    else:
        result = self._data.flat[key]
    if isinstance(result, np.ndarray):
        if result.ndim == 1:
            # A row-slice (int row, slice col) -> keep as a row vector;
            # anything else (col-slice, or a flat slice) -> column vector,
            # matching sympy's Matrix slicing shapes.
            if isinstance(key, tuple) and isinstance(key[0], int):
                result = result.reshape(1, -1)
            else:
                result = result.reshape(-1, 1)
        return Matrix._wrap(result)
    return float(result)

__iter__

__iter__()
Source code in kumiki/rule.py
def __iter__(self):
    return iter(self._data.flatten().tolist())

__len__

__len__() -> int
Source code in kumiki/rule.py
def __len__(self) -> int:
    return int(self._data.size)

__mul__

__mul__(other)
Source code in kumiki/rule.py
def __mul__(self, other):
    if isinstance(other, Matrix):
        return Matrix._wrap(self._data @ other._data)
    return Matrix._wrap(self._data * float(other))

__rmul__

__rmul__(other)
Source code in kumiki/rule.py
def __rmul__(self, other):
    return Matrix._wrap(self._data * float(other))

__truediv__

__truediv__(other)
Source code in kumiki/rule.py
def __truediv__(self, other):
    return Matrix._wrap(self._data / float(other))

__add__

__add__(other)
Source code in kumiki/rule.py
def __add__(self, other):
    other_data = other._data if isinstance(other, Matrix) else other
    return Matrix._wrap(self._data + other_data)

__radd__

__radd__(other)
Source code in kumiki/rule.py
def __radd__(self, other):
    return self.__add__(other)

__sub__

__sub__(other)
Source code in kumiki/rule.py
def __sub__(self, other):
    other_data = other._data if isinstance(other, Matrix) else other
    return Matrix._wrap(self._data - other_data)

__rsub__

__rsub__(other)
Source code in kumiki/rule.py
def __rsub__(self, other):
    other_data = other._data if isinstance(other, Matrix) else other
    return Matrix._wrap(other_data - self._data)

__neg__

__neg__()
Source code in kumiki/rule.py
def __neg__(self):
    return Matrix._wrap(-self._data)

__eq__

__eq__(other)
Source code in kumiki/rule.py
def __eq__(self, other):
    if not isinstance(other, Matrix):
        return NotImplemented
    return self._data.shape == other._data.shape and bool(np.array_equal(self._data, other._data))

__repr__

__repr__() -> str
Source code in kumiki/rule.py
def __repr__(self) -> str:
    return f"Matrix({self._data.tolist()!r})"

Axis dataclass

Axis(position: V3, direction: Direction3D)

position instance-attribute

position: V3

direction instance-attribute

direction: Direction3D

Transform dataclass

Transform(position: V3, orientation: Orientation)

Represents a 3D transformation with position and orientation. Encapsulates both translation and rotation for objects in 3D space.

position instance-attribute

position: V3

orientation instance-attribute

orientation: Orientation

identity classmethod

identity() -> Transform

Create an identity transform at origin with identity orientation.

Source code in kumiki/rule.py
@classmethod
def identity(cls) -> 'Transform':
    """Create an identity transform at origin with identity orientation."""
    return cls(
        position=create_v3(scalar(0), scalar(0), scalar(0)),
        orientation=Orientation.identity()
    )

local_to_global

local_to_global(local_point: V3) -> V3

Convert a point from local coordinates to global world coordinates.

Parameters:

Name Type Description Default
local_point V3

A point in local coordinates

required

Returns:

Type Description
V3

The same point in global world coordinates

Source code in kumiki/rule.py
def local_to_global(self, local_point: V3) -> V3:
    """
    Convert a point from local coordinates to global world coordinates.

    Args:
        local_point: A point in local coordinates

    Returns:
        The same point in global world coordinates
    """
    # Rotate to global frame, then translate to position
    # global = R * local + position
    return safe_transform_vector(self.orientation.matrix, local_point) + self.position

global_to_local

global_to_local(global_point: V3) -> V3

Convert a point from global world coordinates to local coordinates.

Parameters:

Name Type Description Default
global_point V3

A point in global world coordinates

required

Returns:

Type Description
V3

The same point in local coordinates

Source code in kumiki/rule.py
def global_to_local(self, global_point: V3) -> V3:
    """
    Convert a point from global world coordinates to local coordinates.

    Args:
        global_point: A point in global world coordinates

    Returns:
        The same point in local coordinates
    """
    # Translate to origin, then rotate to local frame
    # local = R^T * (global - position)
    translated = global_point - self.position
    return safe_transform_vector(self.orientation.matrix.T, translated)

numeric_local_to_global

numeric_local_to_global(local_point: V3) -> V3

Convert local to global using numeric (Float) math. For hot paths like CSG.

Source code in kumiki/rule.py
def numeric_local_to_global(self, local_point: V3) -> V3:
    """Convert local to global using numeric (Float) math. For hot paths like CSG."""
    return numeric_transform_vector(self.orientation.matrix, local_point) + self.position

numeric_global_to_local

numeric_global_to_local(global_point: V3) -> V3

Convert global to local using numeric (Float) math. For hot paths like CSG.

Source code in kumiki/rule.py
def numeric_global_to_local(self, global_point: V3) -> V3:
    """Convert global to local using numeric (Float) math. For hot paths like CSG."""
    translated = global_point - self.position
    return numeric_transform_vector(self.orientation.matrix.T, translated)

to_global_transform

to_global_transform(old_parent: Transform) -> Transform

Convert this transform to global coordinates relative to a parent transform.

Source code in kumiki/rule.py
def to_global_transform(self, old_parent: 'Transform') -> 'Transform':
    """
    Convert this transform to global coordinates relative to a parent transform.
    """
    return old_parent * self

invert

invert() -> Transform

Return the inverse of this transform.

For a transform T that converts local to global (global = T * local), the inverse converts global to local (local = T^-1 * global).

Source code in kumiki/rule.py
def invert(self) -> 'Transform':
    """
    Return the inverse of this transform.

    For a transform T that converts local to global (global = T * local),
    the inverse converts global to local (local = T^-1 * global).
    """
    # Invert the orientation (transpose for rotation matrices)
    inv_orientation = self.orientation.invert()
    # Transform the position by the inverted orientation and negate
    inv_position = -safe_transform_vector(inv_orientation.matrix, self.position)
    return Transform(position=inv_position, orientation=inv_orientation)

__mul__

__mul__(other: Transform) -> Transform

Compose two transforms: result = self * other.

This applies other first, then self. Equivalent to: global = self.local_to_global(other.local_to_global(local))

Source code in kumiki/rule.py
def __mul__(self, other: 'Transform') -> 'Transform':
    """
    Compose two transforms: result = self * other.

    This applies other first, then self.
    Equivalent to: global = self.local_to_global(other.local_to_global(local))
    """
    new_orientation = self.orientation * other.orientation
    new_position = safe_transform_vector(self.orientation.matrix, other.position) + self.position
    return Transform(position=new_position, orientation=new_orientation)

to_local_transform

to_local_transform(new_parent: Transform) -> Transform

Convert this transform to local coordinates relative to a parent transform.

Source code in kumiki/rule.py
def to_local_transform(self, new_parent: 'Transform') -> 'Transform':
    """
    Convert this transform to local coordinates relative to a parent transform.
    """
    return new_parent.invert() * self

rotate_around_axis

rotate_around_axis(axis: Axis, radians: Numeric) -> Transform

Rotate this transform counterclockwise around an axis and return the new transform.

The axis can be positioned anywhere in space (not just through the origin). Uses Rodrigues' rotation formula after translating to make the axis pass through origin.

Parameters:

Name Type Description Default
axis Axis

Axis with position and direction to rotate around

required
radians Numeric

Angle to rotate in radians (counterclockwise when looking along axis direction)

required

Returns:

Type Description
Transform

New Transform with rotated position and orientation

Source code in kumiki/rule.py
def rotate_around_axis(self, axis: Axis, radians: Numeric) -> 'Transform':
    """
    Rotate this transform counterclockwise around an axis and return the new transform.

    The axis can be positioned anywhere in space (not just through the origin).
    Uses Rodrigues' rotation formula after translating to make the axis pass through origin.

    Args:
        axis: Axis with position and direction to rotate around
        radians: Angle to rotate in radians (counterclockwise when looking along axis direction)

    Returns:
        New Transform with rotated position and orientation
    """
    # Normalize the axis direction
    axis_normalized = safe_normalize_vector(axis.direction)
    kx, ky, kz = axis_normalized[0], axis_normalized[1], axis_normalized[2]

    # Rodrigues' rotation formula for rotation matrix around axis k by angle θ:
    # R = I + sin(θ)K + (1 - cos(θ))K²
    # where K is the skew-symmetric cross-product matrix of k

    # K = [[0, -kz, ky], [kz, 0, -kx], [-ky, kx, 0]]
    K = Matrix([
        [scalar(0), -kz, ky],
        [kz, scalar(0), -kx],
        [-ky, kx, scalar(0)]
    ])

    # K² = K * K
    K_squared = K * K

    # R = I + sin(θ)K + (1 - cos(θ))K²
    I = eye(3)
    rotation_matrix = I + sin(radians) * K + (scalar(1) - cos(radians)) * K_squared

    # To rotate around an axis not through origin:
    # 1. Translate so axis passes through origin
    # 2. Rotate
    # 3. Translate back

    # Translate position relative to axis position
    position_relative = self.position - axis.position

    # Apply rotation to the relative position
    rotated_relative = rotation_matrix * position_relative

    # Translate back
    new_position = rotated_relative + axis.position

    # Apply rotation to orientation (orientation is independent of translation)
    new_orientation_matrix = rotation_matrix * self.orientation.matrix
    new_orientation = Orientation(new_orientation_matrix)

    return Transform(position=new_position, orientation=new_orientation)

Comparison

Bases: Enum

Enum for safe comparison operations

GT class-attribute instance-attribute

GT = '>'

LT class-attribute instance-attribute

LT = '<'

GE class-attribute instance-attribute

GE = '>='

LE class-attribute instance-attribute

LE = '<='

EQ class-attribute instance-attribute

EQ = '=='

NE class-attribute instance-attribute

NE = '!='

Orientation dataclass

Orientation(matrix: Matrix = (lambda: eye(3))())

Represents a 3D rotation using a 3x3 rotation matrix. I guess we never slerp and don't care about memory usage so apparently we're using matrices to implement this class.

matrix class-attribute instance-attribute

matrix: Matrix = field(default_factory=lambda: Matrix.eye(3))

__post_init__

__post_init__()

Convert to Matrix and validate that the matrix is 3x3.

Source code in kumiki/rule.py
def __post_init__(self):
    """Convert to Matrix and validate that the matrix is 3x3."""
    # Convert to Matrix if necessary (handles list/tuple inputs)
    if not isinstance(self.matrix, Matrix):
        object.__setattr__(self, 'matrix', Matrix(self.matrix))

    if self.matrix.shape != (3, 3):
        raise ValueError("Rotation matrix must be 3x3")

multiply

multiply(other: Orientation) -> Orientation

Multiply this orientation with another orientation. Returns a new Orientation representing the combined rotation.

Source code in kumiki/rule.py
def multiply(self, other: 'Orientation') -> 'Orientation':
    """
    Multiply this orientation with another orientation.
    Returns a new Orientation representing the combined rotation.
    """
    if not isinstance(other, Orientation):
        raise TypeError("Can only multiply with another Orientation")
    return Orientation(safe_transform_vector(self.matrix, other.matrix))

invert

invert() -> Orientation

Return the inverse of this orientation. For rotation matrices, the inverse is the transpose.

Source code in kumiki/rule.py
def invert(self) -> 'Orientation':
    """
    Return the inverse of this orientation.
    For rotation matrices, the inverse is the transpose.
    """
    return Orientation(self.matrix.T)

flip

flip(flip_x: bool = False, flip_y: bool = False, flip_z: bool = False) -> Orientation

Return the orientation with the given axes flipped.

Source code in kumiki/rule.py
def flip(self, flip_x: bool = False, flip_y: bool = False, flip_z: bool = False) -> 'Orientation':
    """
    Return the orientation with the given axes flipped.
    """
    # Matrix is frozen, so mutate a raw numpy buffer here and wrap it
    # fresh at the end rather than assigning into an existing Matrix.
    arr = self.matrix._data.copy()
    if flip_x:
        arr[0, :] = -arr[0, :]
    if flip_y:
        arr[:, 0] = -arr[:, 0]
    if flip_z:
        arr[:, 2] = -arr[:, 2]
    return Orientation(Matrix._wrap(arr))

__mul__

__mul__(other: Orientation) -> Orientation

Allow using * operator for multiplication

Source code in kumiki/rule.py
def __mul__(self, other: 'Orientation') -> 'Orientation':
    """Allow using * operator for multiplication"""
    return self.multiply(other)

__repr__

__repr__() -> str
Source code in kumiki/rule.py
def __repr__(self) -> str:
    return f"Orientation(\n{self.matrix}\n)"

rotate_right classmethod

rotate_right() -> Orientation

Rotate right: +X axis rotates to -Y axis (clockwise around Z)

Source code in kumiki/rule.py
@classmethod
def rotate_right(cls) -> 'Orientation':
    """Rotate right: +X axis rotates to -Y axis (clockwise around Z)"""
    matrix = Matrix([
        [0, 1, 0],
        [-1, 0, 0],
        [0, 0, 1]
    ])
    return cls(matrix)

rotate_left classmethod

rotate_left() -> Orientation

Rotate left: +X axis rotates to +Y axis (counterclockwise around Z)

Source code in kumiki/rule.py
@classmethod
def rotate_left(cls) -> 'Orientation':
    """Rotate left: +X axis rotates to +Y axis (counterclockwise around Z)"""
    matrix = Matrix([
        [0, -1, 0],
        [1, 0, 0],
        [0, 0, 1]
    ])
    return cls(matrix)

from_angle_axis classmethod

from_angle_axis(radians: Numeric, axis: Direction3D) -> Orientation

Create an orientation from an angle-axis rotation (Rodrigues' formula).

Source code in kumiki/rule.py
@classmethod
def from_angle_axis(cls, radians: Numeric, axis: Direction3D) -> 'Orientation':
    """Create an orientation from an angle-axis rotation (Rodrigues' formula)."""
    k = safe_normalize_vector(axis)
    kx, ky, kz = k[0], k[1], k[2]
    K = Matrix([
        [scalar(0), -kz, ky],
        [kz, scalar(0), -kx],
        [-ky, kx, scalar(0)]
    ])
    R = eye(3) + sin(radians) * K + (scalar(1) - cos(radians)) * K * K
    return cls(R)

identity staticmethod

identity() -> Orientation

Identity orientation - facing east (+X)

Source code in kumiki/rule.py
@staticmethod
def identity() -> 'Orientation':
    """Identity orientation - facing east (+X)"""
    return Orientation()

from_z_and_y staticmethod

from_z_and_y(z_direction: Direction3D, y_direction: Direction3D) -> Orientation

Create an Orientation from z and y direction vectors. Computes x = y × z to complete the right-handed coordinate system.

Source code in kumiki/rule.py
@staticmethod
def from_z_and_y(z_direction: Direction3D, y_direction: Direction3D) -> 'Orientation':
    """
    Create an Orientation from z and y direction vectors.
    Computes x = y × z to complete the right-handed coordinate system.
    """
    x_direction = cross_product(y_direction, z_direction)
    return Orientation(Matrix([
        [x_direction[0], y_direction[0], z_direction[0]],
        [x_direction[1], y_direction[1], z_direction[1]],
        [x_direction[2], y_direction[2], z_direction[2]]
    ]))

from_z_and_x staticmethod

from_z_and_x(z_direction: Direction3D, x_direction: Direction3D) -> Orientation

Create an Orientation from z and x direction vectors. Computes y = z × x to complete the right-handed coordinate system.

Source code in kumiki/rule.py
@staticmethod
def from_z_and_x(z_direction: Direction3D, x_direction: Direction3D) -> 'Orientation':
    """
    Create an Orientation from z and x direction vectors.
    Computes y = z × x to complete the right-handed coordinate system.
    """
    y_direction = cross_product(z_direction, x_direction)
    return Orientation(Matrix([
        [x_direction[0], y_direction[0], z_direction[0]],
        [x_direction[1], y_direction[1], z_direction[1]],
        [x_direction[2], y_direction[2], z_direction[2]]
    ]))

from_x_and_y staticmethod

from_x_and_y(x_direction: Direction3D, y_direction: Direction3D) -> Orientation

Create an Orientation from x and y direction vectors. Computes z = x × y to complete the right-handed coordinate system.

Source code in kumiki/rule.py
@staticmethod
def from_x_and_y(x_direction: Direction3D, y_direction: Direction3D) -> 'Orientation':
    """
    Create an Orientation from x and y direction vectors.
    Computes z = x × y to complete the right-handed coordinate system.
    """
    z_direction = cross_product(x_direction, y_direction)
    return Orientation(Matrix([
        [x_direction[0], y_direction[0], z_direction[0]],
        [x_direction[1], y_direction[1], z_direction[1]],
        [x_direction[2], y_direction[2], z_direction[2]]
    ]))

from_axis_angle staticmethod

from_axis_angle(axis: Direction3D, radians: Numeric) -> Orientation

Create an Orientation representing a rotation around an axis by an angle. Uses Rodrigues' rotation formula.

Parameters:

Name Type Description Default
axis Direction3D

Direction vector to rotate around (will be normalized)

required
radians Numeric

Angle to rotate in radians

required

Returns:

Type Description
Orientation

Orientation object representing the rotation

Source code in kumiki/rule.py
@staticmethod
def from_axis_angle(axis: Direction3D, radians: Numeric) -> 'Orientation':
    """
    Create an Orientation representing a rotation around an axis by an angle.
    Uses Rodrigues' rotation formula.

    Args:
        axis: Direction vector to rotate around (will be normalized)
        radians: Angle to rotate in radians

    Returns:
        Orientation object representing the rotation
    """
    # Normalize the axis
    axis_normalized = safe_normalize_vector(axis)
    kx, ky, kz = axis_normalized[0], axis_normalized[1], axis_normalized[2]

    # Rodrigues' rotation formula: R = I + sin(θ)K + (1 - cos(θ))K²
    # where K is the skew-symmetric cross-product matrix of k
    K = Matrix([
        [scalar(0), -kz, ky],
        [kz, scalar(0), -kx],
        [-ky, kx, scalar(0)]
    ])
    K_squared = K * K
    I = Matrix.eye(3)
    rotation_matrix = I + sin(radians) * K + (scalar(1) - cos(radians)) * K_squared

    return Orientation(rotation_matrix)

from_euleryZYX staticmethod

from_euleryZYX(yaw: Numeric, pitch: Numeric, roll: Numeric) -> Orientation

Create an Orientation from Euler angles using ZYX rotation sequence.

Parameters:

Name Type Description Default
yaw Numeric

Rotation around Z-axis (radians)

required
pitch Numeric

Rotation around Y-axis (radians)

required
roll Numeric

Rotation around X-axis (radians)

required

Returns:

Type Description
Orientation

Orientation object with combined rotation matrix

The rotation sequence is: 1. Yaw (Z-axis rotation) 2. Pitch (Y-axis rotation) 3. Roll (X-axis rotation)

Source code in kumiki/rule.py
@staticmethod
def from_euleryZYX(yaw: Numeric, pitch: Numeric, roll: Numeric) -> 'Orientation':
    """
    Create an Orientation from Euler angles using ZYX rotation sequence.

    Args:
        yaw: Rotation around Z-axis (radians)
        pitch: Rotation around Y-axis (radians)
        roll: Rotation around X-axis (radians)

    Returns:
        Orientation object with combined rotation matrix

    The rotation sequence is:
    1. Yaw (Z-axis rotation)
    2. Pitch (Y-axis rotation)
    3. Roll (X-axis rotation)
    """
    # Individual rotation matrices
    Rz = Matrix([
        [cos(yaw), -sin(yaw), 0],
        [sin(yaw), cos(yaw), 0],
        [0, 0, 1]
    ])

    Ry = Matrix([
        [cos(pitch), 0, sin(pitch)],
        [0, 1, 0],
        [-sin(pitch), 0, cos(pitch)]
    ])

    Rx = Matrix([
        [1, 0, 0],
        [0, cos(roll), -sin(roll)],
        [0, sin(roll), cos(roll)]
    ])

    # Combined rotation: R = Rz * Ry * Rx
    combined_matrix = Rz * Ry * Rx
    return Orientation(combined_matrix)

facing_west staticmethod

facing_west() -> Orientation

Horizontal timber with top face up. This is the IDENTITY orientation.

  • Length: +X (local) = -X (west) in global
  • Width: +Y (local) = -Y (south) in global
  • Facing: +Z (up)
Source code in kumiki/rule.py
@staticmethod
def facing_west() -> 'Orientation':
    """
    Horizontal timber with top face up.
    This is the IDENTITY orientation.

    - Length: +X (local) = -X (west) in global
    - Width: +Y (local) = -Y (south) in global
    - Facing: +Z (up)
    """
    return Orientation()  # Identity matrix

facing_east staticmethod

facing_east() -> Orientation

Horizontal timber with top face up. 180° rotation around Z axis from facing_west.

  • Length: +X (local) = +X (east) in global
  • Width: +Y (local) = +Y (north) in global
  • Facing: +Z (up)
Source code in kumiki/rule.py
@staticmethod
def facing_east() -> 'Orientation':
    """
    Horizontal timber with top face up.
    180° rotation around Z axis from facing_west.

    - Length: +X (local) = +X (east) in global
    - Width: +Y (local) = +Y (north) in global
    - Facing: +Z (up)
    """
    matrix = Matrix([
        [-1, 0, 0],
        [0, -1, 0],
        [0, 0, 1]
    ])
    return Orientation(matrix)

facing_north staticmethod

facing_north() -> Orientation

Horizontal timber with top face up. 90° counterclockwise rotation around Z axis from facing_west.

  • Length: +X (local) = +Y (north) in global
  • Width: +Y (local) = -X (west) in global
  • Facing: +Z (up)
Source code in kumiki/rule.py
@staticmethod
def facing_north() -> 'Orientation':
    """
    Horizontal timber with top face up.
    90° counterclockwise rotation around Z axis from facing_west.

    - Length: +X (local) = +Y (north) in global
    - Width: +Y (local) = -X (west) in global
    - Facing: +Z (up)
    """
    matrix = Matrix([
        [0, -1, 0],
        [1, 0, 0],
        [0, 0, 1]
    ])
    return Orientation(matrix)

facing_south staticmethod

facing_south() -> Orientation

Horizontal timber with top face up. 90° clockwise rotation around Z axis from facing_west.

  • Length: +X (local) = -Y (south) in global
  • Width: +Y (local) = +X (east) in global
  • Facing: +Z (up)
Source code in kumiki/rule.py
@staticmethod
def facing_south() -> 'Orientation':
    """
    Horizontal timber with top face up.
    90° clockwise rotation around Z axis from facing_west.

    - Length: +X (local) = -Y (south) in global
    - Width: +Y (local) = +X (east) in global
    - Facing: +Z (up)
    """
    matrix = Matrix([
        [0, 1, 0],
        [-1, 0, 0],
        [0, 0, 1]
    ])
    return Orientation(matrix)

pointing_up staticmethod

pointing_up() -> Orientation

Vertical timber with LENGTH pointing upward (+Z). This is the same as pointing_forward.

  • Length (local +X) → +Z (up) in global
  • Width (local +Y) → +Y (north) in global
  • Facing (local +Z) → -X (west) in global
Source code in kumiki/rule.py
@staticmethod
def pointing_up() -> 'Orientation':
    """
    Vertical timber with LENGTH pointing upward (+Z).
    This is the same as pointing_forward.

    - Length (local +X) → +Z (up) in global
    - Width (local +Y) → +Y (north) in global
    - Facing (local +Z) → -X (west) in global
    """
    matrix = Matrix([
        [0, 0, -1],
        [0, 1, 0],
        [1, 0, 0]
    ])
    return Orientation(matrix)

pointing_down staticmethod

pointing_down() -> Orientation

Vertical timber with LENGTH pointing downward (-Z).

  • Length (local +X) → -Z (down) in global
  • Width (local +Y) → +Y (north) in global
  • Facing (local +Z) → +X (east) in global
Source code in kumiki/rule.py
@staticmethod
def pointing_down() -> 'Orientation':
    """
    Vertical timber with LENGTH pointing downward (-Z).

    - Length (local +X) → -Z (down) in global
    - Width (local +Y) → +Y (north) in global
    - Facing (local +Z) → +X (east) in global
    """
    matrix = Matrix([
        [0, 0, 1],
        [0, 1, 0],
        [-1, 0, 0]
    ])
    return Orientation(matrix)

pointing_forward staticmethod

pointing_forward() -> Orientation

Vertical timber with LENGTH pointing upward (+Z). Identical to pointing_up.

  • Length (local +X) → +Z (up) in global
  • Width (local +Y) → +Y (north) in global
  • Facing (local +Z) → -X (west) in global
Source code in kumiki/rule.py
@staticmethod
def pointing_forward() -> 'Orientation':
    """
    Vertical timber with LENGTH pointing upward (+Z).
    Identical to pointing_up.

    - Length (local +X) → +Z (up) in global
    - Width (local +Y) → +Y (north) in global
    - Facing (local +Z) → -X (west) in global
    """
    matrix = Matrix([
        [0, 0, -1],
        [0, 1, 0],
        [1, 0, 0]
    ])
    return Orientation(matrix)

pointing_backward staticmethod

pointing_backward() -> Orientation

Vertical timber with LENGTH pointing upward (+Z), rotated 180° from pointing_forward.

  • Length (local +X) → +Z (up) in global
  • Width (local +Y) → -Y (south) in global
  • Facing (local +Z) → +X (east) in global
Source code in kumiki/rule.py
@staticmethod
def pointing_backward() -> 'Orientation':
    """
    Vertical timber with LENGTH pointing upward (+Z), rotated 180° from pointing_forward.

    - Length (local +X) → +Z (up) in global
    - Width (local +Y) → -Y (south) in global
    - Facing (local +Z) → +X (east) in global
    """
    matrix = Matrix([
        [0, 0, 1],
        [0, -1, 0],
        [1, 0, 0]
    ])
    return Orientation(matrix)

pointing_left staticmethod

pointing_left() -> Orientation

Vertical timber with LENGTH pointing upward (+Z), rotated 90° CCW from pointing_forward.

  • Length (local +X) → +Z (up) in global
  • Width (local +Y) → -X (west) in global
  • Facing (local +Z) → -Y (south) in global
Source code in kumiki/rule.py
@staticmethod
def pointing_left() -> 'Orientation':
    """
    Vertical timber with LENGTH pointing upward (+Z), rotated 90° CCW from pointing_forward.

    - Length (local +X) → +Z (up) in global
    - Width (local +Y) → -X (west) in global
    - Facing (local +Z) → -Y (south) in global
    """
    matrix = Matrix([
        [0, -1, 0],
        [0, 0, -1],
        [1, 0, 0]
    ])
    return Orientation(matrix)

pointing_right staticmethod

pointing_right() -> Orientation

Vertical timber with LENGTH pointing upward (+Z), rotated 90° CW from pointing_forward.

  • Length (local +X) → +Z (up) in global
  • Width (local +Y) → +X (east) in global
  • Facing (local +Z) → +Y (north) in global
Source code in kumiki/rule.py
@staticmethod
def pointing_right() -> 'Orientation':
    """
    Vertical timber with LENGTH pointing upward (+Z), rotated 90° CW from pointing_forward.

    - Length (local +X) → +Z (up) in global
    - Width (local +Y) → +X (east) in global
    - Facing (local +Z) → +Y (north) in global
    """
    matrix = Matrix([
        [0, 1, 0],
        [0, 0, 1],
        [1, 0, 0]
    ])
    return Orientation(matrix)

scalar

scalar(numerator, denominator=1) -> float

Create a float scalar value.

Parameters:

Name Type Description Default
numerator

The numerator (can be int, float, or str)

required
denominator

The denominator (default=1)

1

Returns:

Type Description
float

float value

Examples:

scalar(3) # 3.0 scalar(1, 2) # 0.5 scalar(2.5) # 2.5 scalar("1.5") # 1.5 from string scalar("1/32") # Parses fraction string

Source code in kumiki/rule.py
def scalar(numerator, denominator=1) -> float:
    """
    Create a float scalar value.

    Args:
        numerator: The numerator (can be int, float, or str)
        denominator: The denominator (default=1)

    Returns:
        float value

    Examples:
        scalar(3)             # 3.0
        scalar(1, 2)          # 0.5
        scalar(2.5)           # 2.5
        scalar("1.5")         # 1.5 from string
        scalar("1/32")        # Parses fraction string
    """
    if isinstance(numerator, str):
        text = numerator.strip()
        if "/" in text:
            num_str, den_str = text.split("/", 1)
            value = float(num_str) / float(den_str)
        else:
            value = float(text)
    else:
        value = float(numerator)
    return value / denominator if denominator != 1 else value

sin

sin(x)
Source code in kumiki/rule.py
def sin(x):
    return math.sin(x)

cos

cos(x)
Source code in kumiki/rule.py
def cos(x):
    return math.cos(x)

tan

tan(x)
Source code in kumiki/rule.py
def tan(x):
    return math.tan(x)

atan

atan(x)
Source code in kumiki/rule.py
def atan(x):
    return math.atan(x)

atan2

atan2(y, x)
Source code in kumiki/rule.py
def atan2(y, x):
    return math.atan2(y, x)

acos

acos(x)
Source code in kumiki/rule.py
def acos(x):
    return math.acos(x)

sqrt

sqrt(x)
Source code in kumiki/rule.py
def sqrt(x):
    # Tolerate tiny float noise around zero (e.g. two squares that should be
    # exactly equal, now computed with float rounding) without masking real
    # negative-argument bugs further away from zero.
    if -EPSILON_GENERIC < x < 0:
        return 0.0
    return math.sqrt(x)

simplify

simplify(expr)

No-op: floats need no symbolic simplification. Kept so old call sites (mostly simplify(a - b) == 0-style exactness checks) still parse; see safe_equality_test/safe_zero_test for the epsilon-based replacement.

Source code in kumiki/rule.py
def simplify(expr):
    """No-op: floats need no symbolic simplification. Kept so old call sites
    (mostly `simplify(a - b) == 0`-style exactness checks) still parse; see
    `safe_equality_test`/`safe_zero_test` for the epsilon-based replacement."""
    return expr

eye

eye(n: int) -> Matrix
Source code in kumiki/rule.py
def eye(n: int) -> Matrix:
    return Matrix.eye(n)

det

det(matrix: Matrix) -> float
Source code in kumiki/rule.py
def det(matrix: Matrix) -> float:
    return matrix.det()

prune

prune(value, collapse_mode=None)
Source code in kumiki/rule.py
def prune(value, collapse_mode=None):
    return value

giraffe_evalf

giraffe_evalf(expr) -> float
Source code in kumiki/rule.py
def giraffe_evalf(expr) -> float:
    return float(expr)

giraffe_norm

giraffe_norm(vec: Matrix, collapse_mode=None) -> float

Compute vector norm.

Source code in kumiki/rule.py
def giraffe_norm(vec: Matrix, collapse_mode=None) -> float:
    """Compute vector norm."""
    return vec.norm()

giraffe_det

giraffe_det(matrix: Matrix, collapse_mode=None) -> float

Compute matrix determinant.

Source code in kumiki/rule.py
def giraffe_det(matrix: Matrix, collapse_mode=None) -> float:
    """Compute matrix determinant."""
    return matrix.det()

giraffe_simplify

giraffe_simplify(expr, collapse_mode=None)

No-op (see module-level simplify).

Source code in kumiki/rule.py
def giraffe_simplify(expr, collapse_mode=None):
    """No-op (see module-level `simplify`)."""
    return expr

giraffe_compare

giraffe_compare(a, b, comparison: Comparison, collapse_mode=None, eps: Optional[float] = None) -> bool

Compare two values: evaluates a - b and applies comparison against zero.

eps overrides the default comparison tolerance for this one call.

Examples:

giraffe_compare(x, y, Comparison.GT) # x > y ? giraffe_compare(x, 0, Comparison.EQ) # x == 0 ?

Source code in kumiki/rule.py
def giraffe_compare(a, b, comparison: Comparison, collapse_mode=None, eps: Optional[float] = None) -> bool:
    """
    Compare two values: evaluates ``a - b`` and applies *comparison* against zero.

    *eps* overrides the default comparison tolerance for this one call.

    Examples:
        giraffe_compare(x, y, Comparison.GT)   # x > y ?
        giraffe_compare(x, 0, Comparison.EQ)   # x == 0 ?
    """
    try:
        val = float(a) - float(b)
    except Exception:
        return False
    return _apply_comparison(val, comparison, eps)

giraffe_dot_product

giraffe_dot_product(vec1: Matrix, vec2: Matrix, collapse_mode=None) -> float

Compute dot product.

Source code in kumiki/rule.py
def giraffe_dot_product(vec1: Matrix, vec2: Matrix, collapse_mode=None) -> float:
    """Compute dot product."""
    return vec1.dot(vec2)

giraffe_transform_vector

giraffe_transform_vector(matrix: Matrix, vector: Matrix, collapse_mode=None) -> Matrix

Compute matrix * vector (or matrix * matrix) transformation.

Source code in kumiki/rule.py
def giraffe_transform_vector(matrix: Matrix, vector: Matrix, collapse_mode=None) -> Matrix:
    """Compute matrix * vector (or matrix * matrix) transformation."""
    return matrix * vector

giraffe_normalize_vector

giraffe_normalize_vector(vec: Matrix, collapse_mode=None) -> Matrix

Normalize a vector.

Source code in kumiki/rule.py
def giraffe_normalize_vector(vec: Matrix, collapse_mode=None) -> Matrix:
    """Normalize a vector."""
    norm = giraffe_norm(vec)
    if norm < EPSILON_GENERIC:
        return vec
    return vec / norm

giraffe_magnitude

giraffe_magnitude(vec: Matrix, collapse_mode=None) -> float

Compute vector magnitude. Alias for giraffe_norm.

Source code in kumiki/rule.py
def giraffe_magnitude(vec: Matrix, collapse_mode=None) -> float:
    """Compute vector magnitude. Alias for giraffe_norm."""
    return giraffe_norm(vec)

create_v2

create_v2(x: Numeric, y: Numeric) -> V2

Create a 2D vector

Source code in kumiki/rule.py
def create_v2(x: Numeric, y: Numeric) -> V2:
    """Create a 2D vector"""
    return Matrix([x, y])

create_v3

create_v3(x: Numeric, y: Numeric, z: Numeric) -> V3

Create a 3D vector

Source code in kumiki/rule.py
def create_v3(x: Numeric, y: Numeric, z: Numeric) -> V3:
    """Create a 3D vector"""
    return Matrix([x, y, z])

cross_product

cross_product(v1: V3, v2: V3) -> V3

Calculate cross product of two 3D vectors

Source code in kumiki/rule.py
def cross_product(v1: V3, v2: V3) -> V3:
    """Calculate cross product of two 3D vectors"""
    return Matrix([
        v1[1]*v2[2] - v1[2]*v2[1],
        v1[2]*v2[0] - v1[0]*v2[2],
        v1[0]*v2[1] - v1[1]*v2[0]
    ])

radians

radians(angle: Numeric) -> Numeric

Identity function for angles already in radians. Use this to make it explicit that an angle is in radians.

Parameters:

Name Type Description Default
angle Numeric

Angle value in radians

required

Returns:

Type Description
Numeric

The same angle value (unchanged)

Examples:

radians(pi / 2) # 90 degrees in radians radians(pi / 4) # 45 degrees in radians

Source code in kumiki/rule.py
def radians(angle: Numeric) -> Numeric:
    """
    Identity function for angles already in radians.
    Use this to make it explicit that an angle is in radians.

    Args:
        angle: Angle value in radians

    Returns:
        The same angle value (unchanged)

    Examples:
        radians(pi / 2)      # 90 degrees in radians
        radians(pi / 4)       # 45 degrees in radians
    """
    return angle

degrees

degrees(angle: Numeric) -> Numeric

Convert an angle from degrees to radians.

Parameters:

Name Type Description Default
angle Numeric

Angle value in degrees

required

Returns:

Type Description
Numeric

Angle value in radians

Examples:

degrees(90) # 90 degrees = pi/2 radians degrees(45) # 45 degrees = pi/4 radians degrees(180) # 180 degrees = pi radians

Source code in kumiki/rule.py
def degrees(angle: Numeric) -> Numeric:
    """
    Convert an angle from degrees to radians.

    Args:
        angle: Angle value in degrees

    Returns:
        Angle value in radians

    Examples:
        degrees(90)           # 90 degrees = pi/2 radians
        degrees(45)           # 45 degrees = pi/4 radians
        degrees(180)          # 180 degrees = pi radians
    """
    return angle * pi / scalar(180)

inches

inches(numerator, denominator=1)

Create a measurement in meters from inches.

Parameters:

Name Type Description Default
numerator

The numerator (can be int, float, or str)

required
denominator

The denominator (default=1)

1

Returns:

Type Description

float value in meters

Examples:

inches(1, 32) # 1/32 inch inches(4) # 4 inches inches(3.5) # 3.5 inches inches("1.5") # 1.5 inches from string inches("1/32") # Parses fraction string

Source code in kumiki/rule.py
def inches(numerator, denominator=1):
    """
    Create a measurement in meters from inches.

    Args:
        numerator: The numerator (can be int, float, or str)
        denominator: The denominator (default=1)

    Returns:
        float value in meters

    Examples:
        inches(1, 32)        # 1/32 inch
        inches(4)            # 4 inches
        inches(3.5)          # 3.5 inches
        inches("1.5")        # 1.5 inches from string
        inches("1/32")       # Parses fraction string
    """
    return scalar(numerator, denominator) * INCH_TO_METER

feet

feet(numerator, denominator=1)

Create a measurement in meters from feet.

Parameters:

Name Type Description Default
numerator

The numerator (can be int, float, or str)

required
denominator

The denominator (default=1)

1

Returns:

Type Description

float value in meters

Examples:

feet(8) # 8 feet feet(1, 2) # 1/2 foot feet(6.5) # 6.5 feet

Source code in kumiki/rule.py
def feet(numerator, denominator=1):
    """
    Create a measurement in meters from feet.

    Args:
        numerator: The numerator (can be int, float, or str)
        denominator: The denominator (default=1)

    Returns:
        float value in meters

    Examples:
        feet(8)              # 8 feet
        feet(1, 2)           # 1/2 foot
        feet(6.5)            # 6.5 feet
    """
    return scalar(numerator, denominator) * FOOT_TO_METER

mm

mm(numerator, denominator=1)

Create a measurement in meters from millimeters.

Parameters:

Name Type Description Default
numerator

The numerator (can be int, float, or str)

required
denominator

The denominator (default=1)

1

Returns:

Type Description

float value in meters

Examples:

mm(90) # 90 millimeters mm(1, 2) # 1/2 millimeter mm(25.4) # 25.4 millimeters

Source code in kumiki/rule.py
def mm(numerator, denominator=1):
    """
    Create a measurement in meters from millimeters.

    Args:
        numerator: The numerator (can be int, float, or str)
        denominator: The denominator (default=1)

    Returns:
        float value in meters

    Examples:
        mm(90)               # 90 millimeters
        mm(1, 2)             # 1/2 millimeter
        mm(25.4)             # 25.4 millimeters
    """
    return scalar(numerator, denominator) / 1000

cm

cm(numerator, denominator=1)

Create a measurement in meters from centimeters.

Parameters:

Name Type Description Default
numerator

The numerator (can be int, float, or str)

required
denominator

The denominator (default=1)

1

Returns:

Type Description

float value in meters

Examples:

cm(9) # 9 centimeters cm(1, 2) # 1/2 centimeter cm(2.54) # 2.54 centimeters

Source code in kumiki/rule.py
def cm(numerator, denominator=1):
    """
    Create a measurement in meters from centimeters.

    Args:
        numerator: The numerator (can be int, float, or str)
        denominator: The denominator (default=1)

    Returns:
        float value in meters

    Examples:
        cm(9)                # 9 centimeters
        cm(1, 2)             # 1/2 centimeter
        cm(2.54)             # 2.54 centimeters
    """
    return scalar(numerator, denominator) / 100

m

m(numerator, denominator=1)

Create a measurement in meters.

Parameters:

Name Type Description Default
numerator

The numerator (can be int, float, or str)

required
denominator

The denominator (default=1)

1

Returns:

Type Description

float value in meters

Examples:

m(1) # 1 meter m(1, 2) # 1/2 meter m(2.5) # 2.5 meters

Source code in kumiki/rule.py
def m(numerator, denominator=1):
    """
    Create a measurement in meters.

    Args:
        numerator: The numerator (can be int, float, or str)
        denominator: The denominator (default=1)

    Returns:
        float value in meters

    Examples:
        m(1)                 # 1 meter
        m(1, 2)              # 1/2 meter
        m(2.5)               # 2.5 meters
    """
    return scalar(numerator, denominator)

shaku

shaku(numerator, denominator=1)

Create a measurement in meters from shaku (尺). Traditional Japanese carpentry unit.

1 shaku ≈ 303.03 mm (exactly 10/33 meters)

Parameters:

Name Type Description Default
numerator

The numerator (can be int, float, or str)

required
denominator

The denominator (default=1)

1

Returns:

Type Description

float value in meters

Examples:

shaku(1) # 1 shaku shaku(3, 2) # 3/2 shaku (1.5 shaku) shaku(2.5) # 2.5 shaku

Source code in kumiki/rule.py
def shaku(numerator, denominator=1):
    """
    Create a measurement in meters from shaku (尺).
    Traditional Japanese carpentry unit.

    1 shaku ≈ 303.03 mm (exactly 10/33 meters)

    Args:
        numerator: The numerator (can be int, float, or str)
        denominator: The denominator (default=1)

    Returns:
        float value in meters

    Examples:
        shaku(1)             # 1 shaku
        shaku(3, 2)          # 3/2 shaku (1.5 shaku)
        shaku(2.5)           # 2.5 shaku
    """
    return scalar(numerator, denominator) * SHAKU_TO_METER

sun

sun(numerator, denominator=1)

Create a measurement in meters from sun (寸). Traditional Japanese carpentry unit.

1 sun = 1/10 shaku ≈ 30.303 mm

Parameters:

Name Type Description Default
numerator

The numerator (can be int, float, or str)

required
denominator

The denominator (default=1)

1

Returns:

Type Description

float value in meters

Examples:

sun(1) # 1 sun sun(5) # 5 sun sun(1, 2) # 1/2 sun

Source code in kumiki/rule.py
def sun(numerator, denominator=1):
    """
    Create a measurement in meters from sun (寸).
    Traditional Japanese carpentry unit.

    1 sun = 1/10 shaku ≈ 30.303 mm

    Args:
        numerator: The numerator (can be int, float, or str)
        denominator: The denominator (default=1)

    Returns:
        float value in meters

    Examples:
        sun(1)               # 1 sun
        sun(5)               # 5 sun
        sun(1, 2)            # 1/2 sun
    """
    return scalar(numerator, denominator) * SHAKU_TO_METER / 10

bu

bu(numerator, denominator=1)

Create a measurement in meters from bu (分). Traditional Japanese carpentry unit.

1 bu = 1/10 sun = 1/100 shaku ≈ 3.0303 mm

Parameters:

Name Type Description Default
numerator

The numerator (can be int, float, or str)

required
denominator

The denominator (default=1)

1

Returns:

Type Description

float value in meters

Examples:

bu(1) # 1 bu bu(5) # 5 bu bu(1, 2) # 1/2 bu

Source code in kumiki/rule.py
def bu(numerator, denominator=1):
    """
    Create a measurement in meters from bu (分).
    Traditional Japanese carpentry unit.

    1 bu = 1/10 sun = 1/100 shaku ≈ 3.0303 mm

    Args:
        numerator: The numerator (can be int, float, or str)
        denominator: The denominator (default=1)

    Returns:
        float value in meters

    Examples:
        bu(1)                # 1 bu
        bu(5)                # 5 bu
        bu(1, 2)             # 1/2 bu
    """
    return scalar(numerator, denominator) * SHAKU_TO_METER / 100

safe_zero_test

safe_zero_test(value, eps: Optional[float] = None) -> bool

Test if a value is approximately zero, within eps (default EPSILON_GENERIC).

Source code in kumiki/rule.py
def safe_zero_test(value, eps: Optional[float] = None) -> bool:
    """Test if a value is approximately zero, within *eps* (default EPSILON_GENERIC)."""
    return safe_compare(value, 0, Comparison.EQ, eps=eps)

safe_equality_test

safe_equality_test(value, expected, eps: Optional[float] = None) -> bool

Test if two values are approximately equal, within eps (default EPSILON_GENERIC).

Source code in kumiki/rule.py
def safe_equality_test(value, expected, eps: Optional[float] = None) -> bool:
    """Test if two values are approximately equal, within *eps* (default EPSILON_GENERIC)."""
    return safe_compare(value, expected, Comparison.EQ, eps=eps)

safe_zero_test_sq

safe_zero_test_sq(value_squared, eps: Optional[float] = None) -> bool

Test whether a SQUARED quantity is approximately zero.

Takes a LINEAR tolerance and squares it internally, so eps means the same thing here as everywhere else in the library: a distance in model units, never a distance squared.

safe_zero_test_sq(dx * dx + dy * dy, eps)   # is the distance ~0?

Use this rather than safe_zero_test wherever the value under test is a square. Passing a squared value to safe_zero_test compares it against a linear tolerance, which sounds harmless and is not: at eps=5e-4 it treats any length below 22mm as zero. That has been the shape of two real bugs here already -- polygon edges declared degenerate, and pick tolerances meaning millimetres on one primitive and centimetres on another.

Source code in kumiki/rule.py
def safe_zero_test_sq(value_squared, eps: Optional[float] = None) -> bool:
    """Test whether a SQUARED quantity is approximately zero.

    Takes a LINEAR tolerance and squares it internally, so *eps* means the
    same thing here as everywhere else in the library: a distance in model
    units, never a distance squared.

        safe_zero_test_sq(dx * dx + dy * dy, eps)   # is the distance ~0?

    Use this rather than safe_zero_test wherever the value under test is a
    square. Passing a squared value to safe_zero_test compares it against a
    linear tolerance, which sounds harmless and is not: at eps=5e-4 it treats
    any length below 22mm as zero. That has been the shape of two real bugs
    here already -- polygon edges declared degenerate, and pick tolerances
    meaning millimetres on one primitive and centimetres on another.
    """
    tolerance = EPSILON_GENERIC if eps is None else eps
    return safe_compare(value_squared, 0, Comparison.EQ, eps=tolerance * tolerance)

are_vectors_parallel

are_vectors_parallel(vector1: Matrix, vector2: Matrix, eps: Optional[float] = None) -> bool

Check if two vectors are parallel.

For normalized vectors: dot product ≈ ±1 means parallel

Parameters:

Name Type Description Default
vector1 Matrix

First direction vector

required
vector2 Matrix

Second direction vector

required

Returns:

Type Description
bool

True if |abs(dot_product) - 1| is approximately zero (vectors are parallel)

Source code in kumiki/rule.py
def are_vectors_parallel(vector1: Matrix, vector2: Matrix, eps: Optional[float] = None) -> bool:
    """
    Check if two vectors are parallel.

    For normalized vectors: dot product ≈ ±1 means parallel

    Args:
        vector1: First direction vector
        vector2: Second direction vector

    Returns:
        True if |abs(dot_product) - 1| is approximately zero (vectors are parallel)
    """
    # Compute dot product
    dot_product = vector1.dot(vector2)

    # Check if |abs(dot_product) - 1| is approximately zero
    # This is equivalent to checking if abs(dot_product) is approximately 1
    deviation = Abs(Abs(dot_product) - 1)

    return safe_zero_test(deviation, eps)

are_vectors_perpendicular

are_vectors_perpendicular(vector1: Matrix, vector2: Matrix, eps: Optional[float] = None) -> bool

Check if two vectors are perpendicular.

For any vectors: dot product ≈ 0 means perpendicular

Parameters:

Name Type Description Default
vector1 Matrix

First direction vector

required
vector2 Matrix

Second direction vector

required

Returns:

Type Description
bool

True if dot_product is approximately zero (vectors are perpendicular)

Source code in kumiki/rule.py
def are_vectors_perpendicular(vector1: Matrix, vector2: Matrix, eps: Optional[float] = None) -> bool:
    """
    Check if two vectors are perpendicular.

    For any vectors: dot product ≈ 0 means perpendicular

    Args:
        vector1: First direction vector
        vector2: Second direction vector

    Returns:
        True if dot_product is approximately zero (vectors are perpendicular)
    """
    # Compute dot product
    dot_product = vector1.dot(vector2)

    # Check if dot product is approximately zero
    return safe_zero_test(dot_product, eps)