Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 828471d27a | |||
| cf64a247be | |||
| 813b66d0c5 | |||
| 8c6d6c1630 | |||
| 1afaaa0bae |
@@ -0,0 +1,53 @@
|
|||||||
|
<#
|
||||||
|
Cosa fa: avvia TieMeasureFlow DA SORGENTE su una macchina di sviluppo Windows, senza la build
|
||||||
|
Nuitka (che richiede ~20 minuti): stessi quattro server e stesse porte dell'exe (8780 web,
|
||||||
|
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).
|
||||||
|
|
||||||
|
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
|
||||||
|
%PROGRAMDATA%\Tielogic\licenze\tiemeasureflow.json (impronta: avvio.py --impronta).
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[switch]$Sincronizza, # forza uv sync (dipendenze cambiate)
|
||||||
|
[string]$Ascolto = '127.0.0.1', # indirizzo di ascolto della UI web ($Host e' riservato in PowerShell)
|
||||||
|
[string]$EscludiCamere = '' # backend nativi da saltare (TieVision-Core#339): OECS se c'e' il runtime OECS ma in fiera si usa GenTL
|
||||||
|
)
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$radice = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
||||||
|
$env:UV_PROJECT_ENVIRONMENT = Join-Path $radice 'build\venv313' # stesso ambiente della build
|
||||||
|
$py = Join-Path $env:UV_PROJECT_ENVIRONMENT 'Scripts\python.exe'
|
||||||
|
|
||||||
|
if ($Sincronizza -or -not (Test-Path $py)) {
|
||||||
|
uv sync --python 3.13 --frozen --extra server --extra client --extra vision-worker --group windows
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "uv sync fallito" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# CSS e traduzioni: non sono nel repo, come in build.ps1
|
||||||
|
$flask = Join-Path $radice 'src\frontend\flask_app'
|
||||||
|
Push-Location $flask
|
||||||
|
try {
|
||||||
|
npx --yes tailwindcss@3.4.19 -i static/css/input.css -o static/css/tailwind.css --minify
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Tailwind fallito" }
|
||||||
|
& (Join-Path $env:UV_PROJECT_ENVIRONMENT 'Scripts\pybabel.exe') -q compile -d translations
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "pybabel compile fallito" }
|
||||||
|
} finally { Pop-Location }
|
||||||
|
|
||||||
|
# DLL di WeasyPrint fuori dalla radice (gtk\ nella radice non e' ignorata da git): build\gtk
|
||||||
|
$gtk = Join-Path $radice 'build\gtk'
|
||||||
|
if (-not (Test-Path (Join-Path $gtk '*.dll'))) {
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot 'pdf\prendi-gtk.ps1') -Destinazione $gtk
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "prendi-gtk.ps1 fallito" }
|
||||||
|
}
|
||||||
|
$env:WEASYPRINT_DLL_DIRECTORIES = $gtk
|
||||||
|
|
||||||
|
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
|
||||||
|
exit $LASTEXITCODE
|
||||||
@@ -208,7 +208,8 @@ function fmt(v, cifre) {
|
|||||||
return n.toLocaleString("it-IT", o);
|
return n.toLocaleString("it-IT", o);
|
||||||
}
|
}
|
||||||
// numero senza separatore delle migliaia, per le caselle di input
|
// numero senza separatore delle migliaia, per le caselle di input
|
||||||
function fmtCasella(v) { return Number(v).toLocaleString("it-IT", { maximumFractionDigits: 6, useGrouping: false }); }
|
// #87: cifre = decimali massimi; i parametri camera ne usano 1 (la camera rilegge 11999,993164 per 12000)
|
||||||
|
function fmtCasella(v, cifre) { return Number(v).toLocaleString("it-IT", { maximumFractionDigits: cifre === undefined ? 6 : cifre, useGrouping: false }); }
|
||||||
// legge un numero scritto con virgola o con punto
|
// legge un numero scritto con virgola o con punto
|
||||||
function leggiNumero(testo) {
|
function leggiNumero(testo) {
|
||||||
var n = Number(String(testo).split(" ").join("").replace(",", "."));
|
var n = Number(String(testo).split(" ").join("").replace(",", "."));
|
||||||
@@ -357,7 +358,7 @@ async function unaAnteprima(lato) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ciclo sequenziale: la richiesta dopo parte solo a risposta arrivata, almeno 300 ms dopo la precedente
|
// ciclo sequenziale: la richiesta dopo parte solo a risposta arrivata, almeno 100 ms dopo la precedente (#90: ~10 fps)
|
||||||
async function ciclo(g) {
|
async function ciclo(g) {
|
||||||
while (A.inVolo) await pausa(50);
|
while (A.inVolo) await pausa(50);
|
||||||
while (A.attiva && g === A.gen) {
|
while (A.attiva && g === A.gen) {
|
||||||
@@ -365,7 +366,7 @@ async function ciclo(g) {
|
|||||||
var esito = await unaAnteprima(1280);
|
var esito = await unaAnteprima(1280);
|
||||||
if (g !== A.gen) return;
|
if (g !== A.gen) return;
|
||||||
if (esito === "fatale") { fermaAnteprima(); return; }
|
if (esito === "fatale") { fermaAnteprima(); return; }
|
||||||
await pausa(esito === "errore" ? 1500 : Math.max(300 - (Date.now() - t0), 0));
|
await pausa(esito === "errore" ? 1500 : Math.max(100 - (Date.now() - t0), 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,20 +505,20 @@ function creaParametro(nome, d) {
|
|||||||
cur.disabled = bloccato;
|
cur.disabled = bloccato;
|
||||||
cur.oninput = function () { // il cursore aggiorna la casella subito, la scrittura arriva dopo 300 ms
|
cur.oninput = function () { // il cursore aggiorna la casella subito, la scrittura arriva dopo 300 ms
|
||||||
var v = adatta(d, daPosizione(d, Number(cur.value)));
|
var v = adatta(d, daPosizione(d, Number(cur.value)));
|
||||||
casella.value = fmtCasella(v);
|
casella.value = fmtCasella(v, 1);
|
||||||
modifica(nome, v);
|
modifica(nome, v);
|
||||||
};
|
};
|
||||||
campo.appendChild(cur);
|
campo.appendChild(cur);
|
||||||
}
|
}
|
||||||
casella.type = "text";
|
casella.type = "text";
|
||||||
casella.inputMode = "decimal";
|
casella.inputMode = "decimal";
|
||||||
casella.value = fmtCasella(d.valore);
|
casella.value = fmtCasella(d.valore, 1);
|
||||||
casella.disabled = bloccato;
|
casella.disabled = bloccato;
|
||||||
casella.onchange = function () {
|
casella.onchange = function () {
|
||||||
var v = leggiNumero(casella.value);
|
var v = leggiNumero(casella.value);
|
||||||
if (isNaN(v)) { dire("msgParametri", "Valore non valido per " + ETICHETTE[nome].toLowerCase() + ".", "errore"); casella.value = fmtCasella(P.dati[nome].valore); return; }
|
if (isNaN(v)) { dire("msgParametri", "Valore non valido per " + ETICHETTE[nome].toLowerCase() + ".", "errore"); casella.value = fmtCasella(P.dati[nome].valore, 1); return; }
|
||||||
v = adatta(d, v);
|
v = adatta(d, v);
|
||||||
casella.value = fmtCasella(v);
|
casella.value = fmtCasella(v, 1);
|
||||||
if (cur) cur.value = aPosizione(d, v);
|
if (cur) cur.value = aPosizione(d, v);
|
||||||
modifica(nome, v);
|
modifica(nome, v);
|
||||||
};
|
};
|
||||||
@@ -588,7 +589,7 @@ function aggiornaValori(j) {
|
|||||||
if (!el || P.pendenti.hasOwnProperty(nome)) return;
|
if (!el || P.pendenti.hasOwnProperty(nome)) return;
|
||||||
P.dati[nome].valore = d.valore;
|
P.dati[nome].valore = d.valore;
|
||||||
if (el.select) el.select.value = d.valore;
|
if (el.select) el.select.value = d.valore;
|
||||||
if (el.casella && document.activeElement !== el.casella) el.casella.value = fmtCasella(d.valore);
|
if (el.casella && document.activeElement !== el.casella) el.casella.value = fmtCasella(d.valore, 1);
|
||||||
if (el.cursore && document.activeElement !== el.cursore) el.cursore.value = aPosizione(P.dati[nome], d.valore);
|
if (el.cursore && document.activeElement !== el.cursore) el.cursore.value = aPosizione(P.dati[nome], d.valore);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,6 +169,12 @@ def create_app(folder: Path | str | None = None,
|
|||||||
sorgente = aperte.get(nome)
|
sorgente = aperte.get(nome)
|
||||||
if sorgente is None:
|
if sorgente is None:
|
||||||
sorgente = open_source(nome, None, config=_config())
|
sorgente = open_source(nome, None, config=_config())
|
||||||
|
# TieVision-Core#341 / #89: free-run, serve la scena di adesso, non il frame vecchio in coda
|
||||||
|
if hasattr(sorgente, "solo_ultimo_frame"):
|
||||||
|
try:
|
||||||
|
sorgente.solo_ultimo_frame()
|
||||||
|
except Exception as e: # noqa: BLE001 - producer senza il nodo: si apre lo stesso
|
||||||
|
_log.warning("frame piu' recente non impostabile su %s: %s", nome, e)
|
||||||
_carica_settaggi(nome, sorgente) # #87: predefiniti salvati, se ci sono
|
_carica_settaggi(nome, sorgente) # #87: predefiniti salvati, se ci sono
|
||||||
with lock:
|
with lock:
|
||||||
aperte[nome] = sorgente
|
aperte[nome] = sorgente
|
||||||
|
|||||||
@@ -278,6 +278,40 @@ def test_settaggi_rotti_non_bloccano_l_apertura(cartella, tmp_path, monkeypatch)
|
|||||||
assert c.get("/camera/parametri", params={"source": SORGENTE}).status_code == 200
|
assert c.get("/camera/parametri", params={"source": SORGENTE}).status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_apertura_chiede_il_frame_piu_recente(cartella, tmp_path, monkeypatch):
|
||||||
|
# #89: solo_ultimo_frame() una volta sola all'apertura, non a ogni richiesta
|
||||||
|
class ConUltimoFrame(Finta):
|
||||||
|
chiamate = 0
|
||||||
|
|
||||||
|
def solo_ultimo_frame(self, attivo=True):
|
||||||
|
self.chiamate += 1
|
||||||
|
|
||||||
|
finta = ConUltimoFrame()
|
||||||
|
with _client(cartella, tmp_path / "cfg", monkeypatch, finta) as c:
|
||||||
|
for _ in range(3):
|
||||||
|
assert c.get("/camera/parametri", params={"source": SORGENTE}).status_code == 200
|
||||||
|
assert finta.chiamate == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_frame_piu_recente_non_impostabile_non_blocca_l_apertura(cartella, tmp_path, monkeypatch, caplog):
|
||||||
|
# #89: producer senza il nodo -> avviso nel log, apertura riuscita
|
||||||
|
class SenzaNodo(Finta):
|
||||||
|
def solo_ultimo_frame(self, attivo=True):
|
||||||
|
raise CameraStateError("questo producer non espone StreamBufferHandlingMode")
|
||||||
|
|
||||||
|
with caplog.at_level("WARNING"):
|
||||||
|
with _client(cartella, tmp_path / "cfg", monkeypatch, SenzaNodo()) as c:
|
||||||
|
assert c.get("/camera/parametri", params={"source": SORGENTE}).status_code == 200
|
||||||
|
assert "frame piu' recente non impostabile" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_sorgente_senza_solo_ultimo_frame_si_apre(cartella, tmp_path, monkeypatch):
|
||||||
|
# #89: FolderCamera e simili non hanno il metodo: nessun errore
|
||||||
|
assert not hasattr(Finta(), "solo_ultimo_frame")
|
||||||
|
with _client(cartella, tmp_path / "cfg", monkeypatch, Finta()) as c:
|
||||||
|
assert c.get("/camera/parametri", params={"source": SORGENTE}).status_code == 200
|
||||||
|
|
||||||
|
|
||||||
# ---- pagina
|
# ---- pagina
|
||||||
|
|
||||||
def test_acquisizione_alias_della_pagina(client):
|
def test_acquisizione_alias_della_pagina(client):
|
||||||
|
|||||||
Vendored
+1
-1
Submodule vendor/visionsuite updated: e0d19dee7e...06b549a25d
Reference in New Issue
Block a user