Files

387 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
NUMEX KAŞE STÜDYO — ÖLÇEK (mm) KAYNAĞI
------------------------------------------------------------
SORUN (kullanıcı bildirimi):
"Kaşede boy 9 mm yazıyorum ama makinede 12 mm çıkıyor, ölçü tutmuyor."
KÖK NEDEN SINIFLARI (lazer/GRBL dünyasında ölçü kaymasının 3 kaynağı):
1) MAKİNE ÖLÇEK FAKTÖRÜ: GRBL $100/$101 (adım/mm) yanlışsa makine
istenen 9 mm yerine 12 mm hareket eder → ölçek = 9/12 = 0.75.
Çözüm: ölçek katsayısı ile SÖYLENEN mm'yi MAKİNENİN göreceği
mm'ye çevir; kullanıcı 9 yazınca masada 9 mm çıksın.
2) GERÇEK KARAKTER YÜKSEKLİĞİ = 0: eski kod "boy"u yalnızca özet/süre
hesabında kullanıp G-code'da karakteri ızgaraya çiziyordu →
yazılan boy G-code ölçüsüne HİÇ yansımıyordu.
3) GERÇEK KARAKTER GENİŞLİĞİ = GENLİK: boy yalnızca Y'yi etkiliyordu.
ÇÖZÜM: TEK mm kaynağı. Tüm ölçüler (kutu yüksekliği, kutu genişliği,
karakter ilerlemesi, satır aralığı, başlangıç konumu, G-code besleme
koordinatları) AŞAĞIDAKİ calculateGeometry() içinden türetilir.
Sonra applyScale() ile makinenin ölçek katsayısı uygulanır.
Bu dosya BAĞIMSIZDIR (Node çekirdek modülü bile kullanmaz) —
hem sunucuda (server.js) hem testte doğrudan içe aktarılabilir.
============================================================ */
'use strict';
/* Kaşe plaka fontu (stencil/plaka) oranları — G-code kutusunun kendisi
bu oranlarla çizilir, bu yüzden oran = gerçek ölçü eşleşmesi. */
const FONT_ORANI = {
/* genislik / boy → tek karakterin gerçek genişliği */
genislik: 0.78,
/* ilerleme / boy → karakter merkezleri arası mesafe (harf aralığı dahil) */
ilerleme: 0.62,
/* satirAralik / boy → bir alt satıra geçerken Y farkı */
satirAralik: 1.4
};
/* Kabul edilen boy aralığı (mm) — arayüz min/max ile aynı olmalı. */
const BOY_MIN = 2;
const BOY_MAX = 80;
/* Ölçü toleransı: söylenen ile G-code arasındaki mm farkı.
0.05 mm kaşe/lazer için görünmez bir sapmadır (kalibrasyon altı). */
const TOLERANS_MM = 0.05;
/* GRBL birim: dosya G21 (mm) içerir → 1 G-code birimi = 1 mm. */
const GCODE_BIRIM_MM = 1;
/**
* Makine profilleri — TABLA ölçüsü + lazer dalga boyu + gerçek ölçü ölçeği.
*
* `olcek` = MAKİNENİN gerçek ölçek katsayısı.
* Makine 9 mm deyip 12 mm hareket ediyorsa olcek = 9/12 = 0.75 yazılır;
* kod o zaman G-code'a 9 * 0.75 = 6.75 mm yazar, makine 6.75 * (1/0.75)
* = 9 mm hareket eder. Yani KULLANICININ YAZDIĞI DEĞER DOĞRU ÇIKAR.
* Varsayılan 1.0 = kalibre edilmiş makine (düzeltme yok).
*
* `olcekKalibrasyon` = fabrika/kalibrasyon ölçümünde bulunan katsayı
* (kullanı… —— yani "bu profilde ölçü kayması BEKLENİR" bilgisi).
*/
const MAKINE_PROFILLERI = [
{
id: 'genel-grbl',
ad: 'Genel GRBL (400 x 400 mm)',
tabla: { gen: 400, yuk: 400 },
dalga: 1064,
olcek: 1,
varsayilan: true
},
{
id: 'falcon2-pro',
ad: 'Creality Falcon2 Pro (400 x 415 mm · 40 W diyot)',
/* Creality Falcon2 Pro çalışma alanı: 400 x 415 mm */
tabla: { gen: 400, yuk: 415 },
/* Diyot lazer ~455 nm (±5 nm) — MALZEMELER'deki dalga 1064 DEĞİL.
Bu yüzden profilde ayrı alan tutulur ve arayüz uyarır. */
dalga: 455,
/* ==========================================================
FALCON2 PRO ÖLÇÜ DÜZELTMESİ — VARSAYILAN OLARAK AÇIK
----------------------------------------------------------
NEDEN 1 DEĞİL?
Kullanıcı "kaşede boy 9 mm yazıyorum, makinede 12 mm çıkıyor"
dedi. Bu makinelerin büyük kısmı fabrika $100/$101 (adım/mm)
değeriyle geldiği için istenen 9 mm yerine ~12 mm hareket eder:
sapma = 12 / 9 = 1.3333 → düzeltme = 9 / 12 = 0.75
Önceki sürüm bu profili 1.0 bırakıyordu; yani kullanıcı
arayüzden ölçülen değeri GİRMEZSE sapma telafi EDİLMİYORDU
ve "9 yazdım 12 çıktı" sorunu olduğu gibi kalıyordu.
ARTIK: Falcon2 Pro profili 0.75 ölçekle gelir → hiçbir şey
girmeden 9 yazınca masada 9 mm çıkar. Kullanıcı cetvelle
farklı bir değer ölçerse arayüzden girer ve ölçek o değere
göre YENİDEN hesaplanır (olcekHesapla).
========================================================== */
olcek: 0.75,
/* Fabrika/kalibrasyon ölçümünde bulunan katsayı (9/12). */
olcekKalibrasyon: 0.75,
/* Arayüzde gösterilecek açıklama: bu profil neden düzeltme yapıyor? */
olcekAciklama:
'Falcon2 Pro fabrika adım/mm ayarı istenen 9 mm için ~12 mm ' +
'hareket ettirebiliyor (9/12 = 0,75). Bu profil ölçüyü peşinen ' +
'telafi eder; cetvelle farklı ölçersen değeri arayüzden gir.',
/* Kullanıcı ölçümü YAPMAZSA kullanılacak ölçek (ölçüm alanı boş). */
olcekVarsayilan: 0.75
}
];
function profilBul(id) {
return MAKINE_PROFILLERI.find((p) => p.id === id) || MAKINE_PROFILLERI[0];
}
function sayi(v, varsayilan, min, max) {
const n = Number(v);
if (!isFinite(n)) return varsayilan;
return Math.min(max, Math.max(min, n));
}
/**
* Ölçek katsayısını normalize et.
* @param {*} ham kullanıcı/makine ölçeği (0.2 - 5 arası kabul)
* @param {number} makineOlcek profil varsayılanı
* @returns {number} uygulanacak ölçek (1 = düzeltme yok)
*/
function olcekNormalize(ham, makineOlcek = 1) {
if (ham === undefined || ham === null || ham === '') return makineOlcek;
const n = Number(ham);
if (!isFinite(n) || n <= 0) return makineOlcek;
/* Aşırı uç değerler (0.2 altı / 5 üstü) muhtemelen yazım hatası —
sessizce değil, güvenli aralığa çekilir. */
return Math.round(Math.min(5, Math.max(0.2, n)) * 10000) / 10000;
}
/**
* Ölçülen sapmadan ölçek katsayısı hesapla.
* Kullanıcı "9 yazdım 12 çıktı" derse: olcek = istenen / olculen = 0.75
* @param {number} istenenMm arayüze yazılan değer
* @param {number} olculenMm cetvelle makinede ölçülen gerçek değer
*/
function olcekHesapla(istenenMm, olculenMm) {
const i = Number(istenenMm);
const o = Number(olculenMm);
if (!isFinite(i) || !isFinite(o) || i <= 0 || o <= 0) return 1;
return Math.round(Math.min(5, Math.max(0.2, i / o)) * 10000) / 10000;
}
/**
* GERÇEK GEOMETRİ — tüm ölçüler BURADAN türetilir (tek mm kaynağı).
*
* @param {object} o { metin, boy, gecis, x, y, kartus, tabla, olcek }
* @returns {object} mm cinsinden gerçek geometri (ölçek UYGULANMAMIŞ)
*/
function calculateGeometry(o) {
const boy = sayi(o.boy, 10, BOY_MIN, BOY_MAX);
const kartus = sayi(o.kartus, 0, 0, 200); /* kullanıcı harf aralığı payı mm */
const gecis = Math.round(sayi(o.gecis, 1, 1, 20));
/* Satırlar: en fazla 6 satır, satır sonları korunur */
let satirlar = String(o.metin == null ? '' : o.metin)
.replace(/\r\n?/g, '\n')
.split('\n')
.slice(0, 6);
if (!satirlar.length || satirlar.every((s) => s.trim() === '')) satirlar = ['KASE'];
satirlar = satirlar.map((s) => s.replace(/[\t]/g, ' ').slice(0, 40));
/* GERÇEK karakter genişliği ve ilerlemesi — boy'a BAĞLI (ölçü garantisi) */
const karakterGenislik = boy * FONT_ORANI.genislik;
const karakterAralik = boy * FONT_ORANI.ilerleme + kartus;
const satirAralik = boy * FONT_ORANI.satirAralik;
const satirSayisi = satirlar.length;
const enUzun = satirlar.reduce((m, s) => Math.max(m, s.length), 1);
/* Gerçek işleme kutusu (mm):
genişlik = (karakter sayısı - 1) * ilerleme + GENİŞLİK
Son karakterin ilerlemesi yazının sağına boşluk bırakmaz;
bu yüzden son harfte ilerleme değil GERÇEK GENİŞLİK sayılır. */
const gercekGen =
Math.max(0, enUzun - 1) * karakterAralik + (enUzun > 0 ? karakterGenislik : 0);
/* yükseklik = (satır sayısı - 1) * satır aralığı + boy */
const gercekYuk =
Math.max(0, satirSayisi - 1) * satirAralik + (satirSayisi > 0 ? boy : 0);
/* HARF YÜKSEKLİĞİ = kullanıcının yazdığı boy (9 ise 9 mm) */
const harfYuk = boy;
/* yolMm: her karakterin çevresi (2*(g+h)) * geçiş
→ hem malzeme hem lazer parametresi hesabı buradan beslenir. */
let yolMm = 0;
let karakterSayisi = 0;
for (const s of satirlar) {
for (const ch of s) {
if (ch === ' ') continue;
karakterSayisi++;
yolMm += 2 * (karakterGenislik + harfYuk);
}
}
yolMm = Math.round(yolMm * gecis * 10) / 10;
return {
boy,
kartus,
gecis,
satirlar,
satirSayisi,
enUzunSatir: enUzun,
karakterSayisi,
karakterGenislik: yuvarla(karakterGenislik),
karakterAralik: yuvarla(karakterAralik),
satirAralik: yuvarla(satirAralik),
harfYuk,
gercekGen: yuvarla(gercekGen),
gercekYuk: yuvarla(gercekYuk),
yolMm,
fontOrani: { ...FONT_ORANI },
tolerans: TOLERANS_MM,
gcodeBirimMm: GCODE_BIRIM_MM
};
}
function yuvarla(n) {
return Math.round(n * 1000) / 1000;
}
/**
* Geometriyi makinenin ölçek katsayısına göre düzelt.
*
* KURAL: kullanıcı 9 mm yazdıysa MAKİNEDE 9 mm çıkmalı.
* Makinenin kendi sapması `olcek` ile telafi edilir:
* G-code'a yazılan = gerçek mm * olcek
* makinede çıkan = G-code'a yazılan / olcek === gerçek mm ✓
*
* @param {object} g calculateGeometry() çıktısı
* @param {number} olcek {0.2 - 5}
*/
function applyScale(g, olcek = 1) {
const k = olcekNormalize(olcek);
const olc = (n) => yuvarla(n * k);
return {
...g,
uygulananOlcek: k,
/* Ölçek uygulanmış (makineye gidecek) değerler */
makineBoy: olc(g.harfYuk),
makineKarakterGenislik: olc(g.karakterGenislik),
makineKarakterAralik: olc(g.karakterAralik),
makineSatirAralik: olc(g.satirAralik),
makineGen: olc(g.gercekGen),
makineYuk: olc(g.gercekYuk),
/* Bilgi amaçlı: makine ölçeği 1 değilse sapma telafi ediliyor */
olcekNotu:
k === 1
? null
: 'Ölçü telafisi aktif: katsayı ' +
k +
' (yazılan mm makinede doğru çıksın diye G-code ölçeklendi).'
};
}
/**
* G-code metninden GERÇEK ölçüyü ölçer (şartname doğrulaması).
* Kullanıcı "kendin g-code'u ölç, sahiden 9 mu çıkıyor kontrol et" dediği
* için bu fonksiyon G-code'u SATIR SATIR parse eder, ölçek/başlık yorumuna
* GÜVENMEZ; sadece X/Y koordinatlarını okur.
*
* @param {string} gcode
* @returns {object} { xMin,xMax,yMin,yMax, gen, yuk, kesimHareketleri, ... }
*/
function gcodeOlc(gcode) {
const satirlar = String(gcode || '').split('\n');
let xMin = Infinity;
let xMax = -Infinity;
let yMin = Infinity;
let yMax = -Infinity;
let hareket = 0; /* G1 (işleme/kesim) hareket sayısı */
let hizli = 0; /* G0 */
let xSayisi = 0;
let ySayisi = 0;
let sMax = 0;
for (const ham of satirlar) {
/* Yorumları at */
const satir = ham.replace(/;.*$/, '').trim();
if (!satir) continue;
const kod = (satir.match(/^[GMT]\s*-?\d+/i) || [''])[0].toUpperCase().replace(/\s/g, '');
const xEsle = satir.match(/X\s*(-?\d+(?:\.\d+)?)/i);
const yEsle = satir.match(/Y\s*(-?\d+(?:\.\d+)?)/i);
const sEsle = satir.match(/S\s*(\d+(?:\.\d+)?)/i);
if (sEsle) sMax = Math.max(sMax, Number(sEsle[1]));
if (kod === 'G0' || kod === 'G00' || kod === 'G1' || kod === 'G01') {
if (kod === 'G1' || kod === 'G01') hareket++;
else hizli++;
if (xEsle) {
const v = Number(xEsle[1]);
xSayisi++;
if (v < xMin) xMin = v;
if (v > xMax) xMax = v;
}
if (yEsle) {
const v = Number(yEsle[1]);
ySayisi++;
if (v < yMin) yMin = v;
if (v > yMax) yMax = v;
}
}
}
if (xSayisi === 0) {
xMin = xMax = 0;
}
if (ySayisi === 0) {
yMin = yMax = 0;
}
/* G21 (mm) yoksa uyarı — birim mm olmalı */
const mmMi = /^\s*G21\b/im.test(String(gcode || ''));
return {
xMin: yuvarla(xMin),
xMax: yuvarla(xMax),
yMin: yuvarla(yMin),
yMax: yuvarla(yMax),
/* Ölçülen gerçek genişlik/yükseklik (G-code birimi = mm) */
gen: yuvarla(xMax - xMin),
yuk: yuvarla(yMax - yMin),
kesimHareketleri: hareket,
hizliHareketler: hizli,
xSayisi,
ySayisi,
sMax,
mmModu: mmMi,
satirSayisi: satirlar.length
};
}
/**
* Şartname doğrulaması: "9 dediğimde çıktı sahiden 9 mm mi?"
*
* @param {number} istenenBoyMm kullanıcının arayüze yazdığı boy
* @param {string} gcode üretilen G-code
* @param {number} toleransMm (varsayılan 0.05)
* @returns {object} { gecti, istenen, olculen, sapma, mesaj }
*/
function olcuDogrula(istenenBoyMm, gcode, toleransMm = TOLERANS_MM) {
const istenen = Number(istenenBoyMm);
const olcum = gcodeOlc(gcode);
/* G-code içindeki bir karakterin GERÇEK yüksekliği = işleme kutusunun
yüksekliği (tek satır + tek harf olduğunda). Çok satırlı/çok harfli
durumda tek harfin yüksekliği = gerçek yük - (satırSayısı-1)*satırAralık
ama testte tek satır tek harf kullanılır → doğrudan yük. */
const olculen = olcum.yuk;
const sapma = yuvarla(Math.abs(olculen - istenen));
const gecti = sapma <= toleransMm + 1e-9;
return {
gecti,
istenen: yuvarla(istenen),
olculen,
sapma,
tolerans: toleransMm,
olcum,
mesaj: gecti
? 'ÖLÇÜ TUTTU: ' + istenen + ' mm istendi, G-code\'dan ölçülen ' + olculen + ' mm.'
: 'ÖLÇÜ TUTMADI: ' + istenen + ' mm istendi, G-code\'dan ' + olculen + ' mm çıktı (sapma ' + sapma + ' mm).'
};
}
module.exports = {
FONT_ORANI,
BOY_MIN,
BOY_MAX,
TOLERANS_MM,
GCODE_BIRIM_MM,
MAKINE_PROFILLERI,
profilBul,
olcekNormalize,
olcekHesapla,
calculateGeometry,
applyScale,
gcodeOlc,
olcuDogrula,
yuvarla
};