class PortLayout(spira.Cell): """ """ port = spira.PortParameter() edge = Parameter(fdef_name='create_edge') arrow = Parameter(fdef_name='create_arrow') label = Parameter(fdef_name='create_label') def create_edge(self): dw = self.port.width dl = self.port.length / 10 layer = PLayer(process=self.port.process, purpose=self.port.purpose) p = spira.Box(width=dw, height=dl, layer=layer) # T = transformation_from_vector(self.port) + spira.Rotation(-90) T = transformation_from_vector(self.port) + spira.Rotation( rotation=-90, rotation_center=self.port.midpoint) p.transform(T) return p def create_arrow(self): layer = PLayer(self.port.process, RDD.PURPOSE.PORT.DIRECTION) # w = self.port.length * 3 w = 0.01 # l = 2 # l = self.port.length * 3 l = 0.2 arrow_shape = shapes.ArrowShape(width=w, length=l, head=l * 0.2) p = spira.Polygon(shape=arrow_shape, layer=layer, enable_edges=False) T = transformation_from_vector(self.port) p.transform(T) return p def create_label(self): # enabled_purposes = (RDD.PURPOSE.PORT.INSIDE_EDGE_ENABLED, RDD.PURPOSE.PORT.OUTSIDE_EDGE_ENABLED) # disabled_purposes = (RDD.PURPOSE.PORT.INSIDE_EDGE_DISABLED, RDD.PURPOSE.PORT.OUTSIDE_EDGE_DISABLED) # if self.port.purpose in enabled_purposes: # layer = PLayer(self.port.process, RDD.PURPOSE.PORT.TEXT_ENABLED) # elif self.port.purpose is disabled_purposes: # layer = PLayer(self.port.process, RDD.PURPOSE.PORT.TEXT_DISABLED) # else: # layer = PLayer(self.port.process, RDD.PURPOSE.TEXT) purpose = RDD.PURPOSE.TEXT[self.port.purpose.symbol + 'T'] layer = PLayer(self.port.process, purpose) return spira.Label(position=self.port.midpoint, text=self.port.name, orientation=self.port.orientation, layer=layer) def create_elements(self, elems): elems += self.edge elems += self.label # if not isinstance(self.port, ContactPort): if self.port.purpose.name != 'ContactPort': elems += self.arrow return elems
class Edge(__EdgeElement__): """ Edge elements are object that represents the edge of a polygonal shape. Example ------- >>> edge Edge() """ inward_extend = NumberParameter( default=1, doc='The distance the edge extends inwards to the shape.') inside = Parameter(fdef_name='create_inside') outward_extend = NumberParameter( default=1, doc='The distance the edge extends outwards to the shape.') outside = Parameter(fdef_name='create_outside') def create_inside(self): for e in self.elements: purposes = [ RDD.PURPOSE.PORT.INSIDE_EDGE_ENABLED, RDD.PURPOSE.PORT.INSIDE_EDGE_DISABLED ] if e.layer.purpose in purposes: return e return None def create_outside(self): for e in self.elements: purposes = [ RDD.PURPOSE.PORT.OUTSIDE_EDGE_ENABLED, RDD.PURPOSE.PORT.OUTSIDE_EDGE_DISABLED ] if e.layer.purpose in purposes: return e return None def create_elements(self, elems): layer = PLayer(process=self.process, purpose=RDD.PURPOSE.PORT.INSIDE_EDGE_DISABLED) elems += Box(alias='InsideEdge', width=self.width, height=self.inward_extend, center=Coord(0, self.inward_extend / 2), layer=layer) layer = PLayer(process=self.process, purpose=RDD.PURPOSE.PORT.OUTSIDE_EDGE_DISABLED) elems += Box(alias='OutsideEdge', width=self.width, height=self.outward_extend, center=Coord(0, -self.outward_extend / 2), layer=layer) return elems
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 InputBasic(__InputBasic__): """ """ prefix = StringParameter(default='') flatten = BoolParameter() layer_map = Parameter(default=RDD.GDSII.IMPORT_LAYER_MAP) def __init__(self, file_name, **kwargs): super().__init__(file_name=file_name, **kwargs) self.library = None def read(self): self.library = Library('Imported') for cell in self.parse.dependencies(): self.library += cell return self.library def map_layer(self, layer): L = self.layer_map.get(layer, None) if isinstance(L, __Layer__): return L elif L is None: return L else: return Layer(L)
class NetlistAspects(__Aspects__): """ Defines the nets from the defined elements. """ netlist = Parameter(fdef_name='create_netlist') def create_netlist(self): net = Net() return net
class UnitGridContainer(ParameterInitializer): """ """ grids_per_unit = Parameter(fdef_name='create_grids_per_unit') units_per_grid = Parameter(fdef_name='create_units_per_grid') unit = NumberParameter(default=RDD.GDSII.UNIT) grid = NumberParameter(default=RDD.GDSII.GRID) def create_grids_per_unit(self): return self.unit / self.grid def create_units_per_grid(self): return self.grid / self.unit def validate_parameters(self): if self.grid > self.unit: raise ValueError('The grid should be smaller than the unit.') return True
class RoutePointShape(__RouteSimple__): width = NumberParameter(default=100) angles = Parameter(fdef_name='create_angles') def get_path(self): try: return self.__path__ except: raise ValueError('Path not set for {}'.format( self.__class__.__name__)) def set_path(self, value): self.__path__ = np.asarray(value) path = FunctionParameter(get_path, set_path) def create_midpoint1(self): return self.path[0] def create_midpoint2(self): return self.path[-1] def create_width1(self): return self.width def create_width2(self): return self.width def create_orientation1(self): # return self.angles[0]*180/pi+90 return self.angles[0] * 180 / pi + 90 def create_orientation2(self): # return self.angles[-1]*180/pi-90 return self.angles[-1] * 180 / pi - 90 def create_angles(self): dxdy = self.path[1:] - self.path[:-1] angles = (np.arctan2(dxdy[:, 1], dxdy[:, 0])).tolist() angles = np.array([angles[0]] + angles + [angles[-1]]) return angles def create_points(self, points): diff_angles = (self.angles[1:] - self.angles[:-1]) mean_angles = (self.angles[1:] + self.angles[:-1]) / 2 dx = self.width / 2 * np.cos((mean_angles - pi / 2)) / np.cos( (diff_angles / 2)) dy = self.width / 2 * np.sin((mean_angles - pi / 2)) / np.cos( (diff_angles / 2)) left_points = self.path.T - np.array([dx, dy]) right_points = self.path.T + np.array([dx, dy]) points = np.concatenate([left_points.T, right_points.T[::-1]]) return points
class RouteShape(shapes.Shape): path = Parameter() def create_points(self, points): if isinstance(self.path, gdspy.Path): points = clipping.boolean(subj=self.path.polygons, clip_type='or')[0] elif isinstance(self.path, gdspy.FlexPath): points = self.path.get_polygons()[0] return points
class PCell(Cell): """ """ pcell = BoolParameter(default=True) routes = ElementListParameter( doc='List of `Route` elements connected to the cell.') structures = ElementListParameter( doc='List of cell structures that coalesces the top-level cell.') extract_netlist = Parameter(fdef_name='create_extract_netlist') def __init__(self, **kwargs): super().__init__(**kwargs)
class EdgeAspects(__Aspects__): edges = Parameter(fdef_name='create_edges') def create_edges(self): """ Generate default edges for this polygon. These edges can be transformed using adapters. """ from spira.yevon.geometry.edges.edges import generate_edges return generate_edges(shape=self.shape, layer=self.layer, internal_pid=self.id_string(), transformation=self.transformation)
class __RouteSimple__(shapes.Shape): """ Interface class for shaping route patterns. """ m1 = Parameter(fdef_name='create_midpoint1') m2 = Parameter(fdef_name='create_midpoint2') w1 = Parameter(fdef_name='create_width1') w2 = Parameter(fdef_name='create_width2') o1 = Parameter(fdef_name='create_orientation1') o2 = Parameter(fdef_name='create_orientation2') def create_midpoint1(self): pass def create_midpoint2(self): pass def create_width1(self): pass def create_width2(self): pass def create_orientation1(self): pass def create_orientation2(self): pass
class Cell(__Cell__): """ A Cell encapsulates a set of elements that describes the layout being generated. """ _next_uid = 0 lcar = NumberParameter(default=100) name = Parameter(fdef_name='create_name', doc='Name of the cell instance.') def get_alias(self): if not hasattr(self, '__alias__'): self.__alias__ = self.name.split('__')[0] return self.__alias__ def set_alias(self, value): self.__alias__ = value alias = FunctionParameter( get_alias, set_alias, doc='Functions to generate an alias for cell name.') def __init__(self, name=None, elements=None, ports=None, nets=None, library=None, **kwargs): __Cell__.__init__(self, **kwargs) if name is not None: s = '{}_{}'.format(name, Cell._next_uid) self.__dict__['__name__'] = s Cell.name.__set__(self, s) self.uid = Cell._next_uid Cell._next_uid += 1 if library is not None: self.library = library if elements is not None: self.elements = ElementList(elements) if ports is not None: self.ports = PortList(ports) def __repr__(self): class_string = "[SPiRA: Cell(\'{}\')] (elements {}, ports {})" return class_string.format(self.name, self.elements.__len__(), self.ports.__len__()) def __str__(self): return self.__repr__() def create_name(self): if not hasattr(self, '__name__'): self.__name__ = self.__name_generator__(self) return self.__name__ def expand_transform(self): for S in self.elements.sref: S.expand_transform() S.reference.expand_transform() return self def expand_flat_copy(self, exclude_devices=False): from spira.yevon.gdsii.pcell import Device from spira.yevon.gdsii.polygon import Polygon from spira.yevon.gdsii.sref import SRef from spira.core.transforms.translation import Translation name = '' S = self.expand_transform() C = Cell(name=S.name + '_ExpandedCell') def _traverse_polygons(subj, cell, name): c_name = deepcopy(name) # print(cell) for e in cell.elements: if isinstance(e, SRef): if e.alias is not None: c_name += e.alias + ':' else: c_name += ':' subj = _traverse_polygons(subj=subj, cell=e.reference, name=c_name) # if exclude_devices is True: # if isinstance(e.reference, Device): # subj += e # else: # subj = _traverse_polygons(subj=subj, cell=e.reference, name=c_name) # else: # subj = _traverse_polygons(subj=subj, cell=e.reference, name=c_name) elif isinstance(e, Polygon): e.location_name = c_name # e.transform(expand_transform) # e.shape.move(expand_transform) subj += e # print(e.transformation) c_name = name # print('') return subj D = _traverse_polygons(C, S, name) return D def move(self, midpoint=(0, 0), destination=None, axis=None): """ """ from spira.yevon.geometry.ports.base import __Port__ if destination is None: destination = midpoint midpoint = [0, 0] if issubclass(type(midpoint), __Port__): o = midpoint.midpoint elif isinstance(midpoint, Coord): o = midpoint elif np.array(midpoint).size == 2: o = midpoint elif midpoint in obj.ports: o = obj.ports[midpoint].midpoint else: raise ValueError("[PHIDL] [DeviceReference.move()] ``midpoint`` " + "not array-like, a port, or port name") if issubclass(type(destination), __Port__): d = destination.midpoint elif isinstance(destination, Coord): d = destination elif np.array(destination).size == 2: d = destination elif destination in obj.ports: d = obj.ports[destination].midpoint else: raise ValueError( "[PHIDL] [DeviceReference.move()] ``destination`` " + "not array-like, a port, or port name") if axis == 'x': d = (d[0], o[1]) if axis == 'y': d = (o[0], d[1]) d = Coord(d[0], d[1]) o = Coord(o[0], o[1]) for e in self.elements: e.move(midpoint=o, destination=d) for p in self.ports: mc = np.array(p.midpoint) + np.array(d) - np.array(o) p.move(midpoint=p.midpoint, destination=mc) return self def stretch_p2p(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. """ from spira.core.transforms import stretching from spira.yevon.geometry import bbox_info from spira.yevon.gdsii.polygon import Polygon opposite_port = bbox_info.bbox_info_opposite_boundary_port(self, port) T = stretching.stretch_element_by_port(self, opposite_port, port, destination) if port.bbox is True: self = T(self) else: for i, e in enumerate(self.elements): if isinstance(e, Polygon): if e.id_string() == port.local_pid: self.elements[i] = T(e) return self def nets(self, lcar): return self.elements.nets(lcar=lcar) def create_netlist(self): net = self.nets(lcar=self.lcar).disjoint() return net
class GmshGeometry(__Geometry__): """ Generate a geometry using the Gmsh library. """ _ID = 0 _uid = 0 lcar = NumberParameter(default=100, doc='Mesh characteristic length.') algorithm = IntegerParameter(default=1, doc='Mesh algorithm used by Gmsh.') scale_Factor = NumberParameter(default=1e-6, doc='Mesh coord dimention scaling.') coherence_mesh = BoolParameter(defualt=True, doc='Merge similar points.') process = ProcessParameter() process_polygons = ElementListParameter() mesh_data = Parameter(fdef_name='create_mesh_data') def get_filename(self): if not hasattr(self, '__alias__'): self.__alias__ = '{}_{}'.format(self.process.symbol, GmshGeometry._uid) GmshGeometry._uid += 1 return self.__alias__ def set_filename(self, value): if value is not None: self.__alias__ = value filename = FunctionParameter( get_filename, set_filename, doc='Functions to generate an alias for cell name.') def __init__(self, **kwargs): super().__init__(**kwargs) self.geom = pygmsh.opencascade.Geometry( characteristic_length_min=self.lcar, characteristic_length_max=self.lcar) self.geom.add_raw_code('Mesh.Algorithm = {};'.format(self.algorithm)) self.geom.add_raw_code('Mesh.ScalingFactor = {};'.format( self.scale_Factor)) if self.coherence_mesh is True: self.geom.add_raw_code('Coherence Mesh;') def __physical_surfaces__(self): """ Creates physical surfaces that is compatible with the GMSH library for mesh generation. """ import re surfaces = [] for i, ply in enumerate(self.process_polygons): shape = ply.shape.transform_copy(ply.transformation) layer = RDD.GDSII.EXPORT_LAYER_MAP[ply.layer] pts = [[p[0], p[1], 0] for p in shape.points] surface_label = '{}_{}_{}_{}'.format(layer.number, layer.datatype, GmshGeometry._ID, i) gp = self.geom.add_polygon(pts, lcar=self.lcar, make_surface=True, holes=None) for j, ll in enumerate(gp.lines): pid = ply.shape.segment_labels[j].split(' - hash ') if len(pid) > 1: line_label = "{}*{}*{}".format(pid[0], pid[1], j) else: line_label = "{}*{}*{}".format(pid[0], None, j) self.geom.add_physical(ll, label=line_label) self.geom.add_physical(gp.surface, label=surface_label) # surfaces.append([gp.surface, gp.line_loop]) surfaces.append(gp) GmshGeometry._ID += 1 return surfaces def create_mesh_data(self): """ Generates the mesh data from the created physical surfaces. """ # if len(self.physical_surfaces) > 1: # self.geom.boolean_union(self.physical_surfaces) self.__physical_surfaces__() directory = os.getcwd() + '/debug/gmsh/' mesh_file = '{}{}.msh'.format(directory, self.filename) geo_file = '{}{}.geo'.format(directory, self.filename) vtk_file = '{}{}.vtu'.format(directory, self.filename) if not os.path.exists(directory): os.makedirs(directory) mesh_data = pygmsh.generate_mesh(self.geom, verbose=False, dim=2, prune_vertices=False, remove_faces=False, geo_filename=geo_file) # meshio.write(mesh_file, mesh_data) # meshio.write(vtk_file, mesh_data) return mesh_data
class YtronShape(spira.Shape): """ Class for generating a yTron shape. """ rho = NumberParameter(default=0.2, doc='Angle of concave bend between the arms.') arm_lengths = CoordParameter( default=(5, 3), doc='Length or the left and right arms, respectively.') source_length = NumberParameter(default=5, doc='Length of the source arm.') arm_widths = CoordParameter( default=(2, 2), doc='Width of the left and right arms, respectively.') theta = NumberParameter(default=3, doc='Angle of the left and right arms.') theta_resolution = NumberParameter(default=10, doc='Smoothness of the concave bend.') xc = Parameter(fdef_name='create_xc') yc = Parameter(fdef_name='create_yc') arm_x_left = Parameter(fdef_name='create_arm_x_left') arm_y_left = Parameter(fdef_name='create_arm_y_left') arm_x_right = Parameter(fdef_name='create_arm_x_right') arm_y_right = Parameter(fdef_name='create_arm_y_right') rad_theta = Parameter(fdef_name='create_rad_theta') ml = Parameter(fdef_name='create_midpoint_left') mr = Parameter(fdef_name='create_midpoint_right') ms = Parameter(fdef_name='create_midpoint_source') def create_rad_theta(self): return self.theta * np.pi / 180 def create_xc(self): return self.rho * np.cos(self.rad_theta) def create_yc(self): return self.rho * np.sin(self.rad_theta) def create_arm_x_left(self): return self.arm_lengths[0] * np.sin(self.rad_theta) def create_arm_y_left(self): return self.arm_lengths[0] * np.cos(self.rad_theta) def create_arm_x_right(self): return self.arm_lengths[1] * np.sin(self.rad_theta) def create_arm_y_right(self): return self.arm_lengths[1] * np.cos(self.rad_theta) def create_midpoint_left(self): xc = -(self.xc + self.arm_x_left + self.arm_widths[0] / 2) yc = self.yc + self.arm_y_left return [xc, yc] def create_midpoint_right(self): xc = self.xc + self.arm_x_right + self.arm_widths[1] / 2 yc = self.yc + self.arm_y_right return [xc, yc] def create_midpoint_source(self): xc = (self.arm_widths[1] - self.arm_widths[0]) / 2 yc = -self.source_length + self.yc return [xc, yc] def create_points(self, points): theta = self.theta * np.pi / 180 theta_resolution = self.theta_resolution * np.pi / 180 theta_norm = int((np.pi - 2 * theta) / theta_resolution) + 2 thetalist = np.linspace(-(np.pi - theta), -theta, theta_norm) semicircle_x = self.rho * np.cos(thetalist) semicircle_y = self.rho * np.sin(thetalist) + self.rho xpts = semicircle_x.tolist() + [ self.xc + self.arm_x_right, self.xc + self.arm_x_right + self.arm_widths[1], self.xc + self.arm_widths[1], self.xc + self.arm_widths[1], 0, -(self.xc + self.arm_widths[0]), -(self.xc + self.arm_widths[0]), -(self.xc + self.arm_x_left + self.arm_widths[0]), -(self.xc + self.arm_x_left) ] ypts = semicircle_y.tolist() + [ self.yc + self.arm_y_right, self.yc + self.arm_y_right, self.yc, self.yc - self.source_length, self.yc - self.source_length, self.yc - self.source_length, self.yc, self.yc + self.arm_y_left, self.yc + self.arm_y_left ] points = np.array(list(zip(xpts, ypts))) return points
class __Manhattan__(Cell): port1 = PortParameter(default=None) port2 = PortParameter(default=None) length = NumberParameter(default=20) layer = LayerParameter(number=13) # gds_layer = LayerParameter(number=13) # layer = PhysicalLayerParameter(default=RDD.DEF.PDEFAULT) # layer = PhysicalLayerParameter() # bend_type = StringParameter(default='rectangle') bend_type = StringParameter(default='circular') b1 = Parameter(fdef_name='create_arc_bend_1') b2 = Parameter(fdef_name='create_arc_bend_2') p1 = Parameter(fdef_name='create_port1_position') p2 = Parameter(fdef_name='create_port2_position') quadrant_one = Parameter(fdef_name='create_quadrant_one') quadrant_two = Parameter(fdef_name='create_quadrant_two') quadrant_three = Parameter(fdef_name='create_quadrant_three') quadrant_four = Parameter(fdef_name='create_quadrant_four') def get_radius(self): if self.port1 and self.port2: if hasattr(self, '__radius__'): return self.__radius__ else: dx = abs(self.p2[0] - self.p1[0]) dy = abs(self.p2[1] - self.p1[1]) if dx <= dy: self.__radius__ = dx * 0.2 elif dy <= dx: self.__radius__ = dy * 0.2 # if dx <= dy: # self.__radius__ = dx # elif dy <= dx: # self.__radius__ = dy return self.__radius__ def set_radius(self, value): self.__radius__ = value radius = FunctionParameter(get_radius, set_radius) def route_straight(self, p1, p2): route_shape = RouteSimple(port1=p1, port2=p2, path_type='straight', width_type='straight') # route_shape.apply_merge R1 = RouteGeneral(route_shape=route_shape, connect_layer=self.layer) S = spira.SRef(R1) S.connect(port=S.ports['P1'], destination=p1) # S.connect(port=p1, destination=p2) # T = spira.Rotation(rotation=p2.orientation, center=p1.midpoint) # S.transform(T) # r1.rotate(angle=p2.orientation+90, center=R1.port_input.midpoint) # r1._rotate(rotation=p2.orientation-90, center=R1.port_input.midpoint) # S.move(midpoint=(0,0), destination=p1.midpoint) return S def create_port1_position(self): angle = np.mod(self.port1.orientation, 360) if angle == 90: p1 = [self.port1.midpoint[0], self.port1.midpoint[1]] if angle == 180: p1 = [self.port1.midpoint[1], -self.port1.midpoint[0]] if angle == 270: p1 = [-self.port1.midpoint[0], -self.port1.midpoint[1]] if angle == 0: p1 = [-self.port1.midpoint[1], self.port1.midpoint[0]] return p1 def create_port2_position(self): angle = np.mod(self.port1.orientation, 360) if angle == 90: p2 = [self.port2.midpoint[0], self.port2.midpoint[1]] if angle == 180: p2 = [self.port2.midpoint[1], -self.port2.midpoint[0]] if angle == 270: p2 = [-self.port2.midpoint[0], -self.port2.midpoint[1]] if angle == 0: p2 = [-self.port2.midpoint[1], self.port2.midpoint[0]] return p2 def create_arc_bend_1(self): if self.bend_type == 'circular': rs = RouteArcShape(radius=self.radius, width=self.port1.width, start_angle=0, theta=90) if self.bend_type == 'rectangle': rs = RouteSquareShape(width=self.port1.width, size=(self.radius, self.radius)) B1 = RouteGeneral(route_shape=rs, connect_layer=self.layer) return spira.SRef(B1) def create_arc_bend_2(self): if self.bend_type == 'circular': rs = RouteArcShape(radius=self.radius, width=self.port1.width, start_angle=0, theta=-90) if self.bend_type == 'rectangle': rs = RouteSquareShape(width=self.port1.width, size=(self.radius, self.radius)) B1 = RouteGeneral(route_shape=rs, connect_layer=self.layer) return spira.SRef(B1)
class RouteArcShape(__RouteSimple__): radius = NumberParameter(default=5) width = NumberParameter(default=1) theta = NumberParameter(default=45) start_angle = NumberParameter(default=0) angle_resolution = NumberParameter(default=15) angle1 = Parameter(fdef_name='create_angle1') angle2 = Parameter(fdef_name='create_angle2') def create_midpoint1(self): x = np.cos(self.angle1) y = np.sin(self.angle1) midpoint = [self.radius * x, self.radius * y] return midpoint def create_midpoint2(self): x = np.cos(self.angle2) y = np.sin(self.angle2) midpoint = [self.radius * x, self.radius * y] return midpoint def create_width1(self): return self.width def create_width2(self): return self.width def create_orientation1(self): # return self.start_angle - 180 + 180*(self.theta<0) return self.start_angle - 90 + 180 * (self.theta < 0) def create_orientation2(self): # return self.start_angle + self.theta + 0 - 180*(self.theta<0) return self.start_angle + self.theta + 90 - 180 * (self.theta < 0) def create_angle1(self): angle1 = (self.start_angle + 0) * constants.DEG2RAD return angle1 def create_angle2(self): angle2 = (self.start_angle + self.theta + 0) * constants.DEG2RAD return angle2 def create_points(self, points): inner_radius = self.radius - self.width / 2.0 outer_radius = self.radius + self.width / 2.0 z = int(np.ceil(abs(self.theta) / self.angle_resolution)) t = np.linspace(self.angle1, self.angle2, z) inner_points_x = (inner_radius * np.cos(t)).tolist() inner_points_y = (inner_radius * np.sin(t)).tolist() outer_points_x = (outer_radius * np.cos(t)).tolist() outer_points_y = (outer_radius * np.sin(t)).tolist() xpts = np.array(inner_points_x + outer_points_x[::-1]) ypts = np.array(inner_points_y + outer_points_y[::-1]) points = [[list(p) for p in list(zip(xpts, ypts))]] return points
class Polygon(__Polygon__): """ Element that connects shapes to the GDSII file format. Polygon are objects that represents the shapes in a layout. Examples -------- >>> layer = spira.Layer(number=99) >>> rect_shape = spira.RectangleShape(p1=[0,0], p2=[1,1]) >>> ply = spira.Polygon(shape=rect_shape, layer=layer) """ _next_uid = 0 edges = Parameter(fdef_name='create_edges') def get_alias(self): if not hasattr(self, '__alias__'): self.__alias__ = self.process return self.__alias__ def set_alias(self, value): if value is not None: self.__alias__ = value alias = FunctionParameter( get_alias, set_alias, doc='Functions to generate an alias for cell name.') def __init__(self, shape, layer, transformation=None, **kwargs): super().__init__(shape=shape, layer=layer, transformation=transformation, **kwargs) self.uid = Polygon._next_uid Polygon._next_uid += 1 def __repr__(self): if self is None: return 'Polygon is None!' layer = RDD.GDSII.IMPORT_LAYER_MAP[self.layer] class_string = "[SPiRA: Polygon \'{}\'] (center {}, vertices {}, process {}, purpose {})" return class_string.format(self.alias, self.center, self.count, self.process, self.purpose) def __str__(self): return self.__repr__() def __hash__(self): return hash(self.__repr__()) # NOTE: We are not copying the ports, so they # can be re-calculated for the transformed shape. def __deepcopy__(self, memo): return self.__class__(shape=deepcopy(self.shape), layer=deepcopy(self.layer), transformation=deepcopy(self.transformation)) def id_string(self): sid = '{} - hash {}'.format(self.__repr__(), self.shape.hash_string) return sid def create_edges(self): """ Generate default edges for this polygon. These edges can be transformed using adapters. """ from spira.yevon.geometry.edges.edges import generate_edges return generate_edges(shape=self.shape, layer=self.layer) def flat_copy(self, level=-1): """ Flatten a copy of the polygon. """ S = Polygon(shape=self.shape, layer=self.layer, transformation=self.transformation) S.expand_transform() return S def flatten(self, level=-1): """ Flatten the polygon without creating a copy. """ return self.expand_transform() def nets(self, lcar): from spira.yevon.geometry.nets.net import Net from spira.yevon.vmodel.geometry import GmshGeometry from spira.yevon.geometry.ports.port import ContactPort from spira.yevon import filters if self.purpose == 'METAL': if RDD.ENGINE.GEOMETRY == 'GMSH_ENGINE': geometry = GmshGeometry(lcar=lcar, process=self.layer.process, process_polygons=[deepcopy(self)]) cc = [] for p in self.ports: if isinstance(p, ContactPort): cc.append(p) F = filters.ToggledCompoundFilter() F += filters.NetProcessLabelFilter( process_polygons=[deepcopy(self)]) F += filters.NetDeviceLabelFilter(device_ports=cc) F += filters.NetEdgeFilter(process_polygons=[deepcopy(self)]) net = Net(name=self.process, geometry=geometry) return F(net) # # from spira.yevon.utils.netlist import combine_net_nodes # # net.g = combine_net_nodes(g=net.g, algorithm='d2d') # # net.g = combine_net_nodes(g=net.g, algorithm='s2s') # from spira.yevon.geometry.nets.net import CellNet # cn = CellNet() # cn.g = net.g # # cn.generate_branches() # # cn.detect_dummy_nodes() # return cn return []
class YtronShape(Shape): """ Shape for generating a yTron device. """ rho = NumberParameter(default=0.2) arm_lengths = CoordParameter(default=(5, 3)) source_length = NumberParameter(default=5) arm_widths = CoordParameter(default=(2, 2)) theta = NumberParameter(default=2.5) theta_resolution = NumberParameter(default=10.0) xc = Parameter(fdef_name='create_xc') yc = Parameter(fdef_name='create_yc') arm_x_left = Parameter(fdef_name='create_arm_x_left') arm_y_left = Parameter(fdef_name='create_arm_y_left') arm_x_right = Parameter(fdef_name='create_arm_x_right') arm_y_right = Parameter(fdef_name='create_arm_y_right') rad_theta = Parameter(fdef_name='create_rad_theta') def create_rad_theta(self): return self.theta * np.pi / 180 def create_xc(self): return self.rho * np.cos(self.rad_theta) def create_yc(self): return self.rho * np.sin(self.rad_theta) def create_arm_x_left(self): return self.arm_lengths[0] * np.sin(self.rad_theta) def create_arm_y_left(self): return self.arm_lengths[0] * np.cos(self.rad_theta) def create_arm_x_right(self): return self.arm_lengths[1] * np.sin(self.rad_theta) def create_arm_y_right(self): return self.arm_lengths[1] * np.cos(self.rad_theta) def create_points(self, points): theta = self.theta * np.pi / 180 theta_resolution = self.theta_resolution * np.pi / 180 theta_norm = int((np.pi - 2 * theta) / theta_resolution) + 2 thetalist = np.linspace(-(np.pi - theta), -theta, theta_norm) semicircle_x = self.rho * np.cos(thetalist) semicircle_y = self.rho * np.sin(thetalist) + self.rho xpts = semicircle_x.tolist() + [ self.xc + self.arm_x_right, self.xc + self.arm_x_right + self.arm_widths[1], self.xc + self.arm_widths[1], self.xc + self.arm_widths[1], 0, -(self.xc + self.arm_widths[0]), -(self.xc + self.arm_widths[0]), -(self.xc + self.arm_x_left + self.arm_widths[0]), -(self.xc + self.arm_x_left) ] ypts = semicircle_y.tolist() + [ self.yc + self.arm_y_right, self.yc + self.arm_y_right, self.yc, self.yc - self.source_length, self.yc - self.source_length, self.yc - self.source_length, self.yc, self.yc + self.arm_y_left, self.yc + self.arm_y_left ] points = np.array(list(zip(xpts, ypts))) return points
class Net(__Net__): """ Constructs a graph from the physical geometry generated from the list of elements. """ # g = GraphParameter() g = Parameter() mesh_data = Parameter(fdef_name='create_mesh_data') geometry = GeometryParameter(allow_none=True, default=None) branch_nodes = Parameter(fdef_name='create_branch_nodes') lines = Parameter(fdef_name='create_lines') triangles = Parameter(fdef_name='create_triangles') physical_triangles = Parameter(fdef_name='create_physical_triangles') physical_lines = Parameter(fdef_name='create_physical_lines') name = StringParameter(default='no_name') def __init__(self, **kwargs): super().__init__(**kwargs) if 'g' in kwargs: self.g = kwargs['g'] else: self.g = nx.Graph() self._generate_mesh_graph() def __repr__(self): if self.geometry is None: class_string = "[SPiRA: Net] (name \'{}\', nodes {})" return class_string.format(self.name, self.count) else: class_string = "[SPiRA: Net] (name \'{}\', nodes {}, geometry {})" return class_string.format(self.name, self.count, self.geometry.process.symbol) def __str__(self): return self.__repr__() def _generate_mesh_graph(self): """ Create a graph from the meshed geometry. """ ll = len(self.mesh_data.points) A = np.zeros((ll, ll), dtype=np.int64) for n, triangle in enumerate(self.triangles): self._add_edges(n, triangle, A) for n, triangle in enumerate(self.triangles): self._add_positions(n, triangle) def _add_edges(self, n, tri, A): def update_adj(self, t1, adj_mat, v_pair): if (adj_mat[v_pair[0]][v_pair[1]] != 0): t2 = adj_mat[v_pair[0]][v_pair[1]] - 1 self.g.add_edge(t1, t2, label=None) else: adj_mat[v_pair[0]][v_pair[1]] = t1 + 1 adj_mat[v_pair[1]][v_pair[0]] = t1 + 1 v1 = [tri[0], tri[1], tri[2]] v2 = [tri[1], tri[2], tri[0]] for v_pair in list(zip(v1, v2)): update_adj(self, n, A, v_pair) def _add_positions(self, n, triangle): from spira import settings pp = self.mesh_data.points grids_per_unit = settings.get_grids_per_unit() n1, n2, n3 = pp[triangle[0]], pp[triangle[1]], pp[triangle[2]] x = (n1[0] + n2[0] + n3[0]) / 3 y = (n1[1] + n2[1] + n3[1]) / 3 x = x * grids_per_unit y = y * grids_per_unit self.g.node[n]['vertex'] = triangle self.g.node[n]['position'] = Coord(x, y) self.g.node[n]['display'] = RDD.DISPLAY.STYLE_SET[RDD.PLAYER.METAL] def create_mesh_data(self): return self.geometry.mesh_data def add_new_node(self, n, D, polygon, position, display): num = self.g.number_of_nodes() self.g.add_node(num + 1, position=position, device_reference=D, process_polygon=polygon, display=display) self.g.add_edge(n, num + 1) def create_triangles(self): if 'triangle' not in self.mesh_data.cells: raise ValueError('Triangle not found in cells') return self.mesh_data.cells['triangle'] def create_lines(self): if 'line' not in self.mesh_data.cells: raise ValueError('Line not found in cells') return self.mesh_data.cells['line'] def create_physical_triangles(self): if 'triangle' not in self.mesh_data.cell_data: raise ValueError('Triangle not in meshio cell_data') if 'gmsh:physical' not in self.mesh_data.cell_data['triangle']: raise ValueError('Physical not found in meshio triangle') return self.mesh_data.cell_data['triangle']['gmsh:physical'].tolist() def create_physical_lines(self): if 'line' not in self.mesh_data.cell_data: raise ValueError('Line not in meshio cell_data') if 'gmsh:physical' not in self.mesh_data.cell_data['line']: raise ValueError('Physical not found in meshio triangle') return self.mesh_data.cell_data['line']['gmsh:physical'].tolist() def process_triangles(self): """ Arguments --------- tri : list The surface_id of the triangle corresponding to the index value. name -> 5_0_1 (layer_datatype_polyid) value -> [1 2] (1=surface_id 2=triangle) """ triangles = {} for name, value in self.mesh_data.field_data.items(): for n in self.g.nodes(): surface_id = value[0] if self.physical_triangles[n] == surface_id: layer = int(name.split('_')[0]) datatype = int(name.split('_')[1]) key = (layer, datatype) if key in triangles: triangles[key].append(n) else: triangles[key] = [n] return triangles def process_lines(self): """ Arguments --------- tri : list The surface_id of the triangle corresponding to the index value. name -> 5_0_1 (layer_datatype_polyid) value -> [1 2] (1=surface_id 2=triangle) """ lines = {} for name, value in self.mesh_data.field_data.items(): # print(name, value) # print(self.physical_lines) for n in self.physical_lines: line_id = value[0] if n == line_id: # print(name) # print(value) # print('') polygon_string = name.split('*')[0] polygon_hash = name.split('*')[1] polygon_uid = int(name.split('*')[2]) key = (polygon_string, polygon_hash, polygon_uid) if key in lines: lines[key].append(n) else: lines[key] = [n] return lines def get_triangles_connected_to_line(self): """ Labeling of an edge line: polygon_uid_i [line elm_type] [SPiRA: Polygon 'M5']_17_0 [2 1] Labeling of triangle: layer datatype [triangle elm_type] 50_1_0_0 [1 2] """ # lines = [] # for v in self.process_lines().values(): # lines.extend(v) # print(lines) # triangles = {} # for n in nodes: # for node, triangle in enumerate(self.triangles): # if n == node: # triangles[n] = triangle # return triangles def triangle_nodes(self): """ Get triangle field_data in list form. """ nodes = [] for v in self.process_triangles().values(): nodes.extend(v) triangles = {} for n in nodes: for node, triangle in enumerate(self.triangles): if n == node: triangles[n] = triangle return triangles def transform(self, transformation): for n in self.g.nodes(): self.g.node[n]['position'] = transformation.apply_to_coord( self.g.node[n]['position']) return self def create_branch_nodes(self): """ Nodes that defines different conducting branches. """ from spira.yevon.gdsii.sref import SRef from spira.yevon.geometry.ports import Port branch_nodes = list() for n in self.g.nodes(): if 'device_reference' in self.g.node[n]: D = self.g.node[n]['device_reference'] if isinstance(D, SRef): branch_nodes.append(n) if isinstance(D, Port): branch_nodes.append(n) return branch_nodes def st_nodes(self): """ Nodes that defines different conducting branches. All nodes are ports. Chek port purposes. """ from spira.yevon.gdsii.sref import SRef from spira.yevon.geometry.ports import Port branch_nodes = list() for n in self.g.nodes(): if 'device_reference' in self.g.node[n]: D = self.g.node[n]['device_reference'] P = self.g.node[n]['process_polygon'] # FIXME: Maybe implement node operators (__and__, etc) # if (D.purpose.symbol == 'B') and (P.layer.purpose.symbol == 'DEVICE_METAL'): # branch_nodes.append(n) if D.purpose.symbol == 'C': branch_nodes.append(n) elif D.purpose.symbol == 'D': branch_nodes.append(n) # elif D.purpose.symbol == 'P': # branch_nodes.append(n) elif D.purpose.symbol == 'T': branch_nodes.append(n) # elif (D.purpose.symbol == 'P') and (D.name[1] != 'E'): # branch_nodes.append(n) return branch_nodes def convert_to_branch_node(self, n, uid): pass def del_branch_attrs(self): """ Reset the branch attrs for new branch node creation. """ for n in self.g.nodes(): if 'branch_node' in self.g.node[n]: del self.g.node[n]['branch_node'] return self def convert_pins(self): """ Remove pin node attrs with more than 1 edge connected to it. """ for n in self.g.nodes(): if 'device_reference' in self.g.node[n]: D = self.g.node[n]['device_reference'] if D.purpose.symbol == 'P': if len(self.g.edges(n)) > 0: del self.g.node[n]['device_reference'] return self def convert_device(self): """ Convert a device metal node to a dummy port. Has to be connected to atleast 1 PEdge node. """ from spira.yevon.geometry.ports import Port for n in self.g.nodes(): convert = False P = self.g.node[n]['process_polygon'] if P.layer.purpose.symbol == 'DEVICE_METAL': for i in self.g.neighbors(n): if 'device_reference' in self.g.node[i]: D = self.g.node[i]['device_reference'] # print(D) if D.purpose.symbol == 'P': convert = True if convert is True: port = Port( name='Djj{}'.format(n), midpoint=P.center, process=P.layer.process, ) self.g.node[n]['device_reference'] = port return self def remove_nodes(self): """ Nodes to be removed: 1. Are not a branch node. 2. Are not a device node. 3. Branch nodes must equal the branch id. """ from spira.yevon.gdsii.sref import SRef from spira.yevon.geometry.ports import Port locked_nodes = [] remove_nodes = [] for n in self.g.nodes(): if 'branch_node' in self.g.node[n]: D = self.g.node[n]['branch_node'] if isinstance(D, Port): locked_nodes.append(n) elif 'device_reference' in self.g.node[n]: D = self.g.node[n]['device_reference'] if isinstance(D, (Port, SRef)): locked_nodes.append(n) for n in self.g.nodes(): if n not in locked_nodes: remove_nodes.append(n) self.g.remove_nodes_from(remove_nodes)
class ElectricalConnections(__Connection__): """ """ edges = Parameter(fdef_name='create_edges') def create_elements(self, elems): overlap_elems, edges = self.edges # for p in self.cell.elements: for i, p in enumerate(self.cell.elements): # shape = deepcopy(p.shape).transform(p.transformation).snap_to_grid() # cs = ShapeConnected(original_shape=shape, edges=edges) # elems += Polygon(shape=cs, layer=p.layer) # cs = ShapeConnected(original_shape=shape, edges=edges) # # p.shape = cs # self.cell.elements[i].shape = cs if i == 2: shape = deepcopy(p.shape).transform( p.transformation).snap_to_grid() # shape = p.shape # print(shape.points) cs = ShapeConnected(original_shape=shape, edges=edges) # print(cs.points) # self.cell.elements[i].shape = cs p.shape = cs # elems += Polygon(shape=cs, layer=p.layer) # elems += p return elems def create_edges(self): el = ElementList() for p1 in deepcopy(self.cell.elements): el += p1 for edge in p1.edges: el += edge.outside.transform(edge.transformation) map1 = { RDD.PLAYER.M5.EDGE_CONNECTED: RDD.PLAYER.M5.INSIDE_EDGE_ENABLED } pg_overlap = self.cell.overlap_elements edges = get_derived_elements(el, mapping=map1, store_as_edge=True) for j, pg in enumerate(pg_overlap): for e in pg.elements: for i, edge in enumerate(edges): if edge.overlaps(e): # FIXME: Cannot use this, since Gmsh seems to crach due to the hashing string. # edges[i].pid = p.id_string() # edges[i].pid = '{}_{}'.format(p.__repr__(), p.uid) edges[i].pid = '{}'.format(e.shape.hash_string) return pg_overlap, edges def gdsii_output_electrical_connection(self): elems = ElementList() overlap_elems, edges = self.edges for e in overlap_elems: elems += e for edge in edges: elems += edge.outside for e in self.cell.elements: elems += e D = Cell(name='_ELECTRICAL_CONNECT', elements=elems) D.gdsii_output()
class RouteParallel(__Manhattan__): parallel = Parameter(fdef_name='create_parallel_route') quadrant_one_parallel = Parameter(fdef_name='create_quadrant_one_parallel') q1 = Parameter(fdef_name='create_q1_180') q2 = Parameter(fdef_name='create_q2_180') q3 = Parameter(fdef_name='create_q3_180') q4 = Parameter(fdef_name='create_q4_180') def create_parallel_route(self): p1, p2 = self.p1, self.p2 b1, b2 = self.b2, self.b1 dx = max(p1[0], p2[0]) dy = max(p1[1], p2[1]) if p2[0] > p1[0]: b1, b2 = self.b1, self.b2 h = p2[1] + self.length d1 = [0, h] d2 = [self.ports['T2'].midpoint[0], h] b1.connect(port=b1.ports['P2'], destination=self.ports['T1']) b1.move(midpoint=b1.ports['P2'], destination=d1) b2.connect(port=b2.ports['P2'], destination=b1.ports['P1']) b2.move(midpoint=b2.ports['P1'], destination=d2) r1 = self.route_straight(b1.ports['P2'], self.ports['T1']) r2 = self.route_straight(b2.ports['P1'], self.ports['T2']) r3 = self.route_straight(b1.ports['P1'], b2.ports['P2']) D = spira.Cell(name='Parallel') D += [self.b1, self.b2, r1, r2, r3] t1 = self.ports['T1'] t2 = self.ports['T2'] t1.rotate(angle=self.port1.orientation) t2.rotate(angle=self.port1.orientation) D.rotate(angle=self.port1.orientation, center=self.p1) D.move(midpoint=self.ports['T1'], destination=self.port1) return spira.SRef(D) def create_quadrant_one_parallel(self): p1 = [self.port1.midpoint[0], self.port1.midpoint[1]] p2 = [self.port2.midpoint[0], self.port2.midpoint[1]] b1, b2 = self.b2, self.b1 d1, d2 = [0, 0], [0, 0] if self.port1.orientation == 0: dy = max(p1[1], p2[1]) if p2[0] > p1[0]: b1, b2 = self.b1, self.b2 h = dy + self.length d1 = [0, h] d2 = [self.ports['T2'].midpoint[0], h] elif self.port1.orientation == 90: dx = max(p1[0], p2[0]) if p2[1] > p1[1]: b1, b2 = self.b1, self.b2 h = dx - self.length d1 = [h, 0] d2 = [h, self.ports['T2'].midpoint[1]] elif self.port1.orientation == -90: dx = min(p1[0], p2[0]) if p1[1] > p2[1]: b1, b2 = self.b1, self.b2 h = dx + self.length d1 = [h, 0] d2 = [h, self.ports['T2'].midpoint[1]] elif self.port1.orientation == 180: dy = min(p1[1], p2[1]) if p1[0] > p2[0]: b1, b2 = self.b1, self.b2 elif p2[0] > p1[0]: b1, b2 = self.b2, self.b1 h = dy - self.length d1 = [0, h] d2 = [self.ports['T2'].midpoint[0], h] b1.connect(port=b1.ports['P2'], destination=self.ports['T1']) b1.move(midpoint=b1.ports['P2'], destination=d1) b2.connect(port=b2.ports['P1'], destination=b1.ports['P1']) b2.move(midpoint=b2.ports['P2'], destination=d2) r1 = self.route_straight(b1.ports['P2'], self.ports['T1']) r2 = self.route_straight(b2.ports['P2'], self.ports['T2']) r3 = self.route_straight(b1.ports['P1'], b2.ports['P1']) return [self.b1, self.b2, r1, r2, r3] def create_q1_180(self): b1 = self.b1 b2 = self.b2 b1.connect(port=b1.ports['P2'], destination=self.ports['T1']) h = self.p2[1] + self.radius + self.length b1.move(midpoint=b1.ports['P2'], destination=[0, h]) b2.connect(port=b2.ports['P1'], destination=b1.ports['P1']) b2.move(midpoint=b2.ports['P2'], destination=[self.ports['T2'].midpoint[0], h]) r1 = self.route_straight(b1.ports['P2'], self.ports['T1']) r2 = self.route_straight(b2.ports['P2'], self.ports['T2']) r3 = self.route_straight(b1.ports['P1'], b2.ports['P1']) D = spira.Cell(name='SameQ1') D += [self.b1, self.b2, r1, r2, r3] D += self.ports['T1'] D += self.ports['T2'] D.rotate(angle=self.port1.orientation, center=self.p1) D.move(midpoint=self.ports['T1'], destination=self.port1) return spira.SRef(D) def create_q2_180(self): b1 = self.b1 b2 = self.b2 b1.connect(port=b1.ports['P1'], destination=self.ports['T2']) h = self.p2[1] + self.radius + self.length b1.move(midpoint=b1.ports['P1'], destination=[0, h]) b2.connect(port=b2.ports['P2'], destination=b1.ports['P2']) b2.move(midpoint=b2.ports['P1'], destination=[self.ports['T2'].midpoint[0], h]) r1 = self.route_straight(b1.ports['P1'], self.ports['T1']) r2 = self.route_straight(b2.ports['P1'], self.ports['T2']) r3 = self.route_straight(b1.ports['P2'], b2.ports['P2']) D = spira.Cell(name='SameQ2') D += [self.b1, self.b2, r1, r2, r3] D += self.ports['T1'] D += self.ports['T2'] D.rotate(angle=self.port1.orientation, center=self.p1) D.move(midpoint=self.ports['T1'], destination=self.port1) return spira.SRef(D) def create_q3_180(self): b1 = self.b1 b2 = self.b2 b1.connect(port=b1.ports['P1'], destination=self.ports['T2']) h = self.p1[1] + self.radius + self.length b1.move(midpoint=b1.ports['P1'], destination=[0, h]) b2.connect(port=b2.ports['P2'], destination=b1.ports['P2']) b2.move(midpoint=b2.ports['P1'], destination=[self.ports['T2'].midpoint[0], h]) r1 = self.route_straight(b1.ports['P1'], self.ports['T1']) r2 = self.route_straight(b2.ports['P1'], self.ports['T2']) r3 = self.route_straight(b1.ports['P2'], b2.ports['P2']) D = spira.Cell(name='SameQ3') D += [self.b1, self.b2, r1, r2, r3] D += self.ports['T1'] D += self.ports['T2'] D.rotate(angle=self.port1.orientation, center=self.p1) D.move(midpoint=self.ports['T1'], destination=self.port1) return spira.SRef(D) def create_q4_180(self): b1 = self.b1 b2 = self.b2 b2.connect(port=b2.ports['P1'], destination=self.ports['T1']) h = self.p1[1] + self.radius + self.length b2.move(midpoint=b2.ports['P1'], destination=[0, h]) b1.connect(port=b1.ports['P2'], destination=b2.ports['P2']) b1.move(midpoint=b1.ports['P1'], destination=[self.ports['T2'].midpoint[0], h]) r1 = self.route_straight(b2.ports['P1'], self.ports['T1']) r2 = self.route_straight(b1.ports['P1'], self.ports['T2']) r3 = self.route_straight(b1.ports['P2'], b2.ports['P2']) D = spira.Cell(name='SameQ4') D += [self.b1, self.b2, r1, r2, r3] D += self.ports['T1'] D += self.ports['T2'] D.rotate(angle=self.port1.orientation, center=self.p1) D.move(midpoint=self.ports['T1'], destination=self.port1) return spira.SRef(D)
class Net(__Net__): """ Constructs a graph from the physical geometry generated from the list of elements. """ # g = GraphParameter() g = Parameter() mesh_data = Parameter(fdef_name='create_mesh_data') geometry = GeometryParameter(allow_none=True, default=None) lines = Parameter(fdef_name='create_lines') triangles = Parameter(fdef_name='create_triangles') physical_triangles = Parameter(fdef_name='create_physical_triangles') physical_lines = Parameter(fdef_name='create_physical_lines') name = StringParameter(default='no_name') def __init__(self, **kwargs): super().__init__(**kwargs) if 'g' in kwargs: self.g = kwargs['g'] else: self.g = nx.Graph() self._generate_mesh_graph() def __repr__(self): if self.geometry is None: class_string = "[SPiRA: Net] (name \'{}\', nodes {})" return class_string.format(self.name, self.count) else: class_string = "[SPiRA: Net] (name \'{}\', nodes {}, geometry {})" return class_string.format(self.name, self.count, self.geometry.process.symbol) def __str__(self): return self.__repr__() def _generate_mesh_graph(self): """ Create a graph from the meshed geometry. """ ll = len(self.mesh_data.points) A = np.zeros((ll, ll), dtype=np.int64) for n, triangle in enumerate(self.triangles): self._add_edges(n, triangle, A) for n, triangle in enumerate(self.triangles): self._add_positions(n, triangle) def _add_edges(self, n, tri, A): def update_adj(self, t1, adj_mat, v_pair): if (adj_mat[v_pair[0]][v_pair[1]] != 0): t2 = adj_mat[v_pair[0]][v_pair[1]] - 1 self.g.add_edge(t1, t2, label=None) else: adj_mat[v_pair[0]][v_pair[1]] = t1 + 1 adj_mat[v_pair[1]][v_pair[0]] = t1 + 1 v1 = [tri[0], tri[1], tri[2]] v2 = [tri[1], tri[2], tri[0]] for v_pair in list(zip(v1, v2)): update_adj(self, n, A, v_pair) def _add_positions(self, n, triangle): from spira import settings pp = self.mesh_data.points grids_per_unit = settings.get_grids_per_unit() n1, n2, n3 = pp[triangle[0]], pp[triangle[1]], pp[triangle[2]] x = (n1[0] + n2[0] + n3[0]) / 3 y = (n1[1] + n2[1] + n3[1]) / 3 x = x * grids_per_unit y = y * grids_per_unit self.g.node[n]['vertex'] = triangle self.g.node[n]['position'] = Coord(x, y) self.g.node[n]['display'] = RDD.DISPLAY.STYLE_SET[RDD.PLAYER.METAL] def create_mesh_data(self): return self.geometry.mesh_data def add_new_node(self, n, D, polygon, position, display): num = self.g.number_of_nodes() self.g.add_node(num + 1, position=position, device_reference=D, process_polygon=polygon, display=display) self.g.add_edge(n, num + 1) def create_triangles(self): if 'triangle' not in self.mesh_data.cells: raise ValueError('Triangle not found in cells') return self.mesh_data.cells['triangle'] def create_lines(self): if 'line' not in self.mesh_data.cells: raise ValueError('Line not found in cells') return self.mesh_data.cells['line'] def create_physical_triangles(self): if 'triangle' not in self.mesh_data.cell_data: raise ValueError('Triangle not in meshio cell_data') if 'gmsh:physical' not in self.mesh_data.cell_data['triangle']: raise ValueError('Physical not found in meshio triangle') return self.mesh_data.cell_data['triangle']['gmsh:physical'].tolist() def create_physical_lines(self): if 'line' not in self.mesh_data.cell_data: raise ValueError('Line not in meshio cell_data') if 'gmsh:physical' not in self.mesh_data.cell_data['line']: raise ValueError('Physical not found in meshio triangle') return self.mesh_data.cell_data['line']['gmsh:physical'].tolist() def process_triangles(self): """ Arguments --------- tri : list The surface_id of the triangle corresponding to the index value. name -> 5_0_1 (layer_datatype_polyid) value -> [1 2] (1=surface_id 2=triangle) """ triangles = {} for name, value in self.mesh_data.field_data.items(): for n in self.g.nodes(): surface_id = value[0] if self.physical_triangles[n] == surface_id: layer = int(name.split('_')[0]) datatype = int(name.split('_')[1]) key = (layer, datatype) if key in triangles: triangles[key].append(n) else: triangles[key] = [n] return triangles def process_lines(self): """ Arguments --------- tri : list The surface_id of the triangle corresponding to the index value. name -> 5_0_1 (layer_datatype_polyid) value -> [1 2] (1=surface_id 2=triangle) """ lines = {} for name, value in self.mesh_data.field_data.items(): # print(name, value) # print(self.physical_lines) for n in self.physical_lines: line_id = value[0] if n == line_id: # print(name) # print(value) # print('') polygon_string = name.split('_')[0] polygon_uid = int(name.split('_')[1]) key = (polygon_string, polygon_uid) if key in lines: lines[key].append(n) else: lines[key] = [n] return lines def get_triangles_connected_to_line(self): """ Labeling of an edge line: polygon_uid_i [line elm_type] [SPiRA: Polygon 'M5']_17_0 [2 1] Labeling of triangle: layer datatype [triangle elm_type] 50_1_0_0 [1 2] """ # lines = [] # for v in self.process_lines().values(): # lines.extend(v) # print(lines) # triangles = {} # for n in nodes: # for node, triangle in enumerate(self.triangles): # if n == node: # triangles[n] = triangle # return triangles def triangle_nodes(self): """ Get triangle field_data in list form. """ nodes = [] for v in self.process_triangles().values(): nodes.extend(v) triangles = {} for n in nodes: for node, triangle in enumerate(self.triangles): if n == node: triangles[n] = triangle return triangles def transform(self, transformation): for n in self.g.nodes(): self.g.node[n]['position'] = transformation.apply_to_coord( self.g.node[n]['position']) return self
class VModelProcessFlow(ParameterInitializer): """ """ active_processes = Parameter( doc='Active process layers for virtual model creation.')