Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>G-code Farm Loop Modifier (.3mf Support)</title>
- <style>
- * { margin: 0; padding: 0; box-sizing: border-box; }
- body { font-family: Arial, sans-serif; background: #f5f5f5; padding: 20px; }
- .container { max-width: 1000px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
- h1 { color: #333; margin-bottom: 20px; text-align: center; }
- .section { margin-bottom: 30px; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
- .section h2 { color: #555; margin-bottom: 15px; font-size: 1.2em; }
- .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; }
- .param-group { display: flex; flex-direction: column; }
- .param-group label { margin-bottom: 5px; font-weight: bold; color: #666; }
- .param-group input, .param-group select { padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
- .btn { padding: 10px 20px; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 10px; margin-bottom: 10px; }
- .btn:disabled { background: #ccc; cursor: not-allowed; }
- .btn-primary { background: #007bff; }
- .btn-primary:hover:not(:disabled) { background: #0056b3; }
- .btn-success { background: #28a745; }
- .btn-success:hover:not(:disabled) { background: #218838; }
- .btn-info { background: #17a2b8; }
- .btn-info:hover:not(:disabled) { background: #138496; }
- .file-input { padding: 10px; border: 2px dashed #ccc; border-radius: 4px; text-align: center; cursor: pointer; }
- .file-input:hover { border-color: #007bff; }
- .file-info { background: #e8f5e8; padding: 10px; border-radius: 4px; margin-top: 10px; }
- .status { padding: 10px; border-radius: 4px; margin-bottom: 15px; }
- .status.success { background: #d4edda; color: #155724; }
- .status.error { background: #f8d7da; color: #721c24; }
- .status.info { background: #cce7ff; color: #004085; }
- .status.warning { background: #fff3cd; color: #856404; }
- .preview { background: #f8f9fa; padding: 15px; border-radius: 4px; max-height: 400px; overflow-y: auto; }
- .preview pre { margin: 0; white-space: pre-wrap; font-family: monospace; font-size: 0.9em; }
- .hidden { display: none; }
- .file-type-badge { display: inline-block; padding: 3px 8px; color: white; border-radius: 12px; font-size: 0.8em; margin-left: 10px; }
- .file-type-badge.threemf { background: #ff6b35; }
- .file-type-badge.gcode { background: #28a745; }
- .progress { width: 100%; height: 20px; background: #e9ecef; border-radius: 10px; overflow: hidden; margin: 10px 0; }
- .progress-bar { height: 100%; background: #007bff; transition: width 0.3s ease; }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>G-code Farm Loop Modifier (.3mf Support)</h1>
- <div class="section">
- <h2>Load File</h2>
- <div class="file-input" onclick="document.getElementById('fileInput').click()">
- <input type="file" id="fileInput" accept=".gcode,.g,.txt,.3mf" style="display: none;">
- Click to select G-code file (.gcode, .g, .txt) or 3MF file (.3mf)
- </div>
- <div id="fileInfo" class="file-info hidden"></div>
- <div id="progressContainer" class="hidden">
- <div class="progress">
- <div id="progressBar" class="progress-bar" style="width: 0%"></div>
- </div>
- <div id="progressText">Processing...</div>
- </div>
- </div>
- <div class="section">
- <h2>Parameters</h2>
- <div class="grid">
- <div class="param-group">
- <label for="loopCount">Number of Loops</label>
- <input type="number" id="loopCount" min="1" max="50" value="3">
- </div>
- <div class="param-group">
- <label for="cooldownTemp">Cooldown Temperature (°C)</label>
- <input type="number" id="cooldownTemp" min="20" max="200" value="25">
- </div>
- <div class="param-group">
- <label for="waitTime">Wait Time (seconds)</label>
- <input type="number" id="waitTime" min="0" max="3600" value="30">
- </div>
- <div class="param-group">
- <label for="wipeEnabled">Enable Wipe Sequence</label>
- <select id="wipeEnabled" disabled>
- <option value="true" selected>Yes (Always Enabled)</option>
- </select>
- </div>
- </div>
- </div>
- <div class="section">
- <h2>Process</h2>
- <div id="status" class="status hidden"></div>
- <button id="processBtn" class="btn btn-primary" disabled>Process G-code</button>
- <button id="previewBtn" class="btn btn-primary" disabled>Preview Added Blocks</button>
- <div id="downloadSection" class="hidden">
- <button id="downloadGcodeBtn" class="btn btn-success">Download G-code</button>
- <button id="download3mfBtn" class="btn btn-info hidden">Download 3MF</button>
- </div>
- </div>
- <div id="previewSection" class="section hidden">
- <h2>Preview - New G-code Blocks Added (First Loop)</h2>
- <div class="preview">
- <pre id="previewContent"></pre>
- </div>
- </div>
- </div>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
- <script>
- class GCodeFarmModifier {
- constructor() {
- this.originalGCode = '';
- this.modifiedGCode = '';
- this.fileName = '';
- this.fileType = '';
- this.addedBlocks = '';
- this.threemfZip = null;
- this.gcodeFilePath = null;
- this.init();
- }
- init() {
- const $ = id => document.getElementById(id);
- $('fileInput').addEventListener('change', e => this.loadFile(e));
- $('processBtn').addEventListener('click', () => this.process());
- $('downloadGcodeBtn').addEventListener('click', () => this.downloadGcode());
- $('download3mfBtn').addEventListener('click', () => this.download3mf());
- $('previewBtn').addEventListener('click', () => this.preview());
- }
- updateProgress(percent, text) {
- const container = document.getElementById('progressContainer');
- const bar = document.getElementById('progressBar');
- const textEl = document.getElementById('progressText');
- container.classList.toggle('hidden', false);
- bar.style.width = percent + '%';
- textEl.textContent = text;
- if (percent >= 100) setTimeout(() => container.classList.add('hidden'), 2000);
- }
- async loadFile(event) {
- const file = event.target.files[0];
- if (!file) return;
- this.fileName = file.name;
- this.fileType = file.name.toLowerCase().endsWith('.3mf') ? '3mf' : 'gcode';
- this.reset();
- try {
- this.fileType === '3mf' ? await this.load3mf(file) : await this.loadGcode(file);
- } catch (error) {
- this.showStatus(`Error loading file: ${error.message}`, 'error');
- }
- }
- reset() {
- this.originalGCode = '';
- this.modifiedGCode = '';
- this.addedBlocks = '';
- this.threemfZip = null;
- this.gcodeFilePath = null;
- }
- async loadGcode(file) {
- this.updateProgress(30, 'Reading G-code file...');
- const text = await new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = e => resolve(e.target.result);
- reader.onerror = () => reject(new Error('Failed to read file'));
- reader.readAsText(file);
- });
- if (!text?.trim()) throw new Error('G-code file is empty');
- this.originalGCode = text;
- this.updateProgress(100, 'G-code loaded successfully!');
- this.showFileInfo(file);
- this.updateButtons();
- }
- async load3mf(file) {
- this.updateProgress(30, 'Parsing 3MF structure...');
- const buffer = await new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = e => resolve(e.target.result);
- reader.onerror = () => reject(new Error('Failed to read file'));
- reader.readAsArrayBuffer(file);
- });
- const zip = new JSZip();
- this.threemfZip = await zip.loadAsync(buffer);
- this.updateProgress(70, 'Extracting G-code...');
- const gcodeResult = await this.extractGcodeFrom3mf(this.threemfZip);
- if (!gcodeResult?.content?.trim()) throw new Error('No G-code found in 3MF file');
- this.originalGCode = gcodeResult.content;
- this.gcodeFilePath = gcodeResult.path;
- this.updateProgress(100, 'Complete!');
- this.showFileInfo(file);
- this.updateButtons();
- }
- async extractGcodeFrom3mf(zip) {
- const paths = ['Metadata/plate_1.gcode', 'Metadata/plate_2.gcode', 'Metadata/plate_3.gcode',
- 'Metadata/plate_4.gcode', 'Metadata/plate_5.gcode', 'Metadata/print.gcode'];
- for (const path of paths) {
- const file = zip.file(path);
- if (file) {
- try {
- const content = await file.async('text');
- if (this.isGcode(content)) return { content, path };
- } catch (e) { continue; }
- }
- }
- // Search all files
- const allFiles = [];
- zip.forEach((path, file) => !file.dir && allFiles.push(path));
- for (const path of allFiles.sort((a, b) => (a.includes('.gcode') ? -1 : 1))) {
- const file = zip.file(path);
- if (file) {
- try {
- const content = await file.async('text');
- if (this.isGcode(content)) return { content, path };
- } catch (e) { continue; }
- }
- }
- return null;
- }
- isGcode(content) {
- if (!content || content.length < 50) return false;
- const patterns = [/^G[0-9]+/m, /^M[0-9]+/m, /^T[0-9]+/m, /; generated by/i, /G28/, /G1.*E/];
- return patterns.filter(p => p.test(content)).length >= 3;
- }
- showFileInfo(file) {
- const info = document.getElementById('fileInfo');
- const badge = this.fileType === '3mf' ?
- '<span class="file-type-badge threemf">3MF</span>' :
- '<span class="file-type-badge gcode">G-code</span>';
- const lines = this.originalGCode.split('\n').length;
- const size = (file.size / 1024).toFixed(1);
- info.innerHTML = `<strong>File:</strong> ${file.name} ${badge}<br>
- <strong>Size:</strong> ${size} KB<br>
- <strong>G-code Lines:</strong> ${lines}`;
- info.classList.remove('hidden');
- }
- updateButtons() {
- const hasFile = !!this.originalGCode;
- const hasModified = !!this.modifiedGCode;
- document.getElementById('processBtn').disabled = !hasFile;
- document.getElementById('previewBtn').disabled = !hasFile;
- document.getElementById('downloadSection').classList.toggle('hidden', !hasFile);
- document.getElementById('downloadGcodeBtn').disabled = !hasModified;
- document.getElementById('download3mfBtn').disabled = !hasModified;
- document.getElementById('download3mfBtn').classList.toggle('hidden', this.fileType !== '3mf');
- }
- showStatus(message, type = 'info') {
- const status = document.getElementById('status');
- status.textContent = message;
- status.className = `status ${type}`;
- status.classList.remove('hidden');
- }
- process() {
- if (!this.originalGCode) return this.showStatus('Please load a file first.', 'error');
- try {
- const params = {
- loopCount: parseInt(document.getElementById('loopCount').value),
- cooldownTemp: parseInt(document.getElementById('cooldownTemp').value),
- waitTime: parseInt(document.getElementById('waitTime').value)
- };
- this.showStatus('Processing G-code...', 'info');
- const { header, printCore, footer } = this.parseGCode();
- this.modifiedGCode = this.generateLoops(header, printCore, footer, params);
- this.generatePreview(params);
- this.showStatus(`Successfully processed G-code with ${params.loopCount} loops!`, 'success');
- this.updateButtons();
- } catch (error) {
- this.showStatus(`Error: ${error.message}`, 'error');
- }
- }
- parseGCode() {
- const lines = this.originalGCode.split('\n');
- let headerEnd = lines.findIndex(line => {
- const l = line.trim();
- return (l.startsWith('G1') && l.includes('E')) || l.includes('LAYER_CHANGE') ||
- l.includes('layer') || (l.includes('Z0.') && l.startsWith('G1'));
- });
- let footerStart = -1;
- for (let i = lines.length - 1; i >= 0; i--) {
- const l = lines[i].trim();
- if (l.includes('M400') || l.includes('M104 S0') || l.includes('M140 S0') ||
- l.includes('M84') || (l.startsWith('G1') && l.includes('Z'))) {
- footerStart = i;
- break;
- }
- }
- if (headerEnd === -1) headerEnd = Math.min(50, Math.floor(lines.length * 0.1));
- if (footerStart === -1) footerStart = Math.max(lines.length - 20, Math.floor(lines.length * 0.9));
- return {
- header: lines.slice(0, headerEnd).join('\n'),
- printCore: lines.slice(headerEnd, footerStart).join('\n'),
- footer: lines.slice(footerStart).join('\n')
- };
- }
- generateLoops(header, printCore, footer, params) {
- let result = '';
- const { loopCount, cooldownTemp, waitTime } = params;
- for (let i = 1; i <= loopCount; i++) {
- result += `\n; === LOOP ${i} OF ${loopCount} ===\n`;
- if (header?.trim()) {
- result += (i === 1 ? this.updateTimeEstimates(header, params) : header);
- if (!header.endsWith('\n')) result += '\n';
- }
- if (printCore?.trim()) {
- result += printCore;
- if (!printCore.endsWith('\n')) result += '\n';
- }
- result += '\n' + this.getWipeSequence(cooldownTemp) + '\n';
- if (i < loopCount) {
- result += `\n; === SETUP FOR NEXT LOOP ===\n`;
- result += `M104 S150 ; Set hotend temperature\n`;
- result += `M140 S${cooldownTemp} ; Set bed temperature\n`;
- result += `M109 S150 ; Wait for hotend temperature\n`;
- result += `M190 S${cooldownTemp} ; Wait for bed temperature\n`;
- result += `G4 S${waitTime} ; Wait between loops\n`;
- }
- }
- if (footer?.trim()) {
- result += '\n' + footer;
- if (!footer.endsWith('\n')) result += '\n';
- }
- return result;
- }
- generatePreview(params) {
- const { loopCount, cooldownTemp, waitTime } = params;
- const bedCooldownTime = this.calculateBedCooldownTime(cooldownTemp);
- const formatTime = s => `${Math.floor(s/60)}m ${s%60}s`;
- this.addedBlocks = `; === LOOP 1 OF ${loopCount} ===
- ; HEADER_BLOCK_START
- ; BambuStudio [version]
- ; model printing time: [original_time × ${loopCount}]; total estimated time: [includes cooldown & wipe]
- ; [Original G-code header and print core would be here]
- ; === COOLDOWN & WIPE SEQUENCE ===
- ; Bed cooldown time (65°C → ${cooldownTemp}°C): ${formatTime(bedCooldownTime)}
- ; Wipe sequence time: 1m 10s (optimized back-to-front push)
- ${this.getWipeSequence(cooldownTemp)}
- ${loopCount > 1 ? `; === SETUP FOR NEXT LOOP ===
- ; Wait time between loops: ${formatTime(waitTime)}
- M104 S150 ; Set hotend temperature
- M140 S${cooldownTemp} ; Set bed temperature
- M109 S150 ; Wait for hotend temperature
- M190 S${cooldownTemp} ; Wait for bed temperature
- G4 S${waitTime} ; Wait between loops
- ` : ''}; === TIME BREAKDOWN FOR ${loopCount} LOOPS ===
- ; Total bed cooldown time: ${formatTime(bedCooldownTime * loopCount)}
- ; Total wipe time: ${formatTime(70 * loopCount)}
- ; Total wait time between loops: ${formatTime(waitTime * (loopCount - 1))}
- ; Print time multiplied by ${loopCount} loops`;
- }
- updateTimeEstimates(header, params) {
- const { loopCount, cooldownTemp, waitTime } = params;
- const lines = header.split('\n');
- let foundBambuStudio = false;
- return lines.map((line, index) => {
- // Check if this line is HEADER_BLOCK_START
- if (line.trim() === '; HEADER_BLOCK_START') {
- foundBambuStudio = false;
- return line;
- }
- // Check if this line is BambuStudio version line
- if (line.trim().match(/^;\s*BambuStudio\s+[\d.]+/)) {
- foundBambuStudio = true;
- return line;
- }
- // Check if this is the time estimation line after BambuStudio
- if (foundBambuStudio && line.includes('model printing time:') && line.includes('total estimated time:')) {
- const bambuTimeMatch = line.match(/^(;\s*model printing time:\s*)([^;]+)(;\s*total estimated time:\s*)([^;]*)(.*?)$/);
- if (bambuTimeMatch) {
- const [, prefix, modelTime, middle, totalTime, suffix] = bambuTimeMatch;
- const originalSeconds = this.parseTime(modelTime.trim());
- const additionalTime = (180 + this.calculateBedCooldownTime(cooldownTemp) + 70) * loopCount + waitTime * (loopCount - 1);
- const newModelTime = originalSeconds * loopCount;
- const newTotalTime = newModelTime + additionalTime;
- foundBambuStudio = false; // Reset for next occurrence
- return `${prefix}${this.formatTime(newModelTime)}${middle}${this.formatTime(newTotalTime)}${suffix}`;
- }
- }
- // Fallback for other time estimation formats
- const timeMatch = line.match(/^(;\s*estimated printing time.*?:?\s*)(\d+[hmsd\s]+)(.*)$/i);
- if (timeMatch) {
- const [, prefix, timeStr, suffix] = timeMatch;
- const originalSeconds = this.parseTime(timeStr);
- const additionalTime = (180 + this.calculateBedCooldownTime(cooldownTemp) + 70) * loopCount + waitTime * (loopCount - 1);
- return `${prefix}${this.formatTime(originalSeconds * loopCount + additionalTime)}${suffix}`;
- }
- return line;
- }).join('\n');
- }
- calculateBedCooldownTime(cooldownTemp) {
- let totalTime = 0;
- let currentTemp = 65;
- while (currentTemp > cooldownTemp) {
- totalTime += currentTemp > 50 ? 12 : currentTemp > 40 ? 18 : 30;
- currentTemp--;
- }
- // Add extra time for temperature stability and verification
- totalTime += 90; // 30s + 60s additional cooling time
- return totalTime;
- }
- parseTime(timeStr) {
- const matches = {
- d: timeStr.match(/(\d+)d/),
- h: timeStr.match(/(\d+)h/),
- m: timeStr.match(/(\d+)m/),
- s: timeStr.match(/(\d+)s/)
- };
- return (matches.d ? parseInt(matches.d[1]) * 86400 : 0) +
- (matches.h ? parseInt(matches.h[1]) * 3600 : 0) +
- (matches.m ? parseInt(matches.m[1]) * 60 : 0) +
- (matches.s ? parseInt(matches.s[1]) : 0);
- }
- formatTime(seconds) {
- const units = [
- [86400, 'd'],
- [3600, 'h'],
- [60, 'm'],
- [1, 's']
- ];
- let result = '';
- for (const [divisor, unit] of units) {
- const count = Math.floor(seconds / divisor);
- if (count > 0) {
- result += `${count}${unit} `;
- seconds %= divisor;
- }
- }
- return result.trim() || '0s';
- }
- getWipeSequence(cooldownTemp) {
- return `; === COOLDOWN BEFORE WIPE ===
- M104 S0 ; Turn off hotend
- ; === ENHANCED COOLING WAIT ===
- M117 bed cooldown ; Display message
- M140 S${cooldownTemp} ; Set bed to cooldown temperature
- ; Wait for bed to cool down with temperature monitoring
- M190 S${cooldownTemp} ; Wait for bed to reach cooldown temperature
- M117 wait 5s ; Display message
- G4 S5 ; wait to ensure temperature stability
- ; === WARNING SOUND BEFORE WIPE ===
- M17 ; Enable motors
- M400 S1 ; Wait for movement to complete
- M1006 S1 ; Sound system on
- ; Sharp, alternating pitch alarm pattern
- M1006 A60 B20 L80 C45 D8 M100 E60 F8 N100 ; High pitch tone
- M1006 A20 B20 L80 C35 D8 M100 E25 F8 N100 ; Low pitch tone
- M1006 A60 B20 L80 C45 D8 M100 E60 F8 N100 ; High pitch again
- M1006 A20 B20 L80 C35 D8 M100 E25 F8 N100 ; Low again
- M1006 A60 B20 L80 C45 D8 M100 E60 F8 N100 ; Final high pitch
- M1006 W ; Wait for sound to finish
- M18 ; Disable motors
- G4 S5 ; Pause for 5 seconds
- ; === OPTIMIZED BACK-TO-FRONT WIPE SEQUENCE ===
- G1 X160 Y185 F10000 ; Move to rear-right
- G1 Z2 F600 ; Set wipe height
- M400 ; Wait for moves
- M117 Wipe Level 1...
- ; Sweep across X160→X0 Between Y185 and Y140
- G1 X160 F10000
- G1 Y140 F2000
- G1 Y185 F10000
- G1 X140 F10000
- G1 Y140 F2000
- G1 Y185 F10000
- G1 X120 F10000
- G1 Y140 F2000
- G1 Y185 F10000
- G1 X100 F10000
- G1 Y140 F2000
- G1 Y185 F10000
- G1 X80 F10000
- G1 Y140 F2000
- G1 Y185 F10000
- G1 X60 F10000
- G1 Y140 F2000
- G1 Y185 F10000
- G1 X40 F10000
- G1 Y140 F2000
- G1 Y185 F10000
- G1 X20 F10000
- G1 Y140 F2000
- G1 Y185 F10000
- G1 X0 F10000
- G1 Y140 F2000
- G1 Y180 F30000; Move Y axis
- G1 Y165 F30000
- ; --- Sweep Y140→Y95 ---
- M117 Wipe Level 2...
- G1 X160 Y150 F10000
- G1 Y95 F2000
- G1 Y150 F10000
- G1 X140 F10000
- G1 Y95 F2000
- G1 Y150 F10000
- G1 X120 F10000
- G1 Y95 F2000
- G1 Y150 F10000
- G1 X100 F10000
- G1 Y95 F2000
- G1 Y150 F10000
- G1 X80 F10000
- G1 Y95 F2000
- G1 Y150 F10000
- G1 X60 F10000
- G1 Y95 F2000
- G1 Y150 F10000
- G1 X40 F10000
- G1 Y95 F2000
- G1 Y150 F10000
- G1 X20 F10000
- G1 Y95 F2000
- G1 Y150 F10000
- G1 X0 F10000
- G1 Y95 F2000
- G1 Y150 F10000
- G1 Y180 F30000; Move Y axis
- G1 Y165 F30000
- ; --- Sweep Y95→Y50 ---
- G1 X160 Y105 F10000
- G1 Y50 F2000
- G1 Y105 F10000
- G1 X140 F10000
- G1 Y50 F2000
- G1 Y105 F10000
- G1 X120 F10000
- G1 Y50 F2000
- G1 Y105 F10000
- G1 X100 F10000
- G1 Y50 F2000
- G1 Y105 F10000
- G1 X80 F10000
- G1 Y50 F2000
- G1 Y105 F10000
- G1 X60 F10000
- G1 Y50 F2000
- G1 Y105 F10000
- G1 X40 F10000
- G1 Y50 F2000
- G1 Y105 F10000
- G1 X20 F10000
- G1 Y50 F2000
- G1 Y105 F10000
- G1 X0 F10000
- G1 Y50 F2000
- G1 Y105 F10000
- G1 Y180 F30000; Move Y axis
- G1 Y165 F30000
- ; --- Sweep Y60→Y0 ---
- G1 X160 Y60 F20000
- G1 Y0 F2000
- G1 Y60 F20000
- G1 X140 F20000
- G1 Y0 F2000
- G1 Y60 F20000
- G1 X120 F20000
- G1 Y0 F2000
- G1 Y60 F20000
- G1 X100 F20000
- G1 Y0 F2000
- G1 Y60 F20000
- G1 X80 F20000
- G1 Y0 F2000
- G1 Y60 F20000
- G1 X60 F20000
- G1 Y0 F2000
- G1 Y60 F20000
- G1 X40 F20000
- G1 Y0 F2000
- G1 Y60 F20000
- G1 X20 F20000
- G1 Y0 F2000
- G1 Y60 F20000
- G1 X0 F20000
- G1 Y0 F2000
- G1 Y60 F20000
- G1 Y180 F30000; Move Y axis
- G1 Y165 F30000
- M117 Wipe Complete
- ; === COMPLETION ===
- M400 ; Wait for wipe complete
- ; All parts should now be pushed to front of bed for easy removal`;
- }
- downloadGcode() {
- if (!this.modifiedGCode) return this.showStatus('Please process the G-code first.', 'warning');
- const blob = new Blob([this.modifiedGCode], { type: 'text/plain' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = this.generateFileName('gcode');
- a.click();
- URL.revokeObjectURL(url);
- this.showStatus('G-code downloaded successfully!', 'success');
- }
- async download3mf() {
- if (!this.modifiedGCode || !this.threemfZip) return this.showStatus('Please process the G-code first.', 'warning');
- try {
- this.showStatus('Creating modified 3MF file...', 'info');
- const newZip = new JSZip();
- // Copy all files
- const promises = [];
- this.threemfZip.forEach((path, file) => {
- if (!file.dir) {
- promises.push(file.async('arraybuffer').then(content => newZip.file(path, content)));
- }
- });
- await Promise.all(promises);
- // Update G-code
- newZip.file(this.gcodeFilePath || 'Metadata/print.gcode', this.modifiedGCode);
- const content = await newZip.generateAsync({
- type: 'blob',
- compression: 'DEFLATE',
- compressionOptions: { level: 6 }
- });
- const url = URL.createObjectURL(content);
- const a = document.createElement('a');
- a.href = url;
- a.download = this.generateFileName('3mf');
- a.click();
- URL.revokeObjectURL(url);
- this.showStatus('3MF file downloaded successfully!', 'success');
- } catch (error) {
- this.showStatus(`Error creating 3MF file: ${error.message}`, 'error');
- }
- }
- generateFileName(extension) {
- const params = {
- loopCount: document.getElementById('loopCount').value,
- cooldownTemp: document.getElementById('cooldownTemp').value
- };
- const now = new Date();
- const timestamp = now.toISOString().slice(0, 10).replace(/-/g, '');
- const time = now.toTimeString().slice(0, 8).replace(/:/g, '');
- const baseName = this.fileName.replace(/\.[^/.]+$/, '');
- return `${time}-${baseName}_${timestamp}-${params.loopCount}loop_${params.cooldownTemp}bed.${extension}`;
- }
- preview() {
- const section = document.getElementById('previewSection');
- const content = document.getElementById('previewContent');
- content.textContent = this.addedBlocks || 'No processed G-code available. Please process the file first.';
- section.classList.remove('hidden');
- section.scrollIntoView({ behavior: 'smooth' });
- }
- }
- document.addEventListener('DOMContentLoaded', () => new GCodeFarmModifier());
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment