def test_touches(self): p1 = pya.Polygon(pya.Box(10, 20, 30, 40)) self.assertEqual(p1.touches(pya.Polygon(pya.Box(30, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.Polygon(pya.Box(31, 20, 40, 50))), False) self.assertEqual(p1.touches(pya.Polygon(pya.Box(29, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.SimplePolygon(pya.Box(30, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.SimplePolygon(pya.Box(31, 20, 40, 50))), False) self.assertEqual(p1.touches(pya.SimplePolygon(pya.Box(29, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.Box(30, 20, 40, 50)), True) self.assertEqual(p1.touches(pya.Box(31, 20, 40, 50)), False) self.assertEqual(p1.touches(pya.Box(29, 20, 40, 50)), True) self.assertEqual(p1.touches(pya.Edge(30, 20, 40, 50)), True) self.assertEqual(p1.touches(pya.Edge(31, 20, 40, 50)), False) self.assertEqual(p1.touches(pya.Edge(29, 20, 40, 50)), True) p1 = pya.SimplePolygon(pya.Box(10, 20, 30, 40)) self.assertEqual(p1.touches(pya.Polygon(pya.Box(30, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.Polygon(pya.Box(31, 20, 40, 50))), False) self.assertEqual(p1.touches(pya.Polygon(pya.Box(29, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.SimplePolygon(pya.Box(30, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.SimplePolygon(pya.Box(31, 20, 40, 50))), False) self.assertEqual(p1.touches(pya.SimplePolygon(pya.Box(29, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.Box(30, 20, 40, 50)), True) self.assertEqual(p1.touches(pya.Box(31, 20, 40, 50)), False) self.assertEqual(p1.touches(pya.Box(29, 20, 40, 50)), True) self.assertEqual(p1.touches(pya.Edge(30, 20, 40, 50)), True) self.assertEqual(p1.touches(pya.Edge(31, 20, 40, 50)), False) self.assertEqual(p1.touches(pya.Edge(29, 20, 40, 50)), True) p1 = pya.DPolygon(pya.DBox(10, 20, 30, 40)) self.assertEqual(p1.touches(pya.DPolygon(pya.DBox(30, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.DPolygon(pya.DBox(31, 20, 40, 50))), False) self.assertEqual(p1.touches(pya.DPolygon(pya.DBox(29, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.DSimplePolygon(pya.DBox(30, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.DSimplePolygon(pya.DBox(31, 20, 40, 50))), False) self.assertEqual(p1.touches(pya.DSimplePolygon(pya.DBox(29, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.DBox(30, 20, 40, 50)), True) self.assertEqual(p1.touches(pya.DBox(31, 20, 40, 50)), False) self.assertEqual(p1.touches(pya.DBox(29, 20, 40, 50)), True) self.assertEqual(p1.touches(pya.DEdge(30, 20, 40, 50)), True) self.assertEqual(p1.touches(pya.DEdge(31, 20, 40, 50)), False) self.assertEqual(p1.touches(pya.DEdge(29, 20, 40, 50)), True) p1 = pya.DSimplePolygon(pya.DBox(10, 20, 30, 40)) self.assertEqual(p1.touches(pya.DPolygon(pya.DBox(30, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.DPolygon(pya.DBox(31, 20, 40, 50))), False) self.assertEqual(p1.touches(pya.DPolygon(pya.DBox(29, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.DSimplePolygon(pya.DBox(30, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.DSimplePolygon(pya.DBox(31, 20, 40, 50))), False) self.assertEqual(p1.touches(pya.DSimplePolygon(pya.DBox(29, 20, 40, 50))), True) self.assertEqual(p1.touches(pya.DBox(30, 20, 40, 50)), True) self.assertEqual(p1.touches(pya.DBox(31, 20, 40, 50)), False) self.assertEqual(p1.touches(pya.DBox(29, 20, 40, 50)), True) self.assertEqual(p1.touches(pya.DEdge(30, 20, 40, 50)), True) self.assertEqual(p1.touches(pya.DEdge(31, 20, 40, 50)), False) self.assertEqual(p1.touches(pya.DEdge(29, 20, 40, 50)), True)
def test_extractRad(self): ex = pya.SimplePolygon().extract_rad() self.assertEqual(repr(ex), "[]") sp = pya.SimplePolygon.from_s("(0,0;0,200000;300000,200000;300000,100000;100000,100000;100000,0)") sp = sp.round_corners(10000, 5000, 200) ex = sp.extract_rad() self.assertEqual(ex, [pya.SimplePolygon.from_s("(0,0;0,200000;300000,200000;300000,100000;100000,100000;100000,0)"), 10000.0, 5000.0, 200]) ex = pya.Polygon().extract_rad() self.assertEqual(ex, []) sp = pya.Polygon.from_s("(0,0;0,300000;300000,300000;300000,0/100000,100000;200000,100000;200000,200000;100000,200000)") sp = sp.round_corners(10000, 5000, 200) ex = sp.extract_rad() self.assertEqual(ex, [pya.Polygon.from_s("(0,0;0,300000;300000,300000;300000,0/100000,100000;200000,100000;200000,200000;100000,200000)"), 10000.0, 5000.0, 200]) # double coords too ... ex = pya.DSimplePolygon().extract_rad() self.assertEqual(ex, []) sp = pya.DSimplePolygon.from_s("(0,0;0,200000;300000,200000;300000,100000;100000,100000;100000,0)") sp = sp.round_corners(10000, 5000, 200) ex = sp.extract_rad() # round to integers for better comparison ex[0] = pya.SimplePolygon(ex[0]) self.assertEqual(ex, [pya.SimplePolygon.from_s("(0,0;0,200000;300000,200000;300000,100000;100000,100000;100000,0)"), 10000.0, 5000.0, 200]) ex = pya.DPolygon().extract_rad() self.assertEqual(ex, []) sp = pya.DPolygon.from_s("(0,0;0,300000;300000,300000;300000,0/100000,100000;200000,100000;200000,200000;100000,200000)") sp = sp.round_corners(10000, 5000, 200) ex = sp.extract_rad() # round to integers for better comparison ex[0] = pya.Polygon(ex[0]) self.assertEqual(ex, [pya.Polygon.from_s("(0,0;0,300000;300000,300000;300000,0/100000,100000;200000,100000;200000,200000;100000,200000)"), 10000.0, 5000.0, 200])
def transform_and_rotate(self, center, ex=None): """ Translates the polygon by 'center' and rotates by the 'ex' orientation. Example: if current polygon is a unit square with bottom-left corner at (0,0), then square.transform_and_rotate(DPoint(0, 1), DVector(0, 1)) will rotate the square by 90 degrees and translate it by 1 y-unit. The new square's bottom-left corner will be at (-1, 1). """ if ex is None: ex = pya.DPoint(1, 0) ey = rotate90(ex) polygon_dpoints_transformed = [center + p.x * ex + p.y * ey for p in self.each_point()] self.assign(pya.DSimplePolygon(polygon_dpoints_transformed)) return self
def layout_circle(cell, layer, center, r): """ function to produce the layout of a filled circle cell: layout cell to place the layout layer: which layer to use center: origin DPoint r: radius w: waveguide width theta_start, theta_end: angle in radians units in microns optimal sampling """ arc_function = lambda t: np.array([center.x + r * np.cos(t), center.y + r * np.sin(t)]) t, coords = sample_function(arc_function, [0, 2 * np.pi - 0.001], tol=0.002 / r) dbu = cell.layout().dbu dpoly = pya.DSimplePolygon([pya.DPoint(x, y) for x, y in zip(*coords)]) cell.shapes(layer).insert(dpoly.to_itype(dbu))
def clip(self, x_bounds=(-np.inf, np.inf), y_bounds=(-np.inf, np.inf)): ''' Clips the polygon at four possible boundaries. The boundaries are tuples based on absolute coordinates and cartesian axes. This method is very powerful when used with transform_and_rotate. ''' # Add points exactly at the boundary, so that the filter below works. x_bounds = (np.min(x_bounds), np.max(x_bounds)) y_bounds = (np.min(y_bounds), np.max(y_bounds)) check_within_bounds = lambda p: x_bounds[0] <= p.x and x_bounds[1] >= p.x and \ y_bounds[0] <= p.y and y_bounds[1] >= p.y def intersect_left_boundary(p1, p2, x_bounds, y_bounds): left_most, right_most = (p1, p2) if p1.x < p2.x else (p2, p1) bottom_most, top_most = (p1, p2) if p1.y < p2.y else (p2, p1) if left_most.x < x_bounds[0]: # intersection only if right_most crosses x_bound[0] if right_most.x > x_bounds[0]: # outside the box, on the left y_intersect = np.interp(x_bounds[0], [left_most.x, right_most.x], [ left_most.y, right_most.y]) if y_bounds[0] < y_intersect and y_bounds[1] > y_intersect: return pya.DPoint(float(x_bounds[0]), float(y_intersect)) return None def intersect(p1, p2, x_bounds, y_bounds): intersect_list = list() last_intersect = None def rotate_bounds90(x_bounds, y_bounds, i_times): for i in range(i_times): x_bounds, y_bounds = (-y_bounds[1], -y_bounds[0]), (x_bounds[0], x_bounds[1]) return x_bounds, y_bounds for i in range(4): p1i, p2i = rotate(p1, i * pi / 2), rotate(p2, i * pi / 2) x_boundsi, y_boundsi = rotate_bounds90(x_bounds, y_bounds, i) p = intersect_left_boundary(p1i, p2i, x_boundsi, y_boundsi) if p is not None: last_intersect = i intersect_list.append(rotate(p, -i * pi / 2)) return intersect_list, last_intersect polygon_dpoints_clipped = list() polygon_dpoints = list(self.each_point()) def boundary_vertex(edge_from, edge_to): # left edge:0, top edge:1 etc. # returns the vertex between two edges assert abs(edge_from - edge_to) == 1 if edge_from % 2 == 0: vertical_edge = edge_from horizontal_edge = edge_to else: vertical_edge = edge_to horizontal_edge = edge_from x = x_bounds[(vertical_edge // 2) % 2] y = y_bounds[((horizontal_edge - 1) // 2) % 2] return pya.DPoint(x, y) # Rotate point list so we can start from a point inside # (helps the boundary_vertex algorithm) for idx, point in enumerate(polygon_dpoints): if check_within_bounds(point): break else: # polygon was never within bounds # this can only happen if boundaries are finite # return boundary vertices boundary_vertices = [boundary_vertex(i, i - 1) for i in range(4, 0, -1)] self.assign(pya.DSimplePolygon(boundary_vertices)) return self idx += 1 # make previous_point below already be inside polygon_dpoints = polygon_dpoints[idx:] + polygon_dpoints[:idx] previous_point = polygon_dpoints[-1] previous_intersect = None for point in polygon_dpoints: # compute new intersecting point and add to list intersected_points, last_intersect = intersect( previous_point, point, x_bounds, y_bounds) if previous_intersect is not None and last_intersect is not None and \ last_intersect != previous_intersect: if check_within_bounds(point): # this means that we are entering the box at a different edge # need to add the edge points # this assumes a certain polygon orientation # assume points go counterlockwise, which means that # from edge 0 to 2, it goes through 3 i = previous_intersect while i % 4 != last_intersect: polygon_dpoints_clipped.append(boundary_vertex(i, i - 1)) i = i - 1 polygon_dpoints_clipped.extend(intersected_points) if check_within_bounds(point): polygon_dpoints_clipped.append(point) previous_point = point if last_intersect is not None: previous_intersect = last_intersect self.assign(pya.DSimplePolygon(polygon_dpoints_clipped)) return self
def layout_waveguide_angle(cell, layer, points_list, width, angle): """ Lays out a waveguide (or trace) with a certain width along given points and with fixed orientation at all points. This is very useful for laying out Bezier curves with or without adiabatic tapers. Args: cell: cell to place into layer: layer to place into. It is done with cell.shapes(layer).insert(pya.Polygon) points_list: list of pya.DPoint (at least 2 points) width (microns): constant or list. If list, then it has to have the same length as points angle (degrees) """ if len(points_list) < 2: raise NotImplemented("ERROR: points_list too short") return def norm(self): return sqrt(self.x**2 + self.y**2) try: if len(width) == len(points_list): width_iterator = iter(width) elif len(width) == 2: # assume width[0] is initial width and # width[1] is final width # interpolate with points_list L = curve_length(points_list) distance = 0 widths_list = [width[0]] widths_func = lambda t: (1 - t) * width[0] + t * width[1] old_point = points_list[0] for point in points_list[1:]: distance += norm(point - old_point) old_point = point widths_list.append(widths_func(distance / L)) width_iterator = iter(widths_list) else: width_iterator = repeat(width[0]) except TypeError: width_iterator = repeat(width) finally: points_iterator = iter(points_list) theta = angle * pi / 180 points_low = list() points_high = list() point_width_list = list(zip(points_iterator, width_iterator)) N = len(point_width_list) for i in range(0, N): point, width = point_width_list[i] point_high = (point + 0.5 * width * pya.DPoint(cos(theta + pi / 2), sin(theta + pi / 2))) points_high.append(point_high) point_low = (point + 0.5 * width * pya.DPoint(cos(theta - pi / 2), sin(theta - pi / 2))) points_low.append(point_low) polygon_points = points_high + list(reversed(points_low)) poly = pya.DSimplePolygon(polygon_points) cell.shapes(layer).insert(poly)