def __init__(self, origin, data, box_size, version=None, error_correction=ERROR_CORRECT_M, border=0, alignment='left-bottom'): self._qr_code = qrcode.QRCode(image_factory=ShapelyImageFactory, error_correction=error_correction, border=border, box_size=1, version=version) self._qr_code.add_data(data) self._alignment = Alignment(alignment) self.box_size = box_size self.origin = origin
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
class QRCode(object): """ Quick Response (QR) code part. This part is a simple wrapper around the qrcode library using the ShapelyImageFactory. If you need more flexibility this class might be extended or you can use the shapely output factory ShapelyImageFactory and qrcode directly. :param origin: Lower left corner of the QR code. :param data: Data which is to be encoded into the QR code. :param box_size: Size of each box. :param version: QR code version. :param error_correction: Level of error correction. :param border: Size of the surrounding free border. """ # noinspection PyUnresolvedReferences from qrcode import ERROR_CORRECT_L, ERROR_CORRECT_H, ERROR_CORRECT_M, ERROR_CORRECT_Q def __init__(self, origin, data, box_size, version=None, error_correction=ERROR_CORRECT_M, border=0, alignment='left-bottom'): self._qr_code = qrcode.QRCode(image_factory=ShapelyImageFactory, error_correction=error_correction, border=border, box_size=1, version=version) self._qr_code.add_data(data) self._alignment = Alignment(alignment) self.box_size = box_size self.origin = origin def get_shapely_object(self): self._qr_code.make(fit=True) shapely_code = self._qr_code.make_image(origin=self.origin, scale_factor=self.box_size) offset = self._alignment.calculate_offset(((0, 0), shapely_code.size)) return shapely.affinity.translate(shapely_code.get_shapely_object(), *offset)
def add_to_row(self, cell=None, bbox=None, alignment='left-bottom', realign=True, unique_id=None, allow_region_layer=True): """ Add a new cell to the row. The dimensions of the cell will either be determined directly or can be given via *bbox*. They determined bounding box always assumes a start at (0, 0) to preserve write field alignment. If *realign* is activated, the cell will be shifted, so that the it starts in the first write field quadrant. So if your cell contains structures as (-70, 10) and the write field size is 100, the cell will be shifted to (30, 10). The alignment of the cell can also be changed, but note that only 'left-bottom' guarantees write field alignment. When the region layers are used, these will force an alignment again, though. The position of each added cell inside the grid can be traced when passing a *unique_id* while adding the cell. :param cell: Cell to add. :type: gdsCAD.core.Cell :param bbox: Bounding box. If None, it will be determined based on *cell*. :param alignment: Alignment of the cell inside the grid. :param realign: Realign the cell, so that it starts in the first positive write field. :param unique_id: ID to trace where the cell was placed in the final layout. :param allow_region_layer: Allow a region layer around the cell. :type allow_region_layer: bool """ assert self._current_row, 'Start a new row first' if cell is None: self._current_row['items'].append({ 'cell': None, 'bbox': np.zeros([2, 2]), 'offset': np.zeros(2), 'id': unique_id, 'alignment': Alignment(alignment), 'allow_region_layer': False }) return cell_bbox = np.reshape(cell.bounds, (2, 2)) if bbox is None else bbox if realign: offset = [ self._remove_multiple_y_align(cell_bbox[0][i]) - cell_bbox[0][i] for i in (0, 1) ] cell_bbox += offset else: offset = (0, 0) if bbox is None: cell_bbox[0, :] = 0 bbox = cell_bbox self._current_row['items'].append({ 'cell': cell, 'bbox': bbox, 'offset': offset, 'id': unique_id, 'alignment': Alignment(alignment), 'allow_region_layer': allow_region_layer })
def test_calculate_offset(self): alignment = Alignment('left-bottom') self.assertTupleEqual( tuple(alignment.calculate_offset(((1, 2), (11, 12)))), (-1, -2)) alignment = Alignment('center-bottom') self.assertTupleEqual( tuple(alignment.calculate_offset(((1, 2), (11, 12)))), (-6, -2)) alignment = Alignment('right-center') self.assertTupleEqual( tuple(alignment.calculate_offset(((1, 2), (11, 12)))), (-11, -7)) alignment = Alignment('right-top') self.assertTupleEqual( tuple(alignment.calculate_offset(((1, 2), (11, 12)))), (-11, -12))
class Text: 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 def _invalidate(self): self._bbox = None self._shapely_object = None @property def origin(self): return self._origin # noinspection PyAttributeOutsideInit @origin.setter def origin(self, origin): self._invalidate() self._origin = np.array(origin) assert self._origin.shape == (2,), 'origin not valid' @property def height(self): return self._height # noinspection PyAttributeOutsideInit @height.setter def height(self, height): self._invalidate() assert height > 0, 'Height must be positive' self._height = height @property def font(self): return self._font # noinspection PyAttributeOutsideInit @font.setter def font(self, font): self._invalidate() assert font in _fonts.FONTS, 'Font is "%s" unknown, must be one of %s' % (font, _fonts.FONTS.keys()) self._font = font @property def alignment(self): return self._alignment.alignment @alignment.setter def alignment(self, alignment): self._invalidate() self._alignment = alignment @property def bounding_box(self): if self._bbox is None: self.get_shapely_object() return self._bbox def get_shapely_object(self): if not self.text: self._bbox = None return shapely.geometry.Polygon() if self._shapely_object: return self._shapely_object # 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.unary_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) 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)) self._bbox = np.array(final_text.bounds).reshape(2, 2) self._shapely_object = final_text return final_text