Example #1
0
    def del_colinear_points(self, inarray):
        """Delete colinear points from the given array.

        Args:
            inarray (list): List of points

        Returns:
            list: List of points without colinear points
        """
        if len(inarray) <= 1:
            return
        else:
            outarray = list()  #outarray = np.empty(shape=[0, 2])
            pts = [None, None, inarray[0]]
            for idxnext in range(1, len(inarray)):
                pts = pts[1:] + [inarray[idxnext]]
                # delete identical points
                if np.allclose(*pts[1:]):
                    pts = [None] + pts[0:2]
                    continue
                if pts[0] is not None:
                    if all(mao.round(i[1]) == mao.round(pts[0][1]) for i in pts) \
                            or all(mao.round(i[0]) == mao.round(pts[0][0]) for i in pts):
                        pts = [None] + [pts[0]] + [pts[2]]
                if pts[0] is not None:
                    #save the point before it gets dropped in the next cycle
                    outarray.append(pts[0])
            # save remainder points
            if pts[1] is not None:
                outarray.extend(pts[1:])
            else:
                outarray.append(pts[2])
            return np.array(outarray)
Example #2
0
    def connect_meandered(self, start_pt: QRoutePoint,
                          end_pt: QRoutePoint) -> np.ndarray:
        """
        Meanders using a fixed length and fixed spacing.

        Args:
            start_pt (QRoutePoint): QRoutePoint of the start
            end_pt (QRoutePoint): QRoutePoint of the end

        Returns:
            np.ndarray: Array of points

        Adjusts the width of the meander:
            * Includes the start but not the given end point
            * If it cannot meander just returns the initial start point
        """

        ################################################################
        # Setup

        # Parameters
        meander_opt = self.p.meander
        spacing = meander_opt.spacing  # Horizontal spacing between meanders
        asymmetry = meander_opt.asymmetry
        snap = is_true(self.p.snap)  # snap to xy grid
        prevent_short_edges = is_true(self.p.prevent_short_edges)

        # take care of anchors (do not have set directions)
        anchor_lead = 0
        if end_pt.direction is None:
            # end_direction originates strictly from endpoint + leadout (NOT intermediate stopping anchors)
            self.assign_direction_to_anchor(start_pt, end_pt)
            anchor_lead = spacing

        # Meander length
        length_meander = self._length_segment
        if self.p.snap:
            # handle y distance
            length_meander -= 0  # (end.position - endm.position)[1]

        # Coordinate system (example: x to the right => sideways up)
        forward, sideways = self.get_unit_vectors(start_pt, end_pt, snap)

        # Calculate lengths and meander number
        dist = end_pt.position - start_pt.position
        if snap:
            length_direct = abs(norm(mao.dot(
                dist, forward)))  # in the vertical direction
            length_sideways = abs(norm(mao.dot(
                dist, sideways)))  # in the orthogonal direction
        else:
            length_direct = norm(dist)
            length_sideways = 0

        # Breakup into sections
        meander_number = np.floor(length_direct / spacing)
        if meander_number < 1:
            self.logger.info(f'Zero meanders for {self.name}')
            return np.empty((0, 2), float)

        # The start and end points can have 4 directions each. Depending on the direction
        # there might be not enough space for all the meanders, thus here we adjust
        # meander_number w.r.t. what the start and end points "directionality" allows
        if mao.round(
                mao.dot(start_pt.direction, sideways) *
                mao.dot(end_pt.direction, sideways)) > 0 and (meander_number %
                                                              2) == 0:
            # even meander_number is no good if roots have same orientation (w.r.t sideway)
            meander_number -= 1
        elif mao.round(
                mao.dot(start_pt.direction, sideways) *
                mao.dot(end_pt.direction, sideways)) < 0 and (meander_number %
                                                              2) == 1:
            # odd meander_number is no good if roots have opposite orientation (w.r.t sideway)
            meander_number -= 1

        # should the first meander go sideways or counter sideways?
        start_meander_direction = mao.dot(start_pt.direction, sideways)
        end_meander_direction = mao.dot(end_pt.direction, sideways)
        if start_meander_direction > 0:  # sideway direction
            first_meander_sideways = True
        elif start_meander_direction < 0:  # opposite to sideway direction
            first_meander_sideways = False
        else:
            if end_meander_direction > 0:  # sideway direction
                first_meander_sideways = ((meander_number % 2) == 1)
            elif end_meander_direction < 0:  # opposite to sideway direction
                first_meander_sideways = ((meander_number % 2) == 0)
            else:
                # either direction is fine, so let's just pick one
                first_meander_sideways = True

        # length to distribute on the meanders (excess w.r.t a straight line between start and end)
        length_excess = (length_meander - length_direct - 2 * abs(asymmetry))
        # how much meander offset from center-line is needed to accommodate the length_excess (perpendicular length)
        length_perp = max(0, length_excess / (meander_number * 2.))

        # USES ROW Vectors
        # const vec. of unit normals
        middle_points = [forward] * int(meander_number + 1)
        # index so to multiply other column - creates a column vector
        scale_bys = spacing * np.arange(int(meander_number + 1))[:, None]
        # multiply each one in a linear chain fashion fwd
        middle_points = scale_bys * middle_points
        '''
        middle_points = array([
            [0. , 0. ],
            [0.2, 0. ],
            [0.4, 0. ],
            [0.6, 0. ],
            [0.8, 0. ],
            [1. , 0. ]])
        '''

        ################################################################
        # Calculation
        # including start and end points - there is no overlap in points
        # root_pts = np.concatenate([middle_points,
        #                            end.position[None, :]],  # convert to row vectors
        #                           axis=0)
        side_shift_vecs = np.array([sideways * length_perp] *
                                   len(middle_points))
        asymmetry_vecs = np.array([sideways * asymmetry] * len(middle_points))
        root_pts = middle_points + asymmetry_vecs
        top_pts = root_pts + side_shift_vecs
        bot_pts = root_pts - side_shift_vecs

        ################################################################
        # Combine points
        # Meanest part of the meander

        # Add 2 for the lead and end points in the cpw from
        # pts will have to store properly alternated top_pts and bot_pts
        # it will also store right-most root_pts (end)
        # 2 points from top_pts and bot_pts will be dropped for a complete meander
        pts = np.zeros((len(top_pts) + len(bot_pts) + 1 - 2, 2))
        # need to add the last root_pts in, because there could be a left-over non-meandered segment
        pts[-1, :] = root_pts[-1, :]
        idx_side1_meander, odd = self.get_index_for_side1_meander(
            len(root_pts))
        idx_side2_meander = 2 + idx_side1_meander[:None if odd else -2]
        if first_meander_sideways:
            pts[idx_side1_meander, :] = top_pts[:-1 if odd else None]
            pts[idx_side2_meander, :] = bot_pts[1:None if odd else -1]
        else:
            pts[idx_side1_meander, :] = bot_pts[:-1 if odd else None]
            pts[idx_side2_meander, :] = top_pts[1:None if odd else -1]

        pts += start_pt.position  # move to start position

        if snap:
            if ((mao.dot(start_pt.direction, end_pt.direction) < 0)
                    and (mao.dot(forward, start_pt.direction) <= 0)):
                # pins are pointing opposite directions and diverging
                # the last root_pts need to be sideways aligned with the end.position point
                # and forward aligned with the previous meander point
                pts[-1, abs(forward[0])] = pts[-2, abs(forward[0])]
                pts[-1,
                    abs(forward[0]) - 1] = end_pt.position[abs(forward[0]) - 1]
            else:
                # the last root_pts need to be forward aligned with the end.position point
                pts[-1, abs(forward[0])] = end_pt.position[abs(forward[0])]
                # and if the last root_pts ends outside the CPW amplitude on the side where the last meander is
                # then the last meander needs to be locked on it as well
                if (self.issideways(pts[-1], pts[-3], pts[-2])
                        and self.issideways(pts[-2], root_pts[0]+start_pt.position, root_pts[-1]+start_pt.position))\
                        or (not self.issideways(pts[-1], pts[-3], pts[-2])
                            and not self.issideways(pts[-2], root_pts[0]+start_pt.position,
                                                    root_pts[-1]+start_pt.position)):
                    pts[-2, abs(forward[0])] = end_pt.position[abs(forward[0])]
                    pts[-3, abs(forward[0])] = end_pt.position[abs(forward[0])]
        if abs(asymmetry) > abs(length_perp):
            if not ((mao.dot(start_pt.direction, end_pt.direction) < 0) and
                    (mao.dot(forward, start_pt.direction) <= 0)):
                # pins are "not" pointing opposite directions and diverging
                if start_meander_direction * asymmetry < 0:  # sideway direction
                    pts[0,
                        abs(forward[0])] = start_pt.position[abs(forward[0])]
                    pts[1,
                        abs(forward[0])] = start_pt.position[abs(forward[0])]
                if end_meander_direction * asymmetry < 0:  # opposite sideway direction
                    pts[-2, abs(forward[0])] = end_pt.position[abs(forward[0])]
                    pts[-3, abs(forward[0])] = end_pt.position[abs(forward[0])]

        # Adjust the meander to eliminate the terminating jog (dogleg)
        if prevent_short_edges:
            x2fillet = 2 * self.p.fillet
            # adjust the tail first
            # the meander algorithm adds a final point in line with the tail, to cope with left-over
            # this extra point needs to be moved or not, depending on the tail tip direction
            if abs(mao.dot(end_pt.direction, sideways)) > 0:
                skippoint = 0
            else:
                skippoint = 1
            if 0 < abs(mao.round(end_pt.position[0] - pts[-1, 0])) < x2fillet:
                pts[-1 - skippoint,
                    0 - skippoint] = end_pt.position[0 - skippoint]
                pts[-2 - skippoint,
                    0 - skippoint] = end_pt.position[0 - skippoint]
            if 0 < abs(mao.round(end_pt.position[1] - pts[-1, 1])) < x2fillet:
                pts[-1 - skippoint,
                    1 - skippoint] = end_pt.position[1 - skippoint]
                pts[-2 - skippoint,
                    1 - skippoint] = end_pt.position[1 - skippoint]
            # repeat for the start. here we do not have the extra point
            if 0 < abs(mao.round(start_pt.position[0] - pts[0, 0])) < x2fillet:
                pts[0, 0] = start_pt.position[0]
                pts[1, 0] = start_pt.position[0]
            if 0 < abs(mao.round(start_pt.position[1] - pts[0, 1])) < x2fillet:
                pts[0, 1] = start_pt.position[1]
                pts[1, 1] = start_pt.position[1]

        return pts
Example #3
0
 def test_toolbox_metal_round(self):
     """Test functionality of round in toolbox_metal.py."""
     math_and_overrides.set_decimal_precision(2)
     self.assertEqual(math_and_overrides.round(12.349), 12.35)