Files
3d_Kalibrasyon/index.html
2025-11-09 17:24:14 +03:00

539 lines
30 KiB
HTML
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.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Yazıcı Kalibrasyon</title>
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
/* Utility Classes */
.min-h-screen { min-height: 100vh; }
.bg-gradient { background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%); }
.text-white { color: white; }
.p-4 { padding: 1rem; }
.max-w-6xl { max-width: 72rem; }
.mx-auto { margin-left: auto; margin-right: auto; }
.text-center { text-align: center; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-6 { margin-bottom: 1.5rem; }
.mb-8 { margin-bottom: 2rem; }
.mt-1 { margin-top: 0.25rem; }
.mt-2 { margin-top: 0.5rem; }
.mt-4 { margin-top: 1rem; }
.mt-6 { margin-top: 1.5rem; }
.text-4xl { font-size: 2.25rem; line-height: 2.5rem; }
.text-2xl { font-size: 1.5rem; line-height: 2rem; }
.text-xl { font-size: 1.25rem; line-height: 1.75rem; }
.text-lg { font-size: 1.125rem; line-height: 1.75rem; }
.text-sm { font-size: 0.875rem; line-height: 1.25rem; }
.text-xs { font-size: 0.75rem; line-height: 1rem; }
.font-bold { font-weight: 700; }
.font-semibold { font-weight: 600; }
.bg-slate-800 { background-color: rgba(30, 41, 59, 0.5); }
.bg-slate-700 { background-color: #334155; }
.bg-slate-900 { background-color: #0f172a; }
.text-slate-400 { color: #94a3b8; }
.text-slate-500 { color: #64748b; }
.text-slate-300 { color: #cbd5e1; }
.text-cyan-400 { color: #22d3ee; }
.text-cyan-300 { color: #67e8f9; }
.text-blue-400 { color: #60a5fa; }
.text-blue-300 { color: #93c5fd; }
.text-green-400 { color: #4ade80; }
.text-yellow-400 { color: #facc15; }
.text-yellow-300 { color: #fde047; }
.border { border-width: 1px; }
.border-slate-700 { border-color: #334155; }
.border-slate-600 { border-color: #475569; }
.rounded { border-radius: 0.25rem; }
.rounded-lg { border-radius: 0.5rem; }
.px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
.px-4 { padding-left: 1rem; padding-right: 1rem; }
.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
.p-4 { padding: 1rem; }
.p-6 { padding: 1.5rem; }
.w-full { width: 100%; }
.flex { display: flex; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-2 { gap: 0.5rem; }
.gap-4 { gap: 1rem; }
.grid { display: grid; }
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
.overflow-x-auto { overflow-x: auto; }
.whitespace-nowrap { white-space: nowrap; }
.shadow-lg { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); }
.cursor-pointer { cursor: pointer; }
.transition-all { transition: all 0.3s; }
.backdrop-blur { backdrop-filter: blur(8px); }
.block { display: block; }
.space-y-1 > * + * { margin-top: 0.25rem; }
.space-y-3 > * + * { margin-top: 0.75rem; }
.list-decimal { list-style-type: decimal; }
.list-inside { list-style-position: inside; }
.ml-4 { margin-left: 1rem; }
.overflow-x-auto::-webkit-scrollbar { height: 8px; }
.overflow-x-auto::-webkit-scrollbar-track { background: #1e293b; }
.overflow-x-auto::-webkit-scrollbar-thumb { background: #334155; border-radius: 4px; }
.bg-gradient-cyan { background: linear-gradient(135deg, #06b6d4 0%, #3b82f6 100%); }
.bg-cyan-10 { background-color: rgba(34, 211, 238, 0.1); }
.border-cyan-30 { border-color: rgba(34, 211, 238, 0.3); }
.bg-blue-10 { background-color: rgba(96, 165, 250, 0.1); }
.border-blue-30 { border-color: rgba(96, 165, 250, 0.3); }
input[type="number"] {
-moz-appearance: textfield;
color: white;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 20px 25px -5px rgba(6, 182, 212, 0.3);
}
.tab-active {
background: linear-gradient(135deg, #06b6d4 0%, #3b82f6 100%);
color: white;
}
.tab-inactive {
background-color: rgba(30, 41, 59, 0.5);
color: #94a3b8;
}
.tab-inactive:hover {
background-color: #334155;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
font-family: 'Courier New', monospace;
}
code {
background-color: #0f172a;
padding: 0.125rem 0.5rem;
border-radius: 0.25rem;
}
@media (min-width: 768px) {
.md\\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.md\\:flex-row { flex-direction: row; }
}
@media (max-width: 767px) {
.flex-col { flex-direction: column; }
}
</style>
</head>
<body>
<div id="root"></div>
<script>
const { useState, createElement: e } = React;
// Icon components (simplified versions)
const Download = () => e('svg', { width: 20, height: 20, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2 },
e('path', { d: 'M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3' }));
const Settings = () => e('svg', { width: 20, height: 20, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2 },
e('circle', { cx: 12, cy: 12, r: 3 }),
e('path', { d: 'M12 1v6m0 6v6m8.66-10a9 9 0 1 1-17.32 0' }));
const Move = () => e('svg', { width: 18, height: 18, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2 },
e('polyline', { points: '5 9 2 12 5 15' }),
e('polyline', { points: '9 5 12 2 15 5' }),
e('polyline', { points: '15 19 12 22 9 19' }),
e('polyline', { points: '19 9 22 12 19 15' }),
e('line', { x1: 2, y1: 12, x2: 22, y2: 12 }),
e('line', { x1: 12, y1: 2, x2: 12, y2: 22 }));
const Box = () => e('svg', { width: 18, height: 18, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2 },
e('path', { d: 'M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z' }),
e('polyline', { points: '3.27 6.96 12 12.01 20.73 6.96' }),
e('line', { x1: 12, y1: 22.08, x2: 12, y2: 12 }));
const Maximize2 = () => e('svg', { width: 18, height: 18, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2 },
e('polyline', { points: '15 3 21 3 21 9' }),
e('polyline', { points: '9 21 3 21 3 15' }),
e('line', { x1: 21, y1: 3, x2: 14, y2: 10 }),
e('line', { x1: 3, y1: 21, x2: 10, y2: 14 }));
const Droplet = () => e('svg', { width: 18, height: 18, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2 },
e('path', { d: 'M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z' }));
function PrinterCalibration() {
const [activeTab, setActiveTab] = useState('x-axis');
const [settings, setSettings] = useState({
bedTemp: 60,
nozzleTemp: 200,
nozzleDiameter: 0.4
});
const [xAxisConfig, setXAxisConfig] = useState({ distance: 100, steps: 80, measured: 0 });
const [yAxisConfig, setYAxisConfig] = useState({ distance: 100, steps: 80, measured: 0 });
const [zAxisConfig, setZAxisConfig] = useState({ distance: 10, steps: 400, measured: 0 });
const [extruderConfig, setExtruderConfig] = useState({ distance: 100, steps: 93, measured: 0 });
const [skewConfig, setSkewConfig] = useState({ ac: 141.4, bd: 141.4, ad: 141.4 });
const [flowConfig, setFlowConfig] = useState({
wallThickness: 0.4,
measuredWall: 0,
currentFlow: 100,
layerHeight: 0.2,
lineWidth: 0.4
});
const calculateSteps = (config) => {
if (config.measured > 0) {
return ((config.distance / config.measured) * config.steps).toFixed(2);
}
return config.steps;
};
const calculateSkew = () => {
const ac = parseFloat(skewConfig.ac);
const bd = parseFloat(skewConfig.bd);
const ad = parseFloat(skewConfig.ad);
if (ac > 0 && bd > 0 && ad > 0) {
return {
xy: (Math.atan2(ac - 141.4, 100) * (180 / Math.PI)).toFixed(4),
xz: (Math.atan2(bd - 141.4, 100) * (180 / Math.PI)).toFixed(4),
yz: (Math.atan2(ad - 141.4, 100) * (180 / Math.PI)).toFixed(4)
};
}
return { xy: '0.0000', xz: '0.0000', yz: '0.0000' };
};
const calculateFlow = () => {
if (flowConfig.measuredWall > 0) {
const newFlow = (flowConfig.wallThickness / flowConfig.measuredWall) * flowConfig.currentFlow;
return newFlow.toFixed(2);
}
return flowConfig.currentFlow;
};
const generateGCode = () => {
let gcode = '; 3D Yazıcı Kalibrasyon G-Code\n';
gcode += '; Oluşturma Tarihi: ' + new Date().toLocaleString('tr-TR') + '\n\n';
gcode += `M140 S${settings.bedTemp}\nM104 S${settings.nozzleTemp}\n`;
gcode += `M190 S${settings.bedTemp}\nM109 S${settings.nozzleTemp}\n\n`;
const configs = { 'x-axis': xAxisConfig, 'y-axis': yAxisConfig, 'z-axis': zAxisConfig, 'extruder': extruderConfig };
const axes = { 'x-axis': 'X', 'y-axis': 'Y', 'z-axis': 'Z', 'extruder': 'E' };
if (activeTab === 'skew') {
const skew = calculateSkew();
gcode += `M852 I${skew.xy} J${skew.xz} K${skew.yz}\nM500\n`;
} else if (activeTab === 'flow') {
gcode += '; Akış Kalibrasyonu - Tek Duvarlı Küp\n';
gcode += 'G28\nG90\nM82\n';
gcode += `M221 S${flowConfig.currentFlow} ; Mevcut akış oranı\n`;
gcode += 'G92 E0\nG1 Z0.2 F300\n\n';
// 20x20mm tek duvarlı küp (10 katman)
const size = 20;
const wallThickness = flowConfig.wallThickness;
const layerHeight = flowConfig.layerHeight;
const feedrate = 1800;
for (let layer = 0; layer < 10; layer++) {
const z = (layer + 1) * layerHeight;
gcode += `; Katman ${layer + 1}\n`;
gcode += `G1 Z${z.toFixed(2)} F300\n`;
// Kare çiz (tek duvar)
const startX = 100;
const startY = 100;
gcode += `G1 X${startX} Y${startY} F${feedrate}\n`;
gcode += `G1 X${startX + size} Y${startY} E${((layer * 4 + 1) * 0.5).toFixed(4)} F${feedrate}\n`;
gcode += `G1 X${startX + size} Y${startY + size} E${((layer * 4 + 2) * 0.5).toFixed(4)} F${feedrate}\n`;
gcode += `G1 X${startX} Y${startY + size} E${((layer * 4 + 3) * 0.5).toFixed(4)} F${feedrate}\n`;
gcode += `G1 X${startX} Y${startY} E${((layer * 4 + 4) * 0.5).toFixed(4)} F${feedrate}\n`;
}
gcode += `\n; Hedef duvar kalınlığı: ${wallThickness}mm\n`;
gcode += `; Ölçülen duvar kalınlığı: ${flowConfig.measuredWall || '?'}mm\n`;
gcode += `; Hesaplanan akış: ${calculateFlow()}%\n`;
gcode += `; Slicer'da uygulamak için Flow Rate: ${calculateFlow()}%\n`;
} else {
const config = configs[activeTab];
const axis = axes[activeTab];
gcode += 'G28\nG90\n';
if (activeTab === 'extruder') {
gcode += `M82\nG92 E0\nG1 E${config.distance} F100\n`;
} else {
if (activeTab !== 'z-axis') gcode += 'G1 Z10 F3000\n';
gcode += `G1 ${axis}${config.distance} F${activeTab === 'z-axis' ? '300' : '3000'}\n`;
}
gcode += `M400\n; M92 ${axis}${calculateSteps(config)}\n; M500\n`;
}
gcode += '\nM104 S0\nM140 S0\nG28 X Y\nM84\n';
return gcode;
};
const downloadGCode = () => {
const blob = new Blob([generateGCode()], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `calibration_${activeTab}_${Date.now()}.gcode`;
a.click();
URL.revokeObjectURL(url);
};
const tabs = [
{ id: 'x-axis', name: 'X Ekseni', icon: Move },
{ id: 'y-axis', name: 'Y Ekseni', icon: Move },
{ id: 'z-axis', name: 'Z Ekseni', icon: Move },
{ id: 'extruder', name: 'Extruder', icon: Box },
{ id: 'flow', name: 'Akış', icon: Droplet },
{ id: 'skew', name: 'Çarpıklık', icon: Maximize2 }
];
const renderAxisCalibration = (config, setConfig, axis) => {
return e('div', null,
e('h3', { className: 'text-2xl font-semibold mb-4 text-cyan-400' }, `${axis} Ekseni Kalibrasyonu`),
e('div', { className: 'grid grid-cols-1 md:grid-cols-3 gap-4 mb-6' },
e('div', null,
e('label', { className: 'block text-sm text-slate-400 mb-1' }, 'Hedef Mesafe (mm)'),
e('input', {
type: 'number',
value: config.distance,
onChange: (ev) => setConfig({...config, distance: parseInt(ev.target.value)}),
className: 'w-full bg-slate-700 border border-slate-600 rounded px-3 py-2'
})
),
e('div', null,
e('label', { className: 'block text-sm text-slate-400 mb-1' }, 'Mevcut Steps/mm'),
e('input', {
type: 'number',
step: '0.01',
value: config.steps,
onChange: (ev) => setConfig({...config, steps: parseFloat(ev.target.value)}),
className: 'w-full bg-slate-700 border border-slate-600 rounded px-3 py-2'
})
),
e('div', null,
e('label', { className: 'block text-sm text-slate-400 mb-1' }, 'Ölçülen Mesafe (mm)'),
e('input', {
type: 'number',
step: '0.01',
value: config.measured,
onChange: (ev) => setConfig({...config, measured: parseFloat(ev.target.value)}),
className: 'w-full bg-slate-700 border border-slate-600 rounded px-3 py-2',
placeholder: 'Test sonrası giriniz'
})
)
),
config.measured > 0 && e('div', { className: 'bg-cyan-10 border border-cyan-30 rounded-lg p-4' },
e('h4', { className: 'font-semibold text-cyan-400 mb-2' }, 'Hesaplanan Değer:'),
e('p', { className: 'text-2xl font-bold text-cyan-300' }, `${calculateSteps(config)} steps/mm`)
)
);
};
return e('div', { className: 'min-h-screen bg-gradient text-white p-4' },
e('div', { className: 'max-w-6xl mx-auto' },
e('div', { className: 'text-center mb-8' },
e('h1', { className: 'text-4xl font-bold mb-2', style: { background: 'linear-gradient(135deg, #22d3ee 0%, #3b82f6 100%)', WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent' } },
'3D Yazıcı Kalibrasyon'
),
e('p', { className: 'text-slate-400' }, 'Profesyonel Kalibrasyon ve G-code Üretici')
),
e('div', { className: 'bg-slate-800 backdrop-blur rounded-lg p-6 mb-6 border border-slate-700' },
e('div', { className: 'flex items-center gap-2 mb-4' },
e(Settings),
e('h2', { className: 'text-xl font-semibold' }, 'Genel Ayarlar')
),
e('div', { className: 'grid grid-cols-1 md:grid-cols-3 gap-4' },
e('div', null,
e('label', { className: 'block text-sm text-slate-400 mb-1' }, 'Tabla Sıcaklığı (°C)'),
e('input', {
type: 'number',
value: settings.bedTemp,
onChange: (ev) => setSettings({...settings, bedTemp: parseInt(ev.target.value)}),
className: 'w-full bg-slate-700 border border-slate-600 rounded px-3 py-2'
})
),
e('div', null,
e('label', { className: 'block text-sm text-slate-400 mb-1' }, 'Nozzle Sıcaklığı (°C)'),
e('input', {
type: 'number',
value: settings.nozzleTemp,
onChange: (ev) => setSettings({...settings, nozzleTemp: parseInt(ev.target.value)}),
className: 'w-full bg-slate-700 border border-slate-600 rounded px-3 py-2'
})
),
e('div', null,
e('label', { className: 'block text-sm text-slate-400 mb-1' }, 'Nozzle Çapı (mm)'),
e('input', {
type: 'number',
step: '0.1',
value: settings.nozzleDiameter,
onChange: (ev) => setSettings({...settings, nozzleDiameter: parseFloat(ev.target.value)}),
className: 'w-full bg-slate-700 border border-slate-600 rounded px-3 py-2'
})
)
)
),
e('div', { className: 'flex gap-2 mb-6 overflow-x-auto' },
tabs.map(tab => e('button', {
key: tab.id,
onClick: () => setActiveTab(tab.id),
className: `flex items-center gap-2 px-4 py-3 rounded-lg transition-all whitespace-nowrap ${activeTab === tab.id ? 'tab-active shadow-lg' : 'tab-inactive'}`
},
e(tab.icon),
tab.name
))
),
e('div', { className: 'bg-slate-800 backdrop-blur rounded-lg p-6 border border-slate-700 mb-6' },
activeTab === 'x-axis' && renderAxisCalibration(xAxisConfig, setXAxisConfig, 'X'),
activeTab === 'y-axis' && renderAxisCalibration(yAxisConfig, setYAxisConfig, 'Y'),
activeTab === 'z-axis' && renderAxisCalibration(zAxisConfig, setZAxisConfig, 'Z'),
activeTab === 'extruder' && renderAxisCalibration(extruderConfig, setExtruderConfig, 'Extruder'),
activeTab === 'flow' && e('div', null,
e('h3', { className: 'text-2xl font-semibold mb-4 text-cyan-400' }, 'Akış Kalibrasyonu'),
e('p', { className: 'text-slate-400 mb-6' },
'Tek duvarlı küp bastırın ve duvar kalınlığını ölçerek akış oranını optimize edin.'
),
e('div', { className: 'grid grid-cols-1 md:grid-cols-3 gap-4 mb-6' },
e('div', null,
e('label', { className: 'block text-sm text-slate-400 mb-1' }, 'Hedef Duvar Kalınlığı (mm)'),
e('input', {
type: 'number',
step: '0.01',
value: flowConfig.wallThickness,
onChange: (ev) => setFlowConfig({...flowConfig, wallThickness: parseFloat(ev.target.value)}),
className: 'w-full bg-slate-700 border border-slate-600 rounded px-3 py-2'
}),
e('p', { className: 'text-xs text-slate-500 mt-1' }, 'Genellikle nozzle çapı ile aynı')
),
e('div', null,
e('label', { className: 'block text-sm text-slate-400 mb-1' }, 'Mevcut Akış Oranı (%)'),
e('input', {
type: 'number',
value: flowConfig.currentFlow,
onChange: (ev) => setFlowConfig({...flowConfig, currentFlow: parseInt(ev.target.value)}),
className: 'w-full bg-slate-700 border border-slate-600 rounded px-3 py-2'
}),
e('p', { className: 'text-xs text-slate-500 mt-1' }, 'Slicer\'daki mevcut değer')
),
e('div', null,
e('label', { className: 'block text-sm text-slate-400 mb-1' }, 'Ölçülen Duvar Kalınlığı (mm)'),
e('input', {
type: 'number',
step: '0.01',
value: flowConfig.measuredWall,
onChange: (ev) => setFlowConfig({...flowConfig, measuredWall: parseFloat(ev.target.value)}),
className: 'w-full bg-slate-700 border border-slate-600 rounded px-3 py-2',
placeholder: 'Baskı sonrası ölçün'
}),
e('p', { className: 'text-xs text-slate-500 mt-1' }, 'Kumpas ile ölçüm yapın')
)
),
e('div', { className: 'bg-blue-10 border border-blue-30 rounded-lg p-4 mb-6' },
e('h4', { className: 'font-semibold text-blue-400 mb-2' }, '📐 Ölçüm İpuçları:'),
e('ul', { className: 'text-sm text-slate-300 space-y-1 ml-4 list-inside' },
e('li', null, '• Küpün ortasından ölçüm yapın (üst ve alt köşelerden kaçının)'),
e('li', null, '• Farklı noktalardan 3-5 ölçüm alın ve ortalamasını kullanın'),
e('li', null, '• Baskı tamamen soğuduktan sonra ölçün'),
e('li', null, '• Dijital kumpas kullanarak 0.01mm hassasiyetle ölçün')
)
),
flowConfig.measuredWall > 0 && e('div', { className: 'bg-cyan-10 border border-cyan-30 rounded-lg p-4' },
e('h4', { className: 'font-semibold text-cyan-400 mb-2' }, 'Hesaplanan Değerler:'),
e('div', { className: 'grid grid-cols-1 md:grid-cols-2 gap-4' },
e('div', null,
e('p', { className: 'text-sm text-slate-400' }, 'Yeni Akış Oranı'),
e('p', { className: 'text-2xl font-bold text-cyan-300' }, `${calculateFlow()}%`)
),
e('div', null,
e('p', { className: 'text-sm text-slate-400' }, 'Fark'),
e('p', { className: 'text-xl font-bold text-cyan-300' },
`${(calculateFlow() - flowConfig.currentFlow).toFixed(2)}%`
)
)
),
e('p', { className: 'text-sm text-slate-400 mt-4' },
'Slicer\'ınızda Flow Rate ayarını ',
e('code', { className: 'text-cyan-400' }, `${calculateFlow()}%`),
' olarak değiştirin.'
),
Math.abs(calculateFlow() - flowConfig.currentFlow) > 10 &&
e('p', { className: 'text-sm text-yellow-400 mt-2' },
'⚠️ Uyarı: %10\'dan fazla fark var. Extruder steps/mm kalibrasyonunu kontrol edin.'
)
)
),
activeTab === 'skew' && e('div', null,
e('h3', { className: 'text-2xl font-semibold mb-4 text-cyan-400' }, 'Çarpıklık Kalibrasyonu'),
e('div', { className: 'grid grid-cols-1 md:grid-cols-3 gap-4 mb-6' },
['ac', 'bd', 'ad'].map((key, idx) => e('div', { key },
e('label', { className: 'block text-sm text-slate-400 mb-1' }, ['AC Köşegeni (mm)', 'BD Köşegeni (mm)', 'AD Köşegeni (mm)'][idx]),
e('input', {
type: 'number',
step: '0.01',
value: skewConfig[key],
onChange: (ev) => setSkewConfig({...skewConfig, [key]: ev.target.value}),
className: 'w-full bg-slate-700 border border-slate-600 rounded px-3 py-2'
})
))
),
e('div', { className: 'bg-cyan-10 border border-cyan-30 rounded-lg p-4' },
e('h4', { className: 'font-semibold text-cyan-400 mb-3' }, 'Hesaplanan Çarpıklık Değerleri:'),
e('div', { className: 'grid grid-cols-1 md:grid-cols-3 gap-4' },
['xy', 'xz', 'yz'].map(k => e('div', { key: k },
e('p', { className: 'text-sm text-slate-400' }, `${k.toUpperCase()} Çarpıklığı`),
e('p', { className: 'text-xl font-bold text-cyan-300' }, `${calculateSkew()[k]}°`)
))
)
)
)
),
e('div', { className: 'bg-slate-800 backdrop-blur rounded-lg p-6 border border-slate-700' },
e('div', { className: 'flex flex-col md:flex-row items-center justify-between gap-4' },
e('div', null,
e('h3', { className: 'text-lg font-semibold mb-1' }, 'G-code Oluştur'),
e('p', { className: 'text-sm text-slate-400' }, 'Seçili kalibrasyon için G-code dosyası indir')
),
e('button', {
onClick: downloadGCode,
className: 'flex items-center gap-2 bg-gradient-cyan px-6 py-3 rounded-lg font-semibold transition-all shadow-lg cursor-pointer'
},
e(Download),
'G-code İndir'
)
),
e('div', { className: 'mt-6' },
e('h4', { className: 'text-sm font-semibold text-slate-400 mb-2' }, 'G-code Önizleme:'),
e('div', { className: 'bg-slate-900 rounded-lg p-4 overflow-x-auto' },
e('pre', { className: 'text-xs text-green-400' }, generateGCode())
)
)
)
)
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(e(PrinterCalibration));
</script>
</body>
</html>