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)
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
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)