def _get_cgal_elem(self): """ Get a cgal geometry element representing this object, if any Width is only considered for Wire """ from CGAL.CGAL_Kernel import \ Segment_2,\ Polygon_2,\ Point_2,\ Bbox_2,\ Iso_rectangle_2,\ do_intersect if isinstance(self, Swoop.Rectangle): rect = Rectangle(self.get_point(0), self.get_point(1), check=False) verts = list(rect.vertices_ccw()) if self.get_rot() is not None: angle_obj = angle_match(self.get_rot()) angle = math.radians(angle_obj['angle']) if angle_obj['mirrored']: angle *= -1 origin = rect.center() rmat = Rectangle.rotation_matrix(angle) for i, v in enumerate(verts): verts[i] = np.dot(v - origin, rmat) + origin return Polygon_2(list(map(np2cgal, verts))) elif isinstance(self, Swoop.Wire): p1 = self.get_point(0) p2 = self.get_point(1) if self.get_width() is None or self.get_width() == 0: return Segment_2(np2cgal(p1), np2cgal(p2)) else: # Wire has width # This is important to consider because wires can represent traces # When doing a query, it is important we can pick up the trace if p1[0] > p2[0]: p1, p2 = p2, p1 # p1 is the left endpoint elif p1[0] == p2[0] and p1[1] > p2[1]: p1, p2 = p2, p1 # or the bottom vec = (p2 - p1) vec /= np.linalg.norm(vec) # Normalize radius = np.array(vec[1], -vec[0]) radius *= self.get_width( ) / 2.0 # "Radius" of the wire, perpendicular to it vertices = [] # Go around the vertices of the wire in CCW order # This should give you a rotated rectangle vertices.append(np2cgal(p1 + radius)) vertices.append(np2cgal(p2 + radius)) vertices.append(np2cgal(p2 - radius)) vertices.append(np2cgal(p1 - radius)) return Polygon_2(vertices) elif isinstance(self, Swoop.Polygon): return Polygon_2( [np2cgal(v.get_point()) for v in self.get_vertices()]) else: return None
def _get_cgal_elem(self): """ Get a cgal geometry element representing this object, if any Width is only considered for Wire """ from CGAL.CGAL_Kernel import \ Segment_2,\ Polygon_2,\ Point_2,\ Bbox_2,\ Iso_rectangle_2,\ do_intersect if isinstance(self, Swoop.Rectangle): rect = Rectangle(self.get_point(0), self.get_point(1), check=False) verts = list(rect.vertices_ccw()) if self.get_rot() is not None: angle_obj = angle_match(self.get_rot()) angle = math.radians(angle_obj['angle']) if angle_obj['mirrored']: angle *= -1 origin = rect.center() rmat = Rectangle.rotation_matrix(angle) for i,v in enumerate(verts): verts[i] = np.dot(v - origin, rmat) + origin return Polygon_2(map(np2cgal, verts)) elif isinstance(self, Swoop.Wire): p1 = self.get_point(0) p2 = self.get_point(1) if self.get_width() is None or self.get_width() == 0: return Segment_2(np2cgal(p1), np2cgal(p2)) else: # Wire has width # This is important to consider because wires can represent traces # When doing a query, it is important we can pick up the trace if p1[0] > p2[0]: p1,p2 = p2,p1 # p1 is the left endpoint elif p1[0]==p2[0] and p1[1] > p2[1]: p1,p2 = p2,p1 # or the bottom vec = (p2 - p1) vec /= np.linalg.norm(vec) # Normalize radius = np.array(vec[1], -vec[0]) radius *= self.get_width()/2.0 # "Radius" of the wire, perpendicular to it vertices = [] # Go around the vertices of the wire in CCW order # This should give you a rotated rectangle vertices.append(np2cgal( p1 + radius )) vertices.append(np2cgal( p2 + radius )) vertices.append(np2cgal( p2 - radius )) vertices.append(np2cgal( p1 - radius )) return Polygon_2(vertices) elif isinstance(self, Swoop.Polygon): return Polygon_2([np2cgal(v.get_point()) for v in self.get_vertices()]) else: return None
def rotate(self, degrees): """ Rotate everything around the origin (0,0) Positive is counter-clockwise """ rot_mtx = Rectangle.rotation_matrix(math.radians(degrees)) if isinstance(self, Swoop.Rectangle): # Special case: you can't just rotate each vertex :( origin = (self.get_point(0) + self.get_point(1)) / 2 new_origin = rot_mtx.dot(origin) self.move(new_origin - origin) else: for i in xrange(self.num_points()): self.set_point( rot_mtx.dot(self.get_point(i)), i) if hasattr(self, "get_rot"): rot = self.get_rot() or "R0" angle = angle_match(rot) angle['angle'] = (angle['angle'] + degrees) % 360 self.set_rot(angle_match_to_str(angle))
def rotate(self, degrees): """ Rotate everything around the origin (0,0) Positive is counter-clockwise """ rot_mtx = Rectangle.rotation_matrix(math.radians(degrees)) if isinstance(self, Swoop.Rectangle): # Special case: you can't just rotate each vertex :( origin = (self.get_point(0) + self.get_point(1)) / 2 new_origin = rot_mtx.dot(origin) self.move(new_origin - origin) else: for i in range(self.num_points()): self.set_point(rot_mtx.dot(self.get_point(i)), i) if hasattr(self, "get_rot"): rot = self.get_rot() or "R0" angle = angle_match(rot) angle['angle'] = (angle['angle'] + degrees) % 360 self.set_rot(angle_match_to_str(angle))
def __init__(self, filename): """ Completely overrides the parent constructor Need a few things to init from: -All <elements> (parts on the board) -Random other stuff from <signals> or <plain> :param filename: .brd file to create self from :return: """ from CGAL.CGAL_Kernel import \ Segment_2,\ Polygon_2,\ Point_2,\ Bbox_2,\ Iso_rectangle_2,\ do_intersect # From needs this in order to work # Call from_file in Swoop and get a Swoop.BoardFile super(BoardFile, self).__init__(WithMixin.from_file(filename)) # Tuples of (geometry element, swoop element) # Everything that you can see on the board self._elements = [] #Add all the stuff in <signals> #First wires for wire in self.get_signals().get_wires(): self._elements.append(GeoElem(wire._get_cgal_elem(), wire)) #Then vias for via in self.get_signals().get_vias(): #Circular kernel does not have python bindings yet...so just use bounding box rect = via.get_bounding_box() self._elements.append(GeoElem(Iso_rectangle_2(*rect.bounds_tuple), via)) #Stuff in <plain> for elem in self.get_plain_elements(): cgal_elem = elem._get_cgal_elem() if cgal_elem is not None: self._elements.append(GeoElem(cgal_elem, elem) ) # The actual elements in <elements> # Each element will be represented as a rotated rectangle # The element's children, after translation and rotating, will be in the package_moved attribute for elem in self.get_elements(): package = self.get_libraries().get_package(elem.get_package()) rect = package.get_children().get_bounding_box().reduce(Rectangle.union) if elem.get_rot() is not None: angle = angle_match(elem.get_rot()) else: angle = {'angle': 0, 'mirrored': False} rotmatx = Rectangle.rotation_matrix(math.radians(angle['angle'])) origin = elem.get_point() # Convert rotated rectangle to polygon poly = [] for v in rect.vertices_ccw(): vr = rotmatx.dot(v) # rotate before mirroring if angle['mirrored']: vr[0] *= -1 poly.append(np2cgal(vr + origin)) # Create a copy of the package with all the children rotated/moved elem.package_moved = package[0].clone() for package_elem in elem.package_moved.get_children(): package_elem.rotate(angle['angle']) if angle['mirrored']: package_elem.mirror() package_elem.move(origin) geom = GeoElem(Polygon_2(poly), elem) self._elements.append(geom) elem._extension_geo_elem = geom #Finally, the board outline outline = self.get_plain_elements().\ filtered_by(lambda e: hasattr(e, "get_layer")).\ with_layer("Dimension") if len(outline) > 0: self.bbox = outline.get_bounding_box().reduce(Rectangle.union)
def get_bounding_box(self, layer=None, type=None): """ Get the minimum bounding box enclosing this list of primitive elements More accurate than self._get_cgal_elem().bbox(), because it accounts for segment width :param layer: Swoop layer to filter on """ def max_min(vertex_list, width=None): max = np.maximum.reduce(vertex_list) min = np.minimum.reduce(vertex_list) if width is not None: radius = np.array([width, width]) / 2.0 max += radius min -= radius return max,min if isinstance(self, Swoop.Polygon): vertices = [v.get_point() for v in self.get_vertices()] return Rectangle(*max_min(vertices, self.get_width())) elif isinstance(self, Swoop.Wire): if self.get_curve() is not None: theta = math.radians(self.get_curve()) # angle swept by arc theta = math.fmod(theta, 2*math.pi) p1 = self.get_point(0) # 2 points on the circle p2 = self.get_point(1) return arc_bounding_box(p1, p2, theta).pad(self.get_width() / 2.0) else: vertices = [self.get_point(0), self.get_point(1)] return Rectangle(*max_min(vertices, self.get_width()), check=False) elif isinstance(self, Swoop.Rectangle): #get_cgal_elem already handles rotation bbox = self._get_cgal_elem().bbox() return Rectangle.from_cgal_bbox(bbox, check=False) elif isinstance(self, Swoop.Via) or isinstance(self, Swoop.Pad): #These assume default settings #Unfortunately, restring can change the sizes of things after import center = self.get_point() if self.get_diameter() is None: # Gotta figure it out from drill if self.get_drill() < 1.0: diameter = self.get_drill() + 0.5 else: diameter = self.get_drill()*1.5 else: diameter = self.get_diameter() radius = diameter/2.0 * np.ones(2) angle = 0.0 if isinstance(self, Swoop.Pad) and self.get_rot() is not None: angle_m = angle_match(self.get_rot()) angle = angle_m['angle'] if angle_m['mirrored']: angle = 180.0 - angle rotate_matrix = Rectangle.rotation_matrix(math.radians(angle)) if self.get_shape() in ['long', 'offset']: # This is basically a square pad with 180º arc endcaps if self.get_shape()=='offset': center[0] += diameter/2.0 # Center is offset to the left, correct it rect = Rectangle(center - radius, center + radius) verts = list(rect.vertices()) verts = map(lambda v: rotate_matrix.dot(v - center) + center, verts) left_cap = arc_bounding_box(verts[0], verts[3], pi) right_cap = arc_bounding_box(verts[2], verts[1], pi) rect = Rectangle.union(rect, left_cap) rect = Rectangle.union(rect, right_cap) return rect elif self.get_shape()=='octagon': vertex = np.array([diameter/2.0, diameter/2.0 * math.tan(2*pi / 16 )]) vertex = rotate_matrix.dot(vertex) vertices = [] rot = Rectangle.rotation_matrix(2*pi/8) for i in xrange(8): vertices.append(vertex.copy() + center) vertex = rot.dot(vertex) return Rectangle.from_vertices(vertices, check=True) else: rect = Rectangle(center - radius, center + radius) if self.get_shape()=='square': rect.rotate_resize(angle,origin=center) return rect elif isinstance(self, Swoop.Smd): center = self.get_point() radius = np.array([self.get_dx(), self.get_dy()]) / 2.0 if self.get_rot() is not None: angle = angle_match(self.get_rot()) radius = abs(Rectangle.rotation_matrix(math.radians(angle['angle'])).dot(radius)) return Rectangle(center - radius, center + radius) elif isinstance(self, Swoop.Circle): center = self.get_point() radius = np.ones(2) * (self.get_radius() + self.get_width()/2.0) return Rectangle(center - radius, center + radius) elif isinstance(self, Swoop.Hole): center = self.get_point() radius = np.ones(2) * self.get_drill()/2.0 return Rectangle(center - radius, center + radius) elif isinstance(self, Swoop.Element): # Element objects do not have enough information for the bounding box # That gets set in the constructor if hasattr(self,"_extension_geo_elem"): return self._extension_geo_elem.rect else: tform = self.get_transform() bbox = self.find_package().get_bounding_box(layer=layer, type=type) if bbox is None: return None return tform.apply(bbox) elif isinstance(self, Swoop.Package): rect = None for c in self.get_children(): if ((layer is None) or (hasattr(c,"get_layer") and c.get_layer()==layer))\ and ((type is None) or isinstance(c, type)): r = c.get_bounding_box() if r is not None: if rect is None: rect = r rect = Rectangle.union(rect, r) return rect else: return None
def __init__(self, filename): """ Completely overrides the parent constructor Need a few things to init from: -All <elements> (parts on the board) -Random other stuff from <signals> or <plain> :param filename: .brd file to create self from :return: """ from CGAL.CGAL_Kernel import \ Segment_2,\ Polygon_2,\ Point_2,\ Bbox_2,\ Iso_rectangle_2,\ do_intersect # From needs this in order to work # Call from_file in Swoop and get a Swoop.BoardFile super(BoardFile, self).__init__(WithMixin.from_file(filename)) # Tuples of (geometry element, swoop element) # Everything that you can see on the board self._elements = [] #Add all the stuff in <signals> #First wires for wire in self.get_signals().get_wires(): self._elements.append(GeoElem(wire._get_cgal_elem(), wire)) #Then vias for via in self.get_signals().get_vias(): #Circular kernel does not have python bindings yet...so just use bounding box rect = via.get_bounding_box() self._elements.append( GeoElem(Iso_rectangle_2(*rect.bounds_tuple), via)) #Stuff in <plain> for elem in self.get_plain_elements(): cgal_elem = elem._get_cgal_elem() if cgal_elem is not None: self._elements.append(GeoElem(cgal_elem, elem)) # The actual elements in <elements> # Each element will be represented as a rotated rectangle # The element's children, after translation and rotating, will be in the package_moved attribute for elem in self.get_elements(): package = self.get_libraries().get_package(elem.get_package()) rect = package.get_children().get_bounding_box().reduce( Rectangle.union) if elem.get_rot() is not None: angle = angle_match(elem.get_rot()) else: angle = {'angle': 0, 'mirrored': False} rotmatx = Rectangle.rotation_matrix(math.radians(angle['angle'])) origin = elem.get_point() # Convert rotated rectangle to polygon poly = [] for v in rect.vertices_ccw(): vr = rotmatx.dot(v) # rotate before mirroring if angle['mirrored']: vr[0] *= -1 poly.append(np2cgal(vr + origin)) # Create a copy of the package with all the children rotated/moved elem.package_moved = package[0].clone() for package_elem in elem.package_moved.get_children(): package_elem.rotate(angle['angle']) if angle['mirrored']: package_elem.mirror() package_elem.move(origin) geom = GeoElem(Polygon_2(poly), elem) self._elements.append(geom) elem._extension_geo_elem = geom #Finally, the board outline outline = self.get_plain_elements().\ filtered_by(lambda e: hasattr(e, "get_layer")).\ with_layer("Dimension") if len(outline) > 0: self.bbox = outline.get_bounding_box().reduce(Rectangle.union)
def get_bounding_box(self, layer=None, type=None): """ Get the minimum bounding box enclosing this list of primitive elements More accurate than self._get_cgal_elem().bbox(), because it accounts for segment width :param layer: Swoop layer to filter on """ def max_min(vertex_list, width=None): max = np.maximum.reduce(vertex_list) min = np.minimum.reduce(vertex_list) if width is not None: radius = np.array([width, width]) / 2.0 max += radius min -= radius return max, min if isinstance(self, Swoop.Polygon): vertices = [v.get_point() for v in self.get_vertices()] return Rectangle(*max_min(vertices, self.get_width())) elif isinstance(self, Swoop.Wire): if self.get_curve() is not None: theta = math.radians(self.get_curve()) # angle swept by arc theta = math.fmod(theta, 2 * math.pi) p1 = self.get_point(0) # 2 points on the circle p2 = self.get_point(1) return arc_bounding_box(p1, p2, theta).pad(self.get_width() / 2.0) else: vertices = [self.get_point(0), self.get_point(1)] return Rectangle(*max_min(vertices, self.get_width()), check=False) elif isinstance(self, Swoop.Rectangle): #get_cgal_elem already handles rotation bbox = self._get_cgal_elem().bbox() return Rectangle.from_cgal_bbox(bbox, check=False) elif isinstance(self, Swoop.Via) or isinstance(self, Swoop.Pad): #These assume default settings #Unfortunately, restring can change the sizes of things after import center = self.get_point() if self.get_diameter() is None: # Gotta figure it out from drill if self.get_drill() < 1.0: diameter = self.get_drill() + 0.5 else: diameter = self.get_drill() * 1.5 else: diameter = self.get_diameter() radius = diameter / 2.0 * np.ones(2) angle = 0.0 if isinstance(self, Swoop.Pad) and self.get_rot() is not None: angle_m = angle_match(self.get_rot()) angle = angle_m['angle'] if angle_m['mirrored']: angle = 180.0 - angle rotate_matrix = Rectangle.rotation_matrix(math.radians(angle)) if self.get_shape() in ['long', 'offset']: # This is basically a square pad with 180º arc endcaps if self.get_shape() == 'offset': center[ 0] += diameter / 2.0 # Center is offset to the left, correct it rect = Rectangle(center - radius, center + radius) verts = list(rect.vertices()) verts = [rotate_matrix.dot(v - center) + center for v in verts] left_cap = arc_bounding_box(verts[0], verts[3], pi) right_cap = arc_bounding_box(verts[2], verts[1], pi) rect = Rectangle.union(rect, left_cap) rect = Rectangle.union(rect, right_cap) return rect elif self.get_shape() == 'octagon': vertex = np.array( [diameter / 2.0, diameter / 2.0 * math.tan(2 * pi / 16)]) vertex = rotate_matrix.dot(vertex) vertices = [] rot = Rectangle.rotation_matrix(2 * pi / 8) for i in range(8): vertices.append(vertex.copy() + center) vertex = rot.dot(vertex) return Rectangle.from_vertices(vertices, check=True) else: rect = Rectangle(center - radius, center + radius) if self.get_shape() == 'square': rect.rotate_resize(angle, origin=center) return rect elif isinstance(self, Swoop.Smd): center = self.get_point() radius = np.array([self.get_dx(), self.get_dy()]) / 2.0 if self.get_rot() is not None: angle = angle_match(self.get_rot()) radius = abs( Rectangle.rotation_matrix(math.radians( angle['angle'])).dot(radius)) return Rectangle(center - radius, center + radius) elif isinstance(self, Swoop.Circle): center = self.get_point() radius = np.ones(2) * (self.get_radius() + self.get_width() / 2.0) return Rectangle(center - radius, center + radius) elif isinstance(self, Swoop.Hole): center = self.get_point() radius = np.ones(2) * self.get_drill() / 2.0 return Rectangle(center - radius, center + radius) elif isinstance(self, Swoop.Element): # Element objects do not have enough information for the bounding box # That gets set in the constructor if hasattr(self, "_extension_geo_elem"): return self._extension_geo_elem.rect else: tform = self.get_transform() bbox = self.find_package().get_bounding_box(layer=layer, type=type) if bbox is None: return None return tform.apply(bbox) elif isinstance(self, Swoop.Package): rect = None for c in self.get_children(): if ((layer is None) or (hasattr(c,"get_layer") and c.get_layer()==layer))\ and ((type is None) or isinstance(c, type)): r = c.get_bounding_box() if r is not None: if rect is None: rect = r rect = Rectangle.union(rect, r) return rect else: return None