Esempio n. 1
0
class RectangleShape(Shape):
    """ Creates a rectangular shape. """

    p1 = CoordParameter(default=(0, 0), doc='Bottom left corner coordinate.')
    p2 = CoordParameter(default=(2, 2), doc='Top right corner coodinate.')

    def create_points(self, points):
        points = [[self.p1[0], self.p1[1]], [self.p1[0], self.p2[1]],
                  [self.p2[0], self.p2[1]], [self.p2[0], self.p1[1]]]
        return points
Esempio n. 2
0
class RouteSquareShape(__RouteSimple__):

    gds_layer = LayerParameter(name='ArcLayer', number=91)
    radius = NumberParameter(default=5)
    width = NumberParameter(default=1)
    size = CoordParameter(default=(3, 3))

    def create_midpoint1(self):
        return [-self.size[0], 0]

    def create_midpoint2(self):
        return [0, self.size[1]]

    def create_width1(self):
        return self.width

    def create_width2(self):
        return self.width

    def create_orientation1(self):
        return 90

    def create_orientation2(self):
        return 0

    def create_points(self, points):
        w = self.width / 2
        s1, s2 = self.size
        pts = [[w, -w], [-s1, -w], [-s1, w], [-w, w], [-w, s2], [w, s2]]
        points = np.array([pts])
        return points
Esempio n. 3
0
class WedgeShape(Shape):
    """ wedge, or symmetric trapezium. specified by the center of baselines and the length of the baselines """

    begin_coord = CoordParameter(default=(0, 0))
    end_coord = CoordParameter(default=(10, 0))
    begin_width = NumberParameter(default=3)
    end_width = NumberParameter(default=1)

    def create_points(self, points):
        dist = geom.distance(self.end_coord, self.begin_coord)
        cosangle = (self.end_coord[0] - self.begin_coord[0]) / dist
        sinangle = (self.end_coord[1] - self.begin_coord[1]) / dist
        points = [(self.begin_coord[0] + sinangle * self.begin_width / 2.0,
                   self.begin_coord[1] - cosangle * self.begin_width / 2.0),
                  (self.begin_coord[0] - sinangle * self.begin_width / 2.0,
                   self.begin_coord[1] + cosangle * self.begin_width / 2.0),
                  (self.end_coord[0] - sinangle * self.end_width / 2.0,
                   self.end_coord[1] + cosangle * self.end_width / 2.0),
                  (self.end_coord[0] + sinangle * self.end_width / 2.0,
                   self.end_coord[1] - cosangle * self.end_width / 2.0)]
        return points
Esempio n. 4
0
class CircleShape(Shape):
    """ Creates a circle shape. """

    box_size = CoordParameter(
        default=(2.0, 2.0),
        doc='The width and height of the circle as a coordinate.')
    start_angle = FloatParameter(default=0.0,
                                 doc='Starting angle of the circle shape.')
    end_angle = FloatParameter(
        default=360.0, doc='Degree to which the circle must be completed.')
    angle_step = IntegerParameter(default=3,
                                  doc='The smoothness of the circle.')

    def create_points(self, points):
        sa = self.start_angle * constants.DEG2RAD
        ea = self.end_angle * constants.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))) * np.sign(n_s)
        if n_steps == 0:
            if sa == ea:
                pts = np.array([[
                    math.cos(sa) * h_radius + self.center[0],
                    math.sin(sa) * v_radius + self.center[1]
                ]])
            else:
                pts = np.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 = np.arange(sa, ea + 0.5 * angle_step, angle_step)
        pts = np.column_stack((np.cos(angles), np.sin(angles))) \
                               * np.array([(h_radius, v_radius)]) \
                               + np.array([(self.center[0], self.center[1])])
        points = pts
        return points
Esempio n. 5
0
class Label(__Label__):
    """ Label that contains a text description.

    Example
    -------
    >>> lbl = spira.Label(text='P1', position=(0,0))
    >>> [SPiRA: Label] (P1 at (0,0), texttype 0)
    """

    position = CoordParameter(default=(0, 0))
    orientation = NumberParameter(default=0)

    def __init__(self, position, **kwargs):
        super().__init__(position=position, **kwargs)

    def __repr__(self):
        if self is None:
            return 'Label is None!'
        string = "[SPiRA: Label] ({} at ({}), layer {})"
        return string.format(self.text, self.position, self.layer.name)
Esempio n. 6
0
class SRef(__RefElement__):
    """
    Cell reference (SRef) is the reference to a cell layout
    to create a hierarchical layout structure. It creates copies
    of the ports and terminals defined by the cell. These
    copied ports can be used to connect different
    cell reference instances.

    Examples
    --------
    >>> cell = spira.Cell(name='Layout')
    >>> sref = spira.SRef(structure=cell)
    """

    midpoint = CoordParameter(default=(0, 0))

    def __init__(self,
                 reference,
                 midpoint=(0, 0),
                 alias=None,
                 transformation=None,
                 **kwargs):
        super().__init__(reference=reference,
                         midpoint=midpoint,
                         alias=alias,
                         transformation=transformation,
                         **kwargs)

    def __repr__(self):
        name = self.reference.name
        ps = "[SPiRA: SRef] (\"{}\", alias {}, midpoint {}, transforms {})"
        return (ps.format(name, self.alias, self.midpoint,
                          self.transformation))

    def __str__(self):
        return self.__repr__()

    def __hash__(self):
        return hash(self.__repr__())

    def __deepcopy__(self, memo):
        # return SRef(
        return self.__class__(
            alias=self.alias,
            reference=deepcopy(self.reference),
            midpoint=deepcopy(self.midpoint),
            # midpoint=self.midpoint,
            transformation=deepcopy(self.transformation))

    def __eq__(self, other):
        if not isinstance(other, SRef):
            return False
        return ((self.reference == other.reference)
                and (self.midpoint == other.position)
                and (self.transformation == other.transformation))

    def id_string(self):
        return self.__repr__()

    def dependencies(self):
        from spira.yevon.gdsii.cell_list import CellList
        d = CellList()
        d.add(self.reference)
        d.add(self.reference.dependencies())
        return d

    def net_source(self):
        return 'source: {}'.format(self.reference.name)

    def net_target(self):
        return 'target: {}'.format(self.reference.name)

    def is_valid_path(self):
        return True

    def expand_transform(self):
        from spira.yevon.gdsii.sref import SRef
        from spira.yevon.gdsii.polygon import Polygon
        from spira.core.transforms.identity import IdentityTransform

        C = self.reference.__class__(
            name='{}_{}'.format(self.reference.name,
                                self.transformation.id_string()),
            elements=deepcopy(self.reference.elements),
            ports=deepcopy(self.reference.ports))

        T = self.transformation + spira.Translation(self.midpoint)

        for i, e in enumerate(C.elements):
            if isinstance(e, SRef):
                e.midpoint = self.transformation.apply_to_coord(
                    e.midpoint).move(self.midpoint)
                e.transform(self.transformation)
            elif isinstance(e, Polygon):
                e.transform(T)
        C.ports.transform(T)

        self.reference = C
        self.transformation = None
        self.midpoint = (0, 0)

        return self

    def expand_flat_copy(self):
        """  """
        D = self.reference.expand_flat_copy()
        return SRef(reference=D)
        # return self.__class__(reference=D)

    def flat_copy(self, level=-1):
        if level == 0: return spira.ElementList(self.__copy__())
        elems = self.reference.elements.flat_copy(level - 1)
        T = self.transformation + Translation(self.midpoint)
        elems = elems.transform(T)
        return elems

    def flatten(self, level=-1, name_tree=[]):
        if level == 0: return self.reference
        name_tree.append(self.alias)
        nt = deepcopy(name_tree)
        D = self.reference.flatten(level, name_tree=nt)
        name_tree.pop()
        return D.elements

    def flat_container(self, cc, name_tree=[]):
        if self.alias is None:
            self.alias = ''
        name_tree.append(self.alias)
        nt = deepcopy(name_tree)
        self.reference.flat_container(cc, name_tree=nt)
        name_tree.pop()

    def move(self, midpoint=(0, 0), destination=None, axis=None):
        """ Move the reference internal port to the destination.

        Example:
        --------
        >>> S.move()
        """

        if destination is None:
            destination = midpoint
            midpoint = [0, 0]

        if isinstance(midpoint, Coord):
            o = midpoint
        elif np.array(midpoint).size == 2:
            o = midpoint
        elif midpoint in self.ports.get_names():
            o = self.ports[midpoint.name].midpoint
        elif issubclass(type(midpoint), __Port__):
            o = 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 self.ports.get_names():
            d = self.ports[destination.name].midpoint
        else:
            raise ValueError(
                "[PHIDL] [DeviceReference.move()] ``destination`` " +
                "not array-like, a port, or port name")

        position = np.array([d[0], d[1]]) - np.array([o[0], o[1]])
        self.midpoint = Coord(self.midpoint[0] + position[0],
                              self.midpoint[1] + position[1])
        # dxdy = Coord(self.midpoint[0] + position[0], self.midpoint[1] + position[1])
        # self.translate(dxdy)
        return self

    def connect(self, port, destination, ignore_process=False):
        """ Connect the reference internal port with an external port.

        Example:
        --------
        >>> S.connect()
        """
        if issubclass(type(port), __Port__):
            p = port
        elif port in self.ports.get_names():
            if issubclass(type(port), __Port__):
                p = self.ports[port.name]
            elif isinstance(port, str):
                p = self.ports[port]
        else:
            raise ValueError(
                "[SPiRA] connect() did not receive a Port or " +
                "valid port name - received ({}), ports available " +
                "are ({})".format(port, self.ports.get_names()))

        if not isinstance(destination, spira.Port):
            raise ValueError('Destination has to be a port.')

        if (ignore_process is False) and (p.process != destination.process):
            raise ValueError('Cannot connect ports from different processes.')

        T = vector_match_transform(v1=p, v2=destination)
        self.midpoint = T.apply_to_coord(self.midpoint)
        self.transform(T - spira.Translation(self.midpoint))
        # self.transformation = T - spira.Translation(self.midpoint)
        # self.transformation = T
        # print(T - spira.Translation(self.midpoint))
        # print(T)

        return self

    def distance_alignment(self, port, destination, distance):
        """ Align the reference using an internal port with an external port.

        Example:
        --------
        >>> S.distance_alignment()
        """
        destination = deepcopy(destination)
        self.connect(port, destination)

        L = line_from_point_angle(point=destination.midpoint,
                                  angle=destination.orientation)
        dx, dy = L.get_coord_from_distance(destination, distance)

        # self.move(midpoint=self.midpoint, destination=(dx,dy))
        T = spira.Translation(translation=(dx, dy))
        self.midpoint = T.apply_to_coord(self.midpoint)
        # self.transform(T - spira.Translation(self.midpoint))
        # self.transform(T)

        return self

    def center_alignment(self, p1, p2):
        """
        Place the reference `midpoint` at the virtual
        intersection line of two ports.

        Example
        -------
        >>> s.center_alignment(p1=self.p3, p2=self.via1_i5.ports['M5_P2'])
        """
        l1 = line_from_point_angle(point=p1.midpoint, angle=p1.orientation)
        l2 = line_from_point_angle(point=p2.midpoint, angle=p2.orientation)
        coord = l1.intersection(l2)
        self.move(midpoint=self.midpoint, destination=coord)
        return self

    # FIXME~~~~ Look at lieze_dcsfq.py
    def port_alignment(self, ports, p1, p2):
        """
        Align `ports[0]` of reference with `p1` and 
        `ports[1]` of reference with external port `p2`.

        Example
        -------
        >>>
        """
        self.connect(ports[0], deepcopy(p1))
        # print(self.transformation)

        if isinstance(ports[1], str):
            pin1 = self.ports[ports[1]]
        elif issubclass(type(ports[1]), __Port__):
            # pin1 = ports[1].transform(self.transformation)
            pin1 = ports[1].transform(spira.Translation(self.midpoint))

        # print(pin1)
        # print(p2)
        # print(ports[0])

        if ports[0].orientation in (0, 180):
            T = vector_match_axis(v1=pin1, v2=p2, axis='x')
        elif ports[0].orientation in (90, 270):
            T = vector_match_axis(v1=pin1, v2=p2, axis='y')

        T = T + self.transformation
        # self.midpoint = T.apply_to_coord(Coord(0,0))
        # self.midpoint = T.apply_to_coord(self.midpoint)
        # self.transform(T + spira.Translation(self.midpoint))
        self.transform(T)

        return self

    def stretch_by_factor(self, factor=(1, 1), center=(0, 0)):
        """
        Strecth the entire instance by a factor and around a specified center.

        Example
        -------
        >>> S.stretch_by_factor(factor=(2,1))
        """
        S = self.expand_flat_copy()
        T = spira.Stretch(stretch_factor=factor, stretch_center=center)
        for i, e in enumerate(S.reference.elements):
            S.reference.elements[i].transform(T)
        return self

    def stretch_p2c(self, port_name, destination_coord):
        """
        Stretch port to port. Stretch the polygon by moving
        the polygon edge port to the destination port location.

        Note
        ----
        The opposite port position is used as the stretching center.
        This overcomes the issue of distorting the entiry structure.

        Example
        -------
        >>> S.stretch_p2p(port_name='S1:Sr1:E3_R1', destination_coord=(10,0))
        """
        from spira.yevon.gdsii.polygon import Polygon
        from spira.yevon.geometry.bbox_info import bbox_info_opposite_boundary_port
        D = self.expand_flat_copy()

        port = D.ports[port_name]
        destination = D.ports[destination_name]

        for i, e in enumerate(D.reference.elements.polygons):
            if e.id_string() == port.local_pid:
                opposite_port = bbox_info_opposite_boundary_port(e, port)
                T = stretching.stretch_element_by_port(self, opposite_port,
                                                       port, destination)
                T.apply(D.reference.elements[i])

    def stretch_p2p(self, port_name, destination_name):
        """
        Stretch port to port. Stretch the polygon by moving
        the polygon edge port to the destination port location.

        Note
        ----
        The opposite port position is used as the stretching center.
        This overcomes the issue of distorting the entiry structure.

        Example
        -------
        >>> S.stretch_p2p(port_name='S1:Sr1:E3_R1', destination_name='S2:Sr2:E1_R1')
        """
        from spira.yevon.gdsii.polygon import Polygon
        from spira.yevon.geometry.ports import PortList
        from spira.yevon.geometry.bbox_info import bbox_info_opposite_boundary_port

        D = self.expand_flat_copy()

        # print(D.ports)
        # print('\n------------------\n')

        # print('\n*************************************')
        ports = PortList()
        for e in D.reference.elements.polygons:
            # print(e.edge_ports)
            ports += e.edge_ports

        port = ports[port_name]
        destination = ports[destination_name]

        # print(port)
        # print(destination)

        for i, e in enumerate(D.reference.elements.polygons):
            if e.id_string() == port.local_pid:
                opposite_port = bbox_info_opposite_boundary_port(e, port)
                T = stretching.stretch_element_by_port(self, opposite_port,
                                                       port, destination)
                T.apply(D.reference.elements[i])

    def nets(self, lcar):
        """  """

        from spira.yevon.geometry.nets.net_list import NetList
        nets = NetList()
        nets += self.reference.netlist

        # FIXME: Is this transformation required?
        # T = self.transformation + Translation(self.midpoint)
        # nets.transform(T)

        return nets
Esempio n. 7
0
class Vector(Transformable, ParameterInitializer):
    """ Vector consisting of a point and an orientation. """

    midpoint = CoordParameter(default=(0.0, 0.0), doc='Position of the point.')

    @property
    def x(self):
        return self.midpoint.x

    @property
    def y(self):
        return self.midpoint.y

    def get_angle_rad(self):
        if hasattr(self, '__angle__'):
            return constants.DEG2RAD * self.__angle__
        else:
            return 0.0

    def set_angle_rad(self, value):
        self.__angle__ = (constants.RAD2DEG * value) % 360.0

    angle_rad = FunctionParameter(
        get_angle_rad,
        set_angle_rad,
        doc=
        "The outward facing orientation of the port in radians (stored in degrees by default, converted to radians if needed)"
    )

    def get_angle_deg(self):
        if hasattr(self, '__angle__'):
            return self.__angle__
        else:
            return 0.0

    def set_angle_deg(self, value):
        self.__angle__ = value % 360.0

    orientation = FunctionParameter(
        get_angle_deg,
        set_angle_deg,
        doc="The outward facing orientation of the port.")

    def cos(self):
        return cos(constants.DEG2RAD * self.__angle__)

    def sin(self):
        return sin(constants.DEG2RAD * self.__angle__)

    def tan(self):
        return tan(constants.DEG2RAD * self.__angle__)

    def flip(self):
        return Vector(midpoint=self.midpoint,
                      orientation=(self.__angle__ + 180.0) % 360.0)

    def __getitem__(self, key):
        if key == 0:
            return self.midpoint[0]
        if key == 1:
            return self.midpoint[1]
        else:
            raise IndexError(
                "Vector supports only subscription[0] and [1], not " +
                str(key))

    def __eq__(self, other):
        if not isinstance(other, Vector):
            return False
        return self.midpoint == other.midpoint and (self.angle_deg
                                                    == other.angle_deg)

    def __ne__(self, other):
        return self.midpoint != other.midpoint or (self.angle_deg !=
                                                   other.angle_deg)

    def __repr__(self):
        return "<Vector (%f, %f), a=%f>" % (self.x, self.y, self.angle_deg)

    def transform(self, transformation):
        self.midpoint = transformation.apply_to_coord(self.midpoint)
        self.angle_deg = transformation.apply_to_angle(self.angle_deg)
        return self
Esempio n. 8
0
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
Esempio n. 9
0
class __Shape__(Transformable, ParameterInitializer):

    center = CoordParameter()
    clockwise = BoolParameter(default=False)
    points = PointArrayParameter(fdef_name='create_points')

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def create_points(self, points):
        return points

    @property
    def x_coords(self):
        """ Returns the x coordinates """
        return self.points[:, 0]

    @property
    def y_coords(self):
        """ Returns the y coordinates """
        return self.points[:, 1]

    @property
    def is_closed(self):
        return True

    @property
    def center_of_mass(self):
        """ Get the center of mass of the shape.
        Note: This is not the same as the bounding box center."""
        c = np.mean(self.points, 0)
        return [c[0], c[1]]

    @property
    def orientation(self):
        """ Returns the orientation of the shape.
        Counterclockwise returns +1 and clockwise returns -1. """
        pts = self.points
        T = np.roll(np.roll(pts, 1, 1), 1, 0)
        return -np.sign(sum(np.diff(pts * T, 1, 1)))

    @property
    def area(self):
        """ Returns the area of the shape. """
        pts = self.points
        T = np.roll(np.roll(pts, 1, 1), 1, 0)
        return sum(abs(np.diff(pts * T, 1, 1))) * 0.5

    @property
    def hash_string(self):
        import hashlib
        pts = np.array([self.points])
        hash_key = np.sort([hashlib.sha1(p).hexdigest() for p in pts])
        return str(hash_key[0])

    @property
    def count(self):
        """ Number of points in the shape """
        return self.__len__()

    @property
    def bbox_info(self):
        return bbox_info.bbox_info_from_numpy_array(self.points)

    def reverse_points(self):
        """ If orientation is clockwise, convert to counter-clockwise. """
        sc = constants.CLIPPER_SCALE
        pts = st(self.points, sc)
        if pyclipper.Orientation(pts) is False:
            reverse_poly = pyclipper.ReversePath(pts)
            solution = pyclipper.SimplifyPolygon(reverse_poly)
        else:
            solution = pyclipper.SimplifyPolygon(pts)
        self.points = sf(solution, sc)[0]
        return self
    
    def segments(self):
        """ Returns a list of point pairs 
        with the segments of the shape. """
        p = self.points
        if len(p) < 2:
            return []
        if self.is_closed:
            segments = list(zip(p, np.roll(p, shift=-1, axis=0)))
        else:
            segments = list(zip(p[:-1], p[1:]))
        return segments

    def snap_to_grid(self, grids_per_unit=None):
        """ Snaps all shape points to grid. """
        from spira.settings import get_grids_per_unit
        if grids_per_unit is None:
            grids_per_unit = get_grids_per_unit()
        self.points = (np.floor(self.points * grids_per_unit + 0.5)) / grids_per_unit 
        return self

    def move(self, pos):
        p = np.array([pos[0], pos[1]])
        self.points += p
        return self

    def transform(self, transformation):
        self.points = transformation.apply_to_array(self.points)
        return self

    def make_clockwise(self):
        """ Make sure all points are clockwise ordered. """
        x, y = self.x_coords, self.y_coords
        cx, cy = np.mean(x), np.mean(y)
        a = np.arctan2(y - cy, x - cx)
        order = a.ravel().argsort()
        self.points = np.column_stack((x[order], y[order]))
        return self

    def remove_identicals(self):
        """ Removes consecutive identical points """
        from spira import settings
        pts = self.points
        if len(pts) > 1:
            identicals = np.prod(abs(pts - np.roll(self.points, -1, 0)) < 0.5 / settings.get_grids_per_unit(), 1)
            if not self.is_closed:
                identicals[-1] = False
            self.points = np.delete(pts, identicals.nonzero()[0], 0)
        return self

    def remove_straight_angles(self):
        """ removes points with turn zero or 180 degrees """
        Shape.remove_identicals(self)
        pts = self.points
        if len(pts) > 1:
            straight = (abs(abs((self.turns_rad() + (0.5 * np.pi)) % np.pi) - 0.5 * np.pi) < 0.00001)
            if not self.is_closed:
                straight[0] = False
                straight[-1] = False
            self.points = np.delete(pts, straight.nonzero()[0], 0)
        return self

    def angles_rad(self):
        """ returns the angles (radians) of the connection between each point and the next """
        pts = self.points
        R = np.roll(pts, -1, 0)
        radians = np.arctan2(R[:, 1] - pts[:, 1], R[:, 0] - pts[:, 0])
        return radians

    def turns_rad(self):
        """ returns the angles (radians) of the turn in each point """
        a = self.angles_rad()
        return (a - np.roll(a, 1, 0) + np.pi) % (2 * np.pi) - np.pi

    def intersections(self, other_shape):
        """ the intersections with this shape and the other shape """
        from spira.yevon.utils import geometry as gm

        s = Shape(self.points)
        s.remove_straight_angles()
        segments1 = s.segments()
        if len(segments1) < 1:
            return []

        s = Shape(other_shape)
        s.remove_straight_angles()
        segments2 = s.segments()
        if len(segments2) < 1:
            return []

        intersections = []
        for s1 in segments1:
            intersected_points = []
            for s2 in segments2:
                if gm.lines_cross(s1[0], s1[1], s2[0], s2[1], inclusive=True):
                    c = [gm.intersection(s1[0], s1[1], s2[0], s2[1])]
                    intersected_points += c
            if len(intersected_points) > 0:
                pl = gm.sort_points_on_line([*s1, *intersected_points])
                intersections += [pl[1], pl[2]]

        intersections = gm.points_unique(intersections)
        return Shape(intersections)
Esempio n. 10
0
class Stretch(ReversibleTransform):
    """ Stretch an object using.

    Example
    -------
    >>> s = Stretch()(shape)
    """

    stretch_center = CoordParameter(default=(0, 0))

    def set_stretch_factor(self, value):
        if isinstance(value, Coord):
            self.__stretch_factor__ = value
        else:
            self.__stretch_factor__ = Coord(value[0], value[1])
        if self.__stretch_factor__[0] == 0.0 or self.__stretch_factor__[
                1] == 0.0:
            raise ValueError(
                "Error: Stretch factor cannot be zero in Stretch transform")

    stretch_factor = SetFunctionParameter('__stretch_factor__',
                                          set_stretch_factor)

    def __repr__(self):
        return "[SPiRA: Stretch] (factor {}, center {})".format(
            self.stretch_factor, self.stretch_center)

    def __str__(self):
        return self.__repr__()

    def apply_to_coord(self, coord):
        x1 = self.__stretch_factor__[0] * coord[0]
        x2 = (1 - self.__stretch_factor__[0]) * self.stretch_center[0]
        y1 = self.__stretch_factor__[1] * coord[1]
        y2 = (1 - self.__stretch_factor__[1]) * self.stretch_center[1]
        return Coord(x1 + x2, y1 + y2)

    def reverse_on_coord(self, coord):
        x1 = 1.0 / self.__stretch_factor__[0] * coord[0]
        x2 = (1 - 1.0 / self.__stretch_factor__[0]) * self.stretch_center[0]
        y1 = 1.0 / self.__stretch_factor__[1] * coord[1]
        y2 = (1 - 1.0 / self.__stretch_factor__[1]) * self.stretch_center[1]
        return Coord(x1 + x2, y1 + y2)

    def apply_to_array(self, coords):
        coords *= np.array([self.stretch_factor.x, self.stretch_factor.y])
        x = (1 - self.__stretch_factor__.x) * self.stretch_center.x
        y = (1 - self.__stretch_factor__.y) * self.stretch_center.y
        coords += np.array([x, y])
        return coords

    def reverse_on_array(self, coords):
        coords *= np.array(
            [1.0 / self.stretch_factor.x, 1.0 / self.stretch_factor.y])
        x = (1 - 1.0 / self.__stretch_factor__.x) * self.stretch_center.x
        y = (1 - 1.0 / self.__stretch_factor__.y) * self.stretch_center.y
        coords += np.array([x, y])
        return coords

    def apply_to_angle(self, angle):
        # FIXME: This is required for transforming polygon ports.
        # This is currently just a temporary fix.
        return angle

    def is_identity(self):
        """ Returns True if the transformation does nothing """
        return ((self.stretch_factor.x == 1.0)
                and (self.stretch_factor.y == 1.0))

    def id_string(self):
        return self.__repr__()
Esempio n. 11
0
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
Esempio n. 12
0
class GenericTransform(ReversibleTransform):
    """  """
    def __init__(self,
                 translation=(0, 0),
                 rotation=0,
                 absolute_rotation=False,
                 **kwargs):
        super().__init__(translation=translation,
                         rotation=rotation,
                         absolute_rotation=absolute_rotation,
                         **kwargs)

    def set_rotation(self, value):
        self.__rotation__ = value % 360.0
        if value % 90.0 == 0.0:
            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__ = np.cos(value * constants.DEG2RAD)
            self.__sa__ = np.sin(value * constants.DEG2RAD)

    rotation = SetFunctionParameter(local_name='__rotation__',
                                    fset=set_rotation,
                                    default=0.0)
    magnification = NumberParameter(default=1)
    reflection = BoolParameter(default=False)
    translation = CoordParameter(local_name='__translation__')
    absolute_rotation = BoolParameter(local_name='__absolute_rotation__',
                                      default=False)

    def __str__(self):
        """ Gives a string representing the transform. """
        return "<T {}, R {}, RF {}, M {}>".format(str(self.translation),
                                                  str(self.rotation),
                                                  str(self.reflection),
                                                  str(self.magnification))

    def __translate__(self, coord):
        C = Coord(coord[0] + self.translation.x, coord[1] + self.translation.y)
        return C

    def __rotate__(self, coord):
        return Coord(coord[0] * self.__ca__ - coord[1] * self.__sa__,
                     coord[0] * self.__sa__ + coord[1] * self.__ca__)

    def __magnify__(self, coord):
        return Coord(coord[0] * self.magnification,
                     coord[1] * self.magnification)

    def __inv_translate__(self, coord):
        return Coord(coord[0] - self.translation.x,
                     coord[1] - self.translation.y)

    def __inv_rotate__(self, coord):
        return Coord(coord[0] * self.__ca__ + coord[1] * self.__sa__,
                     -coord[0] * self.__sa__ + coord[1] * self.__ca__)

    def __inv_magnify__(self, coord):
        return Coord(coord[0] / self.magnification,
                     coord[1] / self.magnification)

    def __reflect__(self, coords, p1=(0, 0), p2=(1, 0)):
        if self.reflection is True:
            points = np.array(coords.to_numpy_array())
            p1 = np.array(p1)
            p2 = np.array(p2)
            if np.asarray(points).ndim == 1:
                t = np.dot((p2 - p1), (points - p1)) / norm(p2 - p1)**2
                pts = 2 * (p1 + (p2 - p1) * t) - points
            if np.asarray(points).ndim == 2:
                raise ValueError('This is a array, not an coordinate.')
        else:
            pts = coords
        pts = Coord(pts[0], pts[1])
        return pts

    def __reflect_array__(self, coords, p1=(0, 0), p2=(1, 0)):
        if self.reflection is True:
            points = np.array(coords)
            p1 = np.array(p1)
            p2 = np.array(p2)
            if np.asarray(points).ndim == 1:
                raise ValueError('This is a coordinate, not an array.')
            if np.asarray(points).ndim == 2:
                t = np.dot((p2 - p1), (p2 - p1)) / norm(p2 - p1)**2
                pts = np.array([2 * (p1 + (p2 - p1) * t) - p for p in points])
        else:
            pts = coords
        return pts

    def __translate_array__(self, coords):
        # FIXME: Why should I convert to float!
        coords = [[float(c[0]), float(c[1])] for c in coords]
        coords += np.array([self.translation.x, self.translation.y])
        return coords

    def __rotate_array__(self, coords):
        x_a = np.array([self.__ca__, -self.__sa__])
        y_a = np.array([self.__sa__, self.__ca__])
        coords = np.transpose(
            np.vstack((np.sum(coords * x_a, 1), np.sum(coords * y_a, 1))))
        return coords

    def __magnify_array__(self, coords):
        coords *= np.array([self.magnification, self.magnification])
        return coords

    def apply_to_coord(self, coord):
        coord = self.__reflect__(coord)
        coord = self.__rotate__(coord)
        coord = self.__magnify__(coord)
        coord = self.__translate__(coord)
        return coord

    def apply_to_array(self, coords):
        coords = self.__reflect_array__(coords)
        coords = self.__rotate_array__(coords)
        coords = self.__magnify_array__(coords)
        coords = self.__translate_array__(coords)
        return coords

    def apply_to_angle(self, angle):
        a = angle
        if self.reflection:
            a = -a
        a += self.rotation
        return a % 360.0

    def __add__(self, other):
        if other is None:
            return deepcopy(self)

        # if issubclass(type(other), GenericTransform):
        if isinstance(other, GenericTransform):
            T = GenericTransform()

            if other.reflection is True: s_1 = -1
            else: s_1 = 1

            M1 = 1.0

            if not self.absolute_rotation:
                T.rotation = s_1 * self.rotation + other.rotation
                ca = other.__ca__
                sa = other.__sa__
                # print('A')
            else:
                # print('B')
                T.rotation = s_1 * self.rotation
                ca = 1.0
                sa = 0.0

            # Counterclockwise rotation
            # cx = self.translation.x + ca * other.translation.x * M1 - s_1 * sa * other.translation.y * M1
            # cy = self.translation.y + sa * other.translation.x * M1 + s_1 * ca * other.translation.y * M1
            cx = other.translation.x + ca * self.translation.x * M1 - s_1 * sa * self.translation.y * M1
            cy = other.translation.y + sa * self.translation.x * M1 + s_1 * ca * self.translation.y * M1
            # cx = other.translation.x + ca * self.translation.x * M1 + s_1 * sa * self.translation.y * M1
            # cy = -other.translation.y + sa * self.translation.x * M1 + s_1 * ca * self.translation.y * M1
            T.translation = Coord(cx, cy)

            T.absolute_rotation = self.absolute_rotation or other.absolute_rotation
            T.reflection = (not self.reflection == other.reflection)

        else:
            T = Transform.__add__(self, other)
        return T

    def __iadd__(self, other):

        # return self.__add__(other)

        if other is None:
            return self

        # if issubclass(type(other), GenericTransform):
        if isinstance(other, GenericTransform):
            T = GenericTransform()

            if other.reflection is True: s_1 = -1
            else: s_1 = 1

            M1 = 1.0

            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

            # Counterclockwise rotation
            # cx = self.translation.x + ca * other.translation.x * M1 - s_1 * sa * other.translation.y * M1
            # cy = self.translation.y + sa * other.translation.x * M1 + s_1 * ca * other.translation.y * M1
            cx = other.translation.x + ca * self.translation.x * M1 - s_1 * sa * self.translation.y * M1
            cy = other.translation.y + sa * self.translation.x * M1 + s_1 * ca * self.translation.y * M1
            # cx = other.translation.x + ca * self.translation.x * M1 + s_1 * sa * self.translation.y * M1
            # cy = -other.translation.y + sa * self.translation.x * M1 + s_1 * ca * self.translation.y * M1
            self.translation = Coord(cx, cy)

            self.absolute_rotation = self.absolute_rotation or other.absolute_rotation
            self.reflection = (not self.reflection == other.reflection)

        else:
            raise ValueError('Cannot add transforms')

        return self

    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 __eq__(self, other):
        if other is None: return self.is_identity()
        if not isinstance(other, GenericTransform):
            return False
        return ((self.rotation == other.rotation)
                and (self.translation == other.translation)
                and (self.reflection == other.reflection)
                and (self.magnification == other.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, GenericTransform):
            return False
        return ((self.rotation != other.rotation)
                or (self.translation != other.translation)
                or (self.reflection != other.reflection)
                or (self.magnification != other.magnification))

    def __neg__(self):
        from spira.core.transforms.translation import Translation
        from spira.core.transforms.rotation import Rotation
        T = Translation(translation=-self.translation) + Rotation(
            rotation=-self.rotation, rotation_center=(0, 0))
        # T = Translation(translation=-self.translation)
        # T += Rotation(rotation=-self.rotation, rotation_center=(0,0))
        return T

    def id_string(self):
        """ Gives a hash of the transform (for naming purposes) """
        return self.__str__()

    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.reflection)
                and (self.magnification == 1.0))
Esempio n. 13
0
class __Shape__(Transformable, ParameterInitializer):

    center = CoordParameter()
    clockwise = BoolParameter(default=False)
    points = PointArrayParameter(fdef_name='create_points')

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def create_points(self, points):
        return points

    @property
    def x_coords(self):
        """ Returns the x coordinates """
        return self.points[:, 0]

    @property
    def y_coords(self):
        """ Returns the y coordinates """
        return self.points[:, 1]

    @property
    def is_closed(self):
        return True

    @property
    def center_of_mass(self):
        """ Get the center of mass of the shape.
        Note: This is not the same as the bounding box center."""
        c = np.mean(self.points, 0)
        return [c[0], c[1]]

    @property
    def orientation(self):
        """ Returns the orientation of the shape.
        Counterclockwise returns +1 and clockwise returns -1. """
        pts = self.points
        T = np.roll(np.roll(pts, 1, 1), 1, 0)
        return -np.sign(sum(np.diff(pts * T, 1, 1)))

    @property
    def area(self):
        """ Returns the area of the shape. """
        pts = self.points
        T = np.roll(np.roll(pts, 1, 1), 1, 0)
        return sum(abs(np.diff(pts * T, 1, 1))) * 0.5

    @property
    def hash_string(self):
        import hashlib
        pts = np.array([self.points])
        hash_key = np.sort([hashlib.sha1(p).hexdigest() for p in pts])
        return str(hash_key)

    @property
    def count(self):
        """ Number of points in the shape """
        return self.__len__()

    @property
    def bbox_info(self):
        return bbox_info.bbox_info_from_numpy_array(self.points)

    # @property
    def segments(self):
        """ Returns a list of point pairs 
        with the segments of the shape. """
        p = self.points
        if len(p) < 2:
            return []
        if self.is_closed:
            segments = list(zip(p, np.roll(p, shift=-1, axis=0)))
        else:
            segments = list(zip(p[:-1], p[1:]))
        # segments = [Coord(s[0], s[1]).snap_to_grid() for s in segments]
        # ss = []
        # for segment in segments:
        #     seg = [Coord(s[0], s[1]).snap_to_grid() for s in segment]
        #     ss.append(seg)
        # segments = ss
        # s1 = Coord(s1[0], s1[1]).snap_to_grid()
        return segments

    def snap_to_grid(self, grids_per_unit=None):
        """ Snaps all shape points to grid. """
        from spira.settings import get_grids_per_unit
        if grids_per_unit is None:
            grids_per_unit = get_grids_per_unit()
        # print(self.points)
        self.points = (np.floor(self.points * grids_per_unit +
                                0.5)) / grids_per_unit
        # print(self.points)
        # print('\n\n===========================')
        return self

    def move(self, pos):
        p = np.array([pos[0], pos[1]])
        self.points += p
        return self

    def transform(self, transformation):
        self.points = transformation.apply_to_array(self.points)
        return self

    def make_clockwise(self):
        """ Make sure all points are clockwise ordered. """
        x, y = self.x_coords, self.y_coords
        cx, cy = np.mean(x), np.mean(y)
        a = np.arctan2(y - cy, x - cx)
        order = a.ravel().argsort()
        self.points = np.column_stack((x[order], y[order]))
        return self

    def remove_identicals(self):
        """ Removes consecutive identical points """
        # FIXME: in some cases a series of many points close together
        # is removed, even if they form together a valid shape.
        from spira import settings
        pts = self.points
        if len(pts) > 1:
            identicals = np.prod(
                abs(pts - np.roll(self.points, -1, 0)) <
                0.5 / settings.get_grids_per_unit(), 1)
            if not self.is_closed:
                identicals[-1] = False
            self.points = np.delete(pts, identicals.nonzero()[0], 0)
        return self

    def remove_straight_angles(self):
        """ removes points with turn zero or 180 degrees """
        Shape.remove_identicals(self)
        pts = self.points
        if len(pts) > 1:
            straight = (abs(
                abs((self.turns_rad() +
                     (0.5 * np.pi)) % np.pi) - 0.5 * np.pi) < 0.00001
                        )  # (self.turns_rad()%np.pi == 0.0)
            if not self.is_closed:
                straight[0] = False
                straight[-1] = False
            self.points = np.delete(pts, straight.nonzero()[0], 0)
        return self

    def angles_rad(self):
        """ returns the angles (radians) of the connection between each point and the next """
        pts = self.points
        R = np.roll(pts, -1, 0)
        radians = np.arctan2(R[:, 1] - pts[:, 1], R[:, 0] - pts[:, 0])
        return radians

    def turns_rad(self):
        """ returns the angles (radians) of the turn in each point """
        a = self.angles_rad()
        return (a - np.roll(a, 1, 0) + np.pi) % (2 * np.pi) - np.pi

    def intersections(self, other_shape):
        """ the intersections with this shape and the other shape """
        from spira.yevon.utils.geometry import intersection, lines_cross, lines_coincide, sort_points_on_line, points_unique

        s = Shape(self.points)
        s.remove_straight_angles()
        segments1 = s.segments()
        if len(segments1) < 1:
            return []

        s = Shape(other_shape)
        s.remove_straight_angles()
        segments2 = s.segments()
        if len(segments2) < 1:
            return []

        intersections = []
        for s1 in segments1:
            intersected_points = []
            for s2 in segments2:
                if lines_cross(s1[0], s1[1], s2[0], s2[1], inclusive=True):
                    # print(s1, s2)
                    c = [intersection(s1[0], s1[1], s2[0], s2[1])]
                    # print(c)
                    # print('wjefbwefb')
                    intersected_points += c

            # FIXME Must this be a 0 or a 1!!!
            if len(intersected_points) > 0:
                # if len(intersected_points) > 1:
                pl = sort_points_on_line([*s1, *intersected_points])
                intersections += [pl[1], pl[2]]

        intersections = points_unique(intersections)
        return Shape(intersections)

    def insert(self, i, item):
        """ Inserts a list of points. """
        if isinstance(item, Shape):
            self.points = np.insert(self.points, i, item.points, axis=0)
        elif isinstance(item, (list, np.ndarray)):
            if isinstance(item[0], Coord):
                item[0] = item[0].to_numpy_array()
            if len(item) > 1:
                if isinstance(item[1], Coord):
                    item[1] = item[1].to_numpy_array()
            self.points = np.insert(self.points, i, item, axis=0)
        elif isinstance(item, (Coord, tuple)):
            self.points = np.insert(self.points,
                                    i, [(item[0], item[1])],
                                    axis=0)
        else:
            raise TypeError("Wrong type " + str(type(item)) +
                            " to extend Shape with")
        return self

    def id_string(self):
        return self.__str__()
Esempio n. 14
0
class ParabolicShape(Shape):
    """ parabolic wedge (taper) """

    begin_coord = CoordParameter(default=(0, 0))
    end_coord = CoordParameter(default=(0, 0))
    begin_width = NumberParameter(default=3)
    end_width = NumberParameter(default=1)

    def create_points(self, pts):
        if (self.begin_width > self.end_width):
            ew = self.begin_width
            ec = self.begin_coord
            bw = self.end_width
            bc = self.end_coord
        else:
            bw = self.begin_width
            bc = self.begin_coord
            ew = self.end_width
            ec = self.end_coord

        length = geom.distance(ec, bc)
        angle = geom.angle_rad(ec, bc)
        sinangle = np.sin(angle)
        cosangle = np.cos(angle)

        dx = 0.01

        if abs(ew - bw) < dx:
            pts.extend([
                (bc[0] + sinangle * bw / 2.0, bc[1] - cosangle * bw / 2.0),
                (bc[0] - sinangle * bw / 2.0, bc[1] + cosangle * bw / 2.0),
                (ec[0] - sinangle * bw / 2.0, ec[1] + cosangle * ew / 2.0),
                (ec[0] + sinangle * bw / 2.0, ec[1] - cosangle * ew / 2.0),
                (bc[0] + sinangle * bw / 2.0, bc[1] - cosangle * bw / 2.0)
            ])
            return pts

        if length < 0.0025:
            return pts

        a = 4.0 * length / (ew**2 - bw**2)
        y0 = a * bw**2 / 4.0
        y = y0
        width = bw

        east_shape = [(bc[0] + sinangle * bw / 2.0,
                       bc[1] - cosangle * bw / 2.0)]
        west_shape = [(bc[0] - sinangle * bw / 2.0,
                       bc[1] + cosangle * bw / 2.0)]

        READY = False
        while (not READY):
            width = width + 4 * dx + 4 * math.sqrt(dx * (width + dx))
            y = a * width**2 / 4.0

            if (y - y0 > length):
                READY = True
                coord = ec
                width = ew
            else:
                coord = (bc[0] + (y - y0) * cosangle,
                         bc[1] + (y - y0) * sinangle)

            east_shape.append((coord[0] + sinangle * width / 2.0,
                               coord[1] - cosangle * width / 2.0))
            west_shape.append((coord[0] - sinangle * width / 2.0,
                               coord[1] + cosangle * width / 2.0))

        east_shape.reverse()
        pts += west_shape
        pts += east_shape
        return pts