HPInc / HP-Digital-Microfluidics

HP Digital Microfluidics Software Platform and Libraries
MIT License
3 stars 1 forks source link

Simplify creation of well pad shapes #268

Closed EvanKirshenbaum closed 10 months ago

EvanKirshenbaum commented 10 months ago

To enable the BoardMonitor to draw the wells, which may have oddly-shaped pads, each well is associated with a WellShape:

PadBounds = Sequence[tuple[float,float]]

class WellShape:
    gate_pad_bounds: Final[PadBounds]
    shared_pad_bounds: Final[Sequence[Union[PadBounds, Sequence[PadBounds]]]]
    reagent_id_circle_center: tuple[float,float]
    reagent_id_circle_radius: float

    def __init__(self, *,
                 gate_pad_bounds: PadBounds,
                 shared_pad_bounds: Sequence[Union[PadBounds, Sequence[PadBounds]]],
                 reagent_id_circle_center: tuple[float, float],
                 reagent_id_circle_radius: float = 1):
        ...

Unfortunately, specifying the bounds is ugly, since it depends on the side of the board and the coordinate system used.

I did it for Joey and for Opendrop, but now I'm going to have to do it for Joey version 1.5 (#266) and when I get the Board Description Language in (#263), I'll need to be able to do this for arbitrary layouts, so I might as well figure out how.

What I'd like to do is remove all that and allow you to specify pad bounds by saying "If the well's exit dir was this, these would be the coordinates relative to the exit pad's outer edge" (or maybe relative to the gate's center or the center of the edge shared by the gate and the exit pad) and let the monitor figure out the actual shapes based on the real exit dir and the board's coordinate system"

Migrated from internal repository. Originally created by @EvanKirshenbaum on Apr 29, 2023 at 3:01 PM PDT. Closed on May 01, 2023 at 11:20 AM PDT.
EvanKirshenbaum commented 10 months ago

This issue was referenced by the following commit before migration:

EvanKirshenbaum commented 10 months ago

The new code works on both Joey and Opendrop, which differ in which vertical direction they think is positive. Untested on boards that disagree that east is positive, but it should work, and I doubt we'll find any.

The basic scheme is that you now create a single WellShape, which is constructed with a specific side: Dir in mind. You give it coordinates with the origin at the center of the well's gate. To simplify, the specification of PadBounds arguments, WellShape has class methods

    @classmethod
    def rectangle(cls, center: tuple[float, float], *, 
                  width: float = 1,
                  height: float = 1) -> PadBounds:
        ...

    @classmethod
    def square(cls, center: tuple[float, float], *, side: float=1) -> PadBounds:
        return cls.rectangle(center, width=side, height=side)

Note that all dimensions default to one, so specifying a single pad, or a row or column of pads is easy.

To map a coordinate in one of its PadBounds for a specific well, you use

class WellShape:
    def for_well(self, well: Well, xy: tuple[float, float]) -> tuple[float,float]:
        ...

This takes into account the side of the well (based on its exit_dir relative to the prototype side stored with the WellShape.

To support this, I added

class Orientation:
    def remap(self, xy: tuple[float, float], from_dir: Dir, to_dir: Dir) -> tuple[float,float]:

(which really should be called rotate()). This takes xy and changes it from facing from_dir to facing to_dir.

Note that wells on the opposite side of the board are now considered to be rotated rather than mirrored. I don't think that this should cause problems for anybody, especially since the current boards are all vertically symmetric. If anybody has macros that use just one of a mirrored pair, they may be surprised at which pad turns on on the display, but the effect should be the same.

Migrated from internal repository. Originally created by @EvanKirshenbaum on May 01, 2023 at 11:20 AM PDT.