costycnc.it — Documentazione tecnica

Il Problema del Percorso Unico
nel Taglia-Polistirolo a Filo Caldo

Algoritmo: extract() + Potrace modificato Autore: Boboaca Costel costycnc.it/algoritmo

La maggior parte dei software CNC lavora con un asse Z: quando il percorso finisce, la fresa si alza e salta al punto successivo. Il filo caldo non ha questo lusso.

Se il filo si sposta da un contorno all'altro senza seguire una linea del disegno, lascia una riga spuria visibile sul polistirolo. Il risultato è inaccettabile anche per usi semplici.

Per questo CostyCNC implementa un algoritmo proprietario che trasforma N curve chiuse indipendenti — quelle prodotte da Potrace — in un unico percorso continuo ottimizzato, senza sollevamenti e senza righe spurie.

Il problema fondamentale

Data un'immagine con più contorni (lettere, forme complesse), Potrace produce N path chiusi indipendenti. Per un taglia-polistirolo questi path devono diventare un unico filo ininterrotto che attraversa tutti i contorni nell'ordine ottimale, iniziando da ogni path nel punto più vicino al punto di arrivo del path precedente.


Questo è una variante del problema del Traveling Salesman Problem applicato a curve orientabili, risolto in modo pratico ed efficiente con un approccio greedy con rotazione dei path.

// 01

La pipeline completa

INPUT → Immagine BMP / PNG / JPG / SVG
POTRACE → Traccia i contorni neri → produce N path chiusi con coordinate Bézier
extract()Algoritmo proprietario CostyCNC
Concatena tutti i path in un unico percorso continuo
Per ogni path: trova il punto di ingresso ottimale (rotazione)
Minimizza la distanza tra fine di un path e inizio del successivo
getSVG1() → Converte il percorso unico in GCode G01
Scala con rapporto DPI × fattore utente
Aggiunge M3, G21, G90, G92, velocità F e potenza S
OUTPUT → GCode pronto per filo caldo, percorso garantito continuo
// 02

Il cuore dell'algoritmo: extract()

La funzione extract() risolve il problema centrale: dati N path chiusi, costruisce un unico percorso continuo partendo dall'origine (0,0).

Il punto chiave è che ogni path può essere percorso a partire da qualsiasi nodo della sua sequenza — è un ciclo. Quindi per ogni path scelto, l'algoritmo ruota la lista dei punti per iniziare dal nodo più vicino alla posizione corrente.

Cerca il punto più vicino

Per ogni path rimasto, e per ogni punto di quel path, calcola la distanza euclidea al quadrato dall'ultimo punto del percorso costruito.

Ruota il path

Una volta trovato il path più vicino e il suo punto di ingresso ottimale, la lista viene ruotata circolarmente per iniziare da quel punto. Questo elimina i salti lunghi.

Concatena e chiudi

Il path ruotato viene concatenato al percorso globale. Il loop viene chiuso aggiungendo il punto iniziale in fondo.

Campionamento adattivo

Per efficienza, la ricerca in pathx usa un passo adattivo pstep = max(10, len/100). Su path grandi questo garantisce velocità mantenendo qualità accettabile.

function extract(costyx) {

  // Parte dall'origine della macchina
  pathx = [[0, 0]];

  while (costyx.length) {

    // Passo adattivo: veloce su path grandi
    const pstep = Math.max(
      10,
      Math.floor(pathx.length / 100)
    );

    minDiff = Number.MAX_VALUE;

    // Cerca: per ogni punto in pathx (campionato)
    for (i = 0; i < pathx.length; i += pstep) {
      x = pathx[i][0];
      y = pathx[i][1];

      // Per ogni path rimasto e ogni suo punto
      for (m = 0; m < costyx.length; m++) {
        path = costyx[m];

        for (n = 0; n < path.length; n++) {
          x2 = x - path[n][0];
          y2 = y - path[n][1];
          currDiff = x2*x2 + y2*y2; // no sqrt, più veloce

          if (currDiff < minDiff) {
            minDiff = currDiff;
            pos0 = i;   // dove in pathx
            pat1 = m;   // quale path scegliere
            pos1 = n;   // da dove iniziare
          }
        }
      }
    }

    // Prende il path scelto e lo RUOTA
    // → inizia dal punto più vicino
    let p = costyx.splice(pat1, 1)[0];
    p = p.splice(pos1).concat(p);
    p.push(p[0]); // chiude il loop

    // Inserisce nel percorso globale
    pathx = pathx
      .slice(0, pos0)
      .concat(p, pathx.slice(pos0 - 1));
  }

  return pathx;
}
// 03

Perché questo approccio è corretto per il filo caldo

Il taglia-polistirolo a filo caldo ha una caratteristica unica: ogni spostamento del filo lascia una traccia. Non esiste un "movimento in aria" come nelle fresatrici CNC con asse Z.

Questo significa che l'obiettivo non è semplicemente minimizzare la distanza totale del percorso — è garantire che i punti di giunzione tra path diversi siano fisicamente adiacenti, così la riga di collegamento è invisibile o coincide con una linea già presente nel disegno.

La rotazione del path risolve esattamente questo: invece di "saltare" dall'ultimo punto di un path all'inizio arbitrario del successivo, il filo si connette al punto fisicamente più vicino del path successivo.

Il vantaggio della rotazione

Un path chiuso può iniziare da qualsiasi nodo della sua sequenza. Sfruttare questa proprietà permette di ridurre drasticamente la distanza di collegamento tra path consecutivi — in molti casi portandola a zero quando i path si toccano o si sovrappongono (come nelle lettere).

path A path B percorso unico continuo
// 04

Confronto con altri approcci

Approccio
Risultato sul polistirolo
Complessità per l'utente
Nearest-neighbor greedy puro
Salti tra path non ottimizzati → righe spurie
Automatico
Software CNC standard (con Z)
Non applicabile: presuppone sollevamento fresa
Richiede post-processing manuale
Eulerian path (ideale teorico)
Percorso ottimale, zero righe spurie
Complessità O(n³), troppo lento per uso web
CostyCNC extract() ✦
Giunzioni ottimizzate per rotazione → righe minime
Completamente automatico, funziona nel browser
// 05

Potrace modificato: integrazione con il flusso GCode

CostyCNC non usa Potrace come black-box. La libreria è stata modificata e integrata per esporre i path intermedi come array di coordinate, intercettando il flusso prima della generazione SVG. Questo permette di:

Accedere ai path grezzi

I path Bézier di Potrace vengono intercettati prima della serializzazione SVG e passati direttamente ad extract().

Ricostruire il path unificato

Il risultato di extract() sostituisce la lista di path originale con un singolo path ottimizzato.

Scalare con il rapporto corretto

La funzione getSVG1() applica il rapporto DPI × fattore utente per ottenere dimensioni fisiche reali in millimetri.

Generare GCode diretto

Ogni nodo del percorso finale diventa un comando G01 Xx Yy. Il GCode include M3, velocità F e potenza filo S.

Prova l'algoritmo in azione

CostyCNC è gratuito, funziona nel browser, non richiede installazioni. Carica un'immagine e vedi il GCode generato in tempo reale.