RAIMAD

Subcompos

You can create complex components which themselves consist of other components. To do this, you simple define a class that inherits from rai.Compo and defines a _make() method that builds your component:

import raimad as rai

class Antenna(rai.Compo):
    def _make(self):

        # Create a reflector
        reflector = rai.Circle(20).proxy()

        # Create the active element
        active_element = rai.CustomPoly((
            (0, 5),
            (10, -5),
            (10, 5),
            (0, -5),
            )).proxy()

        # register the reflector and driven element
        # as subcompos

        self.subcompos.reflector = reflector
        self.subcompos.active_element = active_element

antenna = Antenna()
rai.show(antenna)

Note that you cannot register compos as subcompos directly. You must create proxies.

Legible transformations with BoundPoints

Notice how the antenna's active element in the example above is not quite aligned to the circle? As you've learned in Coordinates and Transformations, you can use Proxy().move() method to adjust its position.

However, RAIMAD offers a better way to do transformations called BoundPoints.

A boundpoint is simply an XY coordinate pair with a few special methods that allow the user to perform transformations in reference to that point.

One way to get a boundpoint to a proxy is by using its bounding box. The bounding box, or bbox, is simply an upright rectangle that fits the entirety of a component within itself. It can be accessed using the .bbox property:

rect = rai.RectLW(10, 20).proxy()

rai.show(rect)
print("The center is at:\n", rect.bbox.mid)
print("The bottom left corner is at:\n", rect.bbox.bot_left)
print("The middle of the right edge is at:\n", rect.bbox.mid_right)
The center is at:
 <(0.0, 0.0) bound to <
	Manual Proxy at /  with <Identity Transform> of
	RectLW at \  >
The bottom left corner is at:
 <(-5.0, -10.0) bound to <
	Manual Proxy at /  with <Identity Transform> of
	RectLW at \  >
The middle of the right edge is at:
 <(5.0, 0.0) bound to <
	Manual Proxy at /  with <Identity Transform> of
	RectLW at \  >

As you can see, the objects returned by the .bbox property are not regular tuples, but BoundPoints that hold a reference back to the proxy that was used to create them.

BoundPoints support all of the transformations that you learned about in Coordinates and Transformations. While transforming a proxy directly causes the transformation to be applied in reference to the origin of the proxy's coordinate system, applying a transformation through a boundpoint causes it to be applied in relation to that point:

from math import radians

rect = rai.RectLW(10, 20).proxy()

rai.show(rect)

rect1 = rect.proxy().bbox.bot_right.rotate(radians(15))
rai.show(rect1)

rect2 = rect.proxy().bbox.top_left.rotate(radians(15))
rai.show(rect2)

BoundPoints also have a special .to(point) method, which translates the proxy such that the boundpoint is in the same place as point:

from math import radians

rect = rai.RectLW(10, 20).proxy()

rai.show(rect)

rect1 = rect.proxy().bbox.bot_right.to((5, 5))
rai.show(rect1)

Using bounding box boundpoints, we can fix out Antenna component and line up the reflector and active element:

import raimad as rai

class Antenna(rai.Compo):
    def _make(self):

        # Create a reflector
        reflector = rai.Circle(20).proxy()

        # Create the active element
        active_element = rai.CustomPoly((
            (0, 5),
            (10, -5),
            (10, 5),
            (0, -5),
            )).proxy()

        print(
            "The middle of the reflector is at:",
            tuple(reflector.bbox.mid)
            )

        print(
            "The middle of the active element is at:",
            tuple(active_element.bbox.mid)
            )

        # Now lets align them!!
        reflector.bbox.mid.to(
            active_element.bbox.mid
            )

        # Now they're at the same point!
        assert reflector.bbox.mid == active_element.bbox.mid

        # register the reflector and driven element
        # as subcompos
        self.subcompos.reflector = reflector
        self.subcompos.active_element = active_element

antenna = Antenna()
rai.show(antenna)
The middle of the reflector is at: (0.0, 0.0)
The middle of the active element is at: (5.0, 0.0)

Snapping

You can also snap components together based on their bounding boxes. This allows for rapidly building components out of smaller building blocks without any magic numbers or tedious coordinate calculations:

class IShapedFilter(rai.Compo):
    def _make(self):
        coupler_top = rai.RectLW(10, 2).proxy()
        coupler_bot = rai.RectLW(12, 2).proxy()
        resonator = rai.RectLW(2, 10).proxy()

        coupler_top.snap_above(resonator)
        coupler_bot.snap_below(resonator)

        self.subcompos.coupler_top = coupler_top
        self.subcompos.coupler_bot = coupler_bot
        self.subcompos.resonator = resonator

class FilterReadout(rai.Compo):
    def _make(self):
        coupler = rai.RectLW(12, 2).proxy()
        short = rai.RectLW(2, 5).proxy()
        line = rai.RectLW(2, 10).proxy()

        bend = rai.CustomPoly(((0, 0), (2, 0), (0, 2)))
        bend_right = bend.proxy()
        bend_left = bend.proxy().vflip()

        bend_left.snap_left(coupler)
        bend_right.snap_right(coupler)
        short.snap_below(bend_left)
        line.snap_below(bend_right)

        self.subcompos.coupler = coupler
        self.subcompos.short = short
        self.subcompos.line = line
        self.subcompos.bend_left = bend_left
        self.subcompos.bend_right = bend_right

class FilterWithReadout(rai.Compo):
    def _make(self):
        filter = IShapedFilter().proxy()
        readout = FilterReadout().proxy()

        readout.snap_below(filter)
        readout.move(0, -4)  # Add a small gap

        self.subcompos.readout = readout
        self.subcompos.filter = filter

filter_with_readout = FilterWithReadout()
rai.show(filter_with_readout)