|  |  |  | @@ -6,6 +6,7 @@ from math import ceil, floor | 
		
	
		
			
				|  |  |  |  | from PIL import Image | 
		
	
		
			
				|  |  |  |  | import textwrap | 
		
	
		
			
				|  |  |  |  | import argparse | 
		
	
		
			
				|  |  |  |  | import sys | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # make substitutions for characters not in CP437 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
	
		
			
				
					|  |  |  | @@ -52,25 +53,25 @@ def esc(s): | 
		
	
		
			
				|  |  |  |  | # render a line of text at coordinates | 
		
	
		
			
				|  |  |  |  | # return coordinates of next line | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def textline(s, loc, fontnum): | 
		
	
		
			
				|  |  |  |  | def textline(z, res, s, loc, fontnum): | 
		
	
		
			
				|  |  |  |  |     z.output(f"A{loc[0]},{loc[1]},0,{fontnum},1,1,N,\"{esc(filter(subst(s), "cp437"))}\"\n") | 
		
	
		
			
				|  |  |  |  |     return (loc[0], loc[1]+font_metrics[fontnum][1]) | 
		
	
		
			
				|  |  |  |  |     return (loc[0], loc[1]+font_metrics(res)[fontnum][1]) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # wrap text in a bounding box at coordinates | 
		
	
		
			
				|  |  |  |  | # return coordinates of next line and any unused text | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def textbox(s, loc, bbox, fontnum): | 
		
	
		
			
				|  |  |  |  |     wrapped=textwrap.wrap(filter(subst(s), "cp437"), width=floor(bbox[0]/font_metrics[fontnum][0])) | 
		
	
		
			
				|  |  |  |  | def textbox(z, res, s, loc, bbox, fontnum): | 
		
	
		
			
				|  |  |  |  |     wrapped=textwrap.wrap(filter(subst(s), "cp437"), width=floor(bbox[0]/font_metrics(res)[fontnum][0])) | 
		
	
		
			
				|  |  |  |  |     line=0 | 
		
	
		
			
				|  |  |  |  |     while line*font_metrics[fontnum][1]<bbox[1] and line<len(wrapped): | 
		
	
		
			
				|  |  |  |  |         loc=textline(wrapped[line], loc, fontnum) | 
		
	
		
			
				|  |  |  |  |     while line*font_metrics(res)[fontnum][1]<bbox[1] and line<len(wrapped): | 
		
	
		
			
				|  |  |  |  |         loc=textline(z, res, wrapped[line], loc, fontnum) | 
		
	
		
			
				|  |  |  |  |         line=line+1 | 
		
	
		
			
				|  |  |  |  |     return loc, " ".join(wrapped[line:]) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # render a QR code at coordinates | 
		
	
		
			
				|  |  |  |  | # return size (single value, since QR codes are square) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def qr(s, loc, mul, brdr): | 
		
	
		
			
				|  |  |  |  | def qr(z, s, loc, mul, brdr): | 
		
	
		
			
				|  |  |  |  |     qr = qrcode.QRCode( | 
		
	
		
			
				|  |  |  |  |         box_size=mul, | 
		
	
		
			
				|  |  |  |  |         border=brdr, | 
		
	
	
		
			
				
					|  |  |  | @@ -83,9 +84,30 @@ def qr(s, loc, mul, brdr): | 
		
	
		
			
				|  |  |  |  |     z.output(f"GW10,10,{ceil(padded.width/8)},{padded.height},{padded.tobytes().decode("cp437")}\n") | 
		
	
		
			
				|  |  |  |  |     return img.height | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     # width and height for built-in monospace fonts (includes whitespace) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def font_metrics(res): | 
		
	
		
			
				|  |  |  |  |     m={} | 
		
	
		
			
				|  |  |  |  |     if res==203: | 
		
	
		
			
				|  |  |  |  |         m[1]=(10,14) | 
		
	
		
			
				|  |  |  |  |         m[2]=(12,18) | 
		
	
		
			
				|  |  |  |  |         m[3]=(14,22) | 
		
	
		
			
				|  |  |  |  |         m[4]=(16,26) | 
		
	
		
			
				|  |  |  |  |         m[5]=(34,50) | 
		
	
		
			
				|  |  |  |  |     if res==300: | 
		
	
		
			
				|  |  |  |  |         m[1]=(14,22) | 
		
	
		
			
				|  |  |  |  |         m[2]=(18,30) | 
		
	
		
			
				|  |  |  |  |         m[3]=(22,38) | 
		
	
		
			
				|  |  |  |  |         m[4]=(26,46) | 
		
	
		
			
				|  |  |  |  |         m[5]=(50,82) | 
		
	
		
			
				|  |  |  |  |     return m | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # entrypoint | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def cli(): | 
		
	
		
			
				|  |  |  |  |     parser=argparse.ArgumentParser() | 
		
	
		
			
				|  |  |  |  |     parser.add_argument("id", help="part ID", type=int) | 
		
	
		
			
				|  |  |  |  |     parser.add_argument("id", help="part ID (or IPN)") | 
		
	
		
			
				|  |  |  |  |     parser.add_argument("-i", action="store_true", help="search by IPN instead of part ID") | 
		
	
		
			
				|  |  |  |  |     parser.add_argument("-x", help="label width, in inches (default: 2\")", type=float) | 
		
	
		
			
				|  |  |  |  |     parser.add_argument("-y", help="label height, in inches (default: 1\")", type=float) | 
		
	
		
			
				|  |  |  |  |     parser.add_argument("-g", help="label gap, in inches (default: 0.118\")", type=float) | 
		
	
	
		
			
				
					|  |  |  | @@ -93,7 +115,7 @@ def cli(): | 
		
	
		
			
				|  |  |  |  |     parser.add_argument("-p", help="PartDB base URL") | 
		
	
		
			
				|  |  |  |  |     parser.add_argument("-k", help="PartDB API key") | 
		
	
		
			
				|  |  |  |  |     parser.add_argument("-r", help="printer resolution (default: 203 dpi)", type=int) | 
		
	
		
			
				|  |  |  |  |     parser.add_argument('-v', action='version', version='%(prog)s 0.1.2') | 
		
	
		
			
				|  |  |  |  |     parser.add_argument('-v', action='version', version='%(prog)s 0.3.1') | 
		
	
		
			
				|  |  |  |  |     args=parser.parse_args() | 
		
	
		
			
				|  |  |  |  |     id=args.id | 
		
	
		
			
				|  |  |  |  |     if args.x==None: | 
		
	
	
		
			
				
					|  |  |  | @@ -121,30 +143,27 @@ def cli(): | 
		
	
		
			
				|  |  |  |  |         if res!=203 and res!=300: | 
		
	
		
			
				|  |  |  |  |             raise ValueError("valid resolution options are 203 and 300") | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     # width and height for built-in monospace fonts (includes whitespace) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     font_metrics={} | 
		
	
		
			
				|  |  |  |  |     if res==203: | 
		
	
		
			
				|  |  |  |  |         font_metrics[1]=(10,14) | 
		
	
		
			
				|  |  |  |  |         font_metrics[2]=(12,18) | 
		
	
		
			
				|  |  |  |  |         font_metrics[3]=(14,22) | 
		
	
		
			
				|  |  |  |  |         font_metrics[4]=(16,26) | 
		
	
		
			
				|  |  |  |  |         font_metrics[5]=(34,50) | 
		
	
		
			
				|  |  |  |  |     if res==300: | 
		
	
		
			
				|  |  |  |  |         font_metrics[1]=(14,22) | 
		
	
		
			
				|  |  |  |  |         font_metrics[2]=(18,30) | 
		
	
		
			
				|  |  |  |  |         font_metrics[3]=(22,38) | 
		
	
		
			
				|  |  |  |  |         font_metrics[4]=(26,46) | 
		
	
		
			
				|  |  |  |  |         font_metrics[5]=(50,82) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     # look up the part | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     url=f"{base_url}/api/parts/{id}" | 
		
	
		
			
				|  |  |  |  |     if args.i==True: | 
		
	
		
			
				|  |  |  |  |         url=f"{base_url}/api/parts/?ipn={id}" | 
		
	
		
			
				|  |  |  |  |     else: | 
		
	
		
			
				|  |  |  |  |         url=f"{base_url}/api/parts/{id}" | 
		
	
		
			
				|  |  |  |  |     headers={} | 
		
	
		
			
				|  |  |  |  |     headers["Accept"]="application/json" | 
		
	
		
			
				|  |  |  |  |     if api_key!=None: | 
		
	
		
			
				|  |  |  |  |         headers["Authorization"]=f"Bearer {api_key}" | 
		
	
		
			
				|  |  |  |  |     part=requests.get(url, headers=headers).json() | 
		
	
		
			
				|  |  |  |  |     if args.i==True: # search by IPN | 
		
	
		
			
				|  |  |  |  |         try: | 
		
	
		
			
				|  |  |  |  |             part=part[0]  | 
		
	
		
			
				|  |  |  |  |         except IndexError: | 
		
	
		
			
				|  |  |  |  |             sys.exit("part not found") | 
		
	
		
			
				|  |  |  |  |     try: | 
		
	
		
			
				|  |  |  |  |         if part["status"]==404: # catch a search that returns nothing | 
		
	
		
			
				|  |  |  |  |             sys.exit("part not found") | 
		
	
		
			
				|  |  |  |  |     except KeyError: | 
		
	
		
			
				|  |  |  |  |         pass | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     # render a label for it | 
		
	
		
			
				|  |  |  |  |  | 
		
	
	
		
			
				
					|  |  |  | @@ -159,19 +178,28 @@ def cli(): | 
		
	
		
			
				|  |  |  |  |     z.output("N\n") | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     if res==300: | 
		
	
		
			
				|  |  |  |  |         qr_size=qr(f"{base_url}/en/part/{id}", (10, 10), 6, 3) | 
		
	
		
			
				|  |  |  |  |         qr_size=qr(z, f"{base_url}/en/part/{id}", (10, 10), 6, 3) | 
		
	
		
			
				|  |  |  |  |     else: | 
		
	
		
			
				|  |  |  |  |         qr_size=qr(f"{base_url}/en/part/{id}", (10, 10), 4, 2) | 
		
	
		
			
				|  |  |  |  |         qr_size=qr(z, f"{base_url}/en/part/{id}", (10, 10), 4, 2) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     loc=(15+qr_size, 20) | 
		
	
		
			
				|  |  |  |  |     loc=textline(part["ipn"], loc, 5) | 
		
	
		
			
				|  |  |  |  |     try: | 
		
	
		
			
				|  |  |  |  |         loc=textline(z, res, part["ipn"], loc, 5) | 
		
	
		
			
				|  |  |  |  |     except KeyError: | 
		
	
		
			
				|  |  |  |  |         loc=textline(z, res, "", loc, 5) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     loc, excess=textbox(f"{part["manufacturer"]["name"]} {part["manufacturer_product_number"]}", loc, (label_width-loc[0]-10, label_height-loc[1]-10), 2) | 
		
	
		
			
				|  |  |  |  |     try: | 
		
	
		
			
				|  |  |  |  |         loc, excess=textbox(z, res, f"{part["manufacturer"]["name"]} {part["manufacturer_product_number"]}", loc, (label_width-loc[0]-10, label_height-loc[1]-10), 2) | 
		
	
		
			
				|  |  |  |  |     except KeyError: | 
		
	
		
			
				|  |  |  |  |         loc, excess=textbox(z, res, "", loc, (label_width-loc[0]-10, label_height-loc[1]-10), 2) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     avail_y=floor((20+qr_size-loc[1])/font_metrics[2][1])*font_metrics[2][1] | 
		
	
		
			
				|  |  |  |  |     loc, excess=textbox(part["description"], loc, (label_width-loc[0]-10, avail_y), 2) | 
		
	
		
			
				|  |  |  |  |     avail_y=floor((20+qr_size-loc[1])/font_metrics(res)[2][1])*font_metrics(res)[2][1] | 
		
	
		
			
				|  |  |  |  |     loc, excess=textbox(z, res, part["description"], loc, (label_width-loc[0]-10, avail_y), 2) | 
		
	
		
			
				|  |  |  |  |     if excess!="": | 
		
	
		
			
				|  |  |  |  |         loc=(10, loc[1]) | 
		
	
		
			
				|  |  |  |  |         loc, excess=textbox(excess, loc, (label_width-20, label_height-loc[1]), 2) | 
		
	
		
			
				|  |  |  |  |         loc, excess=textbox(z, res, excess, loc, (label_width-20, label_height-loc[1]), 2) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     z.output("P1\n") | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | if __name__ == "__main__": | 
		
	
		
			
				|  |  |  |  |     cli() | 
		
	
	
		
			
				
					|  |  |  |   |