Skip to content

drawing_target

drawing_target

Target for Draw commands to draw into.

BoundingBox

Bases: FrozenGeneralModel

Class for calculating bounding boxes.

Source code in src/pygerber/gerberx3/math/bounding_box.py
class BoundingBox(FrozenGeneralModel):
    """Class for calculating bounding boxes."""

    NULL: ClassVar[BoundingBox]

    max_x: Offset = Field(default=Offset.NULL)
    max_y: Offset = Field(default=Offset.NULL)

    min_x: Offset = Field(default=Offset.NULL)
    min_y: Offset = Field(default=Offset.NULL)

    @classmethod
    def from_diameter(cls, diameter: Offset) -> BoundingBox:
        """Create a bounding box from a given diameter."""
        half_diameter = diameter / 2
        return cls(
            max_x=half_diameter,
            max_y=half_diameter,
            min_x=-half_diameter,
            min_y=-half_diameter,
        )

    @classmethod
    def from_rectangle(cls, x_size: Offset, y_size: Offset) -> BoundingBox:
        """Create a bounding box from a given diameter."""
        half_x = x_size / 2
        half_y = y_size / 2
        return cls(
            max_x=half_x,
            max_y=half_y,
            min_x=-half_x,
            min_y=-half_y,
        )

    @property
    def width(self) -> Offset:
        """Return width of the bounding box."""
        return self.max_x - self.min_x

    @property
    def height(self) -> Offset:
        """Return height of the bounding box."""
        return self.max_y - self.min_y

    def get_size(self) -> Vector2D:
        """Get bounding box size."""
        return Vector2D(x=self.width, y=self.height)

    @property
    def center(self) -> Vector2D:
        """Return current center of the bounding box."""
        center_x = (self.max_x + self.min_x) / Offset(value=Decimal(2))
        center_y = (self.max_y + self.min_y) / Offset(value=Decimal(2))
        return Vector2D(x=center_x, y=center_y)

    def get_min_vector(self) -> Vector2D:
        """Return Vector2D of min_x and min_y."""
        return Vector2D(x=self.min_x, y=self.min_y)

    def as_pixel_box(
        self,
        dpi: int,
        *,
        dx_max: int = 0,
        dy_max: int = 0,
        dx_min: int = 0,
        dy_min: int = 0,
    ) -> PixelBox:
        """Return box as tuple of ints with order.

        [x0, y0, x1, y1], where x1 >= x0 and y1 >= y0
        """
        return PixelBox(
            (
                self.min_x.as_pixels(dpi) + dx_min,
                self.min_y.as_pixels(dpi) + dy_min,
                self.max_x.as_pixels(dpi) + dx_max,
                self.max_y.as_pixels(dpi) + dy_max,
            ),
        )

    def scale(self, by: Decimal) -> BoundingBox:
        """Return scaled bounding box."""
        dx_dy = (self.get_size() * by) / 2

        return BoundingBox(
            max_x=self.max_x + dx_dy.x,
            max_y=self.max_y + dx_dy.y,
            min_x=self.min_x - dx_dy.x,
            min_y=self.min_y - dx_dy.y,
        )

    def _operator(
        self,
        other: object,
        op: Callable,
    ) -> BoundingBox:
        if isinstance(other, Vector2D):
            return BoundingBox(
                max_x=op(self.max_x, other.x),
                max_y=op(self.max_y, other.y),
                min_x=op(self.min_x, other.x),
                min_y=op(self.min_y, other.y),
            )
        if isinstance(other, (Offset, Decimal, int, float)):
            return BoundingBox(
                max_x=op(self.max_x, other),
                max_y=op(self.max_y, other),
                min_x=op(self.min_x, -other),
                min_y=op(self.min_y, -other),
            )
        return NotImplemented  # type: ignore[unreachable]

    def __add__(self, other: object) -> BoundingBox:
        if isinstance(other, BoundingBox):
            return BoundingBox(
                max_x=max(self.max_x, other.max_x),
                max_y=max(self.max_y, other.max_y),
                min_x=min(self.min_x, other.min_x),
                min_y=min(self.min_y, other.min_y),
            )
        return self._operator(other, operator.add)

    def __sub__(self, other: object) -> BoundingBox:
        return self._operator(other, operator.sub)

    def __mul__(self, other: object) -> BoundingBox:
        return self._operator(other, operator.mul)

    def __truediv__(self, other: object) -> BoundingBox:
        return self._operator(other, operator.truediv)

    def __str__(self) -> str:
        return (
            f"{self.__class__.__qualname__}(max_x={self.max_x}, max_y={self.max_y}, "
            f"min_x={self.min_x}, min_y={self.min_y})"
        )

    def include_point(self, point: Vector2D) -> BoundingBox:
        """Include point in bounding box by extending bounding box overt the point."""
        # Check for the x-coordinate
        new_max_x = max(self.max_x, point.x)
        new_min_x = min(self.min_x, point.x)

        # Check for the y-coordinate
        new_max_y = max(self.max_y, point.y)
        new_min_y = min(self.min_y, point.y)

        return BoundingBox(
            max_x=new_max_x,
            max_y=new_max_y,
            min_x=new_min_x,
            min_y=new_min_y,
        )

width property

width: Offset

Return width of the bounding box.

height property

height: Offset

Return height of the bounding box.

center property

center: Vector2D

Return current center of the bounding box.

from_diameter classmethod

from_diameter(diameter: Offset) -> BoundingBox

Create a bounding box from a given diameter.

Source code in src/pygerber/gerberx3/math/bounding_box.py
@classmethod
def from_diameter(cls, diameter: Offset) -> BoundingBox:
    """Create a bounding box from a given diameter."""
    half_diameter = diameter / 2
    return cls(
        max_x=half_diameter,
        max_y=half_diameter,
        min_x=-half_diameter,
        min_y=-half_diameter,
    )

from_rectangle classmethod

from_rectangle(
    x_size: Offset, y_size: Offset
) -> BoundingBox

Create a bounding box from a given diameter.

Source code in src/pygerber/gerberx3/math/bounding_box.py
@classmethod
def from_rectangle(cls, x_size: Offset, y_size: Offset) -> BoundingBox:
    """Create a bounding box from a given diameter."""
    half_x = x_size / 2
    half_y = y_size / 2
    return cls(
        max_x=half_x,
        max_y=half_y,
        min_x=-half_x,
        min_y=-half_y,
    )

get_size

get_size() -> Vector2D

Get bounding box size.

Source code in src/pygerber/gerberx3/math/bounding_box.py
def get_size(self) -> Vector2D:
    """Get bounding box size."""
    return Vector2D(x=self.width, y=self.height)

get_min_vector

get_min_vector() -> Vector2D

Return Vector2D of min_x and min_y.

Source code in src/pygerber/gerberx3/math/bounding_box.py
def get_min_vector(self) -> Vector2D:
    """Return Vector2D of min_x and min_y."""
    return Vector2D(x=self.min_x, y=self.min_y)

as_pixel_box

as_pixel_box(
    dpi: int,
    *,
    dx_max: int = 0,
    dy_max: int = 0,
    dx_min: int = 0,
    dy_min: int = 0
) -> PixelBox

Return box as tuple of ints with order.

[x0, y0, x1, y1], where x1 >= x0 and y1 >= y0

Source code in src/pygerber/gerberx3/math/bounding_box.py
def as_pixel_box(
    self,
    dpi: int,
    *,
    dx_max: int = 0,
    dy_max: int = 0,
    dx_min: int = 0,
    dy_min: int = 0,
) -> PixelBox:
    """Return box as tuple of ints with order.

    [x0, y0, x1, y1], where x1 >= x0 and y1 >= y0
    """
    return PixelBox(
        (
            self.min_x.as_pixels(dpi) + dx_min,
            self.min_y.as_pixels(dpi) + dy_min,
            self.max_x.as_pixels(dpi) + dx_max,
            self.max_y.as_pixels(dpi) + dy_max,
        ),
    )

scale

scale(by: Decimal) -> BoundingBox

Return scaled bounding box.

Source code in src/pygerber/gerberx3/math/bounding_box.py
def scale(self, by: Decimal) -> BoundingBox:
    """Return scaled bounding box."""
    dx_dy = (self.get_size() * by) / 2

    return BoundingBox(
        max_x=self.max_x + dx_dy.x,
        max_y=self.max_y + dx_dy.y,
        min_x=self.min_x - dx_dy.x,
        min_y=self.min_y - dx_dy.y,
    )

include_point

include_point(point: Vector2D) -> BoundingBox

Include point in bounding box by extending bounding box overt the point.

Source code in src/pygerber/gerberx3/math/bounding_box.py
def include_point(self, point: Vector2D) -> BoundingBox:
    """Include point in bounding box by extending bounding box overt the point."""
    # Check for the x-coordinate
    new_max_x = max(self.max_x, point.x)
    new_min_x = min(self.min_x, point.x)

    # Check for the y-coordinate
    new_max_y = max(self.max_y, point.y)
    new_min_y = min(self.min_y, point.y)

    return BoundingBox(
        max_x=new_max_x,
        max_y=new_max_y,
        min_x=new_min_x,
        min_y=new_min_y,
    )

Vector2D

Bases: FrozenGeneralModel

Tuple wrapper for representing size with custom accessors.

Source code in src/pygerber/gerberx3/math/vector_2d.py
class Vector2D(FrozenGeneralModel):
    """Tuple wrapper for representing size with custom accessors."""

    NULL: ClassVar[Vector2D]
    UNIT_X: ClassVar[Vector2D]
    UNIT_Y: ClassVar[Vector2D]

    x: Offset
    y: Offset

    def as_pixels(self, dpi: int) -> tuple[int, int]:
        """Return size as pixels using given DPI for conversion."""
        return (self.x.as_pixels(dpi), self.y.as_pixels(dpi))

    def __eq__(self, other: object) -> bool:
        if isinstance(other, Vector2D):
            return self.x == other.x and self.y == other.y
        return NotImplemented

    def _operator(
        self,
        other: object,
        op: Callable,
    ) -> Vector2D:
        if isinstance(other, Offset):
            return Vector2D(
                x=op(self.x, other),
                y=op(self.y, other),
            )
        if isinstance(other, Vector2D):
            return Vector2D(
                x=op(self.x, other.x),
                y=op(self.y, other.y),
            )

        if isinstance(other, (Decimal, int, float, str)):
            return Vector2D(
                x=op(self.x, Decimal(other)),
                y=op(self.y, Decimal(other)),
            )
        return NotImplemented  # type: ignore[unreachable]

    def __add__(self, other: object) -> Vector2D:
        return self._operator(other, operator.add)

    def __sub__(self, other: object) -> Vector2D:
        return self._operator(other, operator.sub)

    def __mul__(self, other: object) -> Vector2D:
        return self._operator(other, operator.mul)

    def __truediv__(self, other: object) -> Vector2D:
        return self._operator(other, operator.truediv)

    def __neg__(self) -> Vector2D:
        return Vector2D(x=-self.x, y=-self.y)

    def _i_operator(
        self,
        other: object,
        op: Callable,
    ) -> Self:
        if isinstance(other, Vector2D):
            return self.model_copy(
                update={
                    "x": op(self.x, other.x),
                    "y": op(self.y, other.y),
                },
            )
        if isinstance(other, Offset):
            return self.model_copy(
                update={
                    "x": op(self.x, other),
                    "y": op(self.y, other),
                },
            )
        if isinstance(other, (Decimal, int, float, str)):
            return self.model_copy(
                update={
                    "x": op(self.x, Decimal(other)),
                    "y": op(self.y, Decimal(other)),
                },
            )
        return NotImplemented  # type: ignore[unreachable]

    def __iadd__(self, other: object) -> Self:
        return self._i_operator(other, operator.add)

    def __isub__(self, other: object) -> Self:
        return self._i_operator(other, operator.sub)

    def __imul__(self, other: object) -> Self:
        return self._i_operator(other, operator.mul)

    def __itruediv__(self, other: object) -> Self:
        return self._i_operator(other, operator.truediv)

    def __str__(self) -> str:
        return f"{self.__class__.__qualname__}(x={self.x}, y={self.y})"

    def length(self) -> Offset:
        """Return length of vector."""
        return Offset(value=((self.x * self.x).value + (self.y * self.y).value).sqrt())

    def angle_between_clockwise(self, other: Vector2D) -> float:
        """Calculate angle between two vectors in degrees clockwise."""
        self_norm = self / self.length()
        other_norm = other / other.length()

        dot = other_norm.dot(self_norm)
        determinant = self_norm.determinant(other_norm)

        theta = math.atan2(float(dot.value), float(determinant.value))

        return math.degrees(theta)

    def dot(self, other: Vector2D) -> Offset:
        """Calculate dot product of two vectors."""
        return self.x * other.x + self.y * other.y

    def determinant(self, other: Vector2D) -> Offset:
        """Calculate determinant of matrix constructed from self and other."""
        return self.x * other.y - self.y * other.x

as_pixels

as_pixels(dpi: int) -> tuple[int, int]

Return size as pixels using given DPI for conversion.

Source code in src/pygerber/gerberx3/math/vector_2d.py
def as_pixels(self, dpi: int) -> tuple[int, int]:
    """Return size as pixels using given DPI for conversion."""
    return (self.x.as_pixels(dpi), self.y.as_pixels(dpi))

length

length() -> Offset

Return length of vector.

Source code in src/pygerber/gerberx3/math/vector_2d.py
def length(self) -> Offset:
    """Return length of vector."""
    return Offset(value=((self.x * self.x).value + (self.y * self.y).value).sqrt())

angle_between_clockwise

angle_between_clockwise(other: Vector2D) -> float

Calculate angle between two vectors in degrees clockwise.

Source code in src/pygerber/gerberx3/math/vector_2d.py
def angle_between_clockwise(self, other: Vector2D) -> float:
    """Calculate angle between two vectors in degrees clockwise."""
    self_norm = self / self.length()
    other_norm = other / other.length()

    dot = other_norm.dot(self_norm)
    determinant = self_norm.determinant(other_norm)

    theta = math.atan2(float(dot.value), float(determinant.value))

    return math.degrees(theta)

dot

dot(other: Vector2D) -> Offset

Calculate dot product of two vectors.

Source code in src/pygerber/gerberx3/math/vector_2d.py
def dot(self, other: Vector2D) -> Offset:
    """Calculate dot product of two vectors."""
    return self.x * other.x + self.y * other.y

determinant

determinant(other: Vector2D) -> Offset

Calculate determinant of matrix constructed from self and other.

Source code in src/pygerber/gerberx3/math/vector_2d.py
def determinant(self, other: Vector2D) -> Offset:
    """Calculate determinant of matrix constructed from self and other."""
    return self.x * other.y - self.y * other.x

DrawingTarget

Target for Draw commands to draw into.

Source code in src/pygerber/backend/abstract/drawing_target.py
class DrawingTarget:
    """Target for Draw commands to draw into."""

    coordinate_origin: Vector2D
    bounding_box: BoundingBox

    def __init__(self, coordinate_origin: Vector2D, bounding_box: BoundingBox) -> None:
        """Initialize drawing target."""
        self.coordinate_origin = coordinate_origin
        self.bounding_box = bounding_box

    def __enter__(self) -> None:
        pass

    def __exit__(
        self,
        exc_type: Optional[type[BaseException]],
        exc_value: Optional[BaseException],
        traceback: Optional[TracebackType],
    ) -> None:
        if exc_type is None:
            self._finalize()

    def _finalize(self) -> None:
        """Call at the end of image modification.

        After this call no modifications to image are allowed.
        """

__init__

__init__(
    coordinate_origin: Vector2D, bounding_box: BoundingBox
) -> None

Initialize drawing target.

Source code in src/pygerber/backend/abstract/drawing_target.py
def __init__(self, coordinate_origin: Vector2D, bounding_box: BoundingBox) -> None:
    """Initialize drawing target."""
    self.coordinate_origin = coordinate_origin
    self.bounding_box = bounding_box