Skip to content

Commit cda2e57

Browse files
committedFeb 11, 2022
Added copies, different printing mode, tape cache
1 parent de28a7f commit cda2e57

File tree

7 files changed

+135
-19
lines changed

7 files changed

+135
-19
lines changed
 

‎examples/demo-lib/template1/manifest.yaml

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ params:
1111

1212
layout:
1313
template: |
14-
<p><strong>M</strong>:&nbsp;{{param.default}}</p>
14+
<p>{{param.default}}</p>
1515
css: |
1616
p.test { color: red; }
17+
.label {font-size: 0.8em;}

‎src/tapen/cli.py

+74-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import argparse
33
import logging
44
import sys
5+
from enum import Enum
56
from pathlib import Path
67
from typing import List, Dict, Any, Optional
78

@@ -17,11 +18,40 @@
1718
from tapen.common.domain import PrintJob
1819
from tapen.library import TemplateLibrary
1920
from tapen.printer import get_print_factory, PrinterFactory, TapenPrinter
21+
from tapen.printer.common import PrintingMode, TapeInfo
2022
from tapen.renderer import get_default_renderer, Renderer
2123

2224
LOGGER = logging.getLogger("cli")
2325

2426

27+
class EnumAction(argparse.Action):
28+
"""
29+
Argparse action for handling Enums
30+
"""
31+
32+
def __init__(self, **kwargs):
33+
# Pop off the type value
34+
enum_type = kwargs.pop("type", None)
35+
36+
# Ensure an Enum subclass is provided
37+
if enum_type is None:
38+
raise ValueError("type must be assigned an Enum when using EnumAction")
39+
if not issubclass(enum_type, Enum):
40+
raise TypeError("type must be an Enum when using EnumAction")
41+
42+
# Generate choices from the Enum
43+
kwargs.setdefault("choices", tuple(e.value for e in enum_type))
44+
45+
super(EnumAction, self).__init__(**kwargs)
46+
47+
self._enum = enum_type
48+
49+
def __call__(self, parser, namespace, values, option_string=None):
50+
# Convert value back into an Enum
51+
value = self._enum(values)
52+
setattr(namespace, self.dest, value)
53+
54+
2555
class GlobalConfigFile(GlobalArgsExtension):
2656

2757
@classmethod
@@ -87,6 +117,9 @@ def get_printer(self) -> TapenPrinter:
87117
assert self.__printer_factory is not None, "Class is not initialized. Forgot self.init()?"
88118
return self.__printer_factory.get_first_printer()
89119

120+
def get_cached_tape_info(self, printer_id: Optional[str]=None) -> Optional[TapeInfo]:
121+
return self.__printer_factory.get_cached_tape_info(printer_id)
122+
90123

91124
class ImportLibExtension(BaseCliExtension):
92125
COMMAND_NAME = "import-lib"
@@ -114,8 +147,18 @@ class PrintExtension(BaseCliExtension):
114147

115148
@classmethod
116149
def setup_parser(cls, parser: argparse.ArgumentParser):
117-
parser.add_argument("template", action="store", help="Template to use")
118-
parser.add_argument("data", nargs="*", action="store", help="Data to be printed (will be passed into template)")
150+
parser.add_argument("-m", "--mode", action=EnumAction, type=PrintingMode,
151+
choices=[x.value for x in PrintingMode], default=PrintingMode.HALF_CUT,
152+
help="Print mode (applicable for for more than one labels only)")
153+
parser.add_argument("-c", "--copies", action="store", type=int, default=1,
154+
help="Print mode (applicable for for more than one labels only)")
155+
parser.add_argument("-f", "--force-tape-detection", action="store_true", default=False,
156+
help="Forces tape detection even though there is a cached data")
157+
parser.add_argument("-s", "--skip-printing", action="store_true", default=False,
158+
help="Renders data and skips printing on the real device")
159+
parser.add_argument("template", action="store", type=str, help="Template to use")
160+
parser.add_argument("data", nargs="*", action="store", type=str,
161+
help="Data to be printed (will be passed into template)")
119162

120163
def __is_template_name(self, name: str) -> bool:
121164
return ":" in name
@@ -130,18 +173,39 @@ def handle(self, args: argparse.Namespace):
130173
template = self.template_library.load_template(template_name)
131174
# Load printer data
132175
printer = self.get_printer()
133-
if printer is None:
176+
if printer is None and not args.skip_printing:
134177
CLI.print_error("Printer is not connected.")
135178
exit(1)
136179
else:
137180
CLI.print_info("Detected printer: {}".format(printer))
138-
printer.init()
139-
printer_status = printer.get_status()
140-
CLI.print_info("\tTape: {}".format(printer_status.tape_info))
141-
for x in data:
142-
print_job = PrintJob(template, dict(default=x), cut_tape=False)
143-
bitmap = self.renderer.render_bitmap(print_job, printer_status.tape_info)
144-
printer.print_image(bitmap, print_job.cut_tape)
181+
printer.init()
182+
tape_info = self.get_cached_tape_info()
183+
if tape_info is None or args.force_tape_detection:
184+
if not args.skip_printing or args.force_tape_detection:
185+
printer_status = printer.get_status()
186+
CLI.print_info("\tTape: {}".format(printer_status.tape_info))
187+
tape_info = printer_status.tape_info
188+
else:
189+
if args.skip_printing:
190+
CLI.print_error("Tape information is not available in cache. Skip printing mode is not available")
191+
exit(2)
192+
else:
193+
CLI.print_info("Assuming tape: {}".format(tape_info))
194+
label_num = 0
195+
total_labels = len(data) * args.copies
196+
for i, x in enumerate(data):
197+
print_job = PrintJob(template, dict(default=x))
198+
bitmap = self.renderer.render_bitmap(print_job, tape_info)
199+
if not args.skip_printing:
200+
for c in range(args.copies):
201+
label_num += 1
202+
if args.mode == PrintingMode.HALF_CUT:
203+
cut_tape = label_num == total_labels # Cut the last label
204+
else:
205+
cut_tape = True
206+
printer.print_image(bitmap, cut_tape)
207+
else:
208+
CLI.print_warn("Printing skipped as per user request.")
145209

146210

147211
def main(argv: List[str]):

‎src/tapen/printer/__init__.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
from typing import List, Optional
22

33
from tapen.printer.brother import PTouchFactory
4-
from tapen.printer.common import PrinterFactory, TapenPrinter
4+
from tapen.printer.common import PrinterFactory, TapenPrinter, TapeInfo
55

66
__DEFAULT_PRINT_FACTORY: Optional['DefaultPrinterFactory'] = None
77

88

99
class DefaultPrinterFactory(PrinterFactory):
1010

11+
def get_cached_tape_info(self, printer_id: Optional[str] = None) -> TapeInfo:
12+
return self.__ptouch_fectory.get_cached_tape_info(printer_id)
13+
1114
def __init__(self) -> None:
1215
super().__init__()
1316
self.__ptouch_fectory = PTouchFactory()

‎src/tapen/printer/brother.py

+36-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
from typing import List
1+
import pickle
2+
from pathlib import Path
3+
from typing import List, Optional
4+
5+
from cli_rack.utils import ensure_dir
26

37
from ptouch_py.core import Printer as PTouch_Printer, find_printers
48
from ptouch_py.domain import PTStatus, TapeInfo as PTouch_TapeInfo, TAPE_PARAMS, BaseColorEnum, DEFAULT_DPI
9+
from tapen import config
510
from tapen.printer.common import TapenPrinter, PrinterStatus, Color, TapeInfo, PrinterFactory
611
from PIL.Image import Image
712

13+
TAPE_CACHE_DIR = Path(config.app_dirs.user_cache_dir) / "tape-cache"
14+
815

916
class PTouchTapeInfo(TapeInfo):
1017

@@ -82,14 +89,41 @@ def print_image(self, image: Image, cut_tape=True):
8289
self._ptouch_printer.print_image(image, cut_tape)
8390

8491
def get_status(self) -> PTouchPrinterStatus:
85-
return PTouchPrinterStatus(self._ptouch_printer.get_status())
92+
status = PTouchPrinterStatus(self._ptouch_printer.get_status())
93+
self.__persist_tape_info(status.tape_info)
94+
return status
8695

8796
@property
8897
def verbose_name(self):
8998
return str(self._ptouch_printer)
9099

100+
@property
101+
def id(self) -> str:
102+
return self._ptouch_printer.serial_number
103+
104+
def __persist_tape_info(self, tape_info: TapeInfo):
105+
ensure_dir(str(TAPE_CACHE_DIR))
106+
cache_file = TAPE_CACHE_DIR / (self.id + ".bin")
107+
with open(cache_file, "wb") as f:
108+
pickle.dump(tape_info, f)
109+
91110

92111
class PTouchFactory(PrinterFactory):
93112

113+
def get_cached_tape_info(self, printer_id: Optional[str] = None) -> Optional[TapeInfo]:
114+
ensure_dir(str(TAPE_CACHE_DIR))
115+
cache_file: Optional[Path] = None
116+
if printer_id:
117+
cache_file = TAPE_CACHE_DIR / (printer_id + ".bin")
118+
else:
119+
for x in TAPE_CACHE_DIR.iterdir():
120+
if x.is_file() and x.name.endswith(".bin"):
121+
cache_file = x
122+
continue
123+
if cache_file is None or not cache_file.exists():
124+
return
125+
with open(cache_file, "rb") as f:
126+
return pickle.load(f)
127+
94128
def discover_printers(self) -> List[TapenPrinter]:
95129
return [PTouchPrinter(x) for x in find_printers()]

‎src/tapen/printer/common.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import abc
2+
from enum import Enum
23
from typing import List, Optional
34

45
from PIL.Image import Image
56

6-
from ptouch_py.core import find_printers
7-
8-
97
class Color:
108

119
def __init__(self, id: int, name: str, css_name: str) -> None:
@@ -85,10 +83,20 @@ def get_status(self) -> PrinterStatus:
8583
def verbose_name(self):
8684
raise NotImplementedError
8785

86+
@property
87+
@abc.abstractmethod
88+
def id(self) -> str:
89+
pass
90+
8891
def __str__(self) -> str:
8992
return self.verbose_name
9093

9194

95+
class PrintingMode(Enum):
96+
HALF_CUT = "half-cut"
97+
CUT = "cut"
98+
99+
92100
class PrinterFactory(abc.ABC):
93101

94102
@abc.abstractmethod
@@ -98,3 +106,7 @@ def discover_printers(self) -> List[TapenPrinter]:
98106
def get_first_printer(self) -> Optional[TapenPrinter]:
99107
printers = self.discover_printers()
100108
return printers[0] if len(printers) > 0 else None
109+
110+
@abc.abstractmethod
111+
def get_cached_tape_info(self, printer_id: Optional[str]=None) -> TapeInfo:
112+
pass

‎src/tapen/renderer/common.py

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class Renderer(abc.ABC):
2020
def __init__(self) -> None:
2121
super().__init__()
2222
self.persist_rendered_image_as_file = False
23+
self.job_num = 0
2324

2425
@abc.abstractmethod
2526
def render(self, print_job: PrintJob, tape_params: TapeInfo):

‎src/tapen/renderer/weasyprint.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,9 @@ def render(self, print_job: PrintJob, tape_params: TapeInfo, is_preview=False, d
123123
rendered_label.write_png(result_png, resolution=dpi)
124124
result_png.flush()
125125
result_png.seek(0)
126+
self.job_num += 1
126127
if self.persist_rendered_image_as_file:
127-
path = self.__generate_temp_file("rendered-label.png")
128+
path = self.__generate_temp_file("rendered-label-{}.png".format(self.job_num))
128129
with open(path, "wb") as f:
129130
LOGGER.debug("Persisting generated image at {}".format(path))
130131
f.write(result_png.read())
@@ -134,7 +135,7 @@ def render_bitmap(self, print_job: PrintJob, tape_params: TapeInfo, is_preview=F
134135
png = self.render(print_job, tape_params, is_preview, dpi)
135136
bitmap = Image.open(png, "r", ("png",)).convert("1", dither=0)
136137
if self.persist_rendered_image_as_file:
137-
path = self.__generate_temp_file("rendered-label.bmp")
138+
path = self.__generate_temp_file("rendered-label-{}.bmp".format(self.job_num))
138139
LOGGER.debug("Persisting rendered bitmap at {}".format(path))
139140
bitmap.save(path)
140141

0 commit comments

Comments
 (0)
Please sign in to comment.