lib.printing
1import drawBot 2from typing import overload 3 4from lib import layout, easing 5from datatypes import DLineStyle 6 7 8def drawEdges( 9 frame: tuple, 10 edges: list[layout.BoxEdge], 11 lineStyle: DLineStyle = DLineStyle(), 12 gapXY: float | tuple[float, float] = (0, 0), 13): 14 """Draw selected frame edges with optional corner gaps. 15 16 Args: 17 frame: Rectangle as (x, y, w, h). 18 edges: List of edges to draw ("top", "right", "bottom", "left"). 19 lineStyle: Line style to apply before drawing. 20 gapXY: (gapX, gapY) in mm. The value is split equally per side, so 21 gapX=2 shortens horizontal edges by 1 mm on both left/right ends, 22 and gapY=2 shortens vertical edges by 1 mm on both bottom/top ends. 23 """ 24 frameX, frameY, frameW, frameH = frame 25 gapX, gapY = layout.unpackTwo(gapXY) 26 27 gapHalfX = layout.mm(gapX) / 2 28 gapHalfY = layout.mm(gapY) / 2 29 30 topY = frameY + frameH 31 rightX = frameX + frameW 32 33 edgePositions = { 34 "top": ((frameX + gapHalfX, topY), (rightX - gapHalfX, topY)), 35 "bottom": ((frameX + gapHalfX, frameY), (rightX - gapHalfX, frameY)), 36 "left": ((frameX, frameY + gapHalfY), (frameX, topY - gapHalfY)), 37 "right": ((rightX, frameY + gapHalfY), (rightX, topY - gapHalfY)), 38 } 39 40 with drawBot.savedState(): 41 lineStyle.apply() 42 for edge in edges: 43 if edge in edgePositions: 44 drawBot.line(*edgePositions[edge]) 45 46 47def drawPerforationCircles( 48 container: tuple, 49 radius: float = 2, 50 spacing: float = 80, 51 count: int = 2, 52 align: tuple[layout.AlignX, layout.AlignY] = ("left", "center"), 53) -> tuple: 54 """ 55 Draw a series of perforation circles within a container. 56 57 Args: 58 container: The container coordinates (x, y, w, h). 59 radius: The diameter of each circle in mm. Defaults to 2. 60 spacing: The spacing between circles (center to center) in mm. Defaults to 80. 61 count: The number of circles to draw. Defaults to 2. 62 align: The alignment of the series within the container. Defaults to ("left", "center"). 63 64 Returns: 65 The coordinates of the area occupied by the perforation circles. 66 """ 67 radius = layout.mm(radius) 68 spacing = layout.mm(spacing) 69 spacing -= radius # adjust spacing to be center to center 70 71 # Infer direction: X-anchored (left/right) → vertical stack; X-centred → horizontal spread. 72 isVertical = align[0] != "center" 73 74 span = radius * count + spacing * (count - 1) 75 perfDimensions = (radius, span) if isVertical else (span, radius) 76 perfCoords = layout.align(container, perfDimensions, align) 77 x, y, w, h = perfCoords 78 79 startPos = (x, y) 80 endPos = (x, y + h - radius) if isVertical else (x + w - radius, y) 81 positions = easing.interpolate(start=startPos, stop=endPos, steps=count) 82 83 for [posX, posY] in positions: 84 drawBot.oval(posX, posY, radius, radius) 85 86 return perfCoords 87 88 89@overload 90def drawGraphPaper( 91 container: layout.Coordinates, 92 *, 93 rows: int, 94 cols: int, 95 lineStyle: DLineStyle = DLineStyle(), 96 drawFrame: bool = True, 97) -> None: ... 98 99 100@overload 101def drawGraphPaper( 102 container: layout.Coordinates, 103 *, 104 cellSize: float, 105 lineStyle: DLineStyle = DLineStyle(), 106 drawFrame: bool = True, 107) -> None: ... 108 109 110def drawGraphPaper( 111 container: layout.Coordinates, 112 *, 113 rows: int | None = None, 114 cols: int | None = None, 115 cellSize: float | None = None, 116 lineStyle: DLineStyle = DLineStyle(), 117 drawFrame: bool = True, 118): 119 """Draw a graph paper grid within a container. Accepts either (rows and cols) or cellSize to determine the grid. 120 121 Args: 122 container: Rectangle as (x, y, w, h). 123 rows: Number of horizontal rows. 124 cols: Number of vertical columns. 125 cellSize: Target size for each cell in container units. 126 lineStyle: Line style to apply before drawing. 127 drawFrame: Whether to draw the outer frame of the container. Defaults to True. 128 """ 129 hasGridShape = rows is not None or cols is not None 130 hasSizeShape = cellSize is not None 131 132 if hasGridShape and hasSizeShape: 133 raise TypeError("Use either (rows and cols) or (cellSize), not both") 134 135 if hasGridShape: 136 if rows is None or cols is None: 137 raise TypeError("When using grid shape, both rows and cols are required") 138 if rows <= 0 or cols <= 0: 139 raise ValueError("rows and cols must be greater than 0") 140 elif hasSizeShape: 141 if cellSize <= 0: 142 raise ValueError("cellSize must be greater than 0") 143 else: 144 raise TypeError("Use either (rows and cols) or (cellSize)") 145 146 x, y, w, h = container 147 if cellSize is not None: 148 # Derive count from target cell size. 149 rows = max(1, round(h / cellSize)) 150 cols = max(1, round(w / cellSize)) 151 152 rowHeight = h / rows 153 colWidth = w / cols 154 155 with drawBot.savedState(): 156 lineStyle.apply() 157 if drawFrame: 158 drawBot.rect(x, y, w, h) 159 160 for i in range(1, rows): 161 drawBot.line((x, y + i * rowHeight), (x + w, y + i * rowHeight)) 162 for j in range(1, cols): 163 drawBot.line((x + j * colWidth, y), (x + j * colWidth, y + h))
def
drawEdges( frame: tuple, edges: list[typing.Literal['top', 'right', 'bottom', 'left']], lineStyle: datatypes.data_linestyle.DLineStyle = DLineStyle(strokeWidth=1, strokeColor=(0,), lineDash=None, lineCap='butt', clearFill=True), gapXY: float | tuple[float, float] = (0, 0)):
9def drawEdges( 10 frame: tuple, 11 edges: list[layout.BoxEdge], 12 lineStyle: DLineStyle = DLineStyle(), 13 gapXY: float | tuple[float, float] = (0, 0), 14): 15 """Draw selected frame edges with optional corner gaps. 16 17 Args: 18 frame: Rectangle as (x, y, w, h). 19 edges: List of edges to draw ("top", "right", "bottom", "left"). 20 lineStyle: Line style to apply before drawing. 21 gapXY: (gapX, gapY) in mm. The value is split equally per side, so 22 gapX=2 shortens horizontal edges by 1 mm on both left/right ends, 23 and gapY=2 shortens vertical edges by 1 mm on both bottom/top ends. 24 """ 25 frameX, frameY, frameW, frameH = frame 26 gapX, gapY = layout.unpackTwo(gapXY) 27 28 gapHalfX = layout.mm(gapX) / 2 29 gapHalfY = layout.mm(gapY) / 2 30 31 topY = frameY + frameH 32 rightX = frameX + frameW 33 34 edgePositions = { 35 "top": ((frameX + gapHalfX, topY), (rightX - gapHalfX, topY)), 36 "bottom": ((frameX + gapHalfX, frameY), (rightX - gapHalfX, frameY)), 37 "left": ((frameX, frameY + gapHalfY), (frameX, topY - gapHalfY)), 38 "right": ((rightX, frameY + gapHalfY), (rightX, topY - gapHalfY)), 39 } 40 41 with drawBot.savedState(): 42 lineStyle.apply() 43 for edge in edges: 44 if edge in edgePositions: 45 drawBot.line(*edgePositions[edge])
Draw selected frame edges with optional corner gaps.
Arguments:
- frame: Rectangle as (x, y, w, h).
- edges: List of edges to draw ("top", "right", "bottom", "left").
- lineStyle: Line style to apply before drawing.
- gapXY: (gapX, gapY) in mm. The value is split equally per side, so gapX=2 shortens horizontal edges by 1 mm on both left/right ends, and gapY=2 shortens vertical edges by 1 mm on both bottom/top ends.
def
drawPerforationCircles( container: tuple, radius: float = 2, spacing: float = 80, count: int = 2, align: tuple[typing.Literal['left', 'center', 'right', 'stretch', None], typing.Literal['top', 'center', 'bottom', 'stretch', None]] = ('left', 'center')) -> tuple:
48def drawPerforationCircles( 49 container: tuple, 50 radius: float = 2, 51 spacing: float = 80, 52 count: int = 2, 53 align: tuple[layout.AlignX, layout.AlignY] = ("left", "center"), 54) -> tuple: 55 """ 56 Draw a series of perforation circles within a container. 57 58 Args: 59 container: The container coordinates (x, y, w, h). 60 radius: The diameter of each circle in mm. Defaults to 2. 61 spacing: The spacing between circles (center to center) in mm. Defaults to 80. 62 count: The number of circles to draw. Defaults to 2. 63 align: The alignment of the series within the container. Defaults to ("left", "center"). 64 65 Returns: 66 The coordinates of the area occupied by the perforation circles. 67 """ 68 radius = layout.mm(radius) 69 spacing = layout.mm(spacing) 70 spacing -= radius # adjust spacing to be center to center 71 72 # Infer direction: X-anchored (left/right) → vertical stack; X-centred → horizontal spread. 73 isVertical = align[0] != "center" 74 75 span = radius * count + spacing * (count - 1) 76 perfDimensions = (radius, span) if isVertical else (span, radius) 77 perfCoords = layout.align(container, perfDimensions, align) 78 x, y, w, h = perfCoords 79 80 startPos = (x, y) 81 endPos = (x, y + h - radius) if isVertical else (x + w - radius, y) 82 positions = easing.interpolate(start=startPos, stop=endPos, steps=count) 83 84 for [posX, posY] in positions: 85 drawBot.oval(posX, posY, radius, radius) 86 87 return perfCoords
Draw a series of perforation circles within a container.
Arguments:
- container: The container coordinates (x, y, w, h).
- radius: The diameter of each circle in mm. Defaults to 2.
- spacing: The spacing between circles (center to center) in mm. Defaults to 80.
- count: The number of circles to draw. Defaults to 2.
- align: The alignment of the series within the container. Defaults to ("left", "center").
Returns:
The coordinates of the area occupied by the perforation circles.
def
drawGraphPaper( container: tuple[float, float, float, float], *, rows: int | None = None, cols: int | None = None, cellSize: float | None = None, lineStyle: datatypes.data_linestyle.DLineStyle = DLineStyle(strokeWidth=1, strokeColor=(0,), lineDash=None, lineCap='butt', clearFill=True), drawFrame: bool = True):
111def drawGraphPaper( 112 container: layout.Coordinates, 113 *, 114 rows: int | None = None, 115 cols: int | None = None, 116 cellSize: float | None = None, 117 lineStyle: DLineStyle = DLineStyle(), 118 drawFrame: bool = True, 119): 120 """Draw a graph paper grid within a container. Accepts either (rows and cols) or cellSize to determine the grid. 121 122 Args: 123 container: Rectangle as (x, y, w, h). 124 rows: Number of horizontal rows. 125 cols: Number of vertical columns. 126 cellSize: Target size for each cell in container units. 127 lineStyle: Line style to apply before drawing. 128 drawFrame: Whether to draw the outer frame of the container. Defaults to True. 129 """ 130 hasGridShape = rows is not None or cols is not None 131 hasSizeShape = cellSize is not None 132 133 if hasGridShape and hasSizeShape: 134 raise TypeError("Use either (rows and cols) or (cellSize), not both") 135 136 if hasGridShape: 137 if rows is None or cols is None: 138 raise TypeError("When using grid shape, both rows and cols are required") 139 if rows <= 0 or cols <= 0: 140 raise ValueError("rows and cols must be greater than 0") 141 elif hasSizeShape: 142 if cellSize <= 0: 143 raise ValueError("cellSize must be greater than 0") 144 else: 145 raise TypeError("Use either (rows and cols) or (cellSize)") 146 147 x, y, w, h = container 148 if cellSize is not None: 149 # Derive count from target cell size. 150 rows = max(1, round(h / cellSize)) 151 cols = max(1, round(w / cellSize)) 152 153 rowHeight = h / rows 154 colWidth = w / cols 155 156 with drawBot.savedState(): 157 lineStyle.apply() 158 if drawFrame: 159 drawBot.rect(x, y, w, h) 160 161 for i in range(1, rows): 162 drawBot.line((x, y + i * rowHeight), (x + w, y + i * rowHeight)) 163 for j in range(1, cols): 164 drawBot.line((x + j * colWidth, y), (x + j * colWidth, y + h))
Draw a graph paper grid within a container. Accepts either (rows and cols) or cellSize to determine the grid.
Arguments:
- container: Rectangle as (x, y, w, h).
- rows: Number of horizontal rows.
- cols: Number of vertical columns.
- cellSize: Target size for each cell in container units.
- lineStyle: Line style to apply before drawing.
- drawFrame: Whether to draw the outer frame of the container. Defaults to True.