Fix copy half-cut print handling

This commit is contained in:
Mikei386
2026-06-22 11:35:05 +02:00
parent a6025eef47
commit b26b9fe123
6 changed files with 84 additions and 14 deletions
+66 -11
View File
@@ -37,6 +37,7 @@ PRINT_COMMAND = os.environ.get("PRINT_COMMAND", DEFAULT_PRINT_COMMAND)
PRINT_ENABLED = os.environ.get("PRINT_ENABLED", "1").lower() in {"1", "true", "yes", "on"}
DPI = int(os.environ.get("LABEL_DPI", "180"))
PTOUCH_SUPPORTS_HALFCUT: bool | None = None
PTOUCH_SUPPORTS_COPIES: bool | None = None
TAPE_WIDTHS_MM = [3.5, 6, 9, 12, 18, 24]
PRINTABLE_TAPE_PX = {
@@ -223,7 +224,7 @@ def draw_cutmark(draw: ImageDraw.ImageDraw, width_px: int, height_px: int, scale
y += dash + gap
def render_design(design: dict[str, Any]) -> Path:
def render_design_image(design: dict[str, Any]) -> Image.Image:
tape_width = float(design.get("tapeWidthMm", 24))
if tape_width not in TAPE_WIDTHS_MM:
raise ValueError("Ungueltige Bandbreite.")
@@ -267,6 +268,17 @@ def render_design(design: dict[str, Any]) -> Path:
if design.get("cutMode") in {"cutmark", "cutmark-chain"}:
draw_cutmark(draw, image.width, image.height, scale)
return image
def render_design(design: dict[str, Any], copies: int = 1) -> Path:
image = render_design_image(design)
if copies > 1:
combined = new_label_image(image.width * copies, image.height)
for index in range(copies):
combined.paste(image, (index * image.width, 0))
image = combined
output = LABEL_DIR / f"label-{uuid.uuid4().hex}.png"
image.save(output)
return output
@@ -288,9 +300,35 @@ def ptouch_supports_halfcut() -> bool:
return PTOUCH_SUPPORTS_HALFCUT
def cut_args_for_design(design: dict[str, Any]) -> str:
def ptouch_supports_copies() -> bool:
global PTOUCH_SUPPORTS_COPIES
if PTOUCH_SUPPORTS_COPIES is not None:
return PTOUCH_SUPPORTS_COPIES
result = subprocess.run(
["ptouch-print", "--help"],
capture_output=True,
check=False,
text=True,
timeout=10,
)
help_text = f"{result.stdout}\n{result.stderr}"
PTOUCH_SUPPORTS_COPIES = "--copies" in help_text
return PTOUCH_SUPPORTS_COPIES
def effective_cut_mode(design: dict[str, Any], copies: int) -> str:
cut_mode = str(design.get("cutMode", "auto"))
if copies > 1 and design.get("halfcut", "on") == "on":
if cut_mode == "cutmark":
return "cutmark-chain"
if cut_mode == "auto":
return "chain"
return cut_mode
def cut_args_for_design(design: dict[str, Any], copies: int) -> str:
args = []
if design.get("cutMode") in {"chain", "cutmark-chain"}:
if effective_cut_mode(design, copies) in {"chain", "cutmark-chain"}:
args.append("--chain")
halfcut = design.get("halfcut", "on")
if halfcut == "off":
@@ -307,8 +345,11 @@ def cut_args_for_design(design: dict[str, Any]) -> str:
def run_print_command(image_path: Path, copies: int, design: dict[str, Any]) -> subprocess.CompletedProcess[str]:
cut_args = cut_args_for_design(design)
command = PRINT_COMMAND.format(image=shlex.quote(str(image_path)), copies=copies, cut_args=cut_args)
cut_args = cut_args_for_design(design, copies)
command_copies = copies if "{copies}" in PRINT_COMMAND else 1
command = PRINT_COMMAND.format(image=shlex.quote(str(image_path)), copies=command_copies, cut_args=cut_args)
if copies > 1 and "{copies}" not in PRINT_COMMAND and ptouch_supports_copies():
command = f"{command} --copies {copies}"
if cut_args and "{cut_args}" not in PRINT_COMMAND:
command = f"{command} {cut_args}"
process = subprocess.Popen(
@@ -333,9 +374,21 @@ def run_print_command(image_path: Path, copies: int, design: dict[str, Any]) ->
def print_label(image_path: Path, copies: int, design: dict[str, Any]) -> list[subprocess.CompletedProcess[str]]:
if "{copies}" in PRINT_COMMAND:
return [run_print_command(image_path, copies, design)]
return [run_print_command(image_path, 1, design) for _ in range(copies)]
return [run_print_command(image_path, copies, design)]
def should_render_copies_as_strip(design: dict[str, Any], copies: int) -> bool:
if copies <= 1:
return False
if design.get("halfcut", "on") == "on":
if ptouch_supports_copies() or "{copies}" in PRINT_COMMAND:
return False
raise RuntimeError(
"Mehrere Kopien mit Halbschnitt brauchen einen echten Kopien-Druckjob. "
"Ein langes PNG kann keine Halbschnitte zwischen den Etiketten ausloesen. "
"Bitte eine ptouch-print-Version mit --copies verwenden."
)
return True
def detect_tape_width() -> tuple[float | None, str]:
@@ -428,7 +481,7 @@ def mobile():
def api_preview():
try:
design, _copies = design_from_request()
image_path = render_design(design)
image_path = render_design(design, 1)
return jsonify({"ok": True, "image": f"/labels/{image_path.name}"})
except Exception as exc:
return jsonify({"ok": False, "error": str(exc)}), 400
@@ -438,12 +491,14 @@ def api_preview():
def api_print():
try:
design, copies = design_from_request()
image_path = render_design(design)
render_copies = copies if should_render_copies_as_strip(design, copies) else 1
print_copies = 1 if render_copies > 1 else copies
image_path = render_design(design, render_copies)
if PRINT_ENABLED:
if not PRINT_LOCK.acquire(blocking=False):
raise RuntimeError("Es laeuft bereits ein Druckjob. Bitte warten oder haengenden ptouch-print-Prozess beenden.")
try:
results = print_label(image_path, copies, design)
results = print_label(image_path, print_copies, design)
failed = next((result for result in results if result.returncode != 0), None)
if failed:
raise RuntimeError(format_print_error(failed))
+7
View File
@@ -167,6 +167,13 @@ button.primary {
color: #a7e8af;
}
.field-note {
margin: 0;
color: #b7b7b7;
font-size: 13px;
line-height: 1.35;
}
.bottom-actions {
position: fixed;
left: 0;
+6
View File
@@ -157,6 +157,12 @@ textarea {
line-height: 1.4;
}
.field-note {
color: #b7b7b7;
font-size: 13px;
line-height: 1.35;
}
.sidebar label {
display: grid;
gap: 6px;
+2 -1
View File
@@ -72,7 +72,7 @@
Schnitt
<select id="cutMode">
<option value="auto">Normal</option>
<option value="chain">Kettendruck / kein Endschnitt</option>
<option value="chain">Kettendruck / Streifen</option>
<option value="cutmark">Schnittmarke drucken</option>
<option value="cutmark-chain">Kettendruck + Schnittmarke</option>
</select>
@@ -84,6 +84,7 @@
<option value="off">Aus</option>
</select>
</label>
<p class="field-note">Bei mehreren Kopien mit Halbschnitt wird automatisch Kettendruck verwendet.</p>
</section>
<section>
+2 -1
View File
@@ -71,7 +71,7 @@
Schnitt
<select id="mobileCutMode">
<option value="auto">Normal</option>
<option value="chain">Kettendruck</option>
<option value="chain">Kettendruck / Streifen</option>
<option value="cutmark">Schnittmarke</option>
<option value="cutmark-chain">Kettendruck + Marke</option>
</select>
@@ -83,6 +83,7 @@
<option value="off">Aus</option>
</select>
</label>
<p class="field-note">Bei mehreren Kopien mit Halbschnitt wird automatisch Kettendruck verwendet.</p>
<div class="check-row">
<label><input id="mobileBold" type="checkbox" checked> Fett</label>