Migliorare getSVG1() - Campionamento adattivo delle curve Bézier

Come convertire le curve Bézier di Potrace in G-code fedele usando il campionamento adattivo con tabella precalcolata (base 8 punti, massimo 24).

Questo tutorial spiega come modificare la funzione getSVG1() di CostyCNC per ottenere un percorso G-code che segue davvero la curva, invece di collegare linearmente i punti di controllo.

Il problema

La funzione getSVG1() di CostyCNC converte i contorni generati da Potrace in G-code. Tuttavia, la versione attuale ha un limite: prende i 3 punti di controllo della Bézier e li collega con 3 comandi G01 lineari.

Geometricamente, questo non è la curva: è una spezzata che passa per i punti di controllo. La curva reale passa accanto a quei punti, non attraverso di essi.

Il risultato è un percorso G-code che non segue fedelmente la curva originale di Potrace. Per il filo caldo, questo può causare imprecisioni visibili.

La soluzione: campionamento adattivo con tabella

Invece di usare un numero fisso di punti (es. 8), si decide in base alla lunghezza del segmento: più il segmento è lungo, più punti servono.

La formula è:

N = clamp( ceil(lunghezza / tolleranza), N_min, N_max )

Dove:

Questo approccio adatta automaticamente il numero di punti a ogni segmento, ottimizzando qualità e dimensione del file G-code.

Perché usare una tabella precalcolata

Invece di calcolare la formula di de Casteljau a ogni punto (con potenze e moltiplicazioni), si possono usare coefficienti precalcolati per ogni valore di t.

ApproccioCalcoli per puntoVelocitàPrecisione
Formula di de Casteljau4 moltiplicazioni + 3 somme + 3 potenzeLentaAlta
Tabella precalcolata4 moltiplicazioni + 3 sommeVeloceAlta
La tabella elimina le potenze (mt*mt*mt, t*t*t), che sono la parte più costosa. Con la tabella fai solo:

x = c0*P0x + c1*P1x + c2*P2x + c3*P3x

Dove c0, c1, c2, c3 sono già pronti.

Come calcolare la lunghezza del segmento

La lunghezza approssimata della Bézier si stima con la distanza tra i punti di controllo (poligono di controllo):

L ≈ |P0-P1| + |P1-P2| + |P2-P3|

Questa è una sovrastima della lunghezza reale, ma è veloce da calcolare e va benissimo per decidere N.

Il codice completo

Sostituisci la funzione bezier(i) dentro getSVG1() con questa versione, che usa la tabella precalcolata:

// Tabella dei coefficienti per t = 1/8, 2/8, ..., 7/8
// (valori arrotondati a 4 decimali)
var coeffs8 = [
    [0.6699, 0.2871, 0.0410, 0.0020],
    [0.4219, 0.4219, 0.1406, 0.0156],
    [0.2441, 0.4395, 0.2637, 0.0527],
    [0.1250, 0.3750, 0.3750, 0.1250],
    [0.0527, 0.2637, 0.4395, 0.2441],
    [0.0156, 0.1406, 0.4219, 0.4219],
    [0.0020, 0.0410, 0.2871, 0.6699]
];

function bezier(i) {
    var b = "";

    // P0 = punto finale del segmento precedente
    var P0x, P0y;
    if (i === 0) {
        P0x = curve.c[(curve.n - 1) * 3 + 2].x * sizex;
        P0y = curve.c[(curve.n - 1) * 3 + 2].y * sizey;
    } else {
        P0x = curve.c[(i - 1) * 3 + 2].x * sizex;
        P0y = curve.c[(i - 1) * 3 + 2].y * sizey;
    }

    var P1x = curve.c[i * 3 + 0].x * sizex;
    var P1y = curve.c[i * 3 + 0].y * sizey;
    var P2x = curve.c[i * 3 + 1].x * sizex;
    var P2y = curve.c[i * 3 + 1].y * sizey;
    var P3x = curve.c[i * 3 + 2].x * sizex;
    var P3y = curve.c[i * 3 + 2].y * sizey;

    // --- Lunghezza approssimata (poligono di controllo) ---
    var d01 = Math.sqrt((P1x-P0x)*(P1x-P0x) + (P1y-P0y)*(P1y-P0y));
    var d12 = Math.sqrt((P2x-P1x)*(P2x-P1x) + (P2y-P1y)*(P2y-P1y));
    var d23 = Math.sqrt((P3x-P2x)*(P3x-P2x) + (P3y-P2y)*(P3y-P2y));
    var lunghezza = d01 + d12 + d23;

    // --- Configurazione ---
    var N_base     = 8;      // valore di riferimento
    var N_min      = 4;      // minimo assoluto
    var N_max      = 24;     // MASSIMO ASSOLUTO
    var tolleranza = 0.2;    // mm (regola in base alla tua macchina)

    // --- Calcola N in base alla lunghezza ---
    var N;
    if (lunghezza < 1.0) {
        N = N_min;          // curve molto corte: 4 punti
    } else if (lunghezza < 5.0) {
        N = N_base;         // curve medie: 8 punti
    } else {
        N = Math.ceil(lunghezza / tolleranza);
        if (N < N_base) N = N_base;
        if (N > N_max)  N = N_max;
    }

    // --- Se N non è 8, usa la formula di de Casteljau ---
    // (per N=8 usiamo la tabella precalcolata)
    if (N === 8) {
        // Tabella precalcolata (più veloce)
        for (var k = 0; k < coeffs8.length; k++) {
            var c = coeffs8[k];
            var x = c[0]*P0x + c[1]*P1x + c[2]*P2x + c[3]*P3x;
            var y = c[0]*P0y + c[1]*P1y + c[2]*P2y + c[3]*P3y;
            b += 'G01 X' + x.toFixed(3) + ' Y' + y.toFixed(3) + '\n';
        }
    } else {
        // Formula di de Casteljau (per N diverso da 8)
        for (var j = 1; j <= N; j++) {
            var t = j / N;
            var mt = 1 - t;
            var x = mt*mt*mt * P0x + 3*mt*mt*t * P1x + 3*mt*t*t * P2x + t*t*t * P3x;
            var y = mt*mt*mt * P0y + 3*mt*mt*t * P1y + 3*mt*t*t * P2y + t*t*t * P3y;
            b += 'G01 X' + x.toFixed(3) + ' Y' + y.toFixed(3) + '\n';
        }
    }

    // Punto finale
    b += 'G01 X' + P3x.toFixed(3) + ' Y' + P3y.toFixed(3) + '\n';
    return b;
}

Come scegliere la tolleranza

TolleranzaEffettoQuando usarla
0.05 mmAltissima fedeltà, file grandeLavori di precisione
0.1 mmOttima fedeltàLavori normali
0.2 mmBuona fedeltà, file compattoFilo caldo (consigliato)
0.5 mmFedeltà media, file piccoloBozze, lavori grezzi
1.0 mmBassa fedeltàSolo per test
Per il filo caldo, 0.2 mm è il valore ideale: la curva è fluida, il file non esplode, e la macchina non ha problemi a seguire i punti.

Comportamento risultante

Lunghezza curvaN puntiNote
< 1 mm4Minimo (curve molto corte)
1-5 mm8Base (curve medie) - usa tabella
5-10 mm10-24Adattivo (formula)
> 10 mm24Massimo (curve lunghe)

Vantaggi rispetto a un numero fisso di punti

ApproccioPunti per curvaAdattivo?Velocità
8 punti fissi8 sempre❌ NoMedia
Tabella + adattivo4-24✅ SìAlta
Formula pura4-24✅ SìBassa

Bonus: adattivo anche sulla curvatura

Se vuoi il massimo, puoi combinare lunghezza e curvatura:

// Stima della curvatura: quanto si discosta il punto medio della curva
// dalla corda P0-P3
var mx = 0.125*P0x + 0.375*P1x + 0.375*P2x + 0.125*P3x;
var my = 0.125*P0y + 0.375*P1y + 0.375*P2y + 0.125*P3y;
var cx = (P0x + P3x) / 2;
var cy = (P0y + P3y) / 2;
var curvatura = Math.sqrt((mx-cx)*(mx-cx) + (my-cy)*(my-cy));

// Punti in base a lunghezza E curvatura (sempre max 24)
var N = Math.ceil((lunghezza + curvatura * 4) / tolleranza);
if (N < 4)  N = 4;
if (N > 24) N = 24;

Questo aggiunge punti extra solo dove la curva è più "curva", ottimizzando ancora di più, ma senza mai superare i 24 punti.

Riepilogo

Sostituisci bezier(i) in getSVG1() con la versione basata su tabella precalcolata (per N=8) e formula di de Casteljau (per N≠8).

Vincoli: minimo 4 punti, massimo 24, base 8. Regola la tolleranza in base alle tue esigenze (consigliato 0.2 mm per il filo caldo).

Il risultato è un G-code che segue fedelmente le curve di Potrace, con file più compatti e qualità uniforme.

Need more help?

Sito CostyCNC GitHub