class ShapeStub(__ShapeModifier__): """ stubs the corners of a given shape """ stub_width = NonNegativeNumberProperty(required = True) only_sharp_angles = BoolProperty(default = False) def define_points(self, pts): c = Shape(self.original_shape) if len(c) == 0: return pts if self.original_shape.is_closed(): if not c[0] == c[-1]: c.append(c[0]) #closed curve c.append(c[1]) else: # open curve pts.append(c[0]) min_sw = self.stub_width for i in range(1, len(c) - 1): angle1 = angle_rad(c[i], c[i - 1]) angle2 = angle_rad(c[i + 1], c[i]) turn = (angle2 - angle1 + pi) % (2 * pi) - pi if turn == 0 or (abs(turn) <= (pi / 2.0) and self.only_sharp_angles): pts.append(c[i]) elif abs(turn == pi): LOG.error("ShapeStub::define_points : ERROR : Cannot stub shape with a 180 degree turn") else: d1 = distance(c[i], c[i - 1]) d2 = distance(c[i + 1], c[i]) L = self.stub_width * sin(turn / 2.0) / sin(turn) max_L = max([d1 / 2.0, d2 / 2.0]) if L > max_L: L = max_L sw = L * sin(turn) / sin(turn / 2.0) else: sw = self.stub_width if sw < min_sw: min_sw = sw s1 = (c[i][0] - L * cos(angle1), c[i][1] - L * sin(angle1)) s2 = (c[i][0] + L * cos(angle2), c[i][1] + L * sin(angle2)) pts.append(s1) pts.append(s2) ''' REFACTORED --> moved to constructor if self.original_shape.closed: #closed curve self.close() else: ''' if not self.original_shape.closed: # open curve pts.append(c[-1]) if min_sw < self.stub_width: LOG.warning("Warning: ShapeStub::define_points : Stub width is reduced from " + str(self.stub_width) + " to " + str(min_sw) + "to stub shape.") return pts
class DisplayStyle(StrongPropertyInitializer): color = ColorProperty(default=COLOR_BLACK) edgecolor = ColorProperty(default=COLOR_BLACK) stipple = StippleProperty(default=STIPPLE_NONE) alpha = RestrictedProperty(restriction=RESTRICT_FRACTION, default=1.0) edgewidth = NonNegativeNumberProperty(default=1.0) visible = BoolProperty(default=True) def __str__(self): return "DisplayStyle : color: %s - edgecolor: %s - stipple: %s - alpha: %f - edgewidth: %f - visible: %s" % ( str(self.color), str(self.edgecolor), str( self.stipple), self.alpha, self.edgewidth, self.visible) def blend(self, other, fraction_first_color=0.33): result_color_red = fraction_first_color * self.color.red + ( 1.0 - fraction_first_color) * other.color.red result_color_green = fraction_first_color * self.color.green + ( 1.0 - fraction_first_color) * other.color.green result_color_blue = fraction_first_color * self.color.blue + ( 1.0 - fraction_first_color) * other.color.blue result_color = Color( name="#%02X%02X%02X" % (result_color_red, result_color_green, result_color_blue), red=result_color_red, green=result_color_green, blue=result_color_blue) result_ds = DisplayStyle(color=result_color, edgecolor=self.edgecolor, stipple=self.stipple, alpha=self.alpha) return result_ds
class ShapeEllipseArc(Shape): """ ellipse arc around a given center """ center = Coord2Property(default=(0.0, 0.0)) box_size = Size2Property(default=(1.0, 1.0)) start_angle = AngleProperty(default=0.0) end_angle = AngleProperty(default=90.0) angle_step = AngleProperty(default=TECH.METRICS.ANGLE_STEP) clockwise = BoolProperty(default=False) def __init__(self, **kwargs): super(ShapeEllipseArc, self).__init__(**kwargs) def define_points(self, pts): sa = self.start_angle * DEG2RAD ea = self.end_angle * DEG2RAD h_radius = self.box_size[0] / 2.0 v_radius = self.box_size[1] / 2.0 n_s = float(self.end_angle - self.start_angle) / self.angle_step n_steps = int(math.ceil(abs(n_s))) * numpy.sign(n_s) if n_steps == 0: if sa == ea: pts = numpy.array([[ math.cos(sa) * h_radius + self.center[0], math.sin(sa) * v_radius + self.center[1] ]]) else: pts = numpy.array([[ math.cos(sa) * h_radius + self.center[0], math.sin(sa) * v_radius + self.center[1] ], [ math.cos(ea) * h_radius + self.center[0], math.sin(ea) * v_radius + self.center[1] ]]) return pts angle_step = float(ea - sa) / n_steps if self.clockwise: angle_step = -angle_step sign = -1 else: sign = +1 while sign * sa > sign * ea: ea += sign * 2 * math.pi angles = numpy.arange(sa, ea + 0.5 * angle_step, angle_step) pts = numpy.column_stack( (numpy.cos(angles), numpy.sin(angles))) * numpy.array([ (h_radius, v_radius) ]) + numpy.array([(self.center[0], self.center[1])]) return pts def move(self, position): self.center = (self.center.x + position[0], self.center.y + position[1]) return self def is_empty(self): return self.start_angle == self.end_angle or self.box_size[0] == 0.0 or self.box_size[1] == 0.0
class ShapeRoundedRectangleArc(Shape): center = Coord2Property(default=(0.0, 0.0)) box_size = Size2Property(default=(1.0, 1.0)) radius = PositiveNumberProperty(default=0.1) start_angle = AngleProperty(default=0.0) end_angle = AngleProperty(default=90.0) angle_step = AngleProperty(default=TECH.METRICS.ANGLE_STEP) clockwise = BoolProperty(default=False) def __init__(self, **kwargs): super(ShapeRoundedRectangleArc, self).__init__(**kwargs) # restrict radius if self.radius > min(self.box_size) / 2.0: self.radius = min(self.box_size) / 2.0 def define_points(self, pts): cx = self.center[0] cy = self.center[1] dx = 0.5 * self.box_size[0] dy = 0.5 * self.box_size[1] if self.clockwise: as_sign = -1 else: as_sign = 1 # radius = box: circle arc if (self.radius == self.box_size[0] / 2.0) and ( self.radius == self.box_size[1] / 2.0): pts += ShapeArc( self.center, self.radius, self.start_angle, self.end_angle, angle_step=self.angle_step, clockwise=self.clockwise) # radius = zero: part of rectangle elif self.radius <= 0: for a in arange(self.start_angle, self.end_angle + 45.0, as_sign * 90.0): pts += [(cx + sign(math.cos(DEG2RAD * a)) * dx, cy + sign(math.sin(DEG2RAD * a)) * dy)] # arbitrary else: for a in numpy.arange(self.start_angle, self.end_angle + 45.0, as_sign * 90.0): start = max(self.start_angle, a - a % 90.0 + 45.0 - as_sign * 45.0) end = min(self.end_angle, a - a % 90.0 + 45.0 + as_sign * 45.0) pts += ShapeArc( center=(cx + numpy.sign(math.cos(DEG2RAD * a)) * (dx - self.radius), cy + numpy.sign(math.sin(DEG2RAD * a)) * (dy - self.radius)), radius=self.radius, start_angle=start, end_angle=end, angle_step=self.angle_step, clockwise=self.clockwise) return pts
class GenericGdsiiPPLayerOutputMap(StrongPropertyInitializer): pplayer_map = DictProperty( doc="map of (process, purpose) to (layer,datatype)") ignore_undefined_mappings = BoolProperty(default=False) def __getitem__(self, key, default=None): if (key.process, key.purpose) in self.pplayer_map: (lay, dat) = self.pplayer_map[(key.process, key.purpose)] return GdsiiLayer(number=lay, datatype=dat) else: error_message = "Warning during GDSII export : no corresponding GDSII layer/datatype found for process = %s and purpose = %s" % ( key.process, key.purpose) if self.ignore_undefined_mappings: LOG.warning(error_message) return default else: raise Exception(error_message) def get(self, key, default): return self.__getitem__(key, default)
class GdsiiPPLayerInputMap(GdsiiPPLayerOutputMap): ignore_undefined_mappings = BoolProperty(default=False) def define_layer_map(self): layer_map = super(GdsiiPPLayerInputMap, self).define_layer_map() reverse_layer_map = dict() for pplayer, gdsiilayer in layer_map.items(): reverse_layer_map[(gdsiilayer.number, gdsiilayer.datatype)] = pplayer return reverse_layer_map def __getitem__(self, gdsiilayer, default=None): if (gdsiilayer.number, gdsiilayer.datatype) in self.layer_map: return self.layer_map[(gdsiilayer.number, gdsiilayer.datatype)] else: error_message = "Warning during GDSII import : no corresponding process/purpose layer found for number = %i and datatype = %s" % ( gdsiilayer.number, gdsiilayer.datatype) if self.ignore_undefined_mappings: LOG.warning(error_message) return default else: raise Exception(error_message)
class ShapeSerif(__ShapeModifier__): """ puts a bump on the corners of a given shape """ stub_width = PositiveNumberProperty(required=True) stub_height = PositiveNumberProperty(required=True) tip_width = NonNegativeNumberProperty(required=True) only_sharp_angles = BoolProperty(default=False) def __init__(self, **kwargs): super(ShapeSerif, self).__init__(**kwargs) if self.original_shape.closed: self.close() else: self.open() def define_points(self, pts): c = Shape(self.original_shape) if len(c) == 0: return if self.original_shape.is_closed(): if not c[0] == c[-1]: c.append(c[0]) #closed curve c.append(c[1]) else: # open curve pts.append(c[0]) c.remove_identicals() min_sw = self.stub_width for i in range(1, len(c) - 1): angle1 = angle_rad(c[i], c[i - 1]) angle2 = angle_rad(c[i + 1], c[i]) turn = (angle2 - angle1 + pi) % (2 * pi) - pi if turn == 0 or (abs(turn) <= (pi / 2.0) and self.only_sharp_angles): pts.append(c[i]) elif abs(turn == pi): LOG.error("Cannot stub shape with a 180 degree turn") else: d1 = distance(c[i], c[i - 1]) d2 = distance(c[i + 1], c[i]) L = self.stub_width * sin(turn / 2.0) / sin(turn) max_L = max([d1 / 2.0, d2 / 2.0]) if L > max_L: L = max_L sw = L * sin(turn) / sin(turn / 2.0) else: sw = self.stub_width if sw < min_sw: min_sw = sw theta_div2 = (pi - angle1 + angle2) % (2 * pi) / 2.0 s1 = Coord2(c[i][0] - L * cos(angle1), c[i][1] - L * sin(angle1)) s2 = (c[i][0] + L * cos(angle2), c[i][1] + L * sin(angle2)) B = -self.stub_height * sign(turn) delta = 0.5 * (self.stub_width - self.tip_width) s2 = (s1[0] + B * cos(angle1 + theta_div2) + delta * cos(angle1 + theta_div2 - pi / 2.0), s1[1] + B * sin(angle1 + theta_div2) + delta * sin(angle1 + theta_div2 - pi / 2.0)) s4 = Coord2(c[i][0] + L * cos(angle2), c[i][1] + L * sin(angle2)) s3 = (s4[0] + B * cos(angle2 + pi - theta_div2) + delta * cos(angle2 + pi - theta_div2 + pi / 2.0), s4[1] + B * sin(angle2 + pi - theta_div2) + delta * sin(angle2 + pi - theta_div2 + pi / 2.0)) pts.append(s1) pts.append(s2) pts.append(s3) pts.append(s4) if not self.original_shape.closed: # open curve pts.append(c[-1]) if min_sw < self.stub_width: LOG.warning("Stub width is reduced from " + str(self.stub_width) + " to " + str(min_sw) + "to stub shape.") return pts
class NoDistortTransform(GenericNoDistortTransform): """ A homothetic transformation that does not distort the item it is applied to (angle conservation) The transform is defined by - a mirror around the x-axis - a scaling with respect to the origin - a rotation around the origin - a translation it is also possible to set absolute magnification and rotation, which means that subsequent transformations will not affect the angle and size any further """ # FIXME : Use transformation matrix which is computed once (and cached) def __init__(self, translation = (0.0, 0.0), rotation = 0.0, magnification = 1.0, v_mirror = False, absolute_magnification = False, absolute_rotation = False, **kwargs): # note: translation is no part of gdsII transform. It should be taken into account while writing the file super(NoDistortTransform, self).__init__( translation = translation, rotation = rotation, magnification = magnification, v_mirror = v_mirror, absolute_magnification = absolute_magnification, absolute_rotation =absolute_rotation, **kwargs) translation = Coord2Property("__translation__", default = (0.0, 0.0)) """ the translation coordinate """ #def get_rotation (self): return self.rotation def set_rotation(self, value): self.__rotation__ = value % 360.0 if value % 90.0 == 0.0: # make sure sine and cosine are really zero when needed! if self.__rotation__ == 0.0: self.__ca__ = 1.0 self.__sa__ = 0.0 elif self.__rotation__ == 90.0: self.__ca__ = 0.0 self.__sa__ = 1.0 elif self.__rotation__ == 180.0: self.__ca__ = -1.0 self.__sa__ = 0.0 elif self.__rotation__ == 270.0: self.__ca__ = 0.0 self.__sa__ = -1.0 else: self.__ca__ = cos(value * DEG2RAD) self.__sa__ = sin(value * DEG2RAD) rotation = SetFunctionProperty("__rotation__", set_rotation, default=0.0) """ the rotation around the origin """ magnification = NumberProperty("__magnification__", restriction = RESTRICT_NONZERO, default = 1.0) """ the magnification factor """ v_mirror = BoolProperty("__v_mirror__", default = False) """ the vertical mirror """ flip = v_mirror #""" set absolute magnification on or off""" absolute_magnification = BoolProperty("__absolute_magnification__", default = False) #""" set absolute rotation on or off""" absolute_rotation = BoolProperty("__absolute_rotation__", default = False) def __make_simple__(self): """ internal use: reduces special subclasses to this generic type """ # if you change special types, they should revert to generic type self.__class__ = NoDistortTransform def __translate__(self, coord): """ internal use: applies translation to a coordinate """ return Coord2(coord[0] + self.translation.x, coord[1] + self.translation.y) def __rotate__(self, coord): """ internal use: applies rotation to a coordinate """ return Coord2(coord[0] * self.__ca__ - coord[1] * self.__sa__, coord[0] * self.__sa__ + coord[1] * self.__ca__) def __magnify__(self, coord): """ internal use: applies magnification to a coordinate """ return Coord2(coord[0] * self.magnification, coord[1] * self.magnification) def __v_flip__(self, coord): """ internal use: applies v_mirror to a coordinate """ if self.v_mirror: return Coord2(coord[0], -coord[1]) else: return Coord2(coord[0], coord[1]) def __inv_translate__(self, coord): """ internal use: applies reverse translation to a coordinate """ return Coord2(coord[0] - self.translation.x, coord[1] - self.translation.y) def __inv_rotate__(self, coord): """ internal use: applies reverse rotation to a coordinate """ return Coord2(coord[0] * self.__ca__ + coord[1] * self.__sa__, - coord[0] * self.__sa__ + coord[1] * self.__ca__) def __inv_magnify__(self, coord): """ internal use: applies reverse magnification to a coordinate """ return Coord2(coord[0] / self.magnification, coord[1] / self.magnification) def __inv_v_flip__(self, coord): """ internal use: applies reverse v_mirror to a coordinate """ if self.v_mirror: return Coord2(coord[0], - coord[1]) else: return Coord2(coord[0], coord[1]) def __translate3__(self, coord): """ internal use: applies translation to a 3d coordinate """ return Coord3(coord[0] + self.translation.x, coord[1] + self.translation.y, coord[2]) def __rotate3__(self, coord): """ internal use: applies rotation to a 3d coordinate """ return Coord3(coord[0] * self.__ca__ - coord[1] * self.__sa__, coord[0] * self.__sa__ + coord[1] * self.__ca__, coord[2]) def __magnify3__(self, coord): """ internal use: applies magnification to a 3d coordinate """ return Coord3(coord[0] * self.magnification, coord[1] * self.magnification, coord[2]) def __v_flip3__(self, coord): """ internal use: applies v_mirror to a 3d coordinate """ if self.v_mirror: return Coord3(coord[0], -coord[1], coord[2]) else: return Coord3(coord[0], coord[1], coord[2]) def __inv_translate3__(self, coord): """ internal use: applies reverse translation to a 3d coordinate """ return Coord3(coord[0] - self.translation.x, coord[1] - self.translation.y, coord[2]) def __inv_rotate3__(self, coord): """ internal use: applies reverse rotation to a 3d coordinate """ return Coord3(coord[0] * self.__ca__ + coord[1] * self.__sa__, - coord[0] * self.__sa__ + coord[1] * self.__ca__, coord[2]) def __inv_magnify3__(self, coord): """ internal use: applies reverse magnification to a 3d coordinate """ return Coord3(coord[0] / self.magnification, coord[1] / self.magnification, coord[2]) def __inv_v_flip3__(self, coord): """ internal use: applies reverse v_mirror to a 3d coordinate """ if self.v_mirror: return Coord3(coord[0], - coord[1], coord[2]) else: return Coord3(coord[0], coord[1], coord[2]) def __translate_array__(self, coords): """ internal use: applies translation to a numpy array """ coords += numpy.array([self.translation.x, self.translation.y]) return coords def __rotate_array__(self, coords): """ internal use: applies rotation to a numpy array """ x_a = numpy.array([self.__ca__, -self.__sa__]) y_a = numpy.array([self.__sa__, self.__ca__]) coords = numpy.transpose(numpy.vstack((numpy.sum(coords * x_a, 1), numpy.sum(coords * y_a, 1)))) return coords def __magnify_array__(self, coords): """ internal use: applies magnification to a numpy array """ coords *= numpy.array([self.magnification, self.magnification]) return coords def __v_flip_array__(self, coords): """ internal use: applies v_mirror to a numpy array """ coords *= (numpy.array([False, self.v_mirror]) * -2.0 + 1.0) return coords def __inv_translate_array__(self, coords): """ internal use: applies reverse translation to a numpy array """ coords -= numpy.array([self.translation.x, self.translation.y]) return coords def __inv_rotate_array__(self, coords): """ internal use: applies reverse rotation to a numpy array """ x_a = array([self.__ca__, self.__sa__]) y_a = array([-self.__sa__, self.__ca__]) coords = numpy.transpose(numpy.vstack((numpy.sum(coords * x_a, 1), numpy.sum(coords * y_a, 1)))) return coords def __inv_magnify_array__(self, coords): """ internal use: applies reverse magnification to a numpy array """ coords *= numpy.array([1.0 / self.magnification, 1.0 / self.magnification]) return coords def __inv_v_flip_array__(self, coords): """ internal use: applies reverse v_mirror to a numpy array """ coords *= numpy.array([False, self.v_mirror]) * (-2.0) + 1.0 return coords def __translate_array3__(self, coords): """ internal use: applies translation to a numpy array """ coords += numpy.array([self.translation.x, self.translation.y, 0.0]) return coords def __rotate_array3__(self, coords): """ internal use: applies rotation to a numpy array """ x_a = numpy.array([self.__ca__, -self.__sa__, 0]) y_a = numpy.array([self.__sa__, self.__ca__, 0]) z_a = numpy.array([0, 0, 1.0]) coords = numpy.transpose(numpy.vstack((numpy.sum(coords * x_a, 1), numpy.sum(coords * y_a, 1), numpy.sum(coords * z_a, 1)))) return coords def __magnify_array3__(self, coords): """ internal use: applies magnification to a numpy array """ coords *= numpy.array([self.magnification, self.magnification, 1.0]) return coords def __v_flip_array3__(self, coords): """ internal use: applies v_mirror to a numpy array """ coords *= (numpy.array([False, self.v_mirror, False]) * -2.0 + 1.0) return coords def __inv_translate_array3__(self, coords): """ internal use: applies reverse translation to a numpy array """ coords -= numpy.array([self.translation.x, self.translation.y, 0.0]) return coords def __inv_rotate_array3__(self, coords): """ internal use: applies reverse rotation to a numpy array """ x_a = array([self.__ca__, self.__sa__, 0.0]) y_a = array([-self.__sa__, self.__ca__, 0.0]) z_a = numpy.array([0, 0, 1.0]) coords = numpy.transpose(numpy.vstack((numpy.sum(coords * x_a, 1), numpy.sum(coords * y_a, 1), numpy.sum(coords * z_a, 1)))) return coords def __inv_magnify_array3__(self, coords): """ internal use: applies reverse magnification to a numpy array """ coords *= numpy.array([1.0 / self.magnification, 1.0 / self.magnification, 1.0]) return coords def __inv_v_flip_array3__(self, coords): """ internal use: applies reverse v_mirror to a numpy array """ coords *= numpy.array([False, self.v_mirror, False]) * (-2.0) + 1.0 return coords def apply_to_coord(self, coord): """ applies transformation to a coordinate """ # this could be speeded up # Check the east order coord = self.__v_flip__(coord)# first flip coord = self.__rotate__(coord)# then magnify coord = self.__magnify__(coord)# then rotate coord = self.__translate__(coord) # finally translate return coord def reverse_on_coord(self, coord): """ applies reverse transformation to a coordinate """ # this could be speeded up # Check the right order coord = self.__inv_translate__(coord) # finally translate coord = self.__inv_magnify__(coord)# then rotate coord = self.__inv_rotate__(coord)# then magtnify coord = self.__inv_v_flip__(coord)# first flip return coord def apply_to_array(self, coords): """ internal use: applies transformation to a numpy array""" # this could be speeded up # Check the right order coords = self.__v_flip_array__(coords)# first flip coords = self.__rotate_array__(coords)# then rotate coords = self.__magnify_array__(coords)# then magnify coords = self.__translate_array__(coords) # finally translate return coords def reverse_on_array(self, coord): """ internal use: applies reverse transformation to a numpy array """ # this could be speeded up # Check the right order coords = self.__inv_translate_array__(coords) # finally translate coords = self.__inv_magnify_array__(coords)# then magnify coords = self.__inv_rotate_array__(coords)# then rotate coords = self.__inv_v_flip_array__(coords)# first flip return coords def apply_to_coord3(self, coord): """ applies transformation to a coordinate """ # this could be speeded up # Check the east order coord = self.__v_flip3__(coord)# first flip coord = self.__rotate3__(coord)# then magnify coord = self.__magnify3__(coord)# then rotate coord = self.__translate3__(coord) # finally translate return coord def reverse_on_coord3(self, coord): """ applies reverse transformation to a coordinate """ # this could be speeded up # Check the right order coord = self.__inv_translate3__(coord) # finally translate coord = self.__inv_magnify3__(coord)# then rotate coord = self.__inv_rotate3__(coord)# then magtnify coord = self.__inv_v_flip3__(coord)# first flip return coord def apply_to_array3(self, coords): """ internal use: applies transformation to a numpy array""" # this could be speeded up # Check the right order coords = self.__v_flip_array3__(coords)# first flip coords = self.__rotate_array3__(coords)# then rotate coords = self.__magnify_array3__(coords)# then magnify coords = self.__translate_array3__(coords) # finally translate return coords def reverse_on_array3(self, coord): """ internal use: applies reverse transformation to a numpy array """ # this could be speeded up # Check the right order coords = self.__inv_translate_array3__(coords) # finally translate coords = self.__inv_magnify_array3__(coords)# then magnify coords = self.__inv_rotate_array3__(coords)# then rotate coords = self.__inv_v_flip_array3__(coords)# first flip return coords def apply_to_angle_deg(self, angle): """ applies transformation to an absolute angle (degrees) """ a = angle if self.v_mirror: a = -a a += self.rotation return a % 360.0 def reverse_on_angle_deg(self, angle): """ applies reverse transformation to an absolute angle (degrees)""" a = angle - self.rotation if self.v_mirror: a = -a return a % 360.0 def apply_to_angle_rad(self, angle): """ applies transformation to an absolute angle (radians) """ a = angle if self.v_mirror: a = -a a += self.rotation * DEG2RAD return a % (2 * pi) def reverse_on_angle_rad(self, angle): """ applies reverse transformation to an absolute angle (radians) """ a = angle - self.rotation * DE2RAD if self.v_mirror: a = -a return a % (2 * pi) def apply_to_length(self, length): """ applies transformation to a distance """ return length * self.magnification def reverse_on_length(self, length): """ applies reverse transformation to a distance """ return length / self.magnification def __neg__(self): """ returns the reverse transformation """ from .translation import Translation from .rotation import Rotation from .magnification import Magnification from .mirror import VMirror T = Translation(- self.translation) + Magnification((0.0, 0.0), 1 / self.magnification) + Rotation((0.0, 0.0), -self.rotation) if self.v_mirror: T += VMirror(0.0) return T def __sub__(self, other): """ returns the concatenation of this transform and the reverse of other """ if other is None: return copy.deepcopy(self) if not isinstance(other, __ReversibleTransform__): raise TypeError("Cannot subtract an irreversible transform") return self.__add__(-other) def __isub__(self, other): """ concatenates the reverse of other to this transform """ if other is None: return self if not isinstance(other, __ReversibleTransform__): raise TypeError("Cannot subtract an irreversible transform") return self.__iadd__(self, -other) def __add__(self, other): """ returns the concatenation of this transform and other """ # performs transformation "other" after "self" and returns resulting transform if other is None: return copy.deepcopy(self) if isinstance(other, NoDistortTransform): T = NoDistortTransform() if self.absolute_magnification: M1 = 1.0 else: M1 = other.magnification T.magnification = self.magnification * M1 #flip signs if other.v_mirror: s_1 = -1 else: s_1 = 1 if not self.absolute_rotation: T.rotation = s_1 * self.rotation + other.rotation ca = other.__ca__ sa = other.__sa__ else: T.rotation = s_1 * self.rotation ca = 1.0 sa = 0.0 # tricky part: translation T.translation = Coord2(other.translation.x + ca * self.translation.x * M1 - s_1 * sa * self.translation.y * M1, other.translation.y + sa * self.translation.x * M1 + s_1 * ca * self.translation.y * M1) T.absolute_rotation = self.absolute_rotation or other.absolute_rotation T.absolute_magnification = self.absolute_magnification or other.absolute_magnification T.v_mirror = (not self.v_mirror == other.v_mirror) else: T = Transform.__add__(self, other) return T def __iadd__(self, other): """ concatenates other to this transform """ if other is None: return self # performs transformation other after self and returns self if isinstance(other, NoDistortTransform): # tricky part: translation if self.absolute_magnification: self.magnification = self.magnification * other.magnification M1 = 1 else: M1 = other.magnification #flip signs if other.v_mirror: s_1 = -1 else: s_1 = 1 if not self.absolute_rotation: self.rotation = s_1 * self.rotation + other.rotation ca = other.__ca__ sa = other.__sa__ else: self.rotation = s_1 * self.rotation ca = 1 sa = 0 # tricky part: translation self.translation = (other.translation.x + ca * self.translation.x * M1 - s_1 * sa * self.translation.y * M1, other.translation.y + sa * self.translation.x * M1 + s_1 * ca * self.translation.y * M1) self.absolute_rotation = self.absolute_rotation or other.absolute_rotation self.absolute_magnification = self.absolute_magnification or other.absolute_magnification self.v_mirror = (not self.v_mirror == other.v_mirror) else: raise TypeError("Error: Cannot perform += operation for NoDistortTransform and other transform of type " + str(type(other))) return self def __eq__(self, other): """ check if the transforms do the same thing """ if other is None: return self.is_identity() if not isinstance(other, NoDistortTransform): return False return ((self.rotation == other.rotation) and (self.translation == other.translation) and (self.v_mirror == other.v_mirror) and (self.magnification == other.magnification) and (self.absolute_rotation == other.absolute_rotation) and (self.absolute_magnification == other.absolute_magnification) ) def __ne__(self, other): """ checks if the transforms do different things """ if other is None: return not self.is_identity() if not isinstance(other, NoDistortTransform): return False return ((self.rotation != other.rotation) or (self.translation != other.translation) or (self.v_mirror != other.v_mirror) or (self.magnification != other.magnification) or (self.absolute_rotation != other.absolute_rotation) or (self.absolute_magnification != other.absolute_magnification) ) def is_identity(self): """ returns True if the transformation does nothing """ return ((self.rotation == 0.0) and (self.translation.x == 0.0) and (self.translation.y == 0.0) and not (self.v_mirror) and (self.magnification == 1.0) ) def is_isometric(self): """ returns True if the transformation conserves angles and distances """ return self.magnification == 1.0 def is_homothetic(self): """ returns True if the transformation conserves angles """ return True def is_orthogonal(self): """ returns True if the transformation does only rotate on 90degree angles """ return (self.rotation % 90.0) == 0.0 def is_octogonal(self): """ returns True if the transformation does only rotate on 45degree angles """ return (self.rotation % 45.0) == 0.0 def id_string(self): """ gives a hash of the transform (for naming purposes) """ return str(hash("R" + str(int(self.rotation * 10000)) + "T" + str(int(self.translation[0] * 1000)) + "_" + str(int(self.translation[1] * 1000)) + "M" + str(int(self.magnification * 1000)) + "V" + str(self.v_mirror) + "AM" + str(self.absolute_magnification) + "AR" + str(self.absolute_rotation) )) def __str__(self): """ gives a string representing the transform """ return "R=%s-T=%s-M=%s-V=%s-AM=%s-AR=%s" % (str(int(self.rotation * 10000)), str(int(self.translation[0] * 1000)) + "_" + str(int(self.translation[1] * 1000)), str(int(self.magnification * 1000)), str(self.v_mirror), str(self.absolute_magnification), str(self.absolute_rotation))
class OutputGdsii(OutputBasic): """ Writes GDS output to a stream """ userefcache = BoolProperty(default=False) name_filter = RestrictedProperty( default=TECH.GDSII.NAME_FILTER, restriction=RestrictType(Filter), doc="filter class which is applied to all names") def __init__(self, o_stream=sys.stdout, **kwargs): kwargs["allow_unmatched_kwargs"] = True super(OutputGdsii, self).__init__(o_stream=o_stream, **kwargs) if 'flatten_structure_container' in kwargs: self.flatten_structure_container = kwargs.get( 'flatten_structure_container') elif hasattr(TECH.GDSII, 'FLATTEN_STRUCTURE_CONTAINER'): self.flatten_structure_container = TECH.GDSII.FLATTEN_STRUCTURE_CONTAINER else: self.flatten_structure_container = False self.__ref_referenced_structures__ = set() if sys.platform == "win32": import os import msvcrt msvcrt.setmode(self.o_stream.fileno(), os.O_BINARY) def __init_collector__(self): self.collector = StreamA2BHexCollector(o_stream=self.o_stream) def collect(self, item, **kwargs): self.do_collect(item, **kwargs) return #---------------------------------------------------------------------------- #Layout-level output functions #---------------------------------------------------------------------------- def collect_Library(self, library, **kwargs): self.__collect_library_header__(library) unreferenced_structures = self.library.unreferenced_structures( usecache=self.userefcache) referenced_structures = self.library.referenced_structures( usecache=self.userefcache) self.collect(unreferenced_structures, **kwargs) collected_referenced_structures = [] while len(self.__ref_referenced_structures__) > 0: for rs in referenced_structures: if rs in self.__ref_referenced_structures__: self.collect(rs, **kwargs) collected_referenced_structures.append(rs) for crs in collected_referenced_structures: referenced_structures.remove(crs) self.__ref_referenced_structures__.clear() self.__collect_library_footer__() return def __collect_library_header__(self, library): self.collector += [ __str_record__(gds_records.Header, __hex_int2__(5)), __str_record__( gds_records.BgnLib, __hex_date__(library.modified) + __hex_date__(library.accessed)), __str_record__(gds_records.LibName, __hex_text__(library.name)), __str_record__( gds_records.Units, __hex_float__(self.library.grid / self.library.unit) + __hex_float__(self.library.grid)) ] return def __collect_library_footer__(self): self.library = None self.collector += [__str_record__(gds_records.EndLib)] return def __collect_structure_header__(self, item): sname = self.name_filter(item.name)[0] self.collector += [ __str_record__( gds_records.BgnStr, __hex_date__(item.created) + __hex_date__(item.modified)), __str_record__(gds_records.StrName, __hex_text__(sname)) ] #generate the footer for any structure def __collect_structure_footer__(self, item): self.collector += [__str_record__(gds_records.EndStr)] return #---------------------------------------------------------------------------- #__Element__ level output functions #---------------------------------------------------------------------------- #text def collect_Label(self, item, additional_transform=None): T = item.transformation + additional_transform # make a copy because there is also the height layer = self.map_layer(item.layer) if layer is None: return coordinates = [T.__translate__(item.coordinate)] T.magnification *= item.height self.collector += [ __str_record__(gds_records.Text), self.__str_layer__(layer.number), __str_record__(gds_records.TextType, __hex_int2__(0)), __str_record__( gds_records.Presentation, __hex_int2__((item.h_alignment + 4 * item.v_alignment + 8 * item.font % 4))), __str_record__(gds_records.PathType, __hex_int2__(1)) ] self.collector += __list_transformation__(T) self.collector += [ self.__str_coordinatelist__(coordinates), __str_record__(gds_records.String, __hex_text__(item.text)), __str_record__(gds_records.EndEl) ] return #references def collect_SRef(self, item, additional_transform=None): T = item.transformation + Translation( item.position.snap_to_grid()) + additional_transform coordinates = Shape((0.0, 0.0)).transform(T) sname = self.name_filter(item.reference.name)[0] self.collector += [ __str_record__(gds_records.SRef), __str_record__(gds_records.SName, __hex_text__(sname)) ] self.collector += __list_transformation__(T) self.collector += [ self.__str_coordinatelist__(coordinates), __str_record__(gds_records.EndEl) ] self.__ref_referenced_structures__.add(item.reference) def collect_ARef(self, item, additional_transform=None): T = item.transformation + Translation( item.origin) + additional_transform p = Coord2(item.period).snap_to_grid() corner1 = Coord2(item.n_o_periods[0] * p[0], 0.0) corner2 = Coord2(0.0, item.n_o_periods[1] * p[1]) coordinates = Shape([(0.0, 0.0), corner1, corner2]).transform(T) sname = self.name_filter(item.reference.name)[0] self.collector += [ __str_record__(gds_records.ARef), __str_record__(gds_records.SName, __hex_text__(sname)) ] self.collector += __list_transformation__(T) self.collector += [ __str_record__( gds_records.ColRow, __hex_int2__(item.n_o_periods[0]) + __hex_int2__(item.n_o_periods[1])), self.__str_coordinatelist__(coordinates), __str_record__(gds_records.EndEl) ] self.__ref_referenced_structures__.add(item.reference) return def collect_BoxElement(self, item, additional_transform=None): T = item.transformation + additional_transform layer = self.map_layer(item.layer) if layer is None: return coordinates = T(ShapeRectangle(item.center, item.box_size)).tolist() self.collector += [ __str_record__(gds_records.Box), self.__str_layer__(layer.number), __str_record__(gds_records.BoxType, __hex_int2__(0)), self.__str_coordinatelist__(coordinates), __str_record__(gds_records.EndEl) ] return def collect_path_element(self, layer, coordinates, line_width, path_type): L = self.map_layer(layer) if L is None: return self.collector += [ __str_record__(gds_records.Path), self.__str_layer__(L.number), self.__str_datatype__(L.datatype), __str_record__(gds_records.PathType, __hex_int2__(path_type)), __str_record__(gds_records.Width, __hex_int4__(self.__db_value__(line_width))), self.__str_shape__(coordinates), __str_record__(gds_records.EndEl) ] return def collect_boundary_element(self, layer, coordinates): L = self.map_layer(layer) if L is None: return self.collector += [ __str_record__(gds_records.Boundary), self.__str_layer__(L.number), self.__str_datatype__(L.datatype), self.__str_shape__(coordinates), __str_record__(gds_records.EndEl) ] return def __collect_container_elements__(self, item, sref_level_counter): # FIXME. Containers are PICAZZO classes. This method should be converted to a Filter or a mixin from picazzo.container.container import __StructureContainer__ from ipkiss.primitives.elements.reference import __RefElement__ if isinstance(item, __StructureContainer__) and isinstance( item.elements[0], __RefElement__): sref = item.elements[0] sref_elements = sref.reference.elements sref_transformation = sref.transformation + Translation( translation=sref.position) sref_elements_transformed = sref_elements.transform_copy( transformation=sref_transformation) new_elements = ElementList() if (isinstance(sref.reference, __StructureContainer__)): (inner_container_elements, sref_level_counter) = self.__collect_container_elements__( sref.reference, sref_level_counter + 1) new_elements.extend(inner_container_elements) if len(item.elements) > 1: new_elements.extend(item.elements[1:]) else: new_elements.extend(sref_elements_transformed) if len(item.elements) > 1: new_elements.extend(item.elements[1:]) return (new_elements, sref_level_counter) else: return (item.elements, sref_level_counter) def collect___StructureContainer__(self, item): # FIXME. Containers are PICAZZO classes. This method should be converted to a Filter or a mixin from ipkiss.primitives.elements.reference import __RefElement__ if self.flatten_structure_container and isinstance( item.elements[0], __RefElement__): sref = item.elements[0] (new_elements, sref_levels) = self.__collect_container_elements__( item=item, sref_level_counter=1) sref_transformation = sref.transformation + Translation( translation=sref.position) new_elements_transformed = new_elements.transform_copy( transformation=sref_transformation) if sref_levels >= 1: if len(item.elements) > 1: new_elements_transformed.extend(item.elements[1:]) item.__make_static__() item.elements = new_elements_transformed item.__make_dynamic__() self.collect_Structure(item) #---------------------------------------------------------------------------- # unit conversion #---------------------------------------------------------------------------- def __db_value__(self, value): #convents absolute coordinates to database units return round(value * self.__structure_scale__ * self.grids_per_unit) def __db_value_array__( self, value_array): #faster, direct operation on numpy array result = np.round(value_array * self.__structure_scale__ * self.grids_per_unit) return result #generate coordinate strings from a shape or list of coordinates def __str_coordinatelist__(self, coords): if isinstance(coords, Shape): db_value_coordinates = self.__db_value_array__(coords.points) ret_data = [ "%s%s" % (__hex_int4__(c0), __hex_int4__(c1)) for c0, c1 in db_value_coordinates ] else: ret_data = [ "%s%s" % (__hex_int4__(self.__db_value__( c[0])), __hex_int4__(self.__db_value__(c[1]))) for c in coords ] return __str_record__(gds_records.XY, "".join(ret_data)) def __str_shape__(self, coordinates): db_value_points = self.__db_value_array__(coordinates.points.ravel()) ret_data = [__hex_int4__(p) for p in db_value_points] return __str_record__(gds_records.XY, "".join(ret_data)) def __collect_shape__(self, coordinates): db_value_points = self.__db_value_array__(coordinates.points.ravel()) ret_data = [__hex_int4__(p) for p in db_value_points] self.collector += [__str_record__(gds_records.XY, "".join(ret_data))] def __str_layer__(self, layer_number): return __str_record__(gds_records.Layer, __hex_int2__(layer_number)) def __str_datatype__(self, datatype): return __str_record__(gds_records.DataType, __hex_int2__(datatype))
class Path(__ShapeElement__): path_type = RestrictedProperty(restriction=RestrictValueList( constants.PATH_TYPES), default=constants.PATH_TYPE_NORMAL) absolute_line_width = BoolProperty(default=False) __min_length__ = 2 def __init__(self, layer, shape, line_width=1.0, path_type=constants.PATH_TYPE_NORMAL, transformation=None, **kwargs): super(Path, self).__init__(layer=layer, shape=shape, line_width=line_width, path_type=path_type, transformation=transformation, **kwargs) def set_line_width(self, new_line_width): self.absolute_line_width = bool(new_line_width < 0) self.__line_width__ = abs(new_line_width) #line widths of zero must be allowed for e-beam line_width = SetFunctionProperty("__line_width__", set_line_width, required=True) def size_info(self): if self.line_width == 0.0: return self.shape.size_info.transform_copy(self.transformation) else: # this is slower, but accurate from ...geometry.shapes.modifiers import ShapePath return ShapePath( original_shape=self.shape, path_width=self.line_width, path_type=self.path_type).size_info.transform_copy( self.transformation) # this is fast, but inaccurate #return self.shape.size_info().transform_copy(self.transformation) def convex_hull(self): if self.line_width == 0.0: return self.shape.convex_hull().transform(self.transformation) else: # this is slower, but accurate from ipkiss.geometry.shapes.modifiers import ShapePath return ShapePath(original_shape=self.shape, path_width=self.line_width, path_type=self.path_type).convex_hull().transform( self.transformation) # this is fast, but inaccurate #return self.shape.size_info().transform_copy(self.transformation) def flat_copy(self, level=-1): S = Path(layer=self.layer, shape=self.shape, line_width=self.line_width, path_type=self.path_type, transformation=self.transformation, absolute_line_width=self.absolute_line_width) S.expand_transform() return S def expand_transform(self): if not self.transformation.is_identity(): self.shape = self.shape.transform_copy(self.transformation) if not self.absolute_line_width: self.line_width = self.transformation.apply_to_length( self.line_width) from ...geometry.transforms.identity import IdentityTransform self.transformation = IdentityTransform() return self
class Library(UnitGridContainer, MixinBowl): name = StringProperty(required = True, doc="Unique name for the library") accessed = TimeProperty(doc = "Timestamp at which the library was accessed.") modified = TimeProperty(doc = "Timestamp at which the library was modified.") layout = BoolProperty(default = True, doc="Indicates whether the library contains a layout : in that case, there should be only 1 top-level structure.") allow_empty_structures = BoolProperty(default = True, doc="Indicates whether empty structures are allowed.") def __init__(self, name, **kwargs): super(Library, self).__init__(name=name, **kwargs) self.structures = StructureList() self.__referenced_structures = set() def snap_value(self,value): return settings.snap_value(value, self.grids_per_unit) def snap_coordinate(self,coordinate): return settings.snap_coordinate(coordinate, self.grids_per_unit) def snap_shape(self,coordinates): return settings.snap_shape(coordinates, self.grids_per_unit) def add(self,structure): if isinstance(structure, Structure) or isinstance(structure, StructureList): self.structures.add(structure) else: raise TypeError("Wrong type " + str(type(structure)) + "of argument in Library.structure_exists.") def __iadd__(self, other): if isinstance(other, Structure) or isinstance(other, StructureList): self.structures.add(other) elif isinstance(other, Library): for i in other.structures: self.structures.add(i) return self def structure_exists(self,structure): return structure in self.structures def clean_up(self): if self.allow_empty_structures: return # remove references to empty structures for s in self.structures: empty_e = [] e_list = s.elements for i in range(len(e_list)): if e_list[i].is_empty: empty_e.append(i) del s.elements[empty_e] # remove empty structures empty_s = [] for s in range(0,len(self.structures)): if self.structures[s].is_empty(): empty_s.append(s) del self.structures[empty_s] def is_empty(self): return len(self.structures) == 0 def size_info(self): return self.top_layout().size_info() def flat_copy(self, level = -1): newlib = Library(self.name, self.unit, self.grid) for s in self.unreferenced_structures(): newlib.add(s.flat_copy(level)) newlib.collect_references() return newlib def flatten(self, level = -1): sl = StructureList() for s in self.unreferenced_structures(): sl.add(s.flatten(level)) self.structures = sl self.collect_references() return self def top_layout(self): L = self.unreferenced_structures() if len(L) == 1: return L[0] elif len(L) == 0: if len(self.structures) == 0: return Structure("__empty__") else: LOG.warning("There is no top_level structure in library %s. This could be caused by a circular reference" % self.name) return None elif self.layout: warning_string = """There is more than one top-level structure in library %s. This is ambiguous if the library is to be used as a layout. Please make sure that all but the top-level structures are referred to. #The following structures are 'floating': #%s """ % (self.name, "#\n".join([s.name for s in L])) LOG.warning(warning_string) def set_referenced(self, struct): self.__referenced_structures.add(struct) def unreferenced_structures(self, usecache = False): """returns a list of unreferenced structures""" referred_to_list = self.referenced_structures(usecache = usecache) not_referred_to_list = StructureList() for s in self.structures: if not s in referred_to_list: not_referred_to_list.add(s) return not_referred_to_list def referenced_structures(self, usecache = False): """Build list of referred structures""" if usecache: return StructureList(self.__referenced_structures) referred_to_list = StructureList() for s in self.structures: referred_to_list.add(s.dependencies()) return referred_to_list def collect_references(self, structure = None): if structure is None: s_list = self.structures else: s_list = StructureList(structure) new_s = StructureList() for s1 in s_list: d = s1.dependencies() for s2 in d: new_s.add(s2) self.add(new_s) def check_references(self): """check if all references belong to the library""" Referred_to_list = self.referenced_structures() for s in Referred_to_list: if not s in self.structures: LOG.error("The structure %s you refer to is not part of library %s " % (s.name, self.name)) raise SystemExit Not_Referred_to_list = StructureList() for s in self.structures: if not Referred_to_list.__fast_contains__(s.name): Not_Referred_to_list.__fast_add__(s) if len(Not_Referred_to_list) == 0: return -2 return 0 def __fast_get_structure__(self, str_name): """ returns the structure if it exists or returns None""" for s in self.structures: if s.name == str_name: return s return None def __fast_add__(self, new_str): """add a structure without checking if the structure exists""" self.structures.__fast_add__(new_str) def __contains__(self, item): return self.structures.__contains__(item) def __iter__(self): return self.structures.__iter__() def __getitem__(self, index): return self.structures[index] def clear(self): self.structures.clear() def __eq__(self, other): if not isinstance(other, Library): return False if len(self.structures) != len(other.structures): return False for struct1, struct2 in zip(self.structures,other.structures): if (struct1.name != struct2.name): # check that all structure elements have identical names (this is not required by the __eq__ operator in Structure return False if (struct1 != struct2): return False return True def __ne__(self, other): return not self.__eq__(other)
class OutputBasic(__OutputBasic__): layer_map = RestrictedProperty(default=TECH.GDSII.EXPORT_LAYER_MAP) echo = BoolProperty(default=False) def __init__(self, o_stream=sys.stdout, **kwargs): super(OutputBasic, self).__init__(o_stream=o_stream, **kwargs) self.library = None self.__current_structure__ = None self.__collect_method_dict__ = {} def __init_collector__(self): self.collector = ListCollector() def set_current_structure(self, S): self.__current_structure__ = S self.__structure_scale__ = S.unit / self.unit def define_filter(self): return TECH.GDSII.FILTER def do_collect(self, item, **kwargs): from ..primitives import library if isinstance(item, Library): self.library = item #for performance, to avoid repeated calls to DefinitionProperty in hot code self.grids_per_unit = self.library.grids_per_unit self.unit = self.library.unit if (self.library == None): self.library = get_current_library() super(OutputBasic, self).do_collect(item, **kwargs) return def collect_list(self, item, **kwargs): for i in item: self.collect(i, **kwargs) return def collect_Structure(self, item, **kwargs): if self.echo: LOG.info("Defining Structure %s with %d elements." % (item.name, len(item.elements))) self.set_current_structure(item) self.__collect_structure_header__(item) self.collect(item.elements, **kwargs) self.__collect_structure_footer__(item) return def collect_Library(self, library, usecache=False, **kwargs): self.__collect_library_header__(library) unreferenced_structures = self.library.unreferenced_structures( usecache=usecache) referenced_structures = self.library.referenced_structures( usecache=usecache) self.collect(unreferenced_structures, **kwargs) self.collect(referenced_structures, **kwargs) self.__collect_library_footer__() return def __collect_library_header__(self, library): pass def __collect_library_footer__(self): pass def collect_ElementList(self, item, additional_transform=None, **kwargs): for s in item: self.collect(s, additional_transform=additional_transform, **kwargs) return def collect_StructureList(self, item, **kwargs): for s in item: self.collect(s, **kwargs) return def collect_Group(self, item, additional_transform=None, **kwargs): self.collect(item.elements, additional_transform=item.transformation + additional_transform, **kwargs) return def collect_Boundary(self, item, additional_transform=None, **kwargs): shape = item.shape.transform_copy(item.transformation + additional_transform) shape.snap_to_grid(self.grids_per_unit) shape.remove_identicals() coordinates = shape # BOUNDARIES if len(shape) < 3: LOG.warning( "BOUNDARY with fewer than 3 coordinates not allowed in structure %s" % self.__current_structure__.name) return if len(shape) > TECH.GDSII.MAX_VERTEX_COUNT: LOG.warning("BOUNDARY with more than " + str(TECH.GDSII.MAX_VERTEX_COUNT) + " coordinates not supported in structure " + self.__current_structure__.name) # shape must be closed! if not (coordinates[0] == coordinates[-1]): coordinates.append(coordinates[0]) self.collect_boundary_element(layer=item.layer, coordinates=coordinates) return def collect_Path(self, item, additional_transform=None, **kwargs): shape = item.shape.transform_copy(item.transformation + additional_transform) shape.snap_to_grid(self.grids_per_unit) shape.remove_identicals() coordinates = Shape(shape) if len(coordinates) < 2: if self.write_empty: LOG.warning("PATH with fewer than 2 coordinates not allowed") return if shape.closed: if not (shape[-1] == shape[0]): coordinates.append(shape[0]) self.collect_path_element(layer=item.layer, coordinates=coordinates, line_width=item.line_width, path_type=item.path_type) return def __scale_value__(self, value): return value * self.__structure_scale__ def map_layer(self, layer): L = self.layer_map.get(layer, None) if isinstance(L, GdsiiLayer): return L elif L is None: return L else: return GdsiiLayer(number=L)