Compare commits

...

9 Commits

Author SHA1 Message Date
Adriano 1dd4b0a20c vendor/visionsuite a TieVision-Core 9eb6f4c (solo vs-demo-api, #370-#372); pin in CLAUDE.md, refs #127
Test visione: 63 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019A9CSrwGAJvRpprTEtGzx1
2026-11-06 14:23:01 +01:00
Adriano 271ce73d09 costruttori: CompareDxf con pose_search=quarter, il pezzo capovolto di 180 gradi torna in banda, refs #127
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019A9CSrwGAJvRpprTEtGzx1
2026-11-06 14:14:05 +01:00
Adriano 1bd6c6b011 test: pezzo a L (sample.dxf e il rettangolo con foro non distinguono il capovolgimento), refs #127
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019A9CSrwGAJvRpprTEtGzx1
2026-11-06 14:13:46 +01:00
Adriano d8bca535ac test: ricetta DXF su pezzo asimmetrico capovolto di 180 gradi e pose_search nel grafo (rossi prima di U2), refs #127
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019A9CSrwGAJvRpprTEtGzx1
2026-11-06 14:11:02 +01:00
Adriano 14257304f8 vendor/visionsuite a TieVision-Core ffb93b4 (pose_search, #343); pin in CLAUDE.md e uv.lock (zxing-cpp), refs #127
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019A9CSrwGAJvRpprTEtGzx1
2026-11-06 14:09:59 +01:00
Adriano 5738920c9c sviluppo.ps1: git solo con submodule inizializzato; comando di test corretto (#109)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 12:07:23 +01:00
Adriano 452862e0f3 sviluppo.ps1: ripristinati i backslash dei percorsi (#109)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 11:55:50 +01:00
Adriano 999f47092d sviluppo.ps1 scrive vision-engine-version.txt, gitignore (#109)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 11:54:59 +01:00
Adriano 6441a92062 Archivio sorgente con submodule e vision-engine-version.txt (#109)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 11:54:15 +01:00
10 changed files with 342 additions and 4 deletions
+3
View File
@@ -76,3 +76,6 @@ docker-compose.override.yml
# Generato da packaging/windows/build.ps1 (TieMeasureFlow#82): moduli-strumento dagli entry point
packaging/windows/_strumenti.py
# #109: scritto da packaging/windows/sviluppo.ps1 (sha del submodule), non va versionato
/vision-engine-version.txt
+1 -1
View File
@@ -289,7 +289,7 @@ Nginx (dev): gzip abilitato, `client_max_body_size 50M`, `proxy_read_timeout 120
Il runner di visione (`src/vision/`) e il suo worker FastAPI (`src/vision_worker/`) dipendono da `vendor/visionsuite`, un **submodule** che punta al repo TieVision-Core (`ssh://git@git.tielogic.xyz:222/Adriano/TieVision-Core.git`, ex visionsuite, rinominato il 15/10/2026; `vs-gui` e `vs-assistant` sono ora in TieVision-Studio e TieVision-Assistant, che TieMeasureFlow non usa; il path resta `vendor/visionsuite`): un `git clone` senza `--recurse-submodules` lascia la cartella vuota (chi ha già clonato: `git submodule update --init`). Le librerie di VisionSuite richiedono **Python 3.13**, mentre backend e frontend restano su Python 3.11 — è un albero a parte nel monorepo, isolato con il proprio extra `uv`.
Regola (Adriano, 07/10/2026): il submodule `vendor/visionsuite` segue `origin/main`: a ogni integrazione si porta all'ultimo commit (`git -C vendor/visionsuite fetch origin && git -C vendor/visionsuite checkout origin/main`) e si rilanciano i test della visione. La regola vale ora per `origin/main` di TieVision-Core. Pin attuale: 2e73691.
Regola (Adriano, 07/10/2026): il submodule `vendor/visionsuite` segue `origin/main`: a ogni integrazione si porta all'ultimo commit (`git -C vendor/visionsuite fetch origin && git -C vendor/visionsuite checkout origin/main`) e si rilanciano i test della visione. La regola vale ora per `origin/main` di TieVision-Core. Pin attuale: 9eb6f4c.
Il worker gira in un container separato (`Dockerfile.vision`), non pubblica porte verso l'esterno ed è raggiungibile solo dal server all'indirizzo interno `http://vision:8100`. Il container non ha accesso al repository Git del progetto principale, quindi il commit di VisionSuite in uso (`engine_version`) non può essere scoperto dentro l'immagine: va stampato al momento del build leggendo il commit dalla macchina che esegue `docker compose build`, con la variabile `VISION_ENGINE_VERSION`. Una build senza questa variabile fallisce subito, invece di produrre un worker che risponde con una versione indovinata:
+21 -2
View File
@@ -4,14 +4,15 @@ Nuitka (che richiede ~20 minuti): stessi quattro server e stesse porte dell'exe
8000 backend, 8100 visione, 8765 camera), stessa cartella dati %PROGRAMDATA%\Tielogic\TieMeasureFlow.
Prima dell'avvio: ambiente Python 3.13 in build\venv313 (uv sync, solo con -Sincronizza o se manca),
tailwind.css e traduzioni .mo ricompilati, DLL di WeasyPrint in build\gtk (scaricate con
pdf\prendi-gtk.ps1 se mancano).
pdf\prendi-gtk.ps1 se mancano). Scrive vision-engine-version.txt nella radice (sha del submodule
vendor/visionsuite) se git c'e'; senza git e senza il file si ferma con un errore chiaro (#109).
Come si usa (dalla radice del repo):
powershell -ExecutionPolicy Bypass -File packaging\windows\sviluppo.ps1 [-Sincronizza] [-Ascolto 0.0.0.0] [-EscludiCamere OECS]
Dopo una modifica: Ctrl+C e rilancio (pochi secondi). Template e JS non richiedono altro;
classi Tailwind nuove e msgid nuovi vengono ricompilati a ogni avvio.
Dipende da: uv, npx (Node), la licenza tiemeasureflow di QUESTA macchina in
Dipende da: uv, npx (Node), git (opzionale: senza, serve vision-engine-version.txt gia' nella radice), la licenza tiemeasureflow di QUESTA macchina in
%PROGRAMDATA%\Tielogic\licenze\tiemeasureflow.json (impronta: avvio.py --impronta).
#>
param(
@@ -47,6 +48,24 @@ if (-not (Test-Path (Join-Path $gtk '*.dll'))) {
}
$env:WEASYPRINT_DLL_DIRECTORIES = $gtk
# #109: il worker di visione rifiuta ogni job senza VISION_ENGINE_VERSION, che avvio.py legge da
# <radice>\vision-engine-version.txt (come nel pacchetto, build.ps1). Da sorgente lo scrive questo script.
$versione = Join-Path $radice 'vision-engine-version.txt'
$shaMotore = $null
# #109: solo se il submodule e' inizializzato (ha .git): altrimenti git risale al repo padre e darebbe lo sha di TMF
if ((Get-Command git -ErrorAction SilentlyContinue) -and (Test-Path (Join-Path $radice 'vendor\visionsuite\.git'))) {
$shaMotore = (git -C (Join-Path $radice 'vendor\visionsuite') rev-parse HEAD 2>$null)
if ($LASTEXITCODE -ne 0) { $shaMotore = $null }
}
if ($shaMotore) {
# 40 caratteri ASCII, UTF8 senza BOM e senza a capo (avvio.py fa .strip(), che non toglie il BOM)
[System.IO.File]::WriteAllText($versione, $shaMotore.Trim(), (New-Object System.Text.UTF8Encoding $false))
} elseif (Test-Path $versione) {
Write-Host "git non disponibile: uso $versione = $((Get-Content -Raw $versione).Trim())"
} else {
throw "Manca vision-engine-version.txt e git non puo' leggere vendor\visionsuite. Rimedio: crea l'archivio con 'uv run python scripts/archivio_sorgente.py --out tmf.zip' da un clone con git, oppure scrivi in $versione lo sha del submodule (40 caratteri, ASCII, senza BOM e senza a capo)."
}
if ($EscludiCamere) { $env:VS_CAMERA_ESCLUDI = $EscludiCamere } # #339: stesso percorso camera del PC fiera
Set-Location $radice
& $py (Join-Path $PSScriptRoot 'avvio.py') --senza-browser --senza-finestre --host $Ascolto # errori in console, niente MessageBox
+10
View File
@@ -124,6 +124,16 @@ def test_variabili_del_contratto(avvio, tmp_path):
assert len(e[k]) >= 32
def test_versione_motore_visione_letta_dalla_radice_sorgente(avvio, tmp_path, monkeypatch):
# #109: da sorgente il file vision-engine-version.txt sta nella radice del repo e arriva al worker
prog = tmp_path / "prog"
prog.mkdir()
(prog / "vision-engine-version.txt").write_bytes(b"a" * 40)
monkeypatch.setattr(avvio, "cartella_programma", lambda: prog)
avvio.imposta_ambiente(tmp_path / "dati", "127.0.0.1", {k: "x" * 32 for k in avvio.SEGRETI})
assert os.environ["VISION_ENGINE_VERSION"] == "a" * 40
def test_config_env_generato_una_volta_e_riletto(avvio, tmp_path):
dati = tmp_path / "Tielogic" / "TieMeasureFlow"
primo = avvio.carica_segreti(dati)
+108
View File
@@ -0,0 +1,108 @@
"""Archivio sorgente di TieMeasureFlow per un PC senza git (TieMeasureFlow#109).
Cosa fa
Crea uno zip del repo a un commit, con dentro anche il submodule vendor/visionsuite (che
`git archive` da solo NON include) e il file vision-engine-version.txt nella radice dello zip.
Quel file contiene lo sha del submodule REGISTRATO nel commit (non la HEAD del checkout del
submodule): packaging/windows/avvio.py lo legge e lo passa al worker di visione come
VISION_ENGINE_VERSION; senza, il worker rifiuta ogni job. Formato: 40 caratteri ASCII,
senza BOM e senza a capo (avvio.py usa .strip(), che non toglie un BOM).
Come si usa (dalla radice del repo, con il submodule inizializzato)
uv run python scripts/archivio_sorgente.py --out C:/tmp/tmf.zip [--ref HEAD]
Esce 0 e stampa lo sha; esce 1 con un messaggio e senza lasciare lo zip se il submodule
non e' inizializzato o non ha il commit registrato (git submodule update --init vendor/visionsuite).
Dipende da: git nel PATH, solo libreria standard.
"""
from __future__ import annotations
import argparse
import io
import os
import subprocess
import sys
import tempfile
import zipfile
from pathlib import Path
SUBMODULE = "vendor/visionsuite"
FILE_VERSIONE = "vision-engine-version.txt"
class ErroreArchivio(RuntimeError):
"""Errore chiaro da mostrare all'operatore (niente traceback)."""
def _git(cartella: Path, *args: str, binario: bool = False):
"""Esegue git in `cartella`; solleva ErroreArchivio con lo stderr se fallisce."""
r = subprocess.run(["git", "-C", str(cartella), *args], capture_output=True)
if r.returncode != 0:
raise ErroreArchivio(f"git {' '.join(args)} fallito in {cartella}: {r.stderr.decode(errors='replace').strip()}")
return r.stdout if binario else r.stdout.decode().strip()
def sha_registrato(repo: Path, ref: str) -> str:
"""Sha del submodule registrato nel commit `ref` (gitlink, modo 160000), non la HEAD del checkout."""
campi = _git(repo, "ls-tree", ref, SUBMODULE).split()
if len(campi) < 3 or campi[0] != "160000":
raise ErroreArchivio(f"{SUBMODULE} non e' un submodule registrato in {ref}")
return campi[2]
def crea_archivio(repo: Path, out: Path, ref: str = "HEAD") -> str:
"""Scrive lo zip in `out` e ritorna lo sha del submodule; in caso di errore non lascia nulla."""
repo, out = Path(repo), Path(out)
sha = sha_registrato(repo, ref)
sub = repo / SUBMODULE
# submodule non inizializzato o senza il commit registrato: errore prima di scrivere lo zip
if not (sub / ".git").exists():
raise ErroreArchivio(f"{SUBMODULE} non e' inizializzato: git submodule update --init {SUBMODULE}")
if subprocess.run(["git", "-C", str(sub), "cat-file", "-e", f"{sha}^{{commit}}"], capture_output=True).returncode != 0:
raise ErroreArchivio(f"{SUBMODULE} non ha il commit {sha}: git submodule update --init {SUBMODULE}")
zip_repo = _git(repo, "archive", "--format=zip", ref, binario=True)
zip_sub = _git(sub, "archive", "--format=zip", f"--prefix={SUBMODULE}/", sha, binario=True)
out.parent.mkdir(parents=True, exist_ok=True)
fd, tmp = tempfile.mkstemp(suffix=".zip.tmp", dir=out.parent) # scrittura atomica: o c'e' tutto o niente
os.close(fd)
try:
viste: set[str] = set()
with zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as z:
for blob in (zip_repo, zip_sub):
with zipfile.ZipFile(io.BytesIO(blob)) as sorgente:
for info in sorgente.infolist():
if info.filename in viste:
continue # es. la cartella vendor/visionsuite/ del gitlink, gia' nel primo zip
viste.add(info.filename)
z.writestr(info, sorgente.read(info.filename), compress_type=zipfile.ZIP_DEFLATED)
if FILE_VERSIONE in viste:
raise ErroreArchivio(f"{FILE_VERSIONE} e' gia' nel repo a {ref}: va tolto dal versionamento")
# 40 caratteri ASCII, senza BOM e senza a capo (avvio.py fa .strip(), che non toglie un BOM)
z.writestr(FILE_VERSIONE, sha.encode("ascii"))
os.replace(tmp, out)
except BaseException:
if os.path.exists(tmp):
os.unlink(tmp)
raise
return sha
def main(argv: list[str] | None = None) -> int:
p = argparse.ArgumentParser(description="Zip del sorgente con submodule e vision-engine-version.txt (#109)")
p.add_argument("--out", required=True, type=Path, help="file .zip da creare")
p.add_argument("--ref", default="HEAD", help="commit da archiviare (default HEAD)")
a = p.parse_args(argv)
radice = Path(__file__).resolve().parents[1]
try:
sha = crea_archivio(radice, a.out, a.ref)
except ErroreArchivio as e:
print(f"ERRORE: {e}", file=sys.stderr)
return 1
print(f"{a.out} creato; {FILE_VERSIONE} = {sha}")
return 0
if __name__ == "__main__":
sys.exit(main())
+92
View File
@@ -0,0 +1,92 @@
"""Test di scripts/archivio_sorgente.py (TieMeasureFlow#109): repo temporaneo con un submodule finto.
Uso: uv run --no-project --python 3.13 --with pytest pytest -q scripts/tests/test_archivio_sorgente.py
"""
import importlib.util
import subprocess
import zipfile
from pathlib import Path
import pytest
_spec = importlib.util.spec_from_file_location(
"archivio_sorgente", Path(__file__).resolve().parents[1] / "archivio_sorgente.py")
archivio = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(archivio)
GIT = ["git", "-c", "core.autocrlf=false", "-c", "protocol.file.allow=always",
"-c", "user.name=t", "-c", "user.email=t@t", "-c", "commit.gpgsign=false"]
def _g(cartella, *args):
r = subprocess.run([*GIT, "-C", str(cartella), *args], capture_output=True, text=True)
assert r.returncode == 0, r.stderr
return r.stdout.strip()
def _repo(tmp_path, nome):
d = tmp_path / nome
d.mkdir()
_g(d, "init", "-q", "-b", "main")
return d
@pytest.fixture
def padre(tmp_path):
"""Repo padre con vendor/visionsuite registrato (submodule finto da un repo locale)."""
origine = _repo(tmp_path, "origine")
(origine / "motore.py").write_text("x = 1\n")
_g(origine, "add", ".")
_g(origine, "commit", "-q", "-m", "motore")
p = _repo(tmp_path, "padre")
(p / "app.py").write_text("print('tmf')\n")
_g(p, "add", ".")
_g(p, "commit", "-q", "-m", "app")
_g(p, "submodule", "add", "-q", str(origine), "vendor/visionsuite")
_g(p, "commit", "-q", "-m", "submodule")
return p
def test_zip_con_repo_submodule_e_versione(padre, tmp_path):
atteso = _g(padre, "ls-tree", "HEAD", "vendor/visionsuite").split()[2]
out = tmp_path / "o" / "tmf.zip"
assert archivio.crea_archivio(padre, out) == atteso
with zipfile.ZipFile(out) as z:
nomi = z.namelist()
assert "app.py" in nomi and "vendor/visionsuite/motore.py" in nomi
dati = z.read("vision-engine-version.txt")
assert dati == atteso.encode("ascii") and len(dati) == 40
assert not dati.startswith(b"\xef\xbb\xbf") and b"\n" not in dati # niente BOM, niente a capo
def test_sha_resta_quello_registrato_se_il_submodule_avanza(padre, tmp_path):
registrato = _g(padre, "ls-tree", "HEAD", "vendor/visionsuite").split()[2]
sub = padre / "vendor" / "visionsuite"
(sub / "nuovo.py").write_text("y = 2\n")
_g(sub, "add", ".")
_g(sub, "commit", "-q", "-m", "avanti non registrato")
assert _g(sub, "rev-parse", "HEAD") != registrato
out = tmp_path / "tmf.zip"
assert archivio.crea_archivio(padre, out) == registrato
with zipfile.ZipFile(out) as z:
assert z.read("vision-engine-version.txt").decode() == registrato
assert "vendor/visionsuite/nuovo.py" not in z.namelist() # contenuto del commit registrato
def test_submodule_non_inizializzato_errore_e_nessuno_zip(padre, tmp_path):
clone = tmp_path / "clone"
_g(tmp_path, "clone", "-q", str(padre), str(clone)) # senza --recurse-submodules
out = tmp_path / "tmf.zip"
with pytest.raises(archivio.ErroreArchivio, match="inizializzato"):
archivio.crea_archivio(clone, out)
assert not out.exists() and not list(tmp_path.glob("*.tmp")) # nessuno zip ne' temporaneo
def test_cli_esce_1_con_messaggio(padre, tmp_path, capsys, monkeypatch):
clone = tmp_path / "clone"
_g(tmp_path, "clone", "-q", str(padre), str(clone))
originale = archivio.crea_archivio
# la CLI lavora sul repo che contiene lo script: la si dirotta sul clone senza submodule
monkeypatch.setattr(archivio, "crea_archivio", lambda r, o, ref="HEAD": originale(clone, o, ref))
assert archivio.main(["--out", str(tmp_path / "x.zip")]) == 1
assert "ERRORE" in capsys.readouterr().err
+3
View File
@@ -80,6 +80,9 @@ def _graph(dxf_sha: str, cal_sha: str, band_mm: float, min_points_pct: float, po
"band_plus": float(band_mm),
"band_minus": float(band_mm),
"nominal_unit": "mm",
# #127 (Adriano, 06/11/2026): LocateBlob normalizza l'angolo in (-90, 90], il pezzo a 180 o +-120 gradi
# verrebbe capovolto; "quarter" prova le pose ruotate (Core#343). Lo specchiato resta fuori.
"pose_search": "quarter",
# tolleranza di points_in_pct: nominale = limite, plus = 100 - limite
"points_in_pct_nominal": float(min_points_pct),
"points_in_pct_plus": float(100.0 - min_points_pct),
+69
View File
@@ -22,6 +22,7 @@ from src.vision.costruttori import (
DXF_FILE,
CostruttoreError,
_candidate_anchors,
_graph,
_maschera_pezzo,
build_dxf_compare,
)
@@ -94,6 +95,74 @@ def test_ricetta_dxf_con_riferimento_gia_ruotato(tmp_path):
assert risultato["outputs"]["dxf.points_in_pct"] > 95.0
# #127: pezzo a L (asimmetrico a 180 gradi: sample.dxf e' simmetrico e non distingue il
# capovolgimento). Un rettangolo con foro fuori centro NON basta: l'ICP di Core non lo risolve.
L_PROFILE_MM = [(-100, -50), (100, -50), (100, 50), (-20, 50), (-20, 10), (-100, 10)]
def _l_dxf(path: Path) -> bytes:
import ezdxf
doc = ezdxf.new()
doc.modelspace().add_lwpolyline(L_PROFILE_MM, close=True)
doc.saveas(str(path))
return path.read_bytes()
def _l_scene(angle_deg: float = 0.0) -> np.ndarray:
"""Il pezzo a L (scuro su bianco) a 4 px/mm; l'asse y del DXF e' verso l'alto."""
w, h = int(320 * PX_PER_MM), int(220 * PX_PER_MM)
cx, cy = w // 2, h // 2
img = np.full((h, w), 255, np.uint8)
pts = np.array([(cx + x * PX_PER_MM, cy - y * PX_PER_MM) for x, y in L_PROFILE_MM], np.int32)
cv2.fillPoly(img, [pts], 0)
if angle_deg:
m = cv2.getRotationMatrix2D((cx, cy), angle_deg, 1.0)
img = cv2.warpAffine(img, m, (w, h), flags=cv2.INTER_LINEAR, borderValue=255)
return img
def _pct_a_180(tmp_path, togli_quarter: bool) -> tuple[float, float]:
"""points_in_pct della ricetta costruita a 0 gradi: (sulla scena dritta, a 180 gradi)."""
risultato = build_dxf_compare(_l_scene(0.0), _l_dxf(tmp_path / "l.dxf"), PX_PER_MM, band_mm=2.0)
graph = risultato["graph"]
if togli_quarter: # misura di controllo: senza pose_search il pezzo a 180 va capovolto
for tool in graph["tools"]:
tool["values"].pop("pose_search", None)
for nome, b64 in risultato["files"].items():
(tmp_path / nome).write_bytes(base64.b64decode(b64))
def pct(img):
esito = run_graph(img, graph, calibration={"px_per_mm": PX_PER_MM}, recipe_dir=tmp_path)
assert not esito.failures, esito.failures
return esito.outputs["dxf.points_in_pct"]
return pct(_l_scene(0.0)), pct(_l_scene(180.0))
def test_ricetta_dxf_su_pezzo_capovolto_di_180(tmp_path):
# #127: con pose_search=quarter il pezzo a 180 gradi vale quanto quello dritto
pct_0, pct_180 = _pct_a_180(tmp_path, togli_quarter=False)
assert pct_0 >= 95.0
assert pct_180 >= 95.0 and abs(pct_180 - pct_0) <= 2.0
def test_ricetta_dxf_senza_quarter_il_pezzo_a_180_e_capovolto(tmp_path):
# #127: controllo del test sopra - senza pose_search il disegno finisce capovolto
_, pct_180 = _pct_a_180(tmp_path, togli_quarter=True)
assert pct_180 < 20.0 # misurato ~2,8% (Core#343 riporta 3,3% su PR165)
def test_graph_scrive_pose_search_quarter_nel_compare_dxf():
# #127: il CompareDxf porta pose_search=quarter solo se c'e' l'ancora
con = _graph("a" * 64, "b" * 64, 1.0, 95.0, "dark",
{"row": 0.0, "col": 0.0, "angle_deg": 0.0}, (1.0, 2.0, 3.0))
dxf_tool = next(t for t in con["tools"] if t["kind"] == "CompareDxf")
assert dxf_tool["values"]["pose_search"] == "quarter"
senza = _graph("a" * 64, "b" * 64, 1.0, 95.0, "dark",
{"row": 0.0, "col": 0.0, "angle_deg": 0.0}, None)
assert [t["kind"] for t in senza["tools"]] == ["LocateBlob"]
def test_immagine_senza_pezzo_solleva():
bianca = np.full((600, 800), 255, np.uint8)
with pytest.raises(CostruttoreError, match="pezzo"):
Generated
+34
View File
@@ -2464,6 +2464,7 @@ dependencies = [
{ name = "visionsuite" },
{ name = "vs-measure" },
{ name = "vs-task" },
{ name = "zxing-cpp" },
]
[package.optional-dependencies]
@@ -2485,6 +2486,7 @@ requires-dist = [
{ name = "vs-pm2d", marker = "extra == 'locate'", editable = "vendor/visionsuite/packages/vs-pm2d" },
{ name = "vs-task", editable = "vendor/visionsuite/packages/vs-task" },
{ name = "vs-tools", extras = ["locate", "dxf"], marker = "extra == 'dev'", editable = "vendor/visionsuite/packages/vs-tools" },
{ name = "zxing-cpp", specifier = ">=3.1,<4" },
]
provides-extras = ["locate", "dxf", "dev"]
@@ -2787,3 +2789,35 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" },
{ url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" },
]
[[package]]
name = "zxing-cpp"
version = "3.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b9/30/ad0e0352c593712ebb47143571ff11b130812e2852d7540e7c80cdf23340/zxing_cpp-3.1.1.tar.gz", hash = "sha256:1051a521b21a9fe206702ad4186aeb195154e3e1badcd99576d030723f36382b", size = 1437030, upload-time = "2026-07-29T08:50:59.019Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/c4/d64c1b751561eee75706def600041e4c72642403864ac6c52588fdb54bb3/zxing_cpp-3.1.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:9e558cf4d6d0dd0ae1199541bc8fd01e8fb67e18673faa7ca96e50440fdd6f93", size = 912350, upload-time = "2026-07-29T08:50:23.952Z" },
{ url = "https://files.pythonhosted.org/packages/01/1b/94067d5a5d324a30cd9862296171ec50cda58c9e31317eca53286aeab832/zxing_cpp-3.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ec41a833dc1697e5360b5d9e2620fab1f3e92892b890c31fe85b50a10ca05217", size = 865032, upload-time = "2026-07-29T08:50:25.304Z" },
{ url = "https://files.pythonhosted.org/packages/12/ee/4ab8cf9594959e1dc8f3c0e234d225fd1080cecc349c99cac4850005055a/zxing_cpp-3.1.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:07ac611267b7220b769c182ae33473ee95aca1cc6c57e597755288b557848935", size = 1028402, upload-time = "2026-07-29T08:50:26.935Z" },
{ url = "https://files.pythonhosted.org/packages/12/83/5af471c7ad3fbb11d3efba64b41aba9f209d5dcc2945ca6b0afb29a9fed0/zxing_cpp-3.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a5b32d719a5448f1b2f474e04d2db6ce41cc6973fb5c705d47dbe899361e5f9", size = 1102966, upload-time = "2026-07-29T08:50:28.428Z" },
{ url = "https://files.pythonhosted.org/packages/dd/f4/8b75505b3b2110146769006a0087e1517675af057bb1eaa7709ef8dd507a/zxing_cpp-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:0343458a0fdf3f99c9dcff74dd09be6bae5d0d87a2f99de7876014317b939996", size = 1005571, upload-time = "2026-07-29T08:50:29.806Z" },
{ url = "https://files.pythonhosted.org/packages/20/e8/05b134e41abda4bb3aca00ea2bc16898c9c3641afce5ad4637fd4b1166f0/zxing_cpp-3.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:73a26e6e7c5fa411bfd690c374e3d4a7fdcb41ca57a047839365b0c985e325ad", size = 933099, upload-time = "2026-07-29T08:50:31.309Z" },
{ url = "https://files.pythonhosted.org/packages/56/57/ac717270db6888973eba83e9832fe800808b555df0ebe34e37b6a6e07545/zxing_cpp-3.1.1-cp312-abi3-macosx_10_13_x86_64.whl", hash = "sha256:09dea611a7c9dc7c713a82303b15b733dc71abb1a77454b26b779e33671cef05", size = 911430, upload-time = "2026-07-29T08:50:32.625Z" },
{ url = "https://files.pythonhosted.org/packages/12/70/f14831dd92d5c844a39c03ebe9ba185e073d4467d50b48dcf2a816cae0c5/zxing_cpp-3.1.1-cp312-abi3-macosx_11_0_arm64.whl", hash = "sha256:037cbcaeb0cb12497fc15ced23f6b778fce8a6a1d1bbffddbffd004c6225744d", size = 863740, upload-time = "2026-07-29T08:50:34.23Z" },
{ url = "https://files.pythonhosted.org/packages/0d/f3/3fb2c6c48e6f58382fbbd31965c7caafd81f75b7e6707b011bdb940adb5f/zxing_cpp-3.1.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4dae01111f323f46736fc21f05c14dcaaac06cea5fdc8fd994ba19f6f918c6e", size = 1024253, upload-time = "2026-07-29T08:50:35.599Z" },
{ url = "https://files.pythonhosted.org/packages/0c/30/79683cf7139ee5325fbc68169eb8dc1cb2033ec43339b5f39de990f909a7/zxing_cpp-3.1.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9cf67341949946307d086b302cefd453fb47bc6d6ddc7d088839e9481982757b", size = 1096795, upload-time = "2026-07-29T08:50:36.896Z" },
{ url = "https://files.pythonhosted.org/packages/7d/14/055c5a68a50bdde8378ced94e63f9ce340311e51c87b947f9b95fe69f51a/zxing_cpp-3.1.1-cp312-abi3-win_amd64.whl", hash = "sha256:29f98a91148171460b47a942d137ecc90c4b8097636f23cca65263a56bb025d3", size = 1003677, upload-time = "2026-07-29T08:50:38.328Z" },
{ url = "https://files.pythonhosted.org/packages/5d/32/a827a99fa5e0aee382b5d464cbd2075e1911a69500116705f6695a6accd8/zxing_cpp-3.1.1-cp312-abi3-win_arm64.whl", hash = "sha256:04a8f8b78779ab9b637853a0329770791cfc3095d232c768dc4824b63901ebd0", size = 931324, upload-time = "2026-07-29T08:50:39.632Z" },
{ url = "https://files.pythonhosted.org/packages/b0/30/e98ce9c56bd1f1fe0a1fd0e5c39202da49baa3620031cb80ac7a04759ffb/zxing_cpp-3.1.1-cp313-cp313t-macosx_10_15_x86_64.whl", hash = "sha256:9d291fd958c26066aca97c4a416a9f15475a99c97b253cd4d2c6754a485b01e6", size = 915582, upload-time = "2026-07-29T08:50:41.286Z" },
{ url = "https://files.pythonhosted.org/packages/3d/d8/ab1db4571348e8756c2019425c72b3cb936f72c4a7c2af35687396381c36/zxing_cpp-3.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:670e2946232128b1ebba5b1f623e016ac8f8ad743ae3a0fb2e50b33180f216a2", size = 867699, upload-time = "2026-07-29T08:50:42.815Z" },
{ url = "https://files.pythonhosted.org/packages/6a/09/78a038367fd3d4fc00fa1f696672bfff002b4771814c3b20b1c392872043/zxing_cpp-3.1.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9efc7ed301846a8c060720f09bed8a29fefccef54b5106c291e4136ffe87d089", size = 1030204, upload-time = "2026-07-29T08:50:44.356Z" },
{ url = "https://files.pythonhosted.org/packages/90/7b/0fc91d2d0463164268d06dd3e9b97520f9fe5c79dc6a954c92cd9ac92fbf/zxing_cpp-3.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f37e714ad4fd0ae4dd759b19fef25bd524a2865bc3ca8730b4e318c0cc7800e", size = 1104920, upload-time = "2026-07-29T08:50:45.639Z" },
{ url = "https://files.pythonhosted.org/packages/3b/9d/2adb3c88894b1e018739aae9bd2725733b55c14e3df090a0e01b2bffff14/zxing_cpp-3.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:93918148c1ed7ec60ff172b183ddc9dddfcb59e40867b0e98d79cc2d62a2b41d", size = 1009395, upload-time = "2026-07-29T08:50:47.118Z" },
{ url = "https://files.pythonhosted.org/packages/f8/f1/c7c93c2123701c12cda01ef02662ff010a79d31e86f67e9080d10d19013b/zxing_cpp-3.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:68b8cbd6797228eb983ab616b876cc744db319c64a9491a4806324afd04a8c48", size = 934267, upload-time = "2026-07-29T08:50:48.463Z" },
{ url = "https://files.pythonhosted.org/packages/d2/a8/8c005a5251734f57a30f1e85fa2a8965d53cd0df99d1abf642956153410e/zxing_cpp-3.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b4bd34f71868af0e34b000da4fc885c85a7f0ef37eecc0ec433ff27b263a5b7", size = 915637, upload-time = "2026-07-29T08:50:50.129Z" },
{ url = "https://files.pythonhosted.org/packages/5d/31/a2e693c9771b88e45dd7e52b56c85c169649123cf0eebfb32151efdfb356/zxing_cpp-3.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94e342d390933b9678f71bf6005cf2125cdb27c2355c21fa194e3a672502aac6", size = 867750, upload-time = "2026-07-29T08:50:51.788Z" },
{ url = "https://files.pythonhosted.org/packages/f0/30/d2f7e626b4216bbb47783d7431cd27b151cfe5abeb22aa06f0b130095841/zxing_cpp-3.1.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71df8523deb2fb40b834238e6fa739e210e3a6e27c5b94a99b4106c08e339b9b", size = 1030274, upload-time = "2026-07-29T08:50:53.535Z" },
{ url = "https://files.pythonhosted.org/packages/4e/b9/c4b6db45a3a9f7e34a3faadcce78c2084f0bc2ce0ee8344d61f1149d2318/zxing_cpp-3.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:388626ac8df24f63c2bb17dcd42fd21daeeea6fd6759bd9b1c064b71142da07e", size = 1104873, upload-time = "2026-07-29T08:50:54.941Z" },
{ url = "https://files.pythonhosted.org/packages/c8/8e/8dbf8fcf4d466c7d9b5023ae4cf17da22f328efabd4d7107109ac7737155/zxing_cpp-3.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:fe8172f3c9b17f8fd40fba2ae0ba9728228caeff3587eabf5d99888891d02e62", size = 1032030, upload-time = "2026-07-29T08:50:56.169Z" },
{ url = "https://files.pythonhosted.org/packages/47/38/e547ea4f9a7c8c24a1d3a59869540029e4bad467f9544084c3cee94eb6e0/zxing_cpp-3.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:1992231c161c3eaf5857f7bc35193b8ca621eba0debc51e752403657b88d542a", size = 956213, upload-time = "2026-07-29T08:50:57.524Z" },
]