Layout tutorial (short)#

A Component is like an empty canvas, where you can add polygons, references to other Components and ports (to connect to other components)

In gdsfactory all dimensions are in microns

try:
  import google.colab
  is_running_on_colab = True
  !pip install gdsfactory > /dev/null
  
except ImportError:
  is_running_on_colab = False

Polygons#

You can add polygons to different layers.

import gdsfactory as gf
from gdsfactory.generic_tech import get_generic_pdk

gf.config.rich_output()
gf.CONF.display_type = "klayout"

PDK = get_generic_pdk()
PDK.activate()


@gf.cell
def demo_polygons():
    # Create a blank component (essentially an empty GDS cell with some special features)
    c = gf.Component()

    # Create and add a polygon from separate lists of x points and y points
    # (Can also be added like [(x1,y1), (x2,y2), (x3,y3), ... ]
    poly1 = c.add_polygon(
        [(-8, 6, 7, 9), (-6, 8, 17, 5)], layer=(1, 0)
    )  # GDS layers are tuples of ints (but if we use only one number it assumes the other number is 0)
    return c


c = demo_polygons()
c.plot()  # show it in KLayout
2024-03-09 00:42:56.062 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/demo_polygons.lyp'.

../_images/557f3f82abf369dc24b94d74d232428605be74fdfcc9a5df81b4b54d5c98f329.png

Exercise :

Make a component similar to the one above that has a second polygon in layer (2, 0)

c = gf.Component("myComponent2")
# Create some new geometry from the functions available in the geometry library
t = gf.components.text("Hello!")
r = gf.components.rectangle(size=[5, 10], layer=(2, 0))

# Add references to the new geometry to c, our blank component
text1 = c.add_ref(t)  # Add the text we created as a reference
# Using the << operator (identical to add_ref()), add the same geometry a second time
text2 = c << t
r = c << r  # Add the rectangle we created

# Now that the geometry has been added to "c", we can move everything around:
text1.movey(25)
text2.move([5, 30])
text2.rotate(45)
r.movex(-15)
r.movex(-15)

print(c)
c.plot()
myComponent2: uid dcdd7750, ports [], references ['text_1', 'text_2', 'rectangle_1'], 0 polygons
2024-03-09 00:42:56.342 | WARNING  | gdsfactory.component:_write_library:1951 - UserWarning: Component myComponent2 has invalid transformations. Try component.flatten_offgrid_references() first.
2024-03-09 00:42:56.369 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/myComponent2.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/component.py:1951: UserWarning: Component myComponent2 has invalid transformations. Try component.flatten_offgrid_references() first.
  warnings.warn(

../_images/facdd4d621fd6b2c612a171605972c29d46b5edc1f27209928503a9ddb9a8507.png

You define polygons both from gdstk or Shapely

from shapely.geometry.polygon import Polygon

import gdsfactory as gf

c = gf.Component("Mixed_polygons")
p0 = Polygon(zip((-8, 6, 7, 9), (-6, 8, 17, 5)))
p1 = p0.buffer(1)
p2 = p1.simplify(tolerance=0.1)
c.add_polygon(p0, layer=(1, 0))
c.add_polygon(p1, layer=(2, 0))
c.add_polygon(p2, layer=(3, 0))

c.add_polygon([(-8, 6, 7, 9), (-6, 8, 17, 5)], layer=(4, 0))
c.plot()
2024-03-09 00:42:56.560 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/Mixed_polygons.lyp'.

../_images/e1635f89d069b3bb509b11afe87343beddaf0713bfae2a7132b808eace68d541.png
p0

../_images/d4f26b4dbf440927046b9da7311a3f548600eba805b4931685d05e0c660eae2f.svg
p1 = p0.buffer(1)
p1

../_images/b0fc5c6bcd2952687cbd3f25e45101ce4ef91f10d47a5e3da1697ac130b479c1.svg
pnot = p1 - p0
pnot

../_images/09672a2d246aa2ab402f1f56a350f97998f676edd54100e603d4d58ec6f3bcc6.svg
c = gf.Component("exterior")
c.add_polygon(pnot, layer=(3, 0))
c.plot()
2024-03-09 00:42:56.779 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/exterior.lyp'.

../_images/deda0eaa81c095159054489e5db94bfaff036a23b8a3e247f0f4fddd8b378120.png
p_small = p0.buffer(-1)
p_small

../_images/dd6b60590c367a84c70929b18234122f4d4f67fffa99bc8e1ef215efc128da6c.svg
p_or = pnot | p_small
p_or

../_images/5ef73179d3d7082049ba443f507b896fb1c1778e5c970c4ad169bea28d93dbea.svg
c = gf.Component("p_or")
c.add_polygon(p_or, layer=(1, 0))
c.plot()
2024-03-09 00:42:56.985 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/p_or.lyp'.

../_images/456709d3d7eedc05a8c11eaff320380dc11f7754f01438dfb5fde638a384f869.png
import shapely as sp

p5 = sp.envelope(p0)
p5

../_images/99d8c99cc248a4e9534c1b24dfac2c2e2d6cc0ca17dd8161b5d40bbd03474722.svg
p6 = p5 - p0
p6

../_images/6fd7170d5cd4c3133f4bd859bdb8920e3a0a7cc88841f85d11a0c7b6f1a604f9.svg
c = gf.Component("p6")
c.add_polygon(p6, layer=(1, 0))
c.plot()
2024-03-09 00:42:57.260 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/p6.lyp'.

../_images/b53af812dd4962cc9a01c3b24d7943b00ae78648d87f25414cb9e8170b470b29.png
c = gf.Component("demo_multilayer")
p0 = c.add_polygon(p0, layer={(2, 0), (3, 0)})
c.plot()
2024-03-09 00:42:57.450 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/demo_multilayer.lyp'.

../_images/09282afa7563760d16cee0602bbb4c272ddcbe9330f22ed426b60e81754dc4cf.png
c = gf.Component("demo_mirror")
p0 = c.add_polygon(p0, layer=(1, 0))
p9 = c.add_polygon(p0, layer=(2, 0))
p9.mirror()
c.plot()
2024-03-09 00:42:57.648 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/demo_mirror.lyp'.

../_images/0eb123553e7e4d37bd3dae1d9d5a5b0dfd7d9ba7dff373bdbc39dfedc62b4e44.png
c = gf.Component("demo_xmin")
p0 = c.add_polygon(p0, layer=(1, 0))
p9 = c.add_polygon(p0, layer=(2, 0))
p9.mirror()
p9.xmin = p0.xmax
c.plot()
2024-03-09 00:42:57.848 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/demo_xmin.lyp'.

../_images/d3703a2a9d755c3c67b22ee59603ffd7c6cb8a103ba22de370b84d57bf0c2c2b.png
c = gf.Component("enclosure1")
r = c << gf.components.ring_single()
c.plot()
2024-03-09 00:42:58.071 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/enclosure1.lyp'.

../_images/d1f7ef2364db297165b36a929f61bda3592f30904bdca06fc487475a01ac2a64.png
c = gf.Component("enclosure2")
r = c << gf.components.ring_single()
p = c.get_polygon_bbox()
c.add_polygon(p, layer=(2, 0))
c.plot()
2024-03-09 00:42:58.215 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/enclosure2.lyp'.

../_images/0a22f7c97b6a3807dc7da856ab4365646fc04b7ae06f1452497825f4bd44580a.png
c = gf.Component("enclosure3")
r = c << gf.components.ring_single()
p = c.get_polygon_bbox(top=3, bottom=3)
c.add_polygon(p, layer=(2, 0))
c.plot()
2024-03-09 00:42:58.362 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/enclosure3.lyp'.

../_images/e39d8348ae3605bf1b17375b609ced9d0887c5dea27bd5be6658488fc03ce277.png
c = gf.Component("enclosure3")
r = c << gf.components.ring_single()
p = c.get_polygon_enclosure()
c.add_polygon(p, layer=(2, 0))
c.plot()
2024-03-09 00:42:58.510 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/enclosure3$1.lyp'.

../_images/3822ff6f8de6ecf9dc83a0e59077cbe1f7d1fbf16d996a4b2ad4800dcb5ec517.png
c = gf.Component("enclosure3")
r = c << gf.components.ring_single()
p = c.get_polygon_enclosure()
p2 = p.buffer(3)
c.add_polygon(p2, layer=(2, 0))
c.plot()
2024-03-09 00:42:58.659 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/enclosure3$2.lyp'.

../_images/1c3a4e78e3a4f8d6be900b03ac27c040d4525e6a71dd31c931b069057f731b50.png

Connect ports#

Components can have a “Port” that allows you to connect ComponentReferences together like legos.

You can write a simple function to make a rectangular straight, assign ports to the ends, and then connect those rectangles together.

Notice that connect transform each reference but things won’t remain connected if you move any of the references afterwards.

@gf.cell
def straight(length=10, width=1, layer=(1, 0)):
    c = gf.Component()
    c.add_polygon([(0, 0), (length, 0), (length, width), (0, width)], layer=layer)
    c.add_port(
        name="o1", center=[0, width / 2], width=width, orientation=180, layer=layer
    )
    c.add_port(
        name="o2", center=[length, width / 2], width=width, orientation=0, layer=layer
    )
    return c


c = gf.Component("straights_not_connected")

wg1 = c << straight(length=6, width=2.5, layer=(1, 0))
wg2 = c << straight(length=6, width=2.5, layer=(2, 0))
wg3 = c << straight(length=15, width=2.5, layer=(3, 0))
wg2.movey(10).rotate(10)
wg3.movey(20).rotate(15)

c.plot()
2024-03-09 00:42:58.797 | WARNING  | gdsfactory.component:_write_library:1951 - UserWarning: Component straights_not_connected has invalid transformations. Try component.flatten_offgrid_references() first.
2024-03-09 00:42:58.810 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straights_not_connected.lyp'.
/opt/hostedtoolcache/Python/3.11.8/x64/lib/python3.11/site-packages/gdsfactory/component.py:1951: UserWarning: Component straights_not_connected has invalid transformations. Try component.flatten_offgrid_references() first.
  warnings.warn(

../_images/58070d169f8caf39a84e4c9691a2a7cf6ca60d5e94910014d6c4c55c5d7977db.png

Now we can connect everything together using the ports:

Each straight has two ports: ‘o1’ and ‘o2’, respectively on the East and West sides of the rectangular straight component. These are arbitrary names defined in our straight() function above

# Let's keep wg1 in place on the bottom, and connect the other straights to it.
# To do that, on wg2 we'll grab the "o1" port and connect it to the "o2" on wg1:
wg2.connect("o1", wg1.ports["o2"])
# Next, on wg3 let's grab the "o1" port and connect it to the "o2" on wg2:
wg3.connect("o1", wg2.ports["o2"])

c.plot()
2024-03-09 00:42:58.956 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straights_not_connected.lyp'.

../_images/bada7d018299f975e42d4889f008fa314598d99f5984925de2cf6d45e85fd587.png

Ports can be added by copying existing ports. In the example below, ports are added at the component-level on c from the existing ports of children wg1 and wg3 (i.e. eastmost and westmost ports)

c.add_port("o1", port=wg1.ports["o1"])
c.add_port("o2", port=wg3.ports["o2"])
c.plot()
2024-03-09 00:42:59.178 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straights_not_connected.lyp'.

../_images/b35725972b7352bf75e97f501f91f550e0d2ae0861029d8b1b2b950837b362f6.png

Move and rotate references#

You can move, rotate, and reflect references to Components.

c = gf.Component("straights_connected")

wg1 = c << straight(length=1, layer=(1, 0))
wg2 = c << straight(length=2, layer=(2, 0))
wg3 = c << straight(length=3, layer=(3, 0))

# Create and add a polygon from separate lists of x points and y points
# e.g. [(x1, x2, x3, ...), (y1, y2, y3, ...)]
poly1 = c.add_polygon([(8, 6, 7, 9), (6, 8, 9, 5)], layer=(4, 0))

# Alternatively, create and add a polygon from a list of points
# e.g. [(x1,y1), (x2,y2), (x3,y3), ...] using the same function
poly2 = c.add_polygon([(0, 0), (1, 1), (1, 3), (-3, 3)], layer=(5, 0))

# Shift the first straight we created over by dx = 10, dy = 5
wg1.move([10, 5])

# Shift the second straight over by dx = 10, dy = 0
wg2.move(origin=[0, 0], destination=[10, 0])

# Shift the third straight over by dx = 0, dy = 4. The translation movement consist of the difference between the values of the destination and the origin and can optionally be limited in a single axis.
wg3.move([1, 1], [5, 5], axis="y")

# Then, move again the third straight "from" x=0 "to" x=10 (dx=10)
wg3.movex(0, 10)

c.plot()
2024-03-09 00:42:59.320 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/straights_connected.lyp'.

../_images/e2542a0c93c16fd4651126251bbb1073b768783458e02ff1c9749f89d3421e23.png

Ports#

Your straights wg1/wg2/wg3 are references to other waveguide components.

If you want to add ports to the new Component c you can use add_port, where you can create a new port or use a reference to an existing port from the underlying reference.

You can access the ports of a Component or ComponentReference

wg2.ports

{
    'o1': {'name': 'o1', 'width': 1, 'center': [10.0, 0.5], 'orientation': 180.0, 'layer': [2, 0], 'port_type': 'optical'},
    'o2': {'name': 'o2', 'width': 1, 'center': [12.0, 0.5], 'orientation': 0.0, 'layer': [2, 0], 'port_type': 'optical'}
}

References#

Now that your component c is a multi-straight component, you can add references to that component in a new blank Component c2, then add two references and shift one to see the movement.

c2 = gf.Component("MultiWaveguides")
wg1 = straight()
wg2 = straight(layer=(2, 0))
mwg1_ref = c2.add_ref(wg1)
mwg2_ref = c2.add_ref(wg2)
mwg2_ref.move(destination=[10, 10])
c2.plot()
2024-03-09 00:42:59.473 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/MultiWaveguides.lyp'.

../_images/485b2f585412890c86198d2b5b3a7e87d4e71dc3f2ee43bd6f3e76e52de68e16.png
# Like before, let's connect mwg1 and mwg2 together
mwg1_ref.connect(port="o2", destination=mwg2_ref.ports["o1"])
c2.plot()
2024-03-09 00:42:59.614 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/MultiWaveguides.lyp'.

../_images/f887f8a7703511bee729bc388be63328834a714f39c93fc7d9441f76fcd739b3.png

Labels#

You can add abstract GDS labels to annotate your Components, in order to record information directly into the final GDS file without putting any extra geometry onto any layer This label will display in a GDS viewer, but will not be rendered or printed like the polygons created by gf.components.text().

c2.add_label(text="First label", position=mwg1_ref.center)
c2.add_label(text="Second label", position=mwg2_ref.center)

# labels are useful for recording information
c2.add_label(
    text=f"The x size of this\nlayout is {c2.xsize}",
    position=(c2.xmax, c2.ymax),
    layer=(10, 0),
)
c2.plot()
2024-03-09 00:42:59.757 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/MultiWaveguides.lyp'.

../_images/f887f8a7703511bee729bc388be63328834a714f39c93fc7d9441f76fcd739b3.png

Another simple example

c = gf.Component("rectangle_with_label")
r = c << gf.components.rectangle(size=(1, 1))
r.x = 0
r.y = 0
c.add_label(
    text="Demo label",
    position=(0, 0),
    layer=(1, 0),
)
c.plot()
2024-03-09 00:42:59.895 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/rectangle_with_label.lyp'.

../_images/5167fbd3aededc4fd22062eea9bed016d7f9a49a9f1aebfe749532f07679ba5d.png

Boolean shapes#

If you want to subtract one shape from another, merge two shapes, or perform an XOR on them, you can do that with the boolean() function.

The operation argument should be {not, and, or, xor, ‘A-B’, ‘B-A’, ‘A+B’}. Note that ‘A+B’ is equivalent to ‘or’, ‘A-B’ is equivalent to ‘not’, and ‘B-A’ is equivalent to ‘not’ with the operands switched

c = gf.Component("boolean_demo")
e1 = c.add_ref(gf.components.ellipse(layer=(2, 0)))
e2 = c.add_ref(gf.components.ellipse(radii=(10, 6), layer=(2, 0))).movex(2)
e3 = c.add_ref(gf.components.ellipse(radii=(10, 4), layer=(2, 0))).movex(5)
c.plot()
2024-03-09 00:43:00.038 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/boolean_demo.lyp'.

../_images/ea6ed47b71fdf46eed27f89b241fe5320f8566010fce6b8400c26a3e180e162f.png
c2 = gf.geometry.boolean(A=[e1, e3], B=e2, operation="A-B", layer=(2, 0))
c2.plot()
2024-03-09 00:43:00.191 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/boolean_94e89182.lyp'.

../_images/fa170847ddb55eedb1550fffb29f97e75609cabb1333e319957b7017514e8f65.png

Move Reference by port#

c = gf.Component("ref_port_sample")
mmi = c.add_ref(gf.components.mmi1x2())
bend = c.add_ref(gf.components.bend_circular(layer=(2, 0)))
c.plot()
2024-03-09 00:43:00.351 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ref_port_sample.lyp'.

../_images/ef0ac3e48779d82c246e315bb26260a3c396ce562aa30d2aae3ed267534c850e.png
bend.connect("o1", mmi.ports["o2"])  # connects follow Source, destination syntax
c.plot()
2024-03-09 00:43:00.492 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ref_port_sample.lyp'.

../_images/31b19e4bbbbe2a9e8c21bb2886d1618ec008624f78389ce966aaf1c71d3f8641.png

Mirror reference#

By default the mirror works along the y-axis.

c = gf.Component("ref_mirror")
mmi = c.add_ref(gf.components.mmi1x2())
bend = c.add_ref(gf.components.bend_circular(layer=(2, 0)))
c.plot()
2024-03-09 00:43:00.635 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ref_mirror.lyp'.

../_images/ef0ac3e48779d82c246e315bb26260a3c396ce562aa30d2aae3ed267534c850e.png
mmi.mirror()
c.plot()
2024-03-09 00:43:00.783 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ref_mirror.lyp'.

../_images/9b312994978abd39697341523da10237662ede733bdb1ee4d2192551e0b588eb.png
mmi.mirror_y()
c.plot()
2024-03-09 00:43:00.928 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ref_mirror.lyp'.

../_images/9b312994978abd39697341523da10237662ede733bdb1ee4d2192551e0b588eb.png
mmi.mirror_x()
c.plot()
2024-03-09 00:43:01.150 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/ref_mirror.lyp'.

../_images/e5804404e7a56eb86ebf1005171d1d9915226b345c03bb49cbaf53ecd480a373.png

Write#

You can write your Component to:

  • GDS file (Graphical Database System) or OASIS for chips.

  • gerber for PCB.

  • STL for 3d printing.

import gdsfactory as gf

c = gf.components.cross()
c.write_gds("demo.gds")
c.plot()
2024-03-09 00:43:01.282 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to 'demo.gds'
2024-03-09 00:43:01.297 | INFO     | gdsfactory.technology.layer_views:to_lyp:1017 - LayerViews written to '/tmp/gdsfactory/cross.lyp'.

../_images/68b83733440cf48392ed2431390825b7ac7d5ab354d5d1f7457bfc75aa1c3c4a.png

You can see the GDS file in Klayout viewer.

Sometimes you also want to save the GDS together with metadata (settings, port names, widths, locations …) in YAML

c.write_gds("demo.gds", with_metadata=True)
2024-03-09 00:43:01.426 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to 'demo.gds'
2024-03-09 00:43:01.431 | INFO     | gdsfactory.component:_write_library:2025 - Write YAML metadata to 'demo.yml'

PosixPath('demo.gds')
c.write_oas("demo.oas")
2024-03-09 00:43:01.439 | INFO     | gdsfactory.component:_write_library:2021 - Wrote to 'demo.oas'

PosixPath('demo.oas')
c.write_stl("demo.stl")
Write PosixPath('/home/runner/work/gdsfactory-photonics-training/gdsfactory-photonics-training/notebooks/demo_1_0.stl') zmin = 0.000, height = 0.220
scene = c.to_3d()
scene.show()