class EdgeGenerator(Group, __LayerElement__): """ Generates edge objects for each shape segment. """ shape = ShapeParameter() internal_pid = StringParameter( default='no_pid', doc='A unique polygon ID to which the edge connects.') def create_elements(self, elems): shape = self.shape.remove_straight_angles() shape = shape.reverse_points() for i, s in enumerate(shape.segments()): line_shape = Shape(points=s) L = RDD.GDSII.IMPORT_LAYER_MAP[self.layer] width = RDD[L.process.symbol].MIN_SIZE layer = PLayer(process=L.process, purpose=RDD.PURPOSE.PORT.OUTSIDE_EDGE_DISABLED) elems += Edge(shape=[], line_shape=line_shape, layer=layer, internal_pid=self.internal_pid, width=width, transformation=self.transformation) return elems
class Edge(Path): """ Edge elements are object that represents the edge of a polygonal shape. """ line_shape = ShapeParameter(default=[]) edge_type = RestrictedParameter(default=constants.EDGE_TYPE_NORMAL, restriction=RestrictValueList( constants.EDGE_TYPES)) internal_pid = StringParameter( default='no_pid', doc='A unique polygon ID to which the edge connects.') external_pid = StringParameter( default='no_pid', doc='A unique polygon ID to which the edge connects.') def __init__(self, shape, layer, transformation=None, **kwargs): super().__init__(shape=shape, layer=layer, transformation=transformation, **kwargs) def __repr__(self): if self is None: return 'Edge is None!' layer = RDD.GDSII.IMPORT_LAYER_MAP[self.layer] class_string = "[SPiRA: Edge] (center {}, width {}, process {}, purpose {})" return class_string.format(self.center, self.width, self.layer.process.symbol, self.layer.purpose.symbol) def __str__(self): return self.__repr__() def __hash__(self): return hash(self.__repr__()) def short_string(self): # return "Edge [{}, {}, {}]".format(self.center, self.layer.process.symbol, self.layer.purpose.symbol) # NOTE: We want to ignore the purpose for CIRCUIT_METAL ot DEVICE_METAL net connections. return "Edge [{}, {}]".format(self.center, self.layer.process.symbol) def flat_copy(self, level=-1): """ Flatten a copy of the polygon. """ S = Edge(shape=self.shape, layer=self.layer, transformation=self.transformation) S.expand_transform() return S
class RouteGeneral(Cell): layer = LayerParameter() route_shape = ShapeParameter(doc='Shape of the routing polygon.') port_input = Parameter(fdef_name='create_port_input') port_output = Parameter(fdef_name='create_port_output') gds_layer = Parameter(fdef_name='create_gds_layer') def create_gds_layer(self): ll = spira.Layer( number=self.layer.number, # datatype=RDD.PURPOSE.TERM.datatype datatype=22) return ll def create_port_input(self): term = spira.Port( name='P1', midpoint=self.route_shape.m1, width=self.route_shape.w1, orientation=self.route_shape.o1, # gds_layer=self.gds_layer ) return term def create_port_gdsii_output(self): term = spira.Port( name='P2', midpoint=self.route_shape.m2, width=self.route_shape.w2, orientation=self.route_shape.o2, # gds_layer=self.gds_layer ) return term def create_elements(self, elems): poly = spira.Polygon(shape=self.route_shape, layer=self.layer, enable_edges=False) elems += poly return elems def create_ports(self, ports): ports += self.port_input ports += self.port_output return ports
class EdgeGenerator(Group, __LayerElement__): """ Generates edge objects for each shape segment. """ shape = ShapeParameter() def create_elements(self, elems): xpts = list(self.shape.x_coords) ypts = list(self.shape.y_coords) n = len(xpts) xpts.append(xpts[0]) ypts.append(ypts[0]) clockwise = 0 for i in range(0, n): clockwise += ((xpts[i + 1] - xpts[i]) * (ypts[i + 1] + ypts[i])) if self.layer.name == 'BBOX': bbox = True else: bbox = False for i in range(0, n): name = '{}_e{}'.format(self.layer.name, i) x = np.sign(clockwise) * (xpts[i + 1] - xpts[i]) y = np.sign(clockwise) * (ypts[i] - ypts[i + 1]) orientation = (np.arctan2(x, y) * constants.RAD2DEG) + 90 midpoint = [(xpts[i + 1] + xpts[i]) / 2, (ypts[i + 1] + ypts[i]) / 2] width = np.abs( np.sqrt((xpts[i + 1] - xpts[i])**2 + (ypts[i + 1] - ypts[i])**2)) layer = RDD.GDSII.IMPORT_LAYER_MAP[self.layer] extend = RDD[layer.process.symbol].MIN_SIZE T = Rotation(orientation) + Translation(midpoint) layer = PLayer(process=layer.process, purpose=RDD.PURPOSE.PORT.OUTSIDE_EDGE_DISABLED) shape = shapes.BoxShape(width=width, height=extend) # elems += EdgeSymmetric(width=width, extend=extend, process=layer.process, transformation=T) elems += Edge(shape=shape, layer=layer, width=width, extend=extend, transformation=T) return elems
class __ShapeElement__(__LayerElement__): """ Base class for an edge element. """ shape = ShapeParameter() @property def points(self): return self.shape.points @property def area(self): import gdspy return gdspy.Polygon(self.shape.points).area() @property def count(self): return np.size(self.shape.points, 0) @property def bbox_info(self): return self.shape.bbox_info.transform_copy(self.transformation) @property def center(self): return self.bbox_info.center @center.setter def center(self, destination): self.move(midpoint=self.center, destination=destination) def id_string(self): return '{} - hash {}'.format(self.short_string(), self.shape.hash_string) def is_empty(self): """ Returns `False` is the polygon shape has no points. """ return self.shape.is_empty() def encloses(self, point): """ Returns `True` if the polygon encloses the point. """ from spira.yevon.utils import clipping shape = self.shape.transform_copy(self.transformation) return clipping.encloses(coord=point, points=shape.points) def expand_transform(self): """ Expand the transform by applying it to the shape. """ from spira.core.transforms.identity import IdentityTransform if not self.transformation.is_identity(): self.shape = self.shape.transform_copy(self.transformation) self.transformation = IdentityTransform() return self def flatten(self, level=-1, name_tree=[]): """ Flatten the polygon without creating a copy. """ return self.expand_transform() def stretch(self, factor=(1, 1), center=(0, 0)): """ Stretches the polygon by a factor. """ T = spira.Stretch(stretch_factor=factor, stretch_center=center) return T.apply(self) def stretch_copy(self, factor=(1, 1), center=(0, 0)): """ Stretches a copy of the polygon by a factor. """ T = spira.Stretch(stretch_factor=factor, stretch_center=center) return T.apply_copy(self) def stretch_port(self, port, destination): """ The element by moving the subject port, without distorting the entire element. Note: The opposite port position is used as the stretching center. """ opposite_port = bbox_info.bbox_info_opposite_boundary_port(self, port) T = stretching.stretch_element_by_port(self, opposite_port, port, destination) T.apply(self) return self def move(self, midpoint=(0, 0), destination=None, axis=None): """ Moves the polygon from `midpoint` to a `destination`. """ from spira.yevon.geometry.ports import Port if destination is None: destination = midpoint midpoint = Coord(0, 0) if isinstance(midpoint, Coord): m = midpoint elif np.array(midpoint).size == 2: m = Coord(midpoint) # elif issubclass(type(midpoint), __Port__): elif isinstance(midpoint, Port): m = midpoint.midpoint else: raise ValueError('Midpoint error') # if issubclass(type(destination), __Port__): if isinstance(destination, Port): d = destination.midpoint if isinstance(destination, Coord): d = destination elif np.array(destination).size == 2: d = Coord(destination) else: raise ValueError('Destination error') dxdy = d - m self.translate(dxdy) return self
class __ShapeElement__(__LayerElement__): """ Base class for an edge element. """ shape = ShapeParameter() @property def points(self): return self.shape.points @property def area(self): return gdspy.Polygon(self.shape.points).area() @property def count(self): return np.size(self.shape.points, 0) @property def center(self): return self.bbox_info.center @center.setter def center(self, destination): self.move(midpoint=self.center, destination=destination) @property def bbox_info(self): return self.shape.bbox_info.transform_copy(self.transformation) def id_string(self): sid = '{} - hash {}'.format(self.__repr__(), self.shape.hash_string) return sid def is_empty(self): """ Returns `False` is the polygon shape has no points. """ return self.shape.is_empty() def encloses(self, point): """ Returns `True` if the polygon encloses the point. """ from spira.yevon.utils import clipping shape = self.shape.transform_copy(self.transformation) return clipping.encloses(coord=point, points=shape.points) def expand_transform(self): """ Expand the transform by applying it to the shape. """ from spira.core.transforms.identity import IdentityTransform if not self.transformation.is_identity(): self.shape = self.shape.transform_copy(self.transformation) self.transformation = IdentityTransform() return self # FIXME: Move this to an output generator. def convert_to_gdspy(self, transformation=None): """ Converts a SPiRA polygon to a Gdspy polygon. The extra transformation parameter is the polygon edge ports. """ layer = RDD.GDSII.EXPORT_LAYER_MAP[self.layer] T = self.transformation + transformation shape = self.shape.transform_copy(T) return gdspy.Polygon(points=shape.points, layer=layer.number, datatype=layer.datatype)