1913 lines
76 KiB
HTML
1913 lines
76 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="tr" data-tema="koyu">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Numex Kaşe Stüdyo — Lazer Markalama</title>
|
||
<style>
|
||
/* ============================================================
|
||
NUMEX KAŞE STÜDYO
|
||
Profesyonel lazer markalama arayüzü.
|
||
Tema: koyu (dar) + beyaz (açık) — tek tuşla geçiş.
|
||
Tabla: 400 × 400 mm (40 × 40 cm)
|
||
============================================================ */
|
||
|
||
/* ---------- KOYU TEMA (varsayılan) ---------- */
|
||
html[data-tema="koyu"]{
|
||
--bg:#12151a;
|
||
--bg2:#0d1014;
|
||
--panel:#191d24;
|
||
--panel2:#212631;
|
||
--panel3:#2a303c;
|
||
--line:#2e3542;
|
||
--line2:#3a4252;
|
||
--txt:#e8ebf0;
|
||
--txt2:#a8b0bd;
|
||
--dim:#79828f;
|
||
--accent:#ff7a18;
|
||
--accent-d:#e06400;
|
||
--accent-soft:rgba(255,122,24,.14);
|
||
--ok:#34d399;
|
||
--ok-soft:rgba(52,211,153,.14);
|
||
--red:#ff5252;
|
||
--red-soft:rgba(255,82,82,.15);
|
||
--blue:#4d9dff;
|
||
--shadow:0 8px 28px rgba(0,0,0,.5);
|
||
--grid:#242a34;
|
||
--grid2:#1c222c;
|
||
--mat:#171b22;
|
||
--mat-edge:#39424f;
|
||
}
|
||
|
||
/* ---------- BEYAZ TEMA ---------- */
|
||
html[data-tema="beyaz"]{
|
||
--bg:#eef1f5;
|
||
--bg2:#e4e8ee;
|
||
--panel:#ffffff;
|
||
--panel2:#f6f8fa;
|
||
--panel3:#eceff4;
|
||
--line:#d8dee6;
|
||
--line2:#c3ccd8;
|
||
--txt:#1a1f27;
|
||
--txt2:#4d5866;
|
||
--dim:#7b8695;
|
||
--accent:#e86a00;
|
||
--accent-d:#c95a00;
|
||
--accent-soft:rgba(232,106,0,.12);
|
||
--ok:#0f9d63;
|
||
--ok-soft:rgba(15,157,99,.12);
|
||
--red:#dc2626;
|
||
--red-soft:rgba(220,38,38,.11);
|
||
--blue:#2563eb;
|
||
--shadow:0 6px 22px rgba(20,30,45,.13);
|
||
--grid:#dee4ec;
|
||
--grid2:#e9edf2;
|
||
--mat:#fbfcfd;
|
||
--mat-edge:#b8c2d0;
|
||
}
|
||
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
html,body{height:100%}
|
||
|
||
body{
|
||
background:var(--bg);
|
||
color:var(--txt);
|
||
font-family:"Segoe UI",Roboto,-apple-system,system-ui,sans-serif;
|
||
font-size:13px;
|
||
line-height:1.45;
|
||
overflow:hidden;
|
||
-webkit-font-smoothing:antialiased;
|
||
transition:background .22s ease,color .22s ease;
|
||
}
|
||
|
||
/* ---------- ÜST BAR ---------- */
|
||
header{
|
||
height:52px;
|
||
display:flex;
|
||
align-items:center;
|
||
gap:10px;
|
||
padding:0 14px;
|
||
background:var(--panel);
|
||
border-bottom:1px solid var(--line);
|
||
flex-shrink:0;
|
||
user-select:none;
|
||
}
|
||
.brand{display:flex;align-items:center;gap:9px;padding-right:14px;border-right:1px solid var(--line)}
|
||
.brand-mark{
|
||
width:30px;height:30px;border-radius:8px;
|
||
background:linear-gradient(135deg,var(--accent),var(--accent-d));
|
||
display:grid;place-items:center;
|
||
box-shadow:0 3px 10px var(--accent-soft);
|
||
}
|
||
.brand-mark svg{width:17px;height:17px}
|
||
.brand-txt strong{display:block;font-size:13.5px;font-weight:700;letter-spacing:.2px}
|
||
.brand-txt span{display:block;font-size:10px;color:var(--dim);letter-spacing:.4px;text-transform:uppercase}
|
||
header .grow{flex:1}
|
||
|
||
/* Bayatlayan makine durumu — sağ üst */
|
||
.durum-pil{
|
||
display:flex;align-items:center;gap:7px;
|
||
padding:5px 11px;border-radius:20px;
|
||
background:var(--panel2);border:1px solid var(--line);
|
||
font-size:11.5px;color:var(--txt2);font-weight:600;
|
||
}
|
||
.durum-pil i{
|
||
width:7px;height:7px;border-radius:50%;
|
||
background:var(--dim);display:block;flex-shrink:0;
|
||
}
|
||
.durum-pil.hazir i{background:var(--ok);box-shadow:0 0 8px var(--ok)}
|
||
.durum-pil.calisiyor i{background:var(--accent);animation:nabiz .9s infinite}
|
||
@keyframes nabiz{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.45;transform:scale(.8)}}
|
||
|
||
/* ---------- BUTONLAR ---------- */
|
||
button{
|
||
font-family:inherit;font-size:12.5px;font-weight:600;
|
||
color:var(--txt);background:var(--panel2);
|
||
border:1px solid var(--line);border-radius:7px;
|
||
padding:7px 13px;cursor:pointer;
|
||
transition:background .13s,border-color .13s,transform .07s,color .13s;
|
||
white-space:nowrap;
|
||
display:inline-flex;align-items:center;justify-content:center;gap:6px;
|
||
}
|
||
button:hover{background:var(--panel3);border-color:var(--line2)}
|
||
button:active{transform:translateY(1px)}
|
||
button:disabled{opacity:.42;cursor:not-allowed;transform:none}
|
||
button svg{width:14px;height:14px;flex-shrink:0}
|
||
|
||
button.birincil{
|
||
background:var(--accent);border-color:var(--accent);color:#fff;
|
||
box-shadow:0 2px 10px var(--accent-soft);
|
||
}
|
||
button.birincil:hover{background:var(--accent-d);border-color:var(--accent-d)}
|
||
button.hayalet{background:transparent;border-color:var(--line)}
|
||
button.hayalet:hover{background:var(--panel2)}
|
||
button.ikon{padding:7px;width:34px}
|
||
button.tehlike{color:var(--red)}
|
||
button.tehlike:hover{background:var(--red-soft);border-color:var(--red)}
|
||
button.buyuk{padding:11px 18px;font-size:13.5px;border-radius:9px}
|
||
button.mini{padding:4px 9px;font-size:11.5px;border-radius:6px}
|
||
|
||
/* ---------- LAZER BAĞLANTI GÖSTERGESİ ----------
|
||
Kapalı: gri nokta · Bağlı: yeşil yanıp sönen nokta.
|
||
Kullanıcı bir bakışta "makinaya bağlı mıyım?" sorusunu yanıtlar. */
|
||
.lazer-nokta{
|
||
width:8px;height:8px;border-radius:50%;
|
||
background:var(--line);display:inline-block;margin-left:2px;
|
||
transition:background .25s;
|
||
}
|
||
.lazer-nokta.bagli{
|
||
background:#2ecc71;
|
||
box-shadow:0 0 0 0 rgba(46,204,113,.7);
|
||
animation:lazerNabiz 1.8s infinite;
|
||
}
|
||
@keyframes lazerNabiz{
|
||
0%{box-shadow:0 0 0 0 rgba(46,204,113,.7)}
|
||
70%{box-shadow:0 0 0 7px rgba(46,204,113,0)}
|
||
100%{box-shadow:0 0 0 0 rgba(46,204,113,0)}
|
||
}
|
||
#btnLazer.bagli{border-color:#2ecc71;color:#2ecc71}
|
||
|
||
/* ---------- ANA YERLEŞİM ---------- */
|
||
.app{display:flex;height:calc(100% - 52px);overflow:hidden}
|
||
|
||
.yancol{
|
||
background:var(--panel);
|
||
display:flex;flex-direction:column;
|
||
overflow-y:auto;overflow-x:hidden;
|
||
flex-shrink:0;
|
||
}
|
||
.yancol.sol{width:264px;border-right:1px solid var(--line)}
|
||
.yancol.sag{width:288px;border-left:1px solid var(--line)}
|
||
|
||
.yancol::-webkit-scrollbar{width:9px}
|
||
.yancol::-webkit-scrollbar-track{background:transparent}
|
||
.yancol::-webkit-scrollbar-thumb{background:var(--line2);border-radius:5px;border:2px solid var(--panel)}
|
||
.yancol::-webkit-scrollbar-thumb:hover{background:var(--dim)}
|
||
|
||
/* ---------- BÖLÜM ---------- */
|
||
.bolum{border-bottom:1px solid var(--line);padding:13px 14px}
|
||
.bolum:last-child{border-bottom:none}
|
||
.bolum-baslik{
|
||
display:flex;align-items:center;gap:7px;
|
||
font-size:10.5px;font-weight:700;letter-spacing:.9px;
|
||
text-transform:uppercase;color:var(--dim);
|
||
margin-bottom:11px;
|
||
}
|
||
.bolum-baslik::after{content:"";flex:1;height:1px;background:var(--line)}
|
||
|
||
/* ---------- FORM ELEMANLARI ---------- */
|
||
label{display:block;font-size:11px;color:var(--txt2);margin-bottom:4px;font-weight:600}
|
||
input[type=text],input[type=number],select,textarea{
|
||
width:100%;font-family:inherit;font-size:12.5px;
|
||
color:var(--txt);background:var(--panel2);
|
||
border:1px solid var(--line);border-radius:7px;
|
||
padding:7px 9px;outline:none;
|
||
transition:border-color .13s,box-shadow .13s;
|
||
}
|
||
input:focus,select:focus,textarea:focus{
|
||
border-color:var(--accent);
|
||
box-shadow:0 0 0 3px var(--accent-soft);
|
||
}
|
||
input[type=number]{-moz-appearance:textfield}
|
||
textarea{resize:vertical;min-height:62px;line-height:1.5}
|
||
|
||
select{
|
||
appearance:none;
|
||
background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2379838f' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
|
||
background-repeat:no-repeat;background-position:right 8px center;background-size:13px;
|
||
padding-right:28px;cursor:pointer;
|
||
}
|
||
|
||
/* Kaydırıcı */
|
||
input[type=range]{
|
||
-webkit-appearance:none;appearance:none;
|
||
width:100%;height:5px;border-radius:3px;
|
||
background:var(--panel3);outline:none;cursor:pointer;margin:7px 0;
|
||
}
|
||
input[type=range]::-webkit-slider-thumb{
|
||
-webkit-appearance:none;appearance:none;
|
||
width:16px;height:16px;border-radius:50%;
|
||
background:var(--accent);border:2px solid var(--panel);
|
||
cursor:pointer;box-shadow:0 1px 6px rgba(0,0,0,.35);
|
||
}
|
||
input[type=range]::-moz-range-thumb{
|
||
width:14px;height:14px;border-radius:50%;
|
||
background:var(--accent);border:2px solid var(--panel);cursor:pointer;
|
||
}
|
||
|
||
/* Satır / ızgara */
|
||
.satir{display:grid;gap:9px;margin-bottom:9px}
|
||
.satir.iki{grid-template-columns:1fr 1fr}
|
||
.satir.uc{grid-template-columns:1fr 1fr 1fr}
|
||
.satir:last-child{margin-bottom:0}
|
||
|
||
/* Onay kutusu */
|
||
.onay{display:flex;align-items:center;gap:8px;padding:5px 0;cursor:pointer;font-size:12px}
|
||
/* Konum göstergesi — okunur, girilmez (LightBurn mantığı) */
|
||
.konum-gosterge{background:rgba(255,255,255,.03);border:1px solid var(--line);border-radius:6px;padding:7px 9px;margin:6px 0;font-family:ui-monospace,Consolas,monospace;font-size:11px}
|
||
.kg-satir{display:flex;justify-content:space-between;align-items:center;line-height:1.8}
|
||
.kg-etiket{color:var(--dim);font-size:10px;text-transform:uppercase;letter-spacing:.4px}
|
||
.kg-deger{color:var(--accent);font-weight:600}
|
||
.onay input{display:none}
|
||
.onay .kutu{
|
||
width:16px;height:16px;border-radius:5px;flex-shrink:0;
|
||
border:1.5px solid var(--line2);background:var(--panel2);
|
||
display:grid;place-items:center;transition:all .14s;
|
||
}
|
||
.onay .kutu svg{width:10px;height:10px;stroke:#fff;stroke-width:3.2;fill:none;opacity:0;transition:opacity .14s}
|
||
.onay input:checked + .kutu{background:var(--accent);border-color:var(--accent)}
|
||
.onay input:checked + .kutu svg{opacity:1}
|
||
.onay span{color:var(--txt2);user-select:none}
|
||
.onay:hover .kutu{border-color:var(--accent)}
|
||
|
||
/* ---------- ORTA: TABLA ---------- */
|
||
.orta{
|
||
flex:1;display:flex;flex-direction:column;
|
||
background:var(--bg2);overflow:hidden;min-width:0;
|
||
}
|
||
|
||
.arac-seridi{
|
||
height:42px;display:flex;align-items:center;gap:6px;
|
||
padding:0 12px;background:var(--panel);
|
||
border-bottom:1px solid var(--line);flex-shrink:0;
|
||
}
|
||
.arac-seridi .ayrac{width:1px;height:20px;background:var(--line);margin:0 4px}
|
||
.arac-seridi .grow{flex:1}
|
||
.zoom-etiket{
|
||
font-size:11px;color:var(--dim);font-weight:600;
|
||
min-width:44px;text-align:center;
|
||
}
|
||
|
||
/* Tabla sarmalayıcı */
|
||
.tabla-alan{
|
||
flex:1;position:relative;overflow:hidden;
|
||
display:grid;place-items:center;padding:20px;
|
||
}
|
||
.tabla-kap{
|
||
position:relative;
|
||
box-shadow:var(--shadow);
|
||
border-radius:4px;
|
||
background:var(--mat);
|
||
border:1px solid var(--mat-edge);
|
||
transform-origin:center center;
|
||
transition:transform .12s ease-out;
|
||
}
|
||
#tabla{display:block;border-radius:3px;cursor:crosshair}
|
||
|
||
/* Tabla üstü cetvel köşesi */
|
||
.tabla-rozet{
|
||
position:absolute;top:-1px;left:-1px;
|
||
display:flex;gap:1px;z-index:5;
|
||
pointer-events:none;
|
||
}
|
||
.tabla-rozet b{
|
||
background:var(--panel2);border:1px solid var(--line);
|
||
color:var(--txt2);font-size:9.5px;font-weight:700;
|
||
padding:2px 6px;border-radius:0 0 5px 0;letter-spacing:.3px;
|
||
}
|
||
|
||
/* Alt durum çubuğu */
|
||
.durum-cubugu{
|
||
height:30px;display:flex;align-items:center;gap:14px;
|
||
padding:0 14px;background:var(--panel);
|
||
border-top:1px solid var(--line);flex-shrink:0;
|
||
font-size:11px;color:var(--dim);font-weight:600;
|
||
}
|
||
.durum-cubugu .nokta{color:var(--accent)}
|
||
.durum-cubugu .grow{flex:1}
|
||
.durum-cubugu kbd{
|
||
font-family:inherit;background:var(--panel2);border:1px solid var(--line);
|
||
border-radius:4px;padding:1px 5px;font-size:10px;color:var(--txt2);
|
||
}
|
||
|
||
/* ============================================================
|
||
İŞ ONAY PANELİ (modal)
|
||
confirm() yerine sayfa içi panel: async güvenli, üç net seçenek.
|
||
============================================================ */
|
||
.is-modal{
|
||
position:fixed;inset:0;z-index:200;
|
||
background:rgba(8,10,14,.62);
|
||
backdrop-filter:blur(3px);
|
||
display:flex;align-items:center;justify-content:center;
|
||
padding:20px;
|
||
}
|
||
.is-modal[hidden]{display:none}
|
||
|
||
.is-modal-kutu{
|
||
width:min(520px,96vw);max-height:88vh;overflow:auto;
|
||
background:var(--panel);border:1px solid var(--line);
|
||
border-radius:12px;box-shadow:0 18px 60px rgba(0,0,0,.45);
|
||
display:flex;flex-direction:column;
|
||
}
|
||
|
||
.is-modal-baslik{
|
||
padding:13px 18px;border-bottom:1px solid var(--line);
|
||
font-size:12px;font-weight:800;letter-spacing:1.2px;
|
||
color:var(--accent);
|
||
}
|
||
|
||
.is-modal-govde{
|
||
padding:15px 18px;font-size:12px;line-height:1.75;
|
||
color:var(--txt2);white-space:pre-wrap;font-family:inherit;
|
||
flex:1;
|
||
}
|
||
.is-modal-govde b{color:var(--txt)}
|
||
|
||
.is-modal-eylem{
|
||
display:flex;flex-direction:column;gap:8px;
|
||
padding:0 18px 16px;
|
||
}
|
||
.is-btn{
|
||
width:100%;padding:11px 14px;border-radius:8px;cursor:pointer;
|
||
font-family:inherit;font-size:12.5px;font-weight:700;
|
||
border:1px solid var(--line);background:var(--panel2);color:var(--txt);
|
||
transition:filter .12s,transform .06s;
|
||
}
|
||
.is-btn:hover:not(:disabled){filter:brightness(1.12)}
|
||
.is-btn:active:not(:disabled){transform:translateY(1px)}
|
||
.is-btn:disabled{opacity:.42;cursor:not-allowed}
|
||
|
||
.is-gonder{
|
||
background:var(--accent);color:#06131f;border-color:transparent;
|
||
font-size:13.5px;padding:13px 14px;
|
||
}
|
||
.is-indir{background:var(--panel2)}
|
||
.is-iptal{background:transparent;color:var(--dim)}
|
||
|
||
.is-modal-not{
|
||
padding:0 18px 16px;font-size:10.5px;line-height:1.6;color:var(--dim);
|
||
}
|
||
|
||
/* Dokunma hedefi WCAG 2.5.5: butonlar >=44px */
|
||
.is-btn{min-height:44px}
|
||
.is-modal-eylem{padding-bottom:18px}
|
||
|
||
/* Küçük ekranda modal tam genişlik ve kaydırılabilir */
|
||
@media (max-width:560px){
|
||
.is-modal{padding:10px;align-items:flex-end}
|
||
.is-modal-kutu{
|
||
width:100%;max-height:92vh;border-radius:12px 12px 0 0;
|
||
}
|
||
.is-modal-govde{font-size:11.5px;line-height:1.65;padding:13px 15px}
|
||
.is-modal-baslik{padding:12px 15px;font-size:11px}
|
||
.is-modal-eylem{padding:0 15px 15px}
|
||
.is-modal-not{padding:0 15px 15px}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ================= ÜST BAR ================= -->
|
||
<header>
|
||
<div class="brand">
|
||
<div class="brand-mark">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M12 2v6"/><path d="M9 8h6"/><rect x="7" y="11" width="10" height="9" rx="2"/>
|
||
<path d="M12 20v2"/><path d="M4 22h16"/>
|
||
</svg>
|
||
</div>
|
||
<div class="brand-txt">
|
||
<strong>Numex Kaşe</strong>
|
||
<span>Lazer Markalama Stüdyosu</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="durum-pil" id="durumPil" title="Lazer bağlantı durumu">
|
||
<i></i><span id="durumYazi">Makine hazır</span>
|
||
</div>
|
||
|
||
<div class="grow"></div>
|
||
|
||
<button class="hayalet" id="btnTema" title="Koyu / Beyaz tema geçişi">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/>
|
||
</svg>
|
||
<span id="temaEtiket">Beyaz Tema</span>
|
||
</button>
|
||
|
||
<button class="hayalet" id="btnTemizle">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/>
|
||
</svg>
|
||
Temizle
|
||
</button>
|
||
|
||
<button class="hayalet" id="btnGcode">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
|
||
<path d="M14 2v6h6"/><path d="M12 18v-6"/><path d="M9 15l3 3 3-3"/>
|
||
</svg>
|
||
G-code
|
||
</button>
|
||
|
||
<!-- ACİL DURDURMA — her an erişilebilir olmalı, kesim sırasında
|
||
menü aramaya vakit yoktur. Ctrl-X (0x18) gönderir. -->
|
||
<button class="hayalet tehlike" id="btnAcilDurdur" title="Lazeri ANINDA durdur">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round">
|
||
<rect x="6" y="6" width="12" height="12" rx="1.5"/>
|
||
</svg>
|
||
Durdur
|
||
</button>
|
||
|
||
<!-- LAZER BAĞLANTISI — gerçek makineye seri port köprüsü.
|
||
Yeşil yanınca USB/COM portu açık demektir; G-code doğrudan
|
||
makineye gider (indirilen dosya değil, canlı gönderim). -->
|
||
<button class="hayalet" id="btnLazer">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M5 12h14"/><path d="M12 5v14"/>
|
||
<circle cx="12" cy="12" r="9"/>
|
||
</svg>
|
||
<span id="lazerEtiket">Lazere Bağlan</span>
|
||
<i class="lazer-nokta" id="lazerNokta"></i>
|
||
</button>
|
||
</header>
|
||
|
||
<!-- ================= ANA YERLEŞİM ================= -->
|
||
<div class="app">
|
||
|
||
<!-- ---------- SOL PANEL ---------- -->
|
||
<aside class="yancol sol">
|
||
|
||
<!-- ===== MAKİNE BAĞLANTISI — GERÇEK LAZER =====
|
||
Bu bölüm tarayıcıyı lazere bağlar. Port OTOMATİK bulunur;
|
||
kullanıcıdan port/COM bilgisi istenmez. Tarayıcı yalnızca
|
||
ilk seferde izin listesi gösterebilir, tek tıkla geçilir.
|
||
Bağlanmadan "Lazere Gönder" pasif kalır. -->
|
||
<div class="bolum" id="makineBolumu">
|
||
<div class="bolum-baslik">
|
||
Makine
|
||
<span id="makineDurumEtiket" style="font-size:10.5px;font-weight:600;color:var(--dim);margin-left:auto">Bağlı değil</span>
|
||
</div>
|
||
|
||
<div class="satir">
|
||
<button class="birincil" id="btnLazereGonder" disabled
|
||
title="Önce lazere bağlanın">
|
||
Lazere Gönder
|
||
</button>
|
||
</div>
|
||
|
||
<div class="satir iki">
|
||
<button class="hayalet mini" id="btnMakineSifirla" title="$H — eksenleri sıfırla">Sıfırla</button>
|
||
<button class="hayalet mini" id="btnMakineBasa" title="G0 X0 Y0 — başlangıç noktası">Başa Git</button>
|
||
</div>
|
||
|
||
<!-- Canlı makine günlüğü: GRBL yanıtları (ok/error/durum) -->
|
||
<div id="lazerLog" style="max-height:110px;overflow-y:auto;font-family:ui-monospace,monospace;
|
||
font-size:10.5px;line-height:1.5;background:var(--panel2);border:1px solid var(--line);
|
||
border-radius:6px;padding:6px 7px;margin-top:7px;color:var(--dim)">
|
||
<div style="color:var(--dim)">Makine günlüğü bekleniyor…</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- İçerik türü -->
|
||
<div class="bolum">
|
||
<div class="bolum-baslik">İçerik</div>
|
||
<div class="satir iki">
|
||
<button class="birincil" id="btnMetinEkle">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/>
|
||
</svg>
|
||
Yazı Ekle
|
||
</button>
|
||
<button id="btnCizgiEkle">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<path d="M5 19L19 5"/><circle cx="5" cy="19" r="1.6"/><circle cx="19" cy="5" r="1.6"/>
|
||
</svg>
|
||
Çizgi
|
||
</button>
|
||
</div>
|
||
<div class="satir iki" style="margin-top:9px">
|
||
<button id="btnDikdortgen">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="4" y="5" width="16" height="14" rx="1.6"/>
|
||
</svg>
|
||
Çerçeve
|
||
</button>
|
||
<button id="btnLogo">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<circle cx="12" cy="12" r="8"/><path d="M12 4v4M12 16v4M4 12h4M16 12h4"/>
|
||
</svg>
|
||
Logo
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Seçili öğe -->
|
||
<div class="bolum" id="bolumSecili">
|
||
<div class="bolum-baslik">Seçili Öğe</div>
|
||
<div id="bosSecim" style="font-size:11.5px;color:var(--dim);padding:8px 0;text-align:center">
|
||
Tablada bir öğe seç
|
||
</div>
|
||
|
||
<div id="secimFormu" style="display:none">
|
||
<div class="satir">
|
||
<label for="oIcerik">Metin</label>
|
||
<input type="text" id="oIcerik" placeholder="KASE 1">
|
||
</div>
|
||
<div class="satir iki">
|
||
<div><label for="oFont">Yazı tipi</label>
|
||
<select id="oFont">
|
||
<option value="'Segoe UI',sans-serif">Segoe UI</option>
|
||
<option value="Georgia,serif">Georgia</option>
|
||
<option value="'Courier New',monospace">Courier</option>
|
||
<option value="Impact,sans-serif">Impact</option>
|
||
<option value="Tahoma,sans-serif">Tahoma</option>
|
||
</select>
|
||
</div>
|
||
<div><label for="oBoy">Boy (mm)</label>
|
||
<input type="number" id="oBoy" value="10" min="2" max="80" step="0.5">
|
||
</div>
|
||
</div>
|
||
<!-- KONUM: LightBurn/RDWorks mantığı — tablada sürüklenir, burada
|
||
yalnızca okunur gösterge. Kutuya değer yazmak YOK (profesyonel
|
||
programda konum fareyle belirlenir, formla değil). -->
|
||
<div class="konum-gosterge">
|
||
<div class="kg-satir"><span class="kg-etiket">Konum</span>
|
||
<span class="kg-deger" id="kgKonum">X 0.0 · Y 0.0 mm</span>
|
||
</div>
|
||
<div class="kg-satir"><span class="kg-etiket">Döndürme</span>
|
||
<span class="kg-deger" id="kgAci">0°</span>
|
||
</div>
|
||
</div>
|
||
<div class="satir iki">
|
||
<div><label for="oAci">Döndürme (°)</label>
|
||
<input type="number" id="oAci" value="0" step="1" min="-180" max="180">
|
||
</div>
|
||
<div><label for="oGuc">Güç (%)</label>
|
||
<input type="number" id="oGuc" value="60" step="1" min="1" max="100">
|
||
</div>
|
||
</div>
|
||
<!-- Gerçek ölçü (yazılan/yakılacak alan) — girilmez, gösterilir -->
|
||
<div id="olcuBilgi" style="font:11px/1.6 monospace;color:var(--dim);padding:7px 2px;margin-top:2px;border-top:1px dashed var(--line)"></div>
|
||
<div class="onay" id="onayKalınSatir">
|
||
<input type="checkbox" id="oKalin" checked>
|
||
<label class="kutu" for="oKalin"><svg viewBox="0 0 12 12"><path d="M2 6.5L4.6 9 10 3.2"/></svg></label>
|
||
<span>Kalın (dolgulu)</span>
|
||
</div>
|
||
<div class="onay" id="onayAynaSatir">
|
||
<input type="checkbox" id="oAyna">
|
||
<label class="kutu" for="oAyna"><svg viewBox="0 0 12 12"><path d="M2 6.5L4.6 9 10 3.2"/></svg></label>
|
||
<span>Yatay ayna (kaşe için)</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- İş listesi -->
|
||
<div class="bolum">
|
||
<div class="bolum-baslik">İş Listesi</div>
|
||
<div id="isListesi" style="max-height:190px;overflow-y:auto"></div>
|
||
</div>
|
||
|
||
|
||
</aside>
|
||
|
||
<!-- ---------- ORTA: TABLA ---------- -->
|
||
<main class="orta">
|
||
|
||
<div class="arac-seridi">
|
||
<button class="mini hayalet" id="btnZoomEksi" title="Uzaklaştır">−</button>
|
||
<span class="zoom-etiket" id="zoomEtiket">%100</span>
|
||
<button class="mini hayalet" id="btnZoomArti" title="Yakınlaştır">+</button>
|
||
<button class="mini hayalet" id="btnZoomSifir" title="Sığdır">Sığdır</button>
|
||
|
||
<div class="ayrac"></div>
|
||
|
||
<div class="onay" style="margin:0">
|
||
<input type="checkbox" id="izgaraOn" checked>
|
||
<label class="kutu" for="izgaraOn"><svg viewBox="0 0 12 12"><path d="M2 6.5L4.6 9 10 3.2"/></svg></label>
|
||
<span>Izgara</span>
|
||
</div>
|
||
<div class="onay" style="margin:0">
|
||
<input type="checkbox" id="olcuOn" checked>
|
||
<label class="kutu" for="olcuOn"><svg viewBox="0 0 12 12"><path d="M2 6.5L4.6 9 10 3.2"/></svg></label>
|
||
<span>Cetvel</span>
|
||
</div>
|
||
|
||
<div class="grow"></div>
|
||
|
||
<span style="font-size:11px;color:var(--dim);font-weight:700">
|
||
Tabla <span style="color:var(--accent)">400 × 400 mm</span>
|
||
</span>
|
||
</div>
|
||
|
||
<div class="tabla-alan">
|
||
<div class="tabla-kap" id="tablaKap">
|
||
<div class="tabla-rozet"><b>40 × 40 cm</b></div>
|
||
<canvas id="tabla"></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="durum-cubugu">
|
||
<span>İmleç: <span class="nokta" id="imlecPos">0, 0 mm</span></span>
|
||
<span>Seçili: <span class="nokta" id="durumSecili">yok</span></span>
|
||
<div class="grow"></div>
|
||
<span id="durumUyari"></span>
|
||
<span><kbd>Del</kbd> sil · <kbd>Ctrl</kbd>+<kbd>T</kbd> tema</span>
|
||
</div>
|
||
</main>
|
||
|
||
<!-- ============================================================
|
||
İŞ ONAY PANELİ (modal)
|
||
------------------------------------------------------------
|
||
NEDEN confirm() DEĞİL?
|
||
Tarayıcının confirm() penceresi:
|
||
1) Sayfayı KİLİTLER → altındaki async işler (lazere gönderim)
|
||
askıda kalır, gönderim yarıda kesilir.
|
||
2) Şık değil, iş özetini düzgün gösteremez.
|
||
3) "Tamam" hangi işi yapacağını söylemez → kullanıcı yanlışlıkla
|
||
dosya indirir.
|
||
Bu panel sayfa içinde çalışır: async güvenli, üç net seçenek sunar.
|
||
============================================================ -->
|
||
<div class="is-modal" id="isModal" hidden>
|
||
<div class="is-modal-kutu" role="dialog" aria-modal="true" aria-labelledby="isBaslik">
|
||
<div class="is-modal-baslik" id="isBaslik">İŞ ÖZETİ</div>
|
||
|
||
<div class="is-modal-govde" id="isOzet"></div>
|
||
|
||
<div class="is-modal-eylem">
|
||
<button class="is-btn is-gonder" id="isBtnGonder" type="button">
|
||
🚀 LAZERE GÖNDER
|
||
</button>
|
||
<button class="is-btn is-indir" id="isBtnIndir" type="button">
|
||
💾 Dosya indir (.nc)
|
||
</button>
|
||
<button class="is-btn is-iptal" id="isBtnIptal" type="button">
|
||
✕ İptal
|
||
</button>
|
||
</div>
|
||
|
||
<div class="is-modal-not" id="isNot"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ---------- SAĞ PANEL ---------- -->
|
||
<aside class="yancol sag">
|
||
|
||
<!-- ★ TEST / YAZ — ana eylem -->
|
||
<div class="bolum">
|
||
<div class="bolum-baslik">İşlem</div>
|
||
<div class="satir iki">
|
||
<button class="buyuk" id="btnTest" style="width:100%">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="2.4" fill="currentColor"/>
|
||
</svg>
|
||
TEST
|
||
</button>
|
||
<button class="buyuk birincil" id="btnYaz" style="width:100%">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M12 2v6"/><path d="M9 8h6"/><rect x="7" y="11" width="10" height="9" rx="2"/><path d="M4 22h16"/>
|
||
</svg>
|
||
YAZ
|
||
</button>
|
||
</div>
|
||
<div style="font-size:10.5px;color:var(--dim);margin-top:8px;line-height:1.5">
|
||
<b style="color:var(--red)">TEST</b> → lazeri düşük güçle yakıp
|
||
<b>kırmızı işaret</b> bırakır, konumu gözle doğrularsın.<br>
|
||
<b style="color:var(--accent)">YAZ</b> → her öğeyi <b>kendi gücüyle</b> markalar.
|
||
</div>
|
||
<div class="satir iki" style="margin-top:9px">
|
||
<button class="mini" id="btnTestTemizle">Test sil</button>
|
||
<button class="mini" id="btnTestSecili">Seçiliye test</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ★ GÜÇ / BASINÇ -->
|
||
<div class="bolum">
|
||
<div class="bolum-baslik">Lazer Gücü (Basınç)</div>
|
||
|
||
<div class="satir">
|
||
<label for="gMalzeme">Malzeme</label>
|
||
<select id="gMalzeme">
|
||
<option value="kauçuk">Kauçuk kaşe — %78 · 900 mm/dk</option>
|
||
<option value="lastik">Lastik — %85 · 600 mm/dk</option>
|
||
<option value="ahşap">Ahşap — %55 · 1800 mm/dk</option>
|
||
<option value="pleksi">Pleksi — %65 · 1200 mm/dk</option>
|
||
<option value="mdf">MDF — %60 · 1500 mm/dk</option>
|
||
<option value="deri">Deri — %45 · 2200 mm/dk</option>
|
||
<option value="metal">Boyalı metal — %90 · 500 mm/dk</option>
|
||
<option value="ozel">Özel — elle ayarla</option>
|
||
</select>
|
||
</div>
|
||
<div id="gMalzemeNot" style="font-size:10px;color:var(--dim);margin-bottom:11px;font-style:italic">
|
||
Malzeme seçilince önerilen değerler yüklenir
|
||
</div>
|
||
|
||
<div class="satir">
|
||
<label style="display:flex;justify-content:space-between">
|
||
<span>Güç</span><span id="gGucDeger" style="color:var(--accent);font-weight:700">%78</span>
|
||
</label>
|
||
<input type="range" id="gGuc" min="1" max="100" value="78" step="1">
|
||
</div>
|
||
<div class="satir">
|
||
<label style="display:flex;justify-content:space-between">
|
||
<span>Hız</span><span id="gHizDeger" style="color:var(--accent);font-weight:700">900 mm/dk</span>
|
||
</label>
|
||
<input type="range" id="gHiz" min="100" max="6000" value="900" step="50">
|
||
</div>
|
||
<div class="satir iki">
|
||
<div>
|
||
<label style="display:flex;justify-content:space-between">
|
||
<span>Pas</span><span id="gPasDeger" style="color:var(--accent);font-weight:700">1 pas</span>
|
||
</label>
|
||
<input type="range" id="gPas" min="1" max="10" value="1" step="1">
|
||
</div>
|
||
<div>
|
||
<label style="display:flex;justify-content:space-between">
|
||
<span>Aralık</span><span id="gAralikDeger" style="color:var(--accent);font-weight:700">0.10 mm</span>
|
||
</label>
|
||
<input type="range" id="gAralik" min="0.04" max="0.30" value="0.10" step="0.01">
|
||
</div>
|
||
</div>
|
||
<button class="mini" id="btnGucUygula" style="width:100%;margin-top:9px">
|
||
Ayarları tüm öğelere uygula
|
||
</button>
|
||
</div>
|
||
|
||
<!-- ★ KONTROL LİSTESİ — her madde bağımsız onay -->
|
||
<div class="bolum">
|
||
<div class="bolum-baslik">
|
||
Kontrol Listesi
|
||
<span id="onayOzet" style="margin-left:auto;font-size:10px;letter-spacing:0;text-transform:none"></span>
|
||
</div>
|
||
<div id="onayListesi"></div>
|
||
<div class="satir iki" style="margin-top:10px">
|
||
<button class="mini" id="btnOnayTum">Tümünü işaretle</button>
|
||
<button class="mini" id="btnOnaySifir">Onayları sil</button>
|
||
</div>
|
||
<div style="font-size:10px;color:var(--dim);margin-top:8px;font-style:italic">
|
||
Her maddeyi ayrı ayrı işaretle — birini onaylamak diğerini etkilemez.
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
|
||
<!-- Modüller: editör + simülasyon (çekirdekten önce yüklenir) -->
|
||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||
<script src="js/api.js"></script>
|
||
<script src="js/engrave.js"></script>
|
||
<!-- LAZER KÖPRÜSÜ: api.js sunucuya (HTTP), lazer.js MAKİNEYE (seri port) bağlanır.
|
||
İkisi ayrı kanaldır — G-code üretmek ile göndermek farklı işlerdir. -->
|
||
<script src="js/lazer.js"></script>
|
||
<script src="js/editor.js"></script>
|
||
<script src="js/simulasyon.js"></script>
|
||
|
||
<script>
|
||
/* ============================================================
|
||
NUMEX KAŞE STÜDYO — Çekirdek Mantık
|
||
============================================================ */
|
||
'use strict';
|
||
|
||
/* ---------- SABİTLER ---------- */
|
||
const TABLA_MM = 400; // 40 cm = 400 mm çalışma alanı
|
||
const MIN_ZOOM = 0.35;
|
||
const MAX_ZOOM = 3.2;
|
||
/* mmPx ERKEN tanımlanır (TDZ fix, 2026-09-12): kayıtlı tema açılışta
|
||
temaUygula→ciz() çağırıyor ve ciz mmPx'i okuyor; eski kodda `let mmPx`
|
||
çok aşağıdaydı → "Cannot access 'mmPx' before initialization" → tüm
|
||
script çöküyordu (app ölü: yazı eklenmiyor, bağlanmıyor). */
|
||
let mmPx = 1; // 1 mm kaç piksel
|
||
|
||
/* Lazer güç (basınç) ön ayarları — malzemeye göre araştırılmış değerler.
|
||
Diyot 40W sınıfı, kauçuk kaşe/lastik markalama referans alındı. */
|
||
const GUC_PRESET = {
|
||
'kauçuk' : { ad:'Kauçuk kaşe', guc:78, hiz:900, pas:1, aralik:0.10 },
|
||
'lastik' : { ad:'Lastik', guc:85, hiz:600, pas:1, aralik:0.09 },
|
||
'ahşap' : { ad:'Ahşap', guc:55, hiz:1800, pas:2, aralik:0.12 },
|
||
'pleksi' : { ad:'Pleksi', guc:65, hiz:1200, pas:1, aralik:0.10 },
|
||
'mdf' : { ad:'MDF', guc:60, hiz:1500, pas:2, aralik:0.11 },
|
||
'deri' : { ad:'Deri', guc:45, hiz:2200, pas:1, aralik:0.12 },
|
||
'metal' : { ad:'Boyalı metal',guc:90, hiz:500, pas:3, aralik:0.08 },
|
||
'ozel' : { ad:'Özel', guc:50, hiz:1000, pas:1, aralik:0.10 }
|
||
};
|
||
|
||
/* ---------- DURUM ---------- */
|
||
const S = {
|
||
nesneler : [], // { id, tip, x, y, icerik, boy, font, kalin, ayna, guc, hiz, pas, aralik, malzeme, onay:[] }
|
||
seciliId : null,
|
||
zoom : 1,
|
||
tema : 'koyu',
|
||
testNokta : null, // { x, y, t } — TEST ile bırakılan kırmızı işaret
|
||
testGecmis : [], // birden fazla test noktası geçmişi
|
||
izgaraOn : true,
|
||
olcuOn : true,
|
||
imlec : { x:0, y:0 },
|
||
yaziliyor : false,
|
||
sayac : 0
|
||
};
|
||
|
||
/* Malzeme başına onay maddeleri — HER MADDE BAĞIMSIZ onaylanır. */
|
||
const ONAY_MADDELERI = [
|
||
{ k:'olcum', t:'Ölçü ve konum doğru (mm)' },
|
||
{ k:'metin', t:'Yazı içeriği doğru yazılmış' },
|
||
{ k:'ayna', t:'Kaşe için ayna/ters görünüm ayarlı' },
|
||
{ k:'guc', t:'Lazer gücü (basınç) malzemeye uygun' },
|
||
{ k:'hiz', t:'Hız ve pas sayısı ayarlı' },
|
||
{ k:'kilit', t:'İş parçası tablaya sabitlenmiş' }
|
||
];
|
||
|
||
/* ---------- DOM KISAYOLLARI ---------- */
|
||
const $ = id => document.getElementById(id);
|
||
const canvas = $('tabla');
|
||
const ctx = canvas.getContext('2d');
|
||
|
||
/* ============================================================
|
||
TEMA
|
||
============================================================ */
|
||
function temaUygula(t){
|
||
S.tema = t;
|
||
document.documentElement.setAttribute('data-tema', t);
|
||
$('temaEtiket').textContent = (t === 'koyu') ? 'Beyaz Tema' : 'Dar Tema';
|
||
localStorage.setItem('numex-kase-tema', t);
|
||
ciz();
|
||
}
|
||
$('btnTema').onclick = () => temaUygula(S.tema === 'koyu' ? 'beyaz' : 'koyu');
|
||
|
||
/* Kayıtlı tema açılışta DEĞİL, baslat() sonunda geri yüklenir (TDZ fix):
|
||
temaUygula→ciz() ciz'in bağımlılarını (mmPx/PX/MM…) okuyor; onlar bu
|
||
noktada henüz tanımlı değil → "Cannot access before initialization" → app ölürdü. */
|
||
|
||
/* ============================================================
|
||
RENK OKUMA (CSS değişkenlerinden — tema ile uyumlu)
|
||
============================================================ */
|
||
function renk(ad){
|
||
return getComputedStyle(document.documentElement)
|
||
.getPropertyValue('--' + ad).trim() || '#888';
|
||
}
|
||
|
||
/* ============================================================
|
||
CANVAS / TABLA
|
||
============================================================ */
|
||
/* mmPx yukarıda (sabitlerin yanında) erken tanımlandı — TDZ fix. */
|
||
|
||
function tablaBoyutlandir(){
|
||
// Tabla kapsayıcısının boyutuna göre mm→px ölçeği hesapla
|
||
const alan = canvas.parentElement.parentElement; // .tabla-alan
|
||
const pay = 70;
|
||
const kutu = Math.max(200, Math.min(alan.clientWidth - pay, alan.clientHeight - pay));
|
||
S.temelPx = kutu / TABLA_MM;
|
||
mmPx = S.temelPx * S.zoom;
|
||
|
||
const boyut = Math.round(TABLA_MM * mmPx);
|
||
const dpr = window.devicePixelRatio || 1;
|
||
|
||
canvas.width = Math.round(boyut * dpr);
|
||
canvas.height = Math.round(boyut * dpr);
|
||
canvas.style.width = boyut + 'px';
|
||
canvas.style.height = boyut + 'px';
|
||
|
||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||
canvas._css = boyut;
|
||
ciz();
|
||
}
|
||
|
||
/* mm → canvas px */
|
||
const PX = mm => mm * mmPx;
|
||
/* canvas px → mm */
|
||
const MM = px => px / mmPx;
|
||
|
||
/* ============================================================
|
||
ÇİZİM
|
||
============================================================ */
|
||
function ciz(){
|
||
if (!mmPx) return;
|
||
const B = canvas._css;
|
||
ctx.clearRect(0, 0, B, B);
|
||
|
||
// --- Tabla zemini ---
|
||
ctx.fillStyle = renk('mat');
|
||
ctx.fillRect(0, 0, B, B);
|
||
|
||
// --- Izgara ---
|
||
if (S.izgaraOn){
|
||
// İnce ızgara her 10 mm
|
||
ctx.strokeStyle = renk('grid2');
|
||
ctx.lineWidth = 1;
|
||
ctx.beginPath();
|
||
for (let mm = 10; mm < TABLA_MM; mm += 10){
|
||
const p = Math.round(PX(mm)) + .5;
|
||
ctx.moveTo(p, 0); ctx.lineTo(p, B);
|
||
ctx.moveTo(0, p); ctx.lineTo(B, p);
|
||
}
|
||
ctx.stroke();
|
||
|
||
// Kalın ızgara her 50 mm (5 cm)
|
||
ctx.strokeStyle = renk('grid');
|
||
ctx.lineWidth = 1;
|
||
ctx.beginPath();
|
||
for (let mm = 50; mm < TABLA_MM; mm += 50){
|
||
const p = Math.round(PX(mm)) + .5;
|
||
ctx.moveTo(p, 0); ctx.lineTo(p, B);
|
||
ctx.moveTo(0, p); ctx.lineTo(B, p);
|
||
}
|
||
ctx.stroke();
|
||
}
|
||
|
||
// --- Cetvel (üst + sol) ---
|
||
if (S.olcuOn){
|
||
ctx.fillStyle = renk('dim');
|
||
ctx.font = '9px "Segoe UI",sans-serif';
|
||
ctx.textAlign = 'center';
|
||
ctx.textBaseline = 'top';
|
||
const adim = (mmPx > 1.6) ? 20 : (mmPx > 0.9 ? 50 : 100);
|
||
for (let mm = 0; mm <= TABLA_MM; mm += adim){
|
||
const p = PX(mm);
|
||
// üst
|
||
ctx.fillText(mm, Math.min(Math.max(p, 10), B - 10), 3);
|
||
// sol
|
||
ctx.save();
|
||
ctx.translate(3, Math.min(Math.max(p, 10), B - 10));
|
||
ctx.rotate(-Math.PI/2);
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText(mm, 0, 0);
|
||
ctx.restore();
|
||
}
|
||
ctx.textAlign = 'left';
|
||
}
|
||
|
||
// --- SİMÜLASYON KATMANI: lazerin gezdiği yol canlı çizilir ---
|
||
if (typeof sim !== 'undefined' && sim && sim.aktifMi()){
|
||
const iz = sim.izlenenNoktalar();
|
||
// geçilen yol — turuncu/kırmızı parlayan iz
|
||
ctx.save();
|
||
ctx.lineCap = 'round'; ctx.lineJoin = 'round';
|
||
for (const p of iz){
|
||
const gucOran = (p.nesne && p.nesne.guc != null) ? p.nesne.guc / 100 : .5;
|
||
ctx.beginPath();
|
||
ctx.arc(PX(p.x), PX(p.y), Math.max(1.1, gucOran * 2.2), 0, Math.PI * 2);
|
||
ctx.fillStyle = (p.nesne && p.nesne.guc <= 10)
|
||
? 'rgba(255,82,82,.85)' // TEST izi kırmızı
|
||
: 'rgba(255,122,24,' + (0.35 + gucOran * .55) + ')';
|
||
ctx.fill();
|
||
}
|
||
// lazer kafası — son noktada
|
||
const son = sim.guncelNokta();
|
||
if (son){
|
||
const g = ctx.createRadialGradient(PX(son.x), PX(son.y), 0, PX(son.x), PX(son.y), 16);
|
||
g.addColorStop(0, 'rgba(255,255,255,.95)');
|
||
g.addColorStop(.35, 'rgba(255,122,24,.75)');
|
||
g.addColorStop(1, 'rgba(255,122,24,0)');
|
||
ctx.beginPath();
|
||
ctx.arc(PX(son.x), PX(son.y), 16, 0, Math.PI * 2);
|
||
ctx.fillStyle = g;
|
||
ctx.fill();
|
||
// artı hedef imleci
|
||
ctx.strokeStyle = 'rgba(255,255,255,.9)';
|
||
ctx.lineWidth = 1.4;
|
||
ctx.beginPath();
|
||
ctx.moveTo(PX(son.x) - 9, PX(son.y)); ctx.lineTo(PX(son.x) + 9, PX(son.y));
|
||
ctx.moveTo(PX(son.x), PX(son.y) - 9); ctx.lineTo(PX(son.x), PX(son.y) + 9);
|
||
ctx.stroke();
|
||
}
|
||
ctx.restore();
|
||
}
|
||
|
||
// --- Test noktaları (KIRMIZI işaretler) ---
|
||
S.testGecmis.forEach((n, i) => {
|
||
const soluk = i < S.testGecmis.length - 1;
|
||
const r = soluk ? 6 : 10;
|
||
ctx.globalAlpha = soluk ? .45 : 1;
|
||
|
||
// dış halka
|
||
ctx.beginPath();
|
||
ctx.arc(PX(n.x), PX(n.y), r + 4, 0, Math.PI * 2);
|
||
ctx.strokeStyle = renk('red');
|
||
ctx.lineWidth = 2;
|
||
ctx.stroke();
|
||
|
||
// iç nokta
|
||
ctx.beginPath();
|
||
ctx.arc(PX(n.x), PX(n.y), r * .42, 0, Math.PI * 2);
|
||
ctx.fillStyle = renk('red');
|
||
ctx.fill();
|
||
|
||
// artı işareti
|
||
ctx.strokeStyle = renk('red');
|
||
ctx.lineWidth = 1.6;
|
||
ctx.beginPath();
|
||
ctx.moveTo(PX(n.x) - r - 8, PX(n.y)); ctx.lineTo(PX(n.x) - r - 2, PX(n.y));
|
||
ctx.moveTo(PX(n.x) + r + 2, PX(n.y)); ctx.lineTo(PX(n.x) + r + 8, PX(n.y));
|
||
ctx.moveTo(PX(n.x), PX(n.y) - r - 8); ctx.lineTo(PX(n.x), PX(n.y) - r - 2);
|
||
ctx.moveTo(PX(n.x), PX(n.y) + r + 2); ctx.lineTo(PX(n.x), PX(n.y) + r + 8);
|
||
ctx.stroke();
|
||
|
||
if (!soluk){
|
||
ctx.fillStyle = renk('red');
|
||
ctx.font = 'bold 10px "Segoe UI",sans-serif';
|
||
ctx.textAlign = 'left';
|
||
ctx.fillText('TEST ' + n.x.toFixed(0) + ',' + n.y.toFixed(0), PX(n.x) + r + 11, PX(n.y) - 4);
|
||
}
|
||
ctx.globalAlpha = 1;
|
||
});
|
||
|
||
// --- Nesneler ---
|
||
S.nesneler.forEach(o => cizNesne(o));
|
||
|
||
// --- Seçim çerçevesi + TUTAMAKLAR + DÖNDÜRME OKU ---
|
||
const sec = seciliNesne();
|
||
if (sec) {
|
||
const k = nesneKutu(sec);
|
||
const a = (sec.aci || 0) * Math.PI / 180;
|
||
const cxPx = PX(k.cx), cyPx = PX(k.cy);
|
||
const wPx = PX(k.w), hPx = PX(k.h);
|
||
|
||
ctx.save();
|
||
ctx.translate(cxPx, cyPx);
|
||
ctx.rotate(a);
|
||
|
||
// kesikli seçim çerçevesi
|
||
ctx.strokeStyle = renk('accent');
|
||
ctx.lineWidth = 1.4;
|
||
ctx.setLineDash([5, 4]);
|
||
ctx.strokeRect(-wPx / 2, -hPx / 2, wPx, hPx);
|
||
ctx.setLineDash([]);
|
||
|
||
// 4 köşe tutamağı — büyüt/küçült
|
||
const hR = 5.5;
|
||
ctx.fillStyle = renk('accent');
|
||
ctx.strokeStyle = renk('panel') || '#fff';
|
||
ctx.lineWidth = 2;
|
||
[[-wPx/2,-hPx/2],[wPx/2,-hPx/2],[wPx/2,hPx/2],[-wPx/2,hPx/2]].forEach(([hx,hy]) => {
|
||
ctx.beginPath();
|
||
ctx.rect(hx - hR, hy - hR, hR*2, hR*2);
|
||
ctx.fill();
|
||
ctx.stroke();
|
||
});
|
||
ctx.restore();
|
||
|
||
// DÖNDÜRME OKU — kutunun üstünde, döndürmeyle birlikte hareket eder
|
||
const dn = edit.donmeNoktasi(sec);
|
||
const ust = { x: k.cx, y: k.cy - k.h / 2 };
|
||
const ustPx = { x: PX(ust.x), y: PX(ust.y) };
|
||
const dnPx = { x: PX(dn.x), y: PX(dn.y) };
|
||
|
||
ctx.save();
|
||
ctx.strokeStyle = renk('accent');
|
||
ctx.lineWidth = 1.6;
|
||
ctx.beginPath();
|
||
ctx.moveTo(ustPx.x, ustPx.y);
|
||
ctx.lineTo(dnPx.x, dnPx.y);
|
||
ctx.stroke();
|
||
|
||
// ok başı
|
||
ctx.beginPath();
|
||
ctx.arc(dnPx.x, dnPx.y, 5.5, 0, Math.PI * 2);
|
||
ctx.fillStyle = renk('accent');
|
||
ctx.fill();
|
||
ctx.strokeStyle = renk('panel') || '#fff';
|
||
ctx.lineWidth = 2;
|
||
ctx.stroke();
|
||
// dönme sembolü
|
||
ctx.beginPath();
|
||
ctx.arc(dnPx.x, dnPx.y, 2.2, 0.6, Math.PI * 1.7);
|
||
ctx.strokeStyle = '#fff';
|
||
ctx.lineWidth = 1.3;
|
||
ctx.stroke();
|
||
ctx.restore();
|
||
|
||
// AÇI etiketi
|
||
if (sec.aci){
|
||
ctx.save();
|
||
ctx.font = '600 10px "Segoe UI",sans-serif';
|
||
ctx.fillStyle = renk('accent');
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText((Math.round(sec.aci * 10) / 10) + '°', dnPx.x, dnPx.y - 10);
|
||
ctx.restore();
|
||
}
|
||
|
||
// BOYUT etiketi (alt ortada, mm)
|
||
const boyYazi = (sec.tip === 'metin')
|
||
? (Math.round((sec.boy || 0) * 10) / 10) + ' mm boy'
|
||
: (Math.round(k.w) + ' × ' + Math.round(k.h) + ' mm');
|
||
ctx.save();
|
||
ctx.font = '600 10px "Segoe UI",sans-serif';
|
||
ctx.fillStyle = renk('dim');
|
||
ctx.textAlign = 'center';
|
||
ctx.fillText(boyYazi, cxPx, cyPx + hPx / 2 + 16);
|
||
ctx.restore();
|
||
}
|
||
|
||
// --- YAZMA İMLECİ (canlı yazma modunda yanıp söner) ---
|
||
if (edit.yazimAktifMi()) {
|
||
const y = S.nesneler.find(n => n.id === S.seciliId);
|
||
if (y){
|
||
const k = nesneKutu(y);
|
||
const boyPx = (y.boy || 10) * (mmPx || 1);
|
||
const satirlar = String(y.icerik || '').split('\n');
|
||
const sonSatir = satirlar[satirlar.length - 1] || '';
|
||
ctx.save();
|
||
ctx.font = (y.kalin ? 'bold ' : '') + boyPx + 'px ' + (y.font || 'sans-serif');
|
||
const w = ctx.measureText(sonSatir).width;
|
||
const metinH = boyPx * 1.35;
|
||
const x = PX(k.x) + (y.kalin ? w * 0.5 : w) + 2;
|
||
const yy = PX(k.y) + (satirlar.length - 1) * metinH;
|
||
// imleç — yanıp sönme animasyonu editörde yönetilir
|
||
if (window._numexImlec !== false){
|
||
ctx.strokeStyle = renk('accent');
|
||
ctx.lineWidth = 1.6;
|
||
ctx.beginPath();
|
||
ctx.moveTo(x, yy);
|
||
ctx.lineTo(x, yy + boyPx);
|
||
ctx.stroke();
|
||
}
|
||
ctx.restore();
|
||
}
|
||
}
|
||
|
||
// --- Çalışma alanı kenarı ---
|
||
ctx.strokeStyle = renk('mat-edge');
|
||
ctx.lineWidth = 2;
|
||
ctx.strokeRect(1, 1, B - 2, B - 2);
|
||
|
||
// --- Test modunda artı imleci ---
|
||
if (S._testModu){
|
||
ctx.save();
|
||
ctx.strokeStyle = renk('red');
|
||
ctx.lineWidth = 1;
|
||
ctx.setLineDash([3, 3]);
|
||
ctx.beginPath();
|
||
ctx.moveTo(PX(S.imlec.x), 0); ctx.lineTo(PX(S.imlec.x), B);
|
||
ctx.moveTo(0, PX(S.imlec.y)); ctx.lineTo(B, PX(S.imlec.y));
|
||
ctx.stroke();
|
||
ctx.restore();
|
||
}
|
||
}
|
||
|
||
/* ============================================================
|
||
NESNE MODELİ
|
||
============================================================ */
|
||
function nesneOlustur(tip, ekVeri){
|
||
S.sayac++;
|
||
const malz = 'kauçuk';
|
||
const o = Object.assign({
|
||
id : 'n' + S.sayac,
|
||
tip : tip,
|
||
x : 40 + (S.nesneler.length % 4) * 12,
|
||
y : 40 + (S.nesneler.length % 4) * 12,
|
||
icerik : 'KASE ' + S.sayac,
|
||
boy : 10,
|
||
font : "'Segoe UI',sans-serif",
|
||
kalin : true,
|
||
ayna : false,
|
||
g : 40,
|
||
h : 40,
|
||
malzeme : malz,
|
||
guc : GUC_PRESET[malz].guc,
|
||
hiz : GUC_PRESET[malz].hiz,
|
||
pas : GUC_PRESET[malz].pas,
|
||
aralik : GUC_PRESET[malz].aralik,
|
||
onay : {} // her madde BAĞIMSIZ onaylanır
|
||
}, ekVeri || {});
|
||
S.nesneler.push(o);
|
||
S.seciliId = o.id;
|
||
// NOT: arayuzGuncelle/ciz burada ÇAĞRILMAZ — çağıran taraf (ekle() veya
|
||
// editör.bas()) tek seferde yapar. Aksi halde her tıklamada iki kez
|
||
// çizim + iki kez liste güncellemesi olur (görsel titreme).
|
||
return o;
|
||
}
|
||
|
||
function seciliNesne(){
|
||
return S.nesneler.find(o => o.id === S.seciliId) || null;
|
||
}
|
||
|
||
/* ---------- GERÇEK METİN ÖLÇÜMÜ ----------
|
||
Tahmin YOK. Tarayıcının kendi font ölçüm motoru kullanılır:
|
||
canvas.measureText() → piksel, sonra mm'ye çevrilir.
|
||
Bu sayede ekranda gördüğün genişlik = G-code'a giden genişlik. */
|
||
let _olcTuval = null;
|
||
function _olcumTuvani(){
|
||
if (!_olcTuval) _olcTuval = document.createElement('canvas');
|
||
return _olcTuval.getContext('2d');
|
||
}
|
||
|
||
/* Bir satırın mm cinsinden gerçek genişliği */
|
||
function metinSatirGenislik(satir, boyMm, kalin, font){
|
||
const olc = _olcumTuvani();
|
||
// Fontu, çizimle AYNI biçimde kur (cizNesne ile birebir aynı olmalı)
|
||
olc.font = (kalin ? 'bold ' : '') + boyMm + 'px ' + (font || 'sans-serif');
|
||
return olc.measureText(String(satir)).width; // px cinsinden döner
|
||
}
|
||
|
||
/* Not: çizimde ctx.font = '9px ...' kullanıldığında ve tuval ölçeği PX()
|
||
ile mm→px dönüşümü yapılıyorsa, measureText px değerini verir.
|
||
Boy zaten mm olarak verildiği için, ölçüm de mm ölçeğinde çıkar. */
|
||
function metinGenislik(satirlar, boyMm, kalin, font){
|
||
let en = 0;
|
||
for (const s of String(satirlar).split('\n')){
|
||
en = Math.max(en, metinSatirGenislik(s, boyMm, kalin, font));
|
||
}
|
||
return en;
|
||
}
|
||
|
||
/* Nesnenin mm cinsinden kutusu.
|
||
NOT: editör modülü (js/editor.js) cx, cy, w, h alanlarını da bekler —
|
||
merkez ve boyut, döndürme/boyutlandırma matematiğinde kullanılır. */
|
||
function nesneKutu(o){
|
||
let x, y, g, h;
|
||
if (o.tip === 'metin'){
|
||
const satirlar = String(o.icerik || '').split('\n');
|
||
const bosMu = !String(o.icerik || '').length;
|
||
/* ÖLÇÜ SÖZLEŞMESİ: o.boy = HARFİN GERÇEK YÜKSEKLİĞİ (mm).
|
||
Kullanıcı 9 yazarsa çıktı tam 9 mm olmalı.
|
||
Çok satırda satır aralığı payı SATIR SAYISINA eklenir, harfin
|
||
boyuna DEĞİL — aksi halde 9 mm yazınca 12 mm çıkıyordu. */
|
||
const SATIR_PAYI = 1.35; // satır yüksekliği / harf boyu oranı
|
||
const nSatir = Math.max(1, satirlar.length);
|
||
h = o.boy * (1 + (nSatir - 1) * SATIR_PAYI);
|
||
// Boş yazıda imleç için küçük bir kutu; doluyken GERÇEK ölçüm
|
||
g = bosMu ? Math.max(3, o.boy * 0.55)
|
||
: Math.max(1, metinGenislik(o.icerik, o.boy, o.kalin, o.font));
|
||
// Ölçüm 0 dönerse (font yüklenmemiş) sayaca düş — sessizce 0 bırakma
|
||
if (!bosMu && g < 1) g = Math.max(3, String(o.icerik || '').length * o.boy * 0.6);
|
||
x = o.x; y = o.y;
|
||
} else {
|
||
x = o.x; y = o.y; g = o.g; h = o.h;
|
||
}
|
||
return {
|
||
x, y, g, h, y2: y + h,
|
||
w: g, // editör: genişlik
|
||
cx: x + g / 2, // editör: merkez X
|
||
cy: y + h / 2 // editör: merkez Y
|
||
};
|
||
}
|
||
|
||
function cizNesne(o){
|
||
const secili = (o.id === S.seciliId);
|
||
const k = nesneKutu(o);
|
||
ctx.save();
|
||
|
||
// DÖNDÜRME — nesnenin merkezi etrafında (editörden ayarlanır)
|
||
if (o.aci){
|
||
ctx.translate(PX(k.cx), PX(k.cy));
|
||
ctx.rotate(o.aci * Math.PI / 180);
|
||
ctx.translate(-PX(k.cx), -PX(k.cy));
|
||
}
|
||
|
||
if (o.tip === 'metin'){
|
||
const px = PX(o.boy);
|
||
ctx.font = (o.kalin ? 'bold ' : '') + px + 'px ' + o.font;
|
||
ctx.textBaseline = 'top';
|
||
ctx.fillStyle = secili ? renk('accent') : renk('txt');
|
||
|
||
if (o.ayna){
|
||
// Kaşe için yatay ayna
|
||
ctx.translate(PX(o.x + k.g), PX(o.y));
|
||
ctx.scale(-1, 1);
|
||
ctx.fillText(o.icerik, 0, 0);
|
||
} else {
|
||
ctx.fillText(o.icerik, PX(o.x), PX(o.y));
|
||
}
|
||
}
|
||
else if (o.tip === 'cizgi'){
|
||
ctx.strokeStyle = secili ? renk('accent') : renk('txt');
|
||
ctx.lineWidth = Math.max(1.5, PX(o.boy * .25));
|
||
ctx.beginPath();
|
||
ctx.moveTo(PX(o.x), PX(o.y));
|
||
ctx.lineTo(PX(o.x + o.g), PX(o.y + o.h));
|
||
ctx.stroke();
|
||
}
|
||
else if (o.tip === 'dikdortgen'){
|
||
ctx.strokeStyle = secili ? renk('accent') : renk('txt');
|
||
ctx.lineWidth = Math.max(1.5, PX(o.boy * .18));
|
||
ctx.strokeRect(PX(o.x), PX(o.y), PX(o.g), PX(o.h));
|
||
}
|
||
else if (o.tip === 'logo'){
|
||
const cx = PX(o.x + o.g / 2), cy = PX(o.y + o.h / 2);
|
||
const r = PX(Math.min(o.g, o.h) / 2);
|
||
ctx.strokeStyle = secili ? renk('accent') : renk('txt');
|
||
ctx.lineWidth = Math.max(1.5, PX(o.boy * .16));
|
||
ctx.beginPath(); ctx.arc(cx, cy, r * .8, 0, Math.PI * 2); ctx.stroke();
|
||
ctx.beginPath();
|
||
ctx.moveTo(cx, cy - r); ctx.lineTo(cx, cy + r);
|
||
ctx.moveTo(cx - r, cy); ctx.lineTo(cx + r, cy);
|
||
ctx.stroke();
|
||
ctx.beginPath(); ctx.arc(cx, cy, r * .22, 0, Math.PI * 2); ctx.stroke();
|
||
}
|
||
|
||
ctx.restore();
|
||
}
|
||
|
||
/* ============================================================
|
||
İŞ LİSTESİ + SEÇİM FORMU
|
||
============================================================ */
|
||
function arayuzGuncelle(){
|
||
isListesiCiz();
|
||
secimFormuGuncelle();
|
||
durumGuncelle();
|
||
onayKartiCiz();
|
||
gucArayuzGuncelle();
|
||
}
|
||
|
||
function isListesiCiz(){
|
||
const kap = $('isListesi');
|
||
if (!S.nesneler.length){
|
||
kap.innerHTML = '<div style="font-size:11.5px;color:var(--dim);padding:10px 0;text-align:center">Henüz öğe yok</div>';
|
||
return;
|
||
}
|
||
kap.innerHTML = '';
|
||
S.nesneler.forEach((o, i) => {
|
||
const onaySayi = ONAY_MADDELERI.filter(m => o.onay[m.k]).length;
|
||
const hazir = onaySayi === ONAY_MADDELERI.length;
|
||
|
||
const d = document.createElement('div');
|
||
d.style.cssText = 'display:flex;align-items:center;gap:8px;padding:7px 9px;margin-bottom:5px;' +
|
||
'border-radius:7px;cursor:pointer;border:1px solid ' +
|
||
(o.id === S.seciliId ? 'var(--accent)' : 'var(--line)') + ';' +
|
||
'background:' + (o.id === S.seciliId ? 'var(--accent-soft)' : 'var(--panel2)') + ';transition:all .13s';
|
||
|
||
const turAd = { metin:'Yazı', cizgi:'Çizgi', dikdortgen:'Çerçeve', logo:'Logo' }[o.tip] || o.tip;
|
||
const ad = (o.tip === 'metin') ? o.icerik : turAd;
|
||
const kisa = ad.length > 15 ? ad.slice(0, 15) + '…' : ad;
|
||
|
||
d.innerHTML =
|
||
'<span style="font-size:10px;font-weight:700;color:var(--dim);min-width:14px">' + (i + 1) + '</span>' +
|
||
'<span style="flex:1;font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' +
|
||
kisa + '</span>' +
|
||
'<span style="font-size:9.5px;font-weight:700;padding:2px 6px;border-radius:9px;background:' +
|
||
(hazir ? 'var(--ok-soft)' : 'var(--panel3)') + ';color:' +
|
||
(hazir ? 'var(--ok)' : 'var(--dim)') + '">' + onaySayi + '/' + ONAY_MADDELERI.length + '</span>' +
|
||
'<span class="sil" style="color:var(--dim);font-size:14px;padding:0 2px" title="Sil">×</span>';
|
||
|
||
d.onclick = e => {
|
||
if (e.target.classList.contains('sil')){
|
||
e.stopPropagation();
|
||
nesneSil(o.id);
|
||
return;
|
||
}
|
||
S.seciliId = o.id;
|
||
arayuzGuncelle();
|
||
ciz();
|
||
};
|
||
kap.appendChild(d);
|
||
});
|
||
}
|
||
|
||
function nesneSil(id){
|
||
S.nesneler = S.nesneler.filter(o => o.id !== id);
|
||
if (S.seciliId === id) S.seciliId = S.nesneler.length ? S.nesneler[S.nesneler.length - 1].id : null;
|
||
arayuzGuncelle();
|
||
ciz();
|
||
}
|
||
|
||
function secimFormuGuncelle(){
|
||
const o = seciliNesne();
|
||
if (!o){
|
||
$('bosSecim').style.display = '';
|
||
$('secimFormu').style.display = 'none';
|
||
return;
|
||
}
|
||
$('bosSecim').style.display = 'none';
|
||
$('secimFormu').style.display = '';
|
||
|
||
const sadeceMetin = ['metin','cizgi'].includes(o.tip);
|
||
$('oIcerik').parentElement.style.display = (o.tip === 'metin') ? '' : 'none';
|
||
$('oIcerik').value = o.icerik;
|
||
$('oFont').value = o.font;
|
||
$('oBoy').value = o.boy;
|
||
if ($('oAci')) $('oAci').value = Math.round(o.aci || 0);
|
||
if ($('oGuc')) $('oGuc').value = o.guc != null ? o.guc : 60;
|
||
$('oKalin').checked = o.kalin;
|
||
$('oAyna').checked = o.ayna;
|
||
$('onayKalınSatir').style.display = (o.tip === 'metin') ? '' : 'none';
|
||
$('onayAynaSatir').style.display = (o.tip === 'metin') ? '' : 'none';
|
||
olcuBilgiYaz(o);
|
||
}
|
||
|
||
/* Gerçek ölçü göstergesi — kullanıcı "kaç mm, kaç dakika" görsün.
|
||
Değer girilmez; tabladaki ölçüden hesaplanır. Dürüstlük kuralı: mm = mm. */
|
||
function olcuBilgiYaz(o){
|
||
const el = $('olcuBilgi');
|
||
if (!el) return;
|
||
const k = nesneKutu(o);
|
||
const s = S.ayar || {};
|
||
const hiz = o.hiz != null ? o.hiz : (s.hiz || 100); // mm/dk
|
||
const pas = o.pas != null ? o.pas : (s.pas || 1);
|
||
const aralik = o.aralik != null ? o.aralik : (s.aralik || 0.08);
|
||
|
||
let satirlar = [];
|
||
satirlar.push('ölçü : ' + k.w.toFixed(1) + ' × ' + k.h.toFixed(1) + ' mm');
|
||
// Konum gösterge kutusuna yaz (form değil, okunur bilgi)
|
||
if ($('kgKonum')) $('kgKonum').textContent = 'X ' + o.x.toFixed(1) + ' · Y ' + o.y.toFixed(1) + ' mm';
|
||
if ($('kgAci')) $('kgAci').textContent = Math.round(o.aci || 0) + '°';
|
||
|
||
if (o.tip === 'metin'){
|
||
// Dolgu (tarama) yolu uzunluğu: alan / satır aralığı
|
||
const alan = k.w * k.h; // mm²
|
||
const yol = aralik > 0 ? alan / aralik : 0; // mm toplam tarama
|
||
const dk = hiz > 0 ? (yol * pas) / hiz : 0;
|
||
satirlar.push('tarama: ' + Math.round(yol) + ' mm yol');
|
||
satirlar.push('süre : ' + sureYaz(dk) + (pas > 1 ? ' (' + pas + ' pas)' : ''));
|
||
} else {
|
||
const yol = (o.tip === 'cizgi') ? Math.hypot(o.g, o.h) * pas : (2 * (o.g + o.h)) * pas;
|
||
const dk = hiz > 0 ? yol / hiz : 0;
|
||
satirlar.push('yol : ' + Math.round(yol) + ' mm');
|
||
satirlar.push('süre : ' + sureYaz(dk));
|
||
}
|
||
el.textContent = satirlar.join('\n');
|
||
}
|
||
|
||
function sureYaz(dk){
|
||
if (!dk || !isFinite(dk)) return '—';
|
||
if (dk < 1) return Math.round(dk * 60) + ' sn';
|
||
const t = Math.floor(dk), sn = Math.round((dk - t) * 60);
|
||
return t + ' dk ' + (sn ? sn + ' sn' : '').trim();
|
||
}
|
||
|
||
/* Form bağlantıları */
|
||
$('oIcerik').oninput = e => { const o = seciliNesne(); if (o){ o.icerik = e.target.value; isListesiCiz(); ciz(); } };
|
||
$('oFont').onchange = e => { const o = seciliNesne(); if (o){ o.font = e.target.value; ciz(); } };
|
||
$('oBoy').oninput = e => { const o = seciliNesne(); if (o){ o.boy = +e.target.value || 2; ciz(); } };
|
||
/* oX / oY form kutuları KALDIRILDI — konum tablada fareyle belirlenir.
|
||
(LightBurn/RDWorks mantığı: koordinat yazarak değil, sürükleyerek.)
|
||
Tabla sürüklemesi mevcut editor.js üzerinden o.x / o.y'yi zaten günceller;
|
||
arayüzGuncelle() çağrısı konum göstergesini canlı yansıtır. */
|
||
$('oAci').oninput = e => {
|
||
const o = seciliNesne(); if (!o) return;
|
||
o.aci = Math.max(-180, Math.min(180, +e.target.value || 0));
|
||
arayuzGuncelle(); ciz();
|
||
};
|
||
$('oGuc').oninput = e => {
|
||
const o = seciliNesne(); if (!o) return;
|
||
o.guc = Math.max(1, Math.min(100, +e.target.value || 60));
|
||
gucArayuzGuncelle(); ciz();
|
||
};
|
||
$('oKalin').onchange = e => { const o = seciliNesne(); if (o){ o.kalin = e.target.checked; ciz(); } };
|
||
$('oAyna').onchange = e => { const o = seciliNesne(); if (o){ o.ayna = e.target.checked; ciz(); } };
|
||
|
||
/* Nesneyi tabla sınırları içinde tut — kendi boyutunu hesaba katar,
|
||
böylece yazı kenardan taşmaz ve kullanıcı kaybetmez. */
|
||
function sinirla(o){
|
||
const k = nesneKutu(o);
|
||
const ag = Math.max(2, k.w), ah = Math.max(2, k.h);
|
||
o.x = Math.max(0, Math.min(TABLA_MM - ag, o.x));
|
||
o.y = Math.max(0, Math.min(TABLA_MM - ah, o.y));
|
||
}
|
||
|
||
function durumGuncelle(){
|
||
const o = seciliNesne();
|
||
$('durumSecili').textContent = o
|
||
? ((o.tip === 'metin' ? o.icerik : ({cizgi:'Çizgi',dikdortgen:'Çerçeve',logo:'Logo'}[o.tip])) || '-')
|
||
: 'yok';
|
||
}
|
||
|
||
/* ============================================================
|
||
EKLEME TUŞLARI
|
||
============================================================ */
|
||
/* Ekleme tuşları — nesne oluştur, seç ve (yazı ise) canlı yazmaya geç.
|
||
Kullanıcı sol panele gitmek zorunda kalmadan tablada yazabilsin. */
|
||
function ekle(tip, ek){
|
||
const o = nesneOlustur(tip, ek);
|
||
S.seciliId = o.id;
|
||
if (tip === 'metin') edit.yazimBasla(o, true); // imleç yanıp sönsün, yazmaya hazır
|
||
arayuzGuncelle();
|
||
ciz();
|
||
return o;
|
||
}
|
||
$('btnMetinEkle').onclick = () => ekle('metin');
|
||
$('btnCizgiEkle').onclick = () => ekle('cizgi', { g:50, h:50, boy:2 });
|
||
$('btnDikdortgen').onclick = () => ekle('dikdortgen', { g:60, h:40, boy:2 });
|
||
$('btnLogo').onclick = () => ekle('logo', { g:35, h:35, boy:4 });
|
||
|
||
/* ============================================================
|
||
TABLA ETKİLEŞİMİ — sürükle / seç
|
||
============================================================ */
|
||
let surukle = null;
|
||
|
||
function posMm(ev){
|
||
const r = canvas.getBoundingClientRect();
|
||
const x = (ev.clientX - r.left) / mmPx;
|
||
const y = (ev.clientY - r.top) / mmPx;
|
||
return { x: Math.round(x * 10) / 10, y: Math.round(y * 10) / 10 };
|
||
}
|
||
|
||
/* ============================================================
|
||
KANVAS EDİTÖRÜ — tıkla-yaz, taşı, büyüt, döndür
|
||
Mantık ayrı modülde: public/js/editor.js
|
||
============================================================ */
|
||
const edit = EditoKur({
|
||
S, PX, MM,
|
||
mmPxAl : () => mmPx,
|
||
ciz, arayuzGuncelle, nesneKutu, sinirla, renk,
|
||
nesneOlustur, nesneSil
|
||
});
|
||
|
||
canvas.addEventListener('mousemove', ev => {
|
||
const p = posMm(ev);
|
||
S.imlec = p;
|
||
$('imlecPos').textContent = p.x.toFixed(0) + ', ' + p.y.toFixed(0) + ' mm';
|
||
if (!edit.hareket(ev, p)) {
|
||
// sürükleme yoksa sadece imleç şeklini güncelle
|
||
if (!S._testModu) canvas.style.cursor = edit.imlec(p);
|
||
}
|
||
});
|
||
|
||
canvas.addEventListener('mousedown', ev => {
|
||
// TEST modu: tıklanan yere kırmızı işaret koy (eski davranış korunur)
|
||
if (S._testModu){
|
||
testNoktasiKoy(...Object.values(posMm(ev)));
|
||
return;
|
||
}
|
||
const p = posMm(ev);
|
||
canvas.focus();
|
||
edit.bas(ev, p);
|
||
});
|
||
|
||
canvas.addEventListener('mouseup', ev => { edit.birak(); });
|
||
canvas.addEventListener('mouseleave', () => { edit.birak(); });
|
||
|
||
/* Dokunmatik destek (tablet) */
|
||
canvas.addEventListener('touchstart', ev => {
|
||
if (ev.touches.length !== 1) return;
|
||
const t = ev.touches[0];
|
||
const r = canvas.getBoundingClientRect();
|
||
const p = { x:(t.clientX - r.left)/mmPx, y:(t.clientY - r.top)/mmPx };
|
||
edit.bas({ detail:1, shiftKey:false }, p);
|
||
ev.preventDefault();
|
||
}, { passive:false });
|
||
canvas.addEventListener('touchmove', ev => {
|
||
if (ev.touches.length !== 1) return;
|
||
const t = ev.touches[0];
|
||
const r = canvas.getBoundingClientRect();
|
||
const p = { x:(t.clientX - r.left)/mmPx, y:(t.clientY - r.top)/mmPx };
|
||
edit.hareket({ shiftKey:false }, p);
|
||
ev.preventDefault();
|
||
}, { passive:false });
|
||
canvas.addEventListener('touchend', () => { edit.birak(); });
|
||
|
||
/* Klavye — önce YAZI YAZMA modu, sonra kısayollar.
|
||
DİKKAT: yazma modunda form alanlarına odaklanılmış olsa bile
|
||
harfler tabloya gider; kullanıcı imleci tabloda sanıyordur. */
|
||
document.addEventListener('keydown', ev => {
|
||
if (ev.ctrlKey && ev.key === 't'){ ev.preventDefault(); $('btnTema').click(); return; }
|
||
|
||
const etkin = document.activeElement;
|
||
const formda = etkin && ['INPUT','TEXTAREA','SELECT'].includes(etkin.tagName);
|
||
|
||
// Yazma modu AKTİFSE: form alanında değilsen harfleri tabloya yaz
|
||
if (edit.yazimAktifMi() && !formda){
|
||
if (edit.tus(ev)){ ev.preventDefault(); return; }
|
||
}
|
||
|
||
if (formda) return;
|
||
|
||
// Yazma modu kapalıysa editör kısayolları (Enter ile düzenle, R ile döndür)
|
||
if (edit.tus(ev)){ ev.preventDefault(); return; }
|
||
|
||
if (ev.key === 'Delete' && S.seciliId){ nesneSil(S.seciliId); }
|
||
if (ev.key === 'Escape'){ S._testModu = false; testModuArayuz(); ciz(); }
|
||
});
|
||
|
||
/* Sayfa gövdesine odaklanma — tabloya tıklanınca klavye hazır olsun */
|
||
document.body.setAttribute('tabindex', '-1');
|
||
document.body.style.outline = 'none';
|
||
|
||
/* ============================================================
|
||
GÖRÜNÜM AYARLARI
|
||
============================================================ */
|
||
$('btnZoomArti').onclick = () => { S.zoom = Math.min(MAX_ZOOM, S.zoom * 1.2); tablaBoyutlandir(); zoomYaz(); };
|
||
$('btnZoomEksi').onclick = () => { S.zoom = Math.max(MIN_ZOOM, S.zoom / 1.2); tablaBoyutlandir(); zoomYaz(); };
|
||
$('btnZoomSifir').onclick = () => { S.zoom = 1; tablaBoyutlandir(); zoomYaz(); };
|
||
function zoomYaz(){ $('zoomEtiket').textContent = '%' + Math.round(S.zoom * 100); }
|
||
|
||
$('izgaraOn').onchange = e => { S.izgaraOn = e.target.checked; ciz(); };
|
||
$('olcuOn').onchange = e => { S.olcuOn = e.target.checked; ciz(); };
|
||
|
||
$('btnTemizle').onclick = () => {
|
||
if (!S.nesneler.length && !S.testGecmis.length) return;
|
||
if (confirm('Tüm öğeler ve test işaretleri silinsin mi?')){
|
||
S.nesneler = []; S.seciliId = null; S.testGecmis = []; S.testNokta = null;
|
||
arayuzGuncelle(); ciz();
|
||
}
|
||
};
|
||
|
||
/* ============================================================
|
||
★ TEST — kırmızı işaret (lazer düşük güçle yer işaretler)
|
||
============================================================
|
||
Gerçek makinede bu adım: lazer KAPALI hareket (G0) ile hedeflenen
|
||
noktaya gidilir, sonra ÇOK DÜŞÜK güç (S-min, ~%1-3) ile kısa süre
|
||
yakılarak referans noktası bırakılır. Burada görsel olarak KIRMIZI
|
||
işaretle temsil edilir; kullanıcı konumu gözle doğrular.
|
||
*/
|
||
function testModuArayuz(){
|
||
const btn = $('btnTest');
|
||
if (btn) btn.classList.toggle('birincil', !!S._testModu);
|
||
}
|
||
|
||
/* KONTROL LİSTESİ — seçili öğe için onay maddeleri (Core truncated'da kayıptı;
|
||
arayuzGuncelle bunu her etkileşimde çağırıyordu → "onayKartiCiz is not defined"
|
||
→ tüm arayüz güncellemesi patlıyor, tuval yazıya tepki vermiyordu). */
|
||
function onayKartiCiz(){
|
||
const kap = $('onayListesi'); if (!kap) return;
|
||
const o = S.nesneler.find(n => n.id === S.seciliId);
|
||
if (!o){ kap.innerHTML = '<div style="font-size:11px;color:var(--dim);padding:8px 0;text-align:center">Öğe seçili değil</div>'; return; }
|
||
if (!o.onay) o.onay = {};
|
||
kap.innerHTML = '';
|
||
ONAY_MADDELERI.forEach(m => {
|
||
const d = document.createElement('label');
|
||
d.style.cssText = 'display:flex;align-items:center;gap:8px;padding:6px 4px;font-size:11.5px;cursor:pointer;color:var(--txt)';
|
||
d.innerHTML = '<input type="checkbox" ' + (o.onay[m.k] ? 'checked' : '') + ' style="accent-color:var(--accent)"><span></span>';
|
||
d.querySelector('span').textContent = m.t;
|
||
d.querySelector('input').onchange = e => { o.onay[m.k] = e.target.checked; isListesiCiz(); };
|
||
kap.appendChild(d);
|
||
});
|
||
}
|
||
/* Güç arayüzü — seçili öğenin güç/hız değerlerini panele yansıt (yoksa no-op). */
|
||
function gucArayuzGuncelle(){
|
||
const o = S.nesneler.find(n => n.id === S.seciliId);
|
||
if (!o) return;
|
||
if (o.guc != null && $('gGuc')) { $('gGuc').value = o.guc; if ($('gGucDeger')) $('gGucDeger').textContent = '%' + o.guc; }
|
||
if (o.hiz != null && $('gHiz')) { $('gHiz').value = o.hiz; if ($('gHizDeger')) $('gHizDeger').textContent = o.hiz + ' mm/dk'; }
|
||
}
|
||
|
||
/* ============================================================
|
||
★ KONTROL KATMANI (2026-09-12, Claude tarafından tamamlandı)
|
||
Core'un dosya_yaz'ı burayı "…(kısaltıldı)" placeholder'ıyla yarıda
|
||
bırakmıştı → tüm eylem butonları ölüydü. Burada:
|
||
malzeme/mod ayarları · G-code (GERÇEK engrave motoru: engrave.js) ·
|
||
.nc indir · Falcon2 Pro'ya OTOMATİK seri gönderim (Web Serial) tamamlandı.
|
||
============================================================ */
|
||
|
||
/* Kullanıcı bildirimi (lazer.js window.uyariGoster bekliyor). */
|
||
function uyariGoster(metin, tip){
|
||
const el = $('durumUyari');
|
||
if (el) el.innerHTML = '<span style="color:var(--' + (tip === 'red' ? 'red' : tip === 'ok' ? 'ok' : 'accent') + ');font-weight:700">' + metin + '</span>';
|
||
}
|
||
window.uyariGoster = uyariGoster;
|
||
const uyar = uyariGoster;
|
||
|
||
/* ---------- Malzeme / güç ön ayarları ---------- */
|
||
function malzemeUygula(id){
|
||
const p = GUC_PRESET[id]; if (!p) return;
|
||
if ($('gGuc')) { $('gGuc').value = p.guc; $('gGucDeger').textContent = '%' + p.guc; }
|
||
if ($('gHiz')) { $('gHiz').value = p.hiz; $('gHizDeger').textContent = p.hiz + ' mm/dk'; }
|
||
if ($('gPas')) { $('gPas').value = p.pas; $('gPasDeger').textContent = p.pas + ' pas'; }
|
||
if ($('gAralik')) { $('gAralik').value = p.aralik; $('gAralikDeger').textContent = p.aralik.toFixed(2) + ' mm'; }
|
||
}
|
||
if ($('gMalzeme')) $('gMalzeme').onchange = e => malzemeUygula(e.target.value);
|
||
if ($('gGuc')) $('gGuc').oninput = e => $('gGucDeger').textContent = '%' + e.target.value;
|
||
if ($('gHiz')) $('gHiz').oninput = e => $('gHizDeger').textContent = e.target.value + ' mm/dk';
|
||
if ($('gPas')) $('gPas').oninput = e => $('gPasDeger').textContent = e.target.value + ' pas';
|
||
if ($('gAralik')) $('gAralik').oninput = e => $('gAralikDeger').textContent = (+e.target.value).toFixed(2) + ' mm';
|
||
|
||
/* İşlem türü seçici (Kaşe kabartma-ayna / Düz kazıma / Kesme) — panele enjekte. */
|
||
function modSeciciKur(){
|
||
if ($('isMod') || !$('gMalzeme')) return;
|
||
const bolum = $('gMalzeme').closest('.bolum'); if (!bolum) return;
|
||
const wrap = document.createElement('div');
|
||
wrap.className = 'satir';
|
||
wrap.innerHTML = '<label for="isMod">İşlem türü</label>'
|
||
+ '<select id="isMod">'
|
||
+ '<option value="kase">Kaşe — ayna + kabartma (zemini kazır)</option>'
|
||
+ '<option value="kazima">Düz kazıma (yüzey markalama)</option>'
|
||
+ '<option value="kesme">Kesme (çerçeve/şekil kes)</option>'
|
||
+ '</select>';
|
||
bolum.insertBefore(wrap, bolum.firstChild ? bolum.firstChild.nextSibling : null);
|
||
}
|
||
|
||
/* Aktif engrave ayarları (arayüzden). */
|
||
function aktifAyar(){
|
||
return {
|
||
mod: ($('isMod') && $('isMod').value) || 'kase',
|
||
guc: +($('gGuc') ? $('gGuc').value : 78),
|
||
hiz: +($('gHiz') ? $('gHiz').value : 900),
|
||
pas: +($('gPas') ? $('gPas').value : 1),
|
||
aralik: +($('gAralik') ? $('gAralik').value : 0.10),
|
||
kesmeGuc: 100, kesmeHiz: 600, kesmePas: 3,
|
||
ayna: S.nesneler.some(o => o.ayna),
|
||
kutuFn: nesneKutu
|
||
};
|
||
}
|
||
|
||
/* ---------- GERÇEK G-code üretimi (engrave.js raster motoru) ---------- */
|
||
function gcodeUret(){
|
||
if (!S.nesneler.length){ uyar('Önce tablaya yazı/şekil ekle.', 'red'); return null; }
|
||
if (typeof Engrave === 'undefined'){ uyar('Engrave motoru yüklenmedi.', 'red'); return null; }
|
||
const r = Engrave.uret(S.nesneler, aktifAyar());
|
||
if (r.hata){ uyar(r.hata, 'red'); return null; }
|
||
return r.gcode;
|
||
}
|
||
|
||
/* G-code önizleme penceresi (modal). */
|
||
function gcodeModalGoster(){
|
||
const g = gcodeUret(); if (!g) return;
|
||
const satir = g.split('\n').length;
|
||
let ov = $('gcodeOverlay');
|
||
if (!ov){
|
||
ov = document.createElement('div');
|
||
ov.id = 'gcodeOverlay';
|
||
ov.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:9999';
|
||
ov.innerHTML = '<div style="background:var(--panel);border:1px solid var(--line);border-radius:12px;width:min(680px,92vw);max-height:82vh;display:flex;flex-direction:column;overflow:hidden">'
|
||
+ '<div style="display:flex;justify-content:space-between;align-items:center;padding:12px 16px;border-bottom:1px solid var(--line)"><b>G-code önizleme</b><span id="gcodeKapat" style="cursor:pointer;font-size:20px;color:var(--dim)">×</span></div>'
|
||
+ '<textarea id="gcodeAlan" readonly style="flex:1;min-height:300px;background:var(--mat);color:var(--txt);border:0;padding:14px;font:12px/1.5 monospace;resize:none"></textarea>'
|
||
+ '<div style="display:flex;gap:8px;padding:12px 16px;border-top:1px solid var(--line)"><button class="birincil" id="gcodeIndirBtn" style="flex:1">💾 .nc indir</button><button class="hayalet" id="gcodeKapat2" style="flex:1">Kapat</button></div>'
|
||
+ '</div>';
|
||
document.body.appendChild(ov);
|
||
const kapat = () => ov.style.display = 'none';
|
||
$('gcodeKapat').onclick = kapat;
|
||
$('gcodeKapat2').onclick = kapat;
|
||
$('gcodeIndirBtn').onclick = gcodeIndir;
|
||
ov.addEventListener('click', e => { if (e.target === ov) kapat(); });
|
||
}
|
||
$('gcodeAlan').value = g;
|
||
ov.style.display = 'flex';
|
||
uyar('G-code hazır — ' + satir + ' satır.', 'accent');
|
||
}
|
||
|
||
/* .nc dosyası indir (tarayıcı, sunucu gerekmez). */
|
||
function gcodeIndir(){
|
||
const g = gcodeUret(); if (!g) return;
|
||
const blob = new Blob([g], { type: 'text/plain;charset=utf-8' });
|
||
const url = URL.createObjectURL(blob);
|
||
const a = document.createElement('a');
|
||
a.href = url; a.download = 'kase-' + Date.now() + '.nc';
|
||
document.body.appendChild(a); a.click(); a.remove();
|
||
setTimeout(() => URL.revokeObjectURL(url), 3000);
|
||
uyar('✓ .nc indirildi — LightBurn / LaserGRBL ile açıp yakabilirsin.', 'ok');
|
||
}
|
||
|
||
/* ---------- Lazere OTOMATİK gönderim (Web Serial → Falcon2 Pro) ---------- */
|
||
function lazerVar(){ return typeof window.LazerAPI !== 'undefined' && window.LazerAPI.destekliMi && window.LazerAPI.destekliMi(); }
|
||
|
||
async function lazereBaglan(){
|
||
if (!lazerVar()){ uyar('Web Serial desteklenmiyor — Chrome veya Edge kullan.', 'red'); return false; }
|
||
if (window.LazerAPI.durum().bagli) return true;
|
||
uyar('Lazere bağlanılıyor… (port penceresinden makineni seç)', 'accent');
|
||
const b = await window.LazerAPI.baglan();
|
||
if (!b.tamam){ uyar('Bağlantı başarısız: ' + (b.hata || b.kod), 'red'); return false; }
|
||
uyar('✓ Lazer bağlı — hazır.', 'ok');
|
||
gonderButonDurumu();
|
||
return true;
|
||
}
|
||
|
||
async function lazereGonder(){
|
||
const g = gcodeUret(); if (!g) return;
|
||
if (!await lazereBaglan()) return;
|
||
uyar('🔥 Markalama başladı — durdurmak için Durdur.', 'accent');
|
||
const r = await window.LazerAPI.kes(g, {
|
||
ilerleme: p => uyar('Gönderiliyor… ' + p.gonderilen + '/' + p.toplam + ' satır', 'accent')
|
||
});
|
||
if (r.tamam) uyar('✓ Markalama tamamlandı (' + r.satir + ' satır).', 'ok');
|
||
else if (/ALARM/i.test(String(r.hata || ''))) uyar('⚠️ Makine ALARM durumunda (' + r.hata + '). KURTARMA: "Sıfırla" (home) butonuna bas — kafa köşeye gidip alarmı temizler — sonra tekrar YAZ. (Alarm genelde hareket sırasında reset ya da sınır anahtarından olur.)', 'red');
|
||
else uyar('Durduruldu / hata: ' + (r.hata || r.kod), 'red');
|
||
}
|
||
|
||
function lazerDurdur(){
|
||
if (lazerVar() && window.LazerAPI.acilDurdur){ window.LazerAPI.acilDurdur(); uyar('⏹ Durduruldu (acil).', 'red'); }
|
||
}
|
||
|
||
function gonderButonDurumu(){
|
||
const bagli = lazerVar() && window.LazerAPI.durum && window.LazerAPI.durum().bagli;
|
||
['btnLazereGonder', 'isBtnGonder', 'btnYaz'].forEach(id => { const b = $(id); if (b) b.disabled = !bagli && id === 'btnLazereGonder'; });
|
||
const lz = $('btnLazer'); if (lz) lz.classList.toggle('bagli', !!bagli);
|
||
}
|
||
|
||
/* ---------- YAZI EKLE — CorelDRAW gibi metin kutusu (2026-09-12) ----------
|
||
Eski akış "Yazı Ekle → tuvale kör yaz" idi (non-teknik kullanıcı yapamıyordu).
|
||
Artık tıklayınca net bir metin kutusu açılır; yazıp Ekle deyince tuvale gelir. */
|
||
function yaziEkleModal(mevcut){
|
||
let ov = $('yaziOverlay');
|
||
if (!ov){
|
||
ov = document.createElement('div');
|
||
ov.id = 'yaziOverlay';
|
||
ov.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:10000';
|
||
ov.innerHTML = '<div style="background:var(--panel);border:1px solid var(--line);border-radius:12px;width:min(440px,92vw);padding:22px">'
|
||
+ '<div style="font-weight:700;font-size:15px;margin-bottom:4px">Kaşeye yazılacak metin</div>'
|
||
+ '<div style="font-size:11px;color:var(--dim);margin-bottom:12px">Alt satır için Enter. Bitince "Ekle".</div>'
|
||
+ '<textarea id="yaziInput" rows="3" style="width:100%;box-sizing:border-box;background:var(--mat);color:var(--txt);border:1px solid var(--line);border-radius:8px;padding:11px;font-size:15px;line-height:1.4;resize:vertical" placeholder="Örn: AHMET YILMAZ Mali Müşavir"></textarea>'
|
||
+ '<div style="display:flex;gap:8px;margin-top:16px"><button class="birincil" id="yaziOk" style="flex:1;padding:11px">✓ Ekle</button><button class="hayalet" id="yaziIptal" style="flex:1;padding:11px">İptal</button></div>'
|
||
+ '</div>';
|
||
document.body.appendChild(ov);
|
||
const kapat = () => { ov.style.display = 'none'; };
|
||
const uygula = () => {
|
||
const t = $('yaziInput').value.replace(/\r/g, '').replace(/\n{3,}/g, '\n\n').trim();
|
||
if (t){
|
||
if (ov._duzenle){ ov._duzenle.icerik = t; }
|
||
else {
|
||
const o = nesneOlustur('metin', { icerik: t });
|
||
if (o){ o.icerik = t; S.seciliId = o.id; }
|
||
}
|
||
try { if (typeof edit !== 'undefined' && edit.yazimBitir) edit.yazimBitir(); } catch (e) {}
|
||
arayuzGuncelle(); ciz();
|
||
}
|
||
kapat();
|
||
};
|
||
$('yaziOk').onclick = uygula;
|
||
$('yaziIptal').onclick = kapat;
|
||
$('yaziInput').addEventListener('keydown', e => { if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); uygula(); } });
|
||
ov.addEventListener('click', e => { if (e.target === ov) kapat(); });
|
||
}
|
||
ov._duzenle = (mevcut && mevcut.tip === 'metin') ? mevcut : null;
|
||
$('yaziInput').value = ov._duzenle ? (ov._duzenle.icerik || '') : '';
|
||
ov.style.display = 'flex';
|
||
setTimeout(() => { const i = $('yaziInput'); if (i){ i.focus(); i.select(); } }, 40);
|
||
}
|
||
|
||
/* ---------- Buton bağlama ---------- */
|
||
function butonBagla(id, fn){ const b = $(id); if (b) b.onclick = fn; }
|
||
butonBagla('btnMetinEkle', () => yaziEkleModal());
|
||
butonBagla('btnGcode', gcodeModalGoster);
|
||
butonBagla('isBtnIndir', gcodeIndir);
|
||
butonBagla('isBtnGonder', lazereGonder);
|
||
butonBagla('btnLazereGonder', lazereGonder);
|
||
butonBagla('btnYaz', lazereGonder);
|
||
butonBagla('btnLazer', lazereBaglan);
|
||
butonBagla('btnMakineBasa', () => { if (lazerVar() && window.LazerAPI.durum().bagli) window.LazerAPI.isBaslangica(); else uyar('Önce lazere bağlan.', 'red'); });
|
||
butonBagla('btnMakineSifirla', async () => {
|
||
if (!(lazerVar() && window.LazerAPI.durum().bagli)) { uyar('Önce lazere bağlan.', 'red'); return; }
|
||
uyar('🏠 Makine sıfırlanıyor (home)… kafa köşeye gidip alarmı temizleyecek. Bitince YAZ diyebilirsin.', 'accent');
|
||
try { await window.LazerAPI.sifirla(); } catch (e) { uyar('Sıfırlama hatası: ' + (e && e.message || e), 'red'); }
|
||
});
|
||
butonBagla('btnAcilDurdur', lazerDurdur); // GERÇEK durdur butonu (btnDurdur diye bir şey yoktu → durmuyordu)
|
||
butonBagla('btnGucUygula', () => {
|
||
const guc = +(($('gGuc') || {}).value || 78), hiz = +(($('gHiz') || {}).value || 900),
|
||
pas = +(($('gPas') || {}).value || 1), aralik = +(($('gAralik') || {}).value || 0.10);
|
||
S.nesneler.forEach(o => { o.guc = guc; o.hiz = hiz; o.pas = pas; o.aralik = aralik; });
|
||
arayuzGuncelle(); uyar('✓ Güç/hız ayarları tüm öğelere uygulandı.', 'ok');
|
||
});
|
||
butonBagla('btnOnayTum', () => { const o = S.nesneler.find(n => n.id === S.seciliId); if (o){ if (!o.onay) o.onay = {}; ONAY_MADDELERI.forEach(m => o.onay[m.k] = true); arayuzGuncelle(); } else uyar('Önce bir öğe seç.', 'red'); });
|
||
butonBagla('btnOnaySifir', () => { const o = S.nesneler.find(n => n.id === S.seciliId); if (o){ o.onay = {}; arayuzGuncelle(); } });
|
||
butonBagla('btnTest', async () => {
|
||
if (lazerVar() && window.LazerAPI.durum().bagli){ await window.LazerAPI.isBaslangica(); uyar('TEST: başlangıç noktasına gidildi — konumu gözle doğrula.', 'accent'); }
|
||
else uyar('TEST için önce lazere bağlan (konum doğrulama makinede yapılır).', 'red');
|
||
});
|
||
|
||
/* Bağlantı durumunu izle → gönder butonlarını aç/kapat. */
|
||
if (lazerVar() && window.LazerAPI.dinle){
|
||
try { window.LazerAPI.dinle('durum', gonderButonDurumu); } catch (e) {}
|
||
}
|
||
|
||
/* ============================================================
|
||
BAŞLATMA
|
||
============================================================ */
|
||
/* "Merkeze gel" butonu — konumlandırma için lazeri tasarımın ortasına götürür (yoktu). */
|
||
function merkezeButonKur(){
|
||
if ($('btnMerkeze') || !$('btnMakineBasa')) return;
|
||
const b = document.createElement('button');
|
||
b.className = 'hayalet mini'; b.id = 'btnMerkeze';
|
||
b.textContent = 'Merkeze gel';
|
||
b.title = 'Lazeri tasarımın ortasına götür (rubberı yerleştirmek için)';
|
||
$('btnMakineBasa').parentNode.appendChild(b);
|
||
b.onclick = () => {
|
||
if (!(lazerVar() && window.LazerAPI.durum().bagli)) { uyar('Önce lazere bağlan.', 'red'); return; }
|
||
let cx = TABLA_MM / 2, cy = TABLA_MM / 2;
|
||
try { const s = Engrave.sinirKutu(S.nesneler, nesneKutu); if (s) { cx = s.x + s.w / 2; cy = s.y + s.h / 2; } } catch (e) {}
|
||
window.LazerAPI.komutGonder('G0 X' + cx.toFixed(1) + ' Y' + cy.toFixed(1));
|
||
uyar('Lazer tasarım merkezine gidiyor (' + cx.toFixed(0) + ', ' + cy.toFixed(0) + ' mm) — rubberı oraya hizala.', 'accent');
|
||
};
|
||
}
|
||
|
||
function baslat(){
|
||
try { const t = localStorage.getItem('numex-kase-tema'); if (t) temaUygula(t); } catch (e) {}
|
||
modSeciciKur();
|
||
merkezeButonKur();
|
||
malzemeUygula($('gMalzeme') ? $('gMalzeme').value : 'kauçuk');
|
||
gonderButonDurumu();
|
||
try { tablaBoyutlandir(); } catch (e) {}
|
||
try { arayuzGuncelle(); } catch (e) {}
|
||
try { ciz(); } catch (e) {}
|
||
}
|
||
baslat();
|
||
window.addEventListener('resize', () => { try { tablaBoyutlandir(); zoomYaz(); } catch (e) {} });
|
||
|
||
</script>
|
||
</body>
|
||
</html> |