Пример #1
0
 def add_arc(self, final_angle, radius, final_width=None, n_points=128, shortest=True, **kwargs):
     delta = final_angle - self.angle
     if not np.isclose(normalize_phase(delta), 0):
         if shortest:
             delta = normalize_phase(delta)
         self.add_bend(delta, radius, final_width, n_points, **kwargs)
     return self
Пример #2
0
    def add_bend(self,
                 angle,
                 radius,
                 final_width=None,
                 n_points=128,
                 **kwargs):
        # If number of points is None, default to 128
        n_points = n_points if n_points else 128

        self._current_port.set_port_properties(**kwargs)
        sample_points = max(int(abs(angle) / (np.pi / 2) * n_points), 2)
        final_width = final_width if final_width is not None else self.width

        angle = normalize_phase(
            angle, zero_to_two_pi=True) - (0 if angle > 0 else 2 * np.pi)

        self.add_parameterized_path(
            path=lambda t: [
                radius * np.sin(abs(angle) * t),
                np.sign(angle) * -radius * (np.cos(angle * t) - 1)
            ],
            path_function_supports_numpy=True,
            path_derivative=lambda t: [
                radius * np.cos(abs(angle) * t) * abs(angle),
                np.sign(angle) * radius * (np.sin(angle * t) * angle)
            ],
            width=lambda t: np.outer(1 - t, np.array(self.width)) + np.outer(
                t, np.array(final_width)),
            width_function_supports_numpy=True,
            sample_points=sample_points,
            sample_distance=0)

        return self
Пример #3
0
    def get_shapely_object(self):
        # Let's do the actual rendering

        polygons = list()

        special_handling_chars = '\n'
        font = _fonts.FONTS[self.font]

        # Check the text
        for char in self.text:
            if char in special_handling_chars:
                continue
            assert char in font, 'Character "%s" is not supported by font "%s"' % (
                char, self.font)

        max_x = 0
        cursor_x, cursor_y = 0, 0
        for i, char in enumerate(self.text):
            if char == '\n':
                cursor_x, cursor_y = 0, cursor_y - self.line_spacing
                continue

            char_font = font[char]
            cursor_x += char_font['width'] / 2 * self.height

            for line in char_font['lines']:
                points = np.array(line).T * self.height + (cursor_x, cursor_y)
                polygons.append(shapely.geometry.Polygon(points))

            # Add kerning
            if i < len(self.text) - 1 and self.text[
                    i + 1] not in special_handling_chars:
                kerning = char_font['kerning'][self.text[i + 1]]
                cursor_x += (char_font['width'] / 2 + kerning) * self.height

            max_x = max(max_x, cursor_x + char_font['width'] / 2 * self.height)

        merged_polygon = shapely.ops.cascaded_union(polygons)

        # Handle the alignment, translation and rotation
        if not self.true_bbox_alignment:
            bbox = np.array([[0, max_x], [cursor_y, self.height]]).T
        else:
            bbox = np.array(merged_polygon.bounds).reshape(2, 2)

        offset = self._alignment.calculate_offset(bbox)
        self._bbox = bbox + offset

        if not np.isclose(normalize_phase(self.angle), 0):
            aligned_text = shapely.affinity.translate(merged_polygon, *offset)
            rotated_text = shapely.affinity.rotate(aligned_text,
                                                   self.angle,
                                                   origin=[0, 0],
                                                   use_radians=True)
            final_text = shapely.affinity.translate(rotated_text, *self.origin)
        else:
            final_text = shapely.affinity.translate(merged_polygon,
                                                    *(offset + self.origin))

        return final_text
Пример #4
0
 def __init__(self, origin, height, text='', alignment='left-bottom', angle=0., font='stencil', line_spacing=1.5,
              true_bbox_alignment=False):
     self.origin = origin
     self.height = height
     self.text = str(text)
     self._alignment = Alignment(alignment)
     self.angle = normalize_phase(angle)
     self.font = font
     self.line_spacing = height * line_spacing
     self.true_bbox_alignment = true_bbox_alignment
     self._bbox = None
     self._shapely_object = None
Пример #5
0
    def add_bend(self, angle, radius, final_width=None, n_points=128, **kargs):
        # If number of points is None, default to 128
        n_points = n_points if n_points else 128

        self._current_port.set_port_properties(**kargs)
        final_width = final_width or self.width

        angle = normalize_phase(angle, zero_to_two_pi=True) - (0 if angle > 0 else 2 * np.pi)

        if not np.isclose(radius, 0) and not np.isclose(angle, 0) and radius > 0:
            if angle > 0:
                circle_center = (-np.sin(self.angle) * radius, np.cos(self.angle) * radius) + self.current_port.origin
                start_angle = -np.pi / 2 + self.angle
            else:
                circle_center = (np.sin(self.angle) * radius, -np.cos(self.angle) * radius) + self.current_port.origin
                start_angle = np.pi / 2 + self.angle

            end_angle = start_angle + angle

            # Calculate the points needed for this angle
            points = max(int(abs(end_angle - start_angle) / (np.pi / 2) * n_points), 2)

            phi = np.linspace(start_angle, end_angle, points)
            upper_radius_points = np.linspace(radius - self.width / 2, radius - final_width / 2, points)
            upper_line_points = np.array([upper_radius_points * np.cos(phi),
                                          upper_radius_points * np.sin(phi)]).T + circle_center

            lower_radius_points = np.linspace(radius + self.width / 2, radius + final_width / 2, points)
            lower_line_points = np.array([lower_radius_points * np.cos(phi),
                                          lower_radius_points * np.sin(phi)]).T + circle_center

            polygon = shapely.geometry.Polygon(np.concatenate([upper_line_points, lower_line_points[::-1, :]]))
            self._segments.append((self._current_port.copy(), polygon, abs(angle) * radius))

            endpoint = shapely.geometry.Point(radius * np.cos(end_angle) + circle_center[0],
                                              radius * np.sin(end_angle) + circle_center[1])
            self._current_port.origin = endpoint.coords[0]

        self._current_port.width = final_width
        self._current_port.angle += angle

        # assert self._segments[-1][1].is_valid, \
        #     'Invalid polygon generated: %s' % shapely.validation.explain_validity(self._segments[-1][1])

        return self
Пример #6
0
    def _calculate(self, do_in_wgs=True, do_out_wgs=True):
        angular_spacing = self._angular_spacing
        assert angular_spacing * self._out_ports < np.pi, 'Not enough space for output ports'
        assert angular_spacing * self._in_ports < np.pi, 'Not enough space for input ports'

        if do_out_wgs:
            # Do the output side
            out_origin_port = Port(self._origin, self._angle,
                                   1.).longitudinal_offset(
                                       -self._displacement / 2)
            out_fanout_wgs = [
                Waveguide.make_at_port(
                    out_origin_port.rotated(angle).longitudinal_offset(
                        self._radius))
                for angle in (np.arange(self._out_ports) -
                              (self._out_ports - 1) / 2.) * angular_spacing
            ]

            for wg in out_fanout_wgs:
                wg.add_parameterized_path(
                    path=lambda t: [t * self._taper_length,
                                    np.zeros_like(t)],
                    path_derivative=lambda t:
                    [np.ones_like(t) * self._taper_length,
                     np.zeros_like(t)],
                    path_function_supports_numpy=True,
                    width=self._taper_function,
                    **self._taper_options)

            if self._minimal_final_spacing is None:
                for wg in out_fanout_wgs:
                    wg.add_bend(normalize_phase(-wg.angle + self._angle),
                                self._wg_bend_radius)

            else:
                offsets = (
                    np.arange(self._out_ports) -
                    (self._out_ports - 1) / 2.) * self._minimal_final_spacing
                final_port_heights = [
                    out_origin_port.parallel_offset(offset)
                    for offset in offsets
                ]

                for wg, final_port_height, offset in zip(
                        out_fanout_wgs, final_port_heights, offsets):
                    if np.isclose(offset, 0):
                        continue

                    try:
                        wg.add_route_single_circle_to_port(
                            final_port_height.inverted_direction,
                            on_line_only=True)
                    except AssertionError:
                        # No curve possible, use normal bend
                        wg.add_bend(normalize_phase(-wg.angle + self._angle),
                                    self._wg_bend_radius)

            final_ports = [wg.current_port for wg in out_fanout_wgs]
            for wg in out_fanout_wgs:
                wg.add_straight_segment_until_level_of_port(final_ports)
            self._out_wgs = out_fanout_wgs

        if do_in_wgs:
            # Do the input side
            in_origin_port = Port(self._origin, self._angle + np.pi,
                                  1.).longitudinal_offset(-self._displacement /
                                                          2)
            in_fanout_wgs = [
                Waveguide.make_at_port(
                    in_origin_port.rotated(angle).longitudinal_offset(
                        self._radius))
                for angle in (np.arange(self._in_ports) -
                              (self._in_ports - 1) / 2.) * angular_spacing
            ]

            for wg in in_fanout_wgs:
                wg.add_parameterized_path(
                    path=lambda t: [t * self._taper_length,
                                    np.zeros_like(t)],
                    path_derivative=lambda t:
                    [np.ones_like(t) * self._taper_length,
                     np.zeros_like(t)],
                    path_function_supports_numpy=True,
                    width=self._taper_function,
                    **self._taper_options)

            if self._minimal_final_spacing is None:
                for wg in in_fanout_wgs:
                    wg.add_bend(
                        normalize_phase(-wg.angle + self._angle - np.pi),
                        self._wg_bend_radius)

            else:
                offsets = (
                    np.arange(self._in_ports) -
                    (self._in_ports - 1) / 2.) * self._minimal_final_spacing
                final_port_heights = [
                    in_origin_port.parallel_offset(offset)
                    for offset in offsets
                ]

                for wg, final_port_height, offset in zip(
                        in_fanout_wgs, final_port_heights, offsets):
                    if np.isclose(offset, 0):
                        continue

                    # wg.add_route_single_circle_to_port(final_port_height.inverted_direction, on_line_only=True)

                    try:
                        wg.add_route_single_circle_to_port(
                            final_port_height.inverted_direction,
                            on_line_only=True)
                    except AssertionError:
                        # No curve possible, use normal bend
                        wg.add_bend(
                            normalize_phase(-wg.angle + self._angle - np.pi),
                            self._wg_bend_radius)

            final_ports = [wg.current_port for wg in in_fanout_wgs]
            for wg in in_fanout_wgs:
                wg.add_straight_segment_until_level_of_port(final_ports)

            self._in_wgs = in_fanout_wgs
Пример #7
0
    def add_route_single_circle_to(self,
                                   final_coordinates,
                                   final_angle,
                                   final_width=None,
                                   max_bend_strength=None,
                                   on_line_only=False):
        """
        Connect two points by straight lines and one circle.

        Works for geometries like round edges and others. The final straight line can also be omitted so that
        the waveguide only end on the line described by the support vector and angle.

        By default, this method tries to route to the target with the greatest possible circle. But the valid
        bending range may be limited via the `max_bend_strength` parameter.

        This method does not work for geometries which cannot be connected only by straight lines and one
        circle, such as parallel lines etc.

        Still, this method can prove extremely useful for routing to i.e. grating couplers etc.

        :param final_coordinates: Final destination point.
        :param final_angle: Final angle of the waveguide.
        :param final_width: Final width of the waveguide.
        :param max_bend_strength: The maximum allowed bending radius.
        :param on_line_only: Omit the last straight line and only route to described line.
        """
        # We are given an out angle, but the internal math is for inward pointing lines
        final_angle = normalize_phase(final_angle) + np.pi
        final_width = final_width if final_width is not None else self.width
        # We need to to some linear algebra. We first find the intersection of the two waveguides
        r1 = self._current_port.origin
        r2 = np.array(final_coordinates)
        intersection_point, distance = find_line_intersection(
            r1, self.angle, r2, final_angle)

        assert distance[
            0] >= 0, 'Origin waveguide is too close to target. No curve possible'
        assert on_line_only or distance[
            1] >= 0, 'Target position is too close to target. No curve possible'

        # Calculate the angle bisector
        u1 = np.array([np.cos(self.angle), np.sin(self.angle)])
        u2 = np.array([np.cos(final_angle), np.sin(final_angle)])
        u_half = u1 + u2
        half_angle = np.arctan2(u_half[1], u_half[0])
        diff_angle = normalize_phase(self.angle - final_angle - np.pi)

        _, r1 = find_line_intersection(r1, self.angle + np.pi / 2,
                                       intersection_point, half_angle)

        if not on_line_only:
            max_poss_radius = min(
                np.abs([
                    r1[0], distance[0] / np.tan(diff_angle / 2),
                    distance[1] / np.tan(diff_angle / 2)
                ]))
        else:
            max_poss_radius = min(
                np.abs([r1[0], distance[0] / np.tan(diff_angle / 2)]))

        radius = min([max_bend_strength, max_poss_radius
                      ]) if max_bend_strength is not None else max_poss_radius
        d = abs(radius * np.tan(diff_angle / 2))
        if on_line_only:
            segments = [distance[0] - d, radius * diff_angle]
        else:
            segments = [distance[0] - d, radius * diff_angle, distance[1] - d]
        segment_ratio = np.cumsum(segments / sum(segments))
        segment_widths = [(final_width - self.current_port.width) * ratio +
                          self.current_port.width for ratio in segment_ratio]
        tmp_wg = Waveguide.make_at_port(self._current_port)
        tmp_wg.add_straight_segment(length=distance[0] - d,
                                    final_width=segment_widths[0])
        tmp_wg.add_bend(-diff_angle, radius, final_width=segment_widths[1])
        if not on_line_only:
            tmp_wg.add_straight_segment(distance[1] - d,
                                        final_width=segment_widths[2])

        self._segments.append(
            (self._current_port.copy(), tmp_wg.get_shapely_object(),
             tmp_wg.get_shapely_outline(), tmp_wg.length,
             tmp_wg.center_coordinates))
        self._current_port = tmp_wg.current_port

        return self
Пример #8
0
 def angle(self):
     """
     The angle of the port.
     """
     return normalize_phase(self._angle)