def point_path(points=[(0, 0), (4, 0), (4, 8)], width=1, layer=0): points = np.asarray(points) dxdy = points[1:] - points[:-1] angles = (np.arctan2(dxdy[:, 1], dxdy[:, 0])).tolist() angles = np.array([angles[0]] + angles + [angles[-1]]) diff_angles = (angles[1:] - angles[:-1]) mean_angles = (angles[1:] + angles[:-1]) / 2 dx = width / 2 * np.cos((mean_angles - pi / 2)) / np.cos((diff_angles / 2)) dy = width / 2 * np.sin((mean_angles - pi / 2)) / np.cos((diff_angles / 2)) left_points = points.T - np.array([dx, dy]) right_points = points.T + np.array([dx, dy]) all_points = np.concatenate([left_points.T, right_points.T[::-1]]) D = Device() D.add_polygon(all_points, layer=layer) D.add_port(name=1, midpoint=points[0], width=width, orientation=angles[0] * 180 / pi + 180) D.add_port(name=2, midpoint=points[-1], width=width, orientation=angles[-1] * 180 / pi) return D # quickplot(point_path())
def _import_gds(self): gdsii_lib = gdspy.GdsLibrary() gdsii_lib.read_gds(self.filename) top_level_cells = gdsii_lib.top_level() cellname = self.cellname if cellname is not None: if cellname not in gdsii_lib.cell_dict: raise ValueError( 'The requested cell (named %s) is not present in file %s' % (cellname, self.filename)) topcell = gdsii_lib.cell_dict[cellname] elif cellname is None and len(top_level_cells) == 1: topcell = top_level_cells[0] elif cellname is None and len(top_level_cells) > 1: areas = [] for cell in top_level_cells: areas.append(cell.area()) ind = areas.index(max(areas)) topcell = top_level_cells[ind] D = Device('import_gds') polygons = topcell.get_polygons(by_spec=True) for layer_in_gds, polys in polygons.items(): D.add_polygon(polys, layer=layer_in_gds) return D
def _arc(radius=10, width=0.5, theta=45, start_angle=0, angle_resolution=2.5, layer=0): """ Creates an arc of arclength ``theta`` starting at angle ``start_angle`` """ inner_radius = radius - width / 2 outer_radius = radius + width / 2 angle1 = (start_angle) * pi / 180 angle2 = (start_angle + theta) * pi / 180 t = np.linspace(angle1, angle2, np.ceil(abs(theta) / angle_resolution)) inner_points_x = (inner_radius * cos(t)).tolist() inner_points_y = (inner_radius * sin(t)).tolist() outer_points_x = (outer_radius * cos(t)).tolist() outer_points_y = (outer_radius * sin(t)).tolist() xpts = inner_points_x + outer_points_x[::-1] ypts = inner_points_y + outer_points_y[::-1] D = Device('arc') D.add_polygon(points=(xpts, ypts), layer=layer) D.add_port(name=1, midpoint=(radius * cos(angle1), radius * sin(angle1)), width=width, orientation=start_angle - 90 + 180 * (theta < 0)) D.add_port(name=2, midpoint=(radius * cos(angle2), radius * sin(angle2)), width=width, orientation=start_angle + theta + 90 - 180 * (theta < 0)) D.info['length'] = (abs(theta) * pi / 180) * radius return D
def _make_poly_connection(p1, p2, layer): d = Device() try: for l in layer: d.add_polygon([ p1.endpoints[0], p1.endpoints[1], p2.endpoints[1], p2.endpoints[0] ], layer=l) d.add_polygon([ p1.endpoints[0], p1.endpoints[1], p2.endpoints[0], p2.endpoints[1] ], layer=l) except: d.add_polygon([ p1.endpoints[0], p1.endpoints[1], p2.endpoints[1], p2.endpoints[0] ], layer=layer) d.add_polygon([ p1.endpoints[0], p1.endpoints[1], p2.endpoints[1], p2.endpoints[0] ], layer=l) return join(d)
def from_phidl(component: Device, **kwargs) -> Component: """Returns gf.Component from a phidl Device or function""" device = call_if_func(component, **kwargs) component = Component(name=device.name) for ref in device.references: new_ref = ComponentReference( component=ref.parent, origin=ref.origin, rotation=ref.rotation, magnification=ref.magnification, x_reflection=ref.x_reflection, ) new_ref.owner = component component.add(new_ref) for alias_name, alias_ref in device.aliases.items(): if alias_ref == ref: component.aliases[alias_name] = new_ref for p in device.ports.values(): component.add_port( port=Port( name=p.name, midpoint=p.midpoint, width=p.width, orientation=p.orientation, parent=p.parent, ) ) for poly in device.polygons: component.add_polygon(poly) for label in device.labels: component.add_label( text=label.text, position=label.position, layer=(label.layer, label.texttype), ) return component
def route_basic(port1, port2, path_type='sine', width_type='straight', width1=None, width2=None, num_path_pts=99, layer=0): # Assuming they're both Ports for now point_a = np.array(port1.midpoint) if width1 is None: width1 = port1.width point_b = np.array(port2.midpoint) if width2 is None: width2 = port2.width if round(abs(mod(port1.orientation - port2.orientation, 360)), 3) != 180: raise ValueError( '[DEVICE] route() error: Ports do not face each other (orientations must be 180 apart)' ) orientation = port1.orientation separation = point_b - point_a # Vector drawn from A to B distance = norm(separation) # Magnitude of vector from A to B rotation = np.arctan2( separation[1], separation[0]) * 180 / pi # Rotation of vector from A to B angle = rotation - orientation # If looking out along the normal of ``a``, the angle you would have to look to see ``b`` forward_distance = distance * cos(angle * pi / 180) lateral_distance = distance * sin(angle * pi / 180) # Create a path assuming starting at the origin and setting orientation = 0 # use the "connect" function later to move the path to the correct location xf = forward_distance yf = lateral_distance if path_type == 'straight': curve_fun = lambda t: [xf * t, yf * t] curve_deriv_fun = lambda t: [xf + t * 0, t * 0] if path_type == 'sine': curve_fun = lambda t: [xf * t, yf * (1 - cos(t * pi)) / 2] curve_deriv_fun = lambda t: [xf + t * 0, yf * (sin(t * pi) * pi) / 2] #if path_type == 'semicircle': # def semicircle(t): # t = np.array(t) # x,y = np.zeros(t.shape), np.zeros(t.shape) # ii = (0 <= t) & (t <= 0.5) # jj = (0.5 < t) & (t <= 1) # x[ii] = (cos(-pi/2 + t[ii]*pi/2))*xf # y[ii] = (sin(-pi/2 + t[ii]*pi/2)+1)*yf*2 # x[jj] = (cos(pi*3/2 - t[jj]*pi)+2)*xf/2 # y[jj] = (sin(pi*3/2 - t[jj]*pi)+1)*yf/2 # return x,y # curve_fun = semicircle # curve_deriv_fun = None if width_type == 'straight': width_fun = lambda t: (width2 - width1) * t + width1 if width_type == 'sine': width_fun = lambda t: (width2 - width1) * (1 - cos(t * pi) ) / 2 + width1 route_path = gdspy.Path(width=width1, initial_point=(0, 0)) route_path.parametric(curve_fun, curve_deriv_fun, number_of_evaluations=num_path_pts, max_points=199, final_width=width_fun, final_distance=None) route_path_polygons = route_path.polygons # Make the route path into a Device with ports, and use "connect" to move it # into the proper location D = Device() D.add_polygon(route_path_polygons, layer=layer) p1 = D.add_port(name=1, midpoint=(0, 0), width=width1, orientation=180) p2 = D.add_port(name=2, midpoint=[forward_distance, lateral_distance], width=width2, orientation=0) D.info['length'] = route_path.length D.rotate(angle=180 + port1.orientation - p1.orientation, center=p1.midpoint) D.move(origin=p1, destination=port1) return D
def _get_glyph(font, letter): """ Get a block reference to the given letter """ if not isinstance(letter, six.string_types) and len(letter) == 1: raise TypeError("Letter must be a string of length 1. Got: (%s)." % (letter)) if not isinstance(font, freetype.Face): raise TypeError( ("font %r must be a freetype font face. " "Load a font using _get_font_by_name first.") % (font)) if getattr(font, "gds_glyphs", None) is None: font.gds_glyphs = {} if letter in font.gds_glyphs: return font.gds_glyphs[letter] # Get the font name font_name = font.get_sfnt_name( freetype.TT_NAME_IDS['TT_NAME_ID_PS_NAME']).string.decode() if not font_name: # If there is no postscript name, use the family name font_name = font.family_name.replace(" ", "_") block_name = "*char_%s_0x%2X" % (font_name, ord(letter)) # Load control points from font file font.load_char(letter, freetype.FT_LOAD_FLAGS['FT_LOAD_NO_BITMAP']) glyph = font.glyph outline = glyph.outline points = np.array(outline.points, dtype=float) / font.size.ascender tags = outline.tags # Add polylines start, end = 0, -1 polylines = [] for contour in outline.contours: start = end + 1 end = contour # Build up the letter as a curve cpoint = start curve = gdspy.Curve(*points[cpoint], tolerance=0.001) while cpoint <= end: # Figure out what sort of point we are looking at if tags[cpoint] & 1: # We are at an on-curve control point. The next point may be # another on-curve point, in which case we create a straight # line interpolation, or it may be a quadratic or cubic # bezier curve. But first we check if we are at the end of the array if cpoint == end: ntag = tags[start] npoint = points[start] else: ntag = tags[cpoint + 1] npoint = points[cpoint + 1] # Then add the control points if ntag & 1: curve.L(*npoint) cpoint += 1 elif ntag & 2: # We are at a cubic bezier curve point if cpoint + 3 <= end: curve.C(*points[cpoint + 1:cpoint + 4].flatten()) elif cpoint + 2 <= end: plist = list(points[cpoint + 1:cpoint + 3].flatten()) plist.extend(points[start]) curve.C(*plist) else: raise ValueError( "Missing bezier control points. We require at least" " two control points to get a cubic curve.") cpoint += 3 else: # Otherwise we're at a quadratic bezier curve point if cpoint + 2 > end: cpoint_2 = start end_tag = tags[start] else: cpoint_2 = cpoint + 2 end_tag = tags[cpoint_2] p1 = points[cpoint + 1] p2 = points[cpoint_2] # Check if we are at a sequential control point. In that case, # p2 is actually the midpoint of p1 and p2. if end_tag & 1 == 0: p2 = (p1 + p2) / 2 # Add the curve curve.Q(p1[0], p1[1], p2[0], p2[1]) cpoint += 2 else: # We are looking at a control point if not tags[cpoint] & 2: # We are at a quadratic sequential control point. # Check if we're at the end of the segment if cpoint == end: cpoint_1 = start end_tag = tags[start] else: cpoint_1 = cpoint + 1 end_tag = tags[cpoint_1] # If we are at the beginning, this is a special case, # we need to reset the starting position if cpoint == start: p0 = points[end] p1 = points[cpoint] p2 = points[cpoint_1] if tags[end] & 1 == 0: # If the last point is also a control point, then the end is actually # halfway between here and the last point p0 = (p0 + p1) / 2 # And reset the starting position of the spline curve = gdspy.Curve(*p0, tolerance=0.001) else: # The first control point is at the midpoint of this control point and the # previous control point p0 = points[cpoint - 1] p1 = points[cpoint] p2 = points[cpoint_1] p0 = (p0 + p1) / 2 # Check if we are at a sequential control point again if end_tag & 1 == 0: p2 = (p1 + p2) / 2 # And add the segment curve.Q(p1[0], p1[1], p2[0], p2[1]) cpoint += 1 else: raise ValueError( "Sequential control points not valid for cubic splines." ) polylines.append(gdspy.Polygon(curve.get_points())) # Construct the device device = Device(block_name) if polylines: letter_polyline = polylines[0] for polyline in polylines[1:]: letter_polyline = gdspy.boolean(letter_polyline, polyline, "xor") device.add_polygon(letter_polyline) # Cache the return value and return it font.gds_glyphs[letter] = (device, glyph.advance.x / font.size.ascender) return font.gds_glyphs[letter]
def text(text='abcd', size=10, justify='left', layer=0, font="DEPLOF"): """ Creates geometries of text Parameters ---------- text : str Text string to be written. size : int or float Size of the text justify : {'left', 'right', 'center'} Justification of the text. layer : int, array-like[2], or set Specific layer(s) to put polygon geometry on. font: str Font face to use. Default DEPLOF does not require additional libraries, otherwise freetype will be used to load fonts. Font can be given either by name (e.g. "Times New Roman"), or by file path. OTF or TTF fonts are supported. Returns ------- t : Device A Device containing the text geometry. """ t = Device('text') xoffset = 0 yoffset = 0 face = font if face == "DEPLOF": scaling = size / 1000 for line in text.split('\n'): l = Device(name='textline') for c in line: ascii_val = ord(c) if c == ' ': xoffset += 500 * scaling elif (33 <= ascii_val <= 126) or (ascii_val == 181): for poly in _glyph[ascii_val]: xpts = np.array(poly)[:, 0] * scaling ypts = np.array(poly)[:, 1] * scaling l.add_polygon([xpts + xoffset, ypts + yoffset], layer=layer) xoffset += (_width[ascii_val] + _indent[ascii_val]) * scaling else: valid_chars = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~µ' warnings.warn('[PHIDL] text(): Warning, some characters ignored, no geometry for character "%s" with ascii value %s. ' \ 'Valid characters: %s' % (chr(ascii_val), ascii_val, valid_chars)) t.add_ref(l) yoffset -= 1500 * scaling xoffset = 0 else: from .font import _get_font_by_name, _get_font_by_file, _get_glyph # Load the font # If we've passed a valid file, try to load that, otherwise search system fonts font = None if (face.endswith(".otf") or face.endswith(".ttf")) and os.path.exists(face): font = _get_font_by_file(face) else: try: font = _get_font_by_name(face) except ValueError: pass if font is None: raise ValueError(( '[PHIDL] Failed to find font: "%s". ' + 'Try specifying the exact (full) path to the .ttf or .otf file. ' + 'Otherwise, it might be resolved by rebuilding the matplotlib font cache' ) % (face)) # Render each character for line in text.split('\n'): l = Device('textline') xoffset = 0 for letter in line: letter_dev = Device("letter") letter_template, advance_x = _get_glyph(font, letter) for poly in letter_template.polygons: letter_dev.add_polygon(poly.polygons, layer=layer) ref = l.add_ref(letter_dev) ref.move(destination=(xoffset, 0)) ref.magnification = size xoffset += size * advance_x ref = t.add_ref(l) ref.move(destination=(0, yoffset)) yoffset -= size justify = justify.lower() for l in t.references: if justify == 'left': pass if justify == 'right': l.xmax = 0 if justify == 'center': l.move(origin=l.center, destination=(0, 0), axis='x') t.flatten() return t