""" PortPather tutorial: Using .at() syntax """ from masque import RenderPather, Pattern, Port, R90 from masque.file.gdsii import writefile from basic_shapes import GDS_OPTS from pather import map_layer, prepare_tools def main() -> None: # Reuse the same patterns (pads, bends, vias) and tools as in pather.py library, M1_tool, M2_tool = prepare_tools() # Create a RenderPather and place some initial pads (same as Pather tutorial) rpather = RenderPather(library, tools=M2_tool) rpather.place('pad', offset=(18_000, 30_000), port_map={'wire_port': 'VCC'}) rpather.place('pad', offset=(18_000, 60_000), port_map={'wire_port': 'GND'}) rpather.pattern.label(layer='M2', string='VCC', offset=(18e3, 30e3)) rpather.pattern.label(layer='M2', string='GND', offset=(18e3, 60e3)) # # Routing with .at() chaining # # The .at(port_name) method returns a PortPather object which wraps the Pather # and remembers the selected port(s). This allows method chaining. # Route VCC: 6um South, then West to x=0. # (Note: since the port points North into the pad, path() moves South by default) (rpather.at('VCC') .path(ccw=False, length=6_000) # Move South, turn West (Clockwise) .path_to(ccw=None, x=0) # Continue West to x=0 ) # Route GND: 5um South, then West to match VCC's x-coordinate. rpather.at('GND').path(ccw=False, length=5_000).path_to(ccw=None, x=rpather['VCC'].x) # # Tool management and manual plugging # # We can use .retool() to change the tool for specific ports. # We can also use .plug() directly on a PortPather. # Manually add a via to GND and switch to M1_tool for subsequent segments (rpather.at('GND') .plug('v1_via', 'top') .retool(M1_tool) # this only retools the 'GND' port ) # We can also pass multiple ports to .at(), and then use .mpath() on them. # Here we bundle them, turn South, and retool both to M1 (VCC gets an auto-via). (rpather.at(['GND', 'VCC']) .mpath(ccw=True, xmax=-10_000, spacing=5_000) # Move West to -10k, turn South .retool(M1_tool) # Retools both GND and VCC .mpath(ccw=True, emax=50_000, spacing=1_200) # Turn East, moves 50um extension .mpath(ccw=False, emin=1_000, spacing=1_200) # U-turn back South .mpath(ccw=False, emin=2_000, spacing=4_500) # U-turn back West ) # Retool VCC back to M2 and move both to x=-28k rpather.at('VCC').retool(M2_tool) rpather.at(['GND', 'VCC']).mpath(ccw=None, xmin=-28_000) # Final segments to -50k rpather.at('VCC').path_to(ccw=None, x=-50_000, out_ptype='m1wire') with rpather.at('GND').toolctx(M2_tool): rpather.at('GND').path_to(ccw=None, x=-40_000) rpather.at('GND').path_to(ccw=None, x=-50_000) # # Branching with save_copy and into_copy # # .save_copy(new_name) creates a port copy and keeps the original selected. # .into_copy(new_name) creates a port copy and selects the new one. # Create a tap on GND (rpather.at('GND') .path(ccw=None, length=5_000) # Move GND further West .save_copy('GND_TAP') # Mark this location for a later branch .pathS(length=10_000, jog=-10_000) # Continue GND with an S-bend ) # Branch VCC and follow the new branch (rpather.at('VCC') .path(ccw=None, length=5_000) .into_copy('VCC_BRANCH') # We are now manipulating 'VCC_BRANCH' .path(ccw=True, length=5_000) # VCC_BRANCH turns South ) # The original 'VCC' port remains at x=-55k, y=VCC.y # # Port set management: add, drop, rename, delete # # Route the GND_TAP we saved earlier. (rpather.at('GND_TAP') .retool(M1_tool) .path(ccw=True, length=10_000) # Turn South .rename_to('GND_FEED') # Give it a more descriptive name .retool(M1_tool) # Re-apply tool to the new name ) # We can manage the active set of ports in a PortPather pp = rpather.at(['VCC_BRANCH', 'GND_FEED']) pp.add_port('GND') # Now tracking 3 ports pp.drop_port('VCC_BRANCH') # Now tracking 2 ports: GND_FEED, GND pp.path_each(ccw=None, length=5_000) # Move both 5um forward (length > transition size) # We can also delete ports from the pather entirely rpather.at('VCC').delete() # VCC is gone (we have VCC_BRANCH instead) # # Advanced Connections: path_into and path_from # # path_into routes FROM the selected port TO a target port. # path_from routes TO the selected port FROM a source port. # Create a destination component dest_ports = { 'in_A': Port((0, 0), rotation=R90, ptype='m2wire'), 'in_B': Port((5_000, 0), rotation=R90, ptype='m2wire') } library['dest'] = Pattern(ports=dest_ports) # Place dest so that its ports are to the West and South of our current wires. # Rotating by pi/2 makes the ports face West (pointing East). rpather.place('dest', offset=(-100_000, -100_000), rotation=R90, port_map={'in_A': 'DEST_A', 'in_B': 'DEST_B'}) # Connect GND_FEED to DEST_A # Since GND_FEED is moving South and DEST_A faces West, a single bend will suffice. rpather.at('GND_FEED').path_into('DEST_A') # Connect VCC_BRANCH to DEST_B using path_from rpather.at('DEST_B').path_from('VCC_BRANCH') # # Direct Port Transformations and Metadata # (rpather.at('GND') .set_ptype('m1wire') # Change metadata .translate((1000, 0)) # Shift the port 1um East .rotate(R90 / 2) # Rotate it 45 degrees .set_rotation(R90) # Force it to face West ) # Demonstrate .plugged() to acknowledge a manual connection # (Normally used when you place components so their ports perfectly overlap) rpather.add_port_pair(offset=(0, 0), names=('TMP1', 'TMP2')) rpather.at('TMP1').plugged('TMP2') # Removes both ports # # Rendering and Saving # # Since we used RenderPather, we must call .render() to generate the geometry. rpather.render() library['PortPather_Tutorial'] = rpather.pattern library.map_layers(map_layer) writefile(library, 'port_pather.gds', **GDS_OPTS) print("Tutorial complete. Output written to port_pather.gds") if __name__ == '__main__': main()