diff --git a/README.md b/README.md index 8332c52..c9274b8 100644 --- a/README.md +++ b/README.md @@ -49,3 +49,6 @@ pip install git+https://mpxd.net/code/jan/masque.git@release - de-embedding - boolean ops * DOCS DOCS DOCS +* Tests tests tests +* check renderpather +* pather and renderpather examples diff --git a/examples/ellip_grating.py b/examples/ellip_grating.py index 0c34cce..88cc936 100644 --- a/examples/ellip_grating.py +++ b/examples/ellip_grating.py @@ -3,28 +3,33 @@ import numpy import masque -import masque.file.klamath +from masque.file import gdsii from masque import shapes def main(): pat = masque.Pattern(name='ellip_grating') - for rmin in numpy.arange(10, 15, 0.5): - pat.shapes.append(shapes.Arc( + layer = (0, 0) + pat.shapes[layer].extend([ + shapes.Arc( radii=(rmin, rmin), width=0.1, angles=(-numpy.pi/4, numpy.pi/4), - layer=(0, 0), - )) + ) + for rmin in numpy.arange(10, 15, 0.5)] + ) - pat.labels.append(masque.Label(string='grating centerline', offset=(1, 0), layer=(1, 2))) + pat.label(string='grating centerline', offset=(1, 0), layer=(1, 2)) pat.scale_by(1000) pat.visualize() - pat2 = pat.copy() - pat2.name = 'grating2' - masque.file.klamath.writefile((pat, pat2), 'out.gds.gz', 1e-9, 1e-3) + lib = { + 'ellip_grating': pat, + 'grating2': pat.copy(), + } + + gdsii.writefile(lib, 'out.gds.gz', meters_per_unit=1e-9, logical_units_per_unit=1e-3) if __name__ == '__main__': diff --git a/examples/pic2mask.py b/examples/pic2mask.py index 0f0d9a1..0e2516a 100644 --- a/examples/pic2mask.py +++ b/examples/pic2mask.py @@ -33,7 +33,9 @@ pyplot.show(block=False) # Create the layout from the contours # pat = Pattern() -pat.shapes = [Polygon(vertices=vv) for vv in contours if len(vv) < 1_000] +pat.shapes[(0, 0)].extend([ + Polygon(vertices=vv) for vv in contours if len(vv) < 1_000 + ]) lib = {} lib['my_mask_name'] = pat diff --git a/examples/test_rep.py b/examples/test_rep.py index dbc6d72..5ed6a6a 100644 --- a/examples/test_rep.py +++ b/examples/test_rep.py @@ -16,8 +16,9 @@ def main(): cell_name = 'ellip_grating' pat = masque.Pattern() + + layer = (0, 0) for rmin in numpy.arange(10, 15, 0.5): - layer = (0, 0) pat.shapes[layer].append(Arc( radii=(rmin, rmin), width=0.1, diff --git a/examples/tutorial/basic_shapes.py b/examples/tutorial/basic_shapes.py index 8c9550b..f8e1eef 100644 --- a/examples/tutorial/basic_shapes.py +++ b/examples/tutorial/basic_shapes.py @@ -32,9 +32,10 @@ def hole( Returns: Pattern containing a circle. """ - pat = Pattern(shapes=[ - Circle(radius=radius, offset=(0, 0), layer=layer), - ]) + pat = Pattern() + pat.shapes[layer].append( + Circle(radius=radius, offset=(0, 0)) + ) return pat @@ -58,8 +59,9 @@ def triangle( (numpy.cos( - pi / 6), numpy.sin( - pi / 6)), ]) * radius - pat = Pattern(shapes=[ - Polygon(offset=(0, 0), layer=layer, vertices=vertices), + pat = Pattern() + pat.shapes[layer].extend([ + Polygon(offset=(0, 0), vertices=vertices), ]) return pat @@ -84,16 +86,18 @@ def smile( pat = Pattern() # Add all the shapes we want - pat.shapes += [ - Circle(radius=radius, offset=(0, 0), layer=layer), # Outer circle - Circle(radius=radius / 10, offset=(radius / 3, radius / 3), layer=secondary_layer), - Circle(radius=radius / 10, offset=(-radius / 3, radius / 3), layer=secondary_layer), + pat.shapes[layer] += [ + Circle(radius=radius, offset=(0, 0)), # Outer circle + ] + + pat.shapes[secondary_layer] += [ + Circle(radius=radius / 10, offset=(radius / 3, radius / 3)), + Circle(radius=radius / 10, offset=(-radius / 3, radius / 3)), Arc( radii=(radius * 2 / 3, radius * 2 / 3), # Underlying ellipse radii angles=(7 / 6 * pi, 11 / 6 * pi), # Angles limiting the arc width=radius / 10, offset=(0, 0), - layer=secondary_layer, ), ] diff --git a/examples/tutorial/devices.py b/examples/tutorial/devices.py index b12fb6f..6211024 100644 --- a/examples/tutorial/devices.py +++ b/examples/tutorial/devices.py @@ -82,18 +82,18 @@ def perturbed_l3( # Build L3 cavity, using references to the provided hole pattern pat = Pattern() - pat.refs += [ - Ref(hole, scale=r, offset=(lattice_constant * x, - lattice_constant * y)) + pat.refs[hole] += [ + Ref(scale=r, offset=(lattice_constant * x, + lattice_constant * y)) for x, y, r in xyr] # Add rectangular undercut aids min_xy, max_xy = pat.get_bounds_nonempty(hole_lib) trench_dx = max_xy[0] - min_xy[0] - pat.shapes += [ - Polygon.rect(ymin=max_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width, layer=trench_layer), - Polygon.rect(ymax=min_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width, layer=trench_layer), + pat.shapes[trench_layer] += [ + Polygon.rect(ymin=max_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width), + Polygon.rect(ymax=min_xy[1], xmin=min_xy[0], lx=trench_dx, ly=trench_width), ] # Ports are at outer extents of the device (with y=0) @@ -131,9 +131,9 @@ def waveguide( # Build the pattern pat = Pattern() - pat.refs += [ - Ref(hole, offset=(lattice_constant * x, - lattice_constant * y)) + pat.refs[hole] += [ + Ref(offset=(lattice_constant * x, + lattice_constant * y)) for x, y in xy] # Ports are at outer edges, with y=0 @@ -170,9 +170,9 @@ def bend( # Build the pattern pat= Pattern() - pat.refs += [ - Ref(hole, offset=(lattice_constant * x, - lattice_constant * y)) + pat.refs[hole] += [ + Ref(offset=(lattice_constant * x, + lattice_constant * y)) for x, y in xy] # Figure out port locations. @@ -209,9 +209,9 @@ def y_splitter( # Build pattern pat = Pattern() - pat.refs += [ - Ref(hole, offset=(lattice_constant * x, - lattice_constant * y)) + pat.refs[hole] += [ + Ref(offset=(lattice_constant * x, + lattice_constant * y)) for x, y in xy] # Determine port locations @@ -248,30 +248,30 @@ def main(interactive: bool = True) -> None: # Turn our dict of devices into a Library -- useful for getting abstracts lib = Library(devices) - abv = lib.abstract_view() # lets us use abv[cell] instead of lib.abstract(cell) # # Build a circuit # - circ = Builder(library=lib) + # Create a builder, and add the circuit to our library as "my_circuit" + circ = Builder(library=lib, name='my_circuit') # Start by placing a waveguide. Call its ports "in" and "signal". - circ.place(abv['wg10'], offset=(0, 0), port_map={'left': 'in', 'right': 'signal'}) + circ.place('wg10', offset=(0, 0), port_map={'left': 'in', 'right': 'signal'}) # Extend the signal path by attaching the "left" port of a waveguide. # Since there is only one other port ("right") on the waveguide we # are attaching (wg10), it automatically inherits the name "signal". - circ.plug(abv['wg10'], {'signal': 'left'}) + circ.plug('wg10', {'signal': 'left'}) # Attach a y-splitter to the signal path. # Since the y-splitter has 3 ports total, we can't auto-inherit the # port name, so we have to specify what we want to name the unattached # ports. We can call them "signal1" and "signal2". - circ.plug(abv['ysplit'], {'signal': 'in'}, {'top': 'signal1', 'bot': 'signal2'}) + circ.plug('ysplit', {'signal': 'in'}, {'top': 'signal1', 'bot': 'signal2'}) # Add a waveguide to both signal ports, inheriting their names. - circ.plug(abv['wg05'], {'signal1': 'left'}) - circ.plug(abv['wg05'], {'signal2': 'left'}) + circ.plug('wg05', {'signal1': 'left'}) + circ.plug('wg05', {'signal2': 'left'}) # Add a bend to both ports. # Our bend's ports "left" and "right" refer to the original counterclockwise @@ -280,22 +280,22 @@ def main(interactive: bool = True) -> None: # to "signal2" to bend counterclockwise. # We could also use `mirrored=(True, False)` to mirror one of the devices # and then use same device port on both paths. - circ.plug(abv['bend0'], {'signal1': 'right'}) - circ.plug(abv['bend0'], {'signal2': 'left'}) + circ.plug('bend0', {'signal1': 'right'}) + circ.plug('bend0', {'signal2': 'left'}) # We add some waveguides and a cavity to "signal1". - circ.plug(abv['wg10'], {'signal1': 'left'}) - circ.plug(abv['l3cav'], {'signal1': 'input'}) - circ.plug(abv['wg10'], {'signal1': 'left'}) + circ.plug('wg10', {'signal1': 'left'}) + circ.plug('l3cav', {'signal1': 'input'}) + circ.plug('wg10', {'signal1': 'left'}) # "signal2" just gets a single of equivalent length - circ.plug(abv['wg28'], {'signal2': 'left'}) + circ.plug('wg28', {'signal2': 'left'}) # Now we bend both waveguides back towards each other - circ.plug(abv['bend0'], {'signal1': 'right'}) - circ.plug(abv['bend0'], {'signal2': 'left'}) - circ.plug(abv['wg05'], {'signal1': 'left'}) - circ.plug(abv['wg05'], {'signal2': 'left'}) + circ.plug('bend0', {'signal1': 'right'}) + circ.plug('bend0', {'signal2': 'left'}) + circ.plug('wg05', {'signal1': 'left'}) + circ.plug('wg05', {'signal2': 'left'}) # To join the waveguides, we attach a second y-junction. # We plug "signal1" into the "bot" port, and "signal2" into the "top" port. @@ -303,19 +303,16 @@ def main(interactive: bool = True) -> None: # This operation would raise an exception if the ports did not line up # correctly (i.e. they required different rotations or translations of the # y-junction device). - circ.plug(abv['ysplit'], {'signal1': 'bot', 'signal2': 'top'}, {'in': 'signal_out'}) + circ.plug('ysplit', {'signal1': 'bot', 'signal2': 'top'}, {'in': 'signal_out'}) # Finally, add some more waveguide to "signal_out". - circ.plug(abv['wg10'], {'signal_out': 'left'}) + circ.plug('wg10', {'signal_out': 'left'}) # We can also add text labels for our circuit's ports. # They will appear at the uppermost hierarchy level, while the individual # device ports will appear further down, in their respective cells. ports_to_data(circ.pattern) - # Add the pattern into our library - lib['my_circuit'] = circ.pattern - # Check if we forgot to include any patterns... ooops! if dangling := lib.dangling_refs(): print('Warning: The following patterns are referenced, but not present in the' diff --git a/examples/tutorial/library.py b/examples/tutorial/library.py index fe31265..5c8dfd0 100644 --- a/examples/tutorial/library.py +++ b/examples/tutorial/library.py @@ -1,4 +1,4 @@ -from typing import Sequence, Callable +from typing import Sequence, Callable, Any from pprint import pformat import numpy @@ -38,7 +38,7 @@ def main() -> None: # lib['triangle'] = lambda: basic_shapes.triangle(devices.RADIUS) - opts = dict( + opts: dict[str, Any] = dict( lattice_constant = devices.LATTICE_CONSTANT, hole = 'triangle', ) @@ -73,9 +73,7 @@ def main() -> None: circ2.plug('tri_wg10', {'output': 'left'}) # Add the circuit to the device library. - # It has already been generated, so we can use `set_const` as a shorthand for - # `lib['mixed_wg_cav'] = lambda: circ2.pattern` - lib.set_const('mixed_wg_cav', circ2.pattern) + lib['mixed_wg_cav'] = circ2.pattern # @@ -87,7 +85,7 @@ def main() -> None: # ... that lets us continue from where we left off. circ3.plug('tri_bend0', {'input': 'right'}) - circ3.plug('tri_bend0', {'input': 'left'}, mirrored=(True, False)) # mirror since no tri y-symmetry + circ3.plug('tri_bend0', {'input': 'left'}, mirrored=True) # mirror since no tri y-symmetry circ3.plug('tri_bend0', {'input': 'right'}) circ3.plug('bend0', {'output': 'left'}) circ3.plug('bend0', {'output': 'left'}) @@ -96,7 +94,7 @@ def main() -> None: circ3.plug('tri_wg28', {'input': 'right'}) circ3.plug('tri_wg10', {'input': 'right', 'output': 'left'}) - lib.set_const('loop_segment', circ3.pattern) + lib['loop_segment'] = circ3.pattern # # Write all devices into a GDS file @@ -128,7 +126,6 @@ if __name__ == '__main__': # name = port_map.get(name, name) # if name is None: # continue -# self.pattern.labels += [ -# Label(string=name, offset=self.ports[name].offset, layer=layer)] +# self.pattern.label(string=name, offset=self.ports[name].offset, layer=label_layer) # return self #