Fix copy half-cut print handling
This commit is contained in:
@@ -104,7 +104,7 @@ Umgebungsvariablen:
|
||||
- `LABEL_DPI=180` passt zum PT-P700.
|
||||
- `LABEL_DIR=/data/labels` speichert erzeugte PNG-Dateien.
|
||||
|
||||
Wenn `ptouch-print` auf deinem Setup andere Optionen braucht, nur `PRINT_COMMAND` anpassen. Der Platzhalter `{image}` wird von der App ersetzt. Wenn dein Kommando selbst Kopien unterstuetzt, kannst du zusaetzlich `{copies}` verwenden; sonst druckt die App mehrere Kopien durch mehrere Druckaufrufe. Fuer Schnittoptionen kannst du optional `{cut_args}` im Kommando platzieren; ohne Platzhalter haengt die App die Schnittargumente automatisch an.
|
||||
Wenn `ptouch-print` auf deinem Setup andere Optionen braucht, nur `PRINT_COMMAND` anpassen. Der Platzhalter `{image}` wird von der App ersetzt. Bei `Halbschnitt: Ein` nutzt die App fuer mehrere Kopien `ptouch-print --copies` und erzwingt automatisch Kettendruck, damit der Drucker Kopiengrenzen fuer Halbschnitte kennt und nicht nach jeder Kopie voll schneidet. Bei `Halbschnitt: Aus` oder wenn `--copies` nicht verfuegbar ist, werden Kopien als ein langes Druckbild gerendert. Fuer Schnittoptionen kannst du optional `{cut_args}` im Kommando platzieren; ohne Platzhalter haengt die App die Schnittargumente automatisch an.
|
||||
|
||||
Hinweis: Der PT-P700-Halbschnitt ist in upstream `ptouch-print` nicht als CLI-Option vorhanden. Dieses Projekt patcht `ptouch-print` beim Containerstart um `--halfcut` und `--no-halfcut`.
|
||||
|
||||
|
||||
+65
-10
@@ -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)]
|
||||
|
||||
|
||||
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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user