Source code for jitx.placement

"""
Component placement and positioning
===================================

This module provides classes for positioning components and other objects
on the board with transformations and side placement.
"""

from __future__ import annotations

from typing import cast, overload, Self

from .transform import Transform, Point
from .layerindex import Side
from ._structural import Proxy, Structural


[docs] class Placement(Transform): """Component placement with side information. Extends Transform to include which side of the board the component is placed on. """ side: Side = Side.Top """Which side of the board this placement is on.""" @overload def __init__( self, xform: Point | Transform | Placement, /, *, on: Side | None = None ): ... @overload def __init__( self, translate: Point, rotate: float = 0, scale: float | tuple[float, float] = (1, 1), *, on: Side | None = None, ): ... def __init__( self, translate: Point | Transform | Placement, rotate: float = 0, scale: float | tuple[float, float] = (1, 1), *, on: Side | None = None, ): """Initialize a placement. Args: xform: Transform, point, or existing placement to adopt. on: Which side to place on. """ if isinstance(translate, Transform): xform = translate scale = xform._scale side = on or Side.Top if side == Side.Bottom: scale = (scale[0] * -1, scale[1]) if isinstance(xform, Placement): side = side * xform.side self.side = side super().__init__(xform._translate, xform._rotate, scale) else: if on == Side.Bottom: if not isinstance(scale, tuple): scale = scale, scale scale = (scale[0] * -1, scale[1]) super().__init__(translate, rotate, scale) self.side = on or Side.Top def __repr__(self): return f"Placement({super().__repr__()}, {self.side})"
[docs] def clone(self): """Create a copy of this placement.""" placement = super().clone() placement.side = self.side return placement
def _post_mul(self, left: Transform, right: Transform): left = cast(Placement, left) # otherwise we wouldn't be here side = left.side if isinstance(right, Placement): side = side * right.side self.side = side return self def __imul__(self, other: Transform) -> Self: if isinstance(other, Placement): self.side = self.side * other.side return super().__imul__(other) def _post_rmul(self, result: Transform, left: Transform): # not called if left was a Placement, no need to check its side placement = Placement(result) placement.side = self.side return placement
[docs] def inverse(self) -> Placement: """Compute the inverse of this placement.""" # the scale already carries the flip, `on=` would apply it a second time placement = Placement(super().inverse()) placement.side = self.side return placement
[docs] class Kinematic[T: Transform]: """Mixin for objects that can have transforms applied.""" transform: T | None = None """The transform applied to this object."""
[docs] class Positionable(Structural, Kinematic[Placement]): """Base class for objects that can be positioned on the board.""" def __matmul__(self, where: Point | Transform | Placement) -> Self: # FIXME implement proxy handling for everything in translation # return Proxy.create(self).at(where) return self.at(where) @overload def at( self, point: Point, /, *, on: Side = Side.Top, rotate: float = 0 ) -> Self: ... @overload def at(self, xform: Transform | Placement, /, *, on: Side = Side.Top) -> Self: ... @overload def at( self, x: float, y: float, /, *, on: Side = Side.Top, rotate: float = 0 ) -> Self: ...
[docs] def at( self, x: Placement | Transform | Point | float, y: float | None = None, /, *, on: Side = Side.Top, rotate: float = 0, ): """Place this object relative to its frame of reference. Note that this modifies the object, and does not create a copy. Args: x: x-value, transform, or placement to adopt. y: y-value if x is an x-value. This argument is only valid in that context. rotate: Rotation in degrees to apply to the object. Only applicable if not supplying a transform or placement. on: If set to bottom, this object will be placed on the "opposite" side from its frame of reference. This means if the frame of reference is on the bottom of the board, setting this to "bottom" will actually put the object back on top. Returns: the object itself.""" if isinstance(x, Placement | Transform | tuple): assert y is None placement = Placement(x, on=on) placement._rotate += rotate else: assert isinstance(y, float | int) placement = Placement((x, y), on=on) placement._rotate += rotate if isinstance(self, Proxy): # setting the transform does not taint the proxy. with Proxy.override(): self.transform = placement else: self.transform = placement return self