Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Advanced Laser-Cut Box Generator</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| padding: 20px; | |
| } | |
| .container { | |
| max-width: 1400px; | |
| margin: 0 auto; | |
| background: rgba(255, 255, 255, 0.95); | |
| border-radius: 20px; | |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); | |
| backdrop-filter: blur(10px); | |
| overflow: hidden; | |
| } | |
| .header { | |
| background: linear-gradient(135deg, #2c3e50, #34495e); | |
| color: white; | |
| padding: 30px; | |
| text-align: center; | |
| } | |
| .header h1 { | |
| font-size: 2.5em; | |
| margin-bottom: 10px; | |
| font-weight: 300; | |
| } | |
| .header p { | |
| opacity: 0.9; | |
| font-size: 1.1em; | |
| } | |
| .main-content { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 0; | |
| min-height: 800px; | |
| } | |
| .controls { | |
| padding: 40px; | |
| background: #f8f9fa; | |
| border-right: 1px solid #e9ecef; | |
| overflow-y: auto; | |
| } | |
| .preview { | |
| padding: 40px; | |
| background: white; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .section { | |
| margin-bottom: 30px; | |
| background: white; | |
| border-radius: 12px; | |
| padding: 25px; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08); | |
| border: 1px solid #e9ecef; | |
| } | |
| .section h3 { | |
| color: #2c3e50; | |
| margin-bottom: 20px; | |
| font-size: 1.2em; | |
| font-weight: 600; | |
| border-bottom: 2px solid #3498db; | |
| padding-bottom: 10px; | |
| } | |
| .form-group { | |
| margin-bottom: 20px; | |
| } | |
| .form-row { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr 1fr; | |
| gap: 15px; | |
| margin-bottom: 20px; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 500; | |
| color: #34495e; | |
| font-size: 0.9em; | |
| } | |
| input, select { | |
| width: 100%; | |
| padding: 12px 15px; | |
| border: 2px solid #e9ecef; | |
| border-radius: 8px; | |
| font-size: 0.95em; | |
| transition: all 0.3s ease; | |
| background: white; | |
| } | |
| input:focus, select:focus { | |
| outline: none; | |
| border-color: #3498db; | |
| box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1); | |
| } | |
| .btn { | |
| background: linear-gradient(135deg, #3498db, #2980b9); | |
| color: white; | |
| border: none; | |
| padding: 15px 30px; | |
| border-radius: 8px; | |
| font-size: 1em; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| width: 100%; | |
| margin-top: 10px; | |
| } | |
| .btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 25px rgba(52, 152, 219, 0.3); | |
| } | |
| .btn:active { | |
| transform: translateY(0); | |
| } | |
| .preview-canvas { | |
| width: 100%; | |
| max-width: 500px; | |
| height: 400px; | |
| border: 2px solid #e9ecef; | |
| border-radius: 12px; | |
| background: #fdfdfd; | |
| margin-bottom: 20px; | |
| } | |
| .dimensions-display { | |
| background: #f8f9fa; | |
| padding: 20px; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| font-family: 'Courier New', monospace; | |
| } | |
| .help-text { | |
| font-size: 0.8em; | |
| color: #7f8c8d; | |
| margin-top: 5px; | |
| line-height: 1.4; | |
| } | |
| .warning { | |
| background: #fff3cd; | |
| border: 1px solid #ffeaa7; | |
| color: #856404; | |
| padding: 12px; | |
| border-radius: 6px; | |
| margin-top: 10px; | |
| font-size: 0.9em; | |
| } | |
| .success { | |
| background: #d4edda; | |
| border: 1px solid #c3e6cb; | |
| color: #155724; | |
| padding: 12px; | |
| border-radius: 6px; | |
| margin-top: 10px; | |
| font-size: 0.9em; | |
| } | |
| .kerf-test { | |
| background: #e8f4f8; | |
| border: 1px solid #bee5eb; | |
| padding: 15px; | |
| border-radius: 8px; | |
| margin-top: 15px; | |
| } | |
| .joint-preview { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); | |
| gap: 15px; | |
| margin-top: 15px; | |
| } | |
| .joint-option { | |
| text-align: center; | |
| padding: 15px; | |
| border: 2px solid #e9ecef; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| background: white; | |
| } | |
| .joint-option:hover { | |
| border-color: #3498db; | |
| background: #f8f9fa; | |
| } | |
| .joint-option.selected { | |
| border-color: #3498db; | |
| background: #e8f4f8; | |
| } | |
| .joint-diagram { | |
| width: 80px; | |
| height: 40px; | |
| margin: 0 auto 10px; | |
| background: #34495e; | |
| position: relative; | |
| border-radius: 2px; | |
| } | |
| .finger-joint::after { | |
| content: ''; | |
| position: absolute; | |
| top: -5px; | |
| left: 10px; | |
| right: 10px; | |
| height: 50px; | |
| background: repeating-linear-gradient(to right, #3498db 0px, #3498db 8px, transparent 8px, transparent 16px); | |
| } | |
| .download-section { | |
| text-align: center; | |
| margin-top: 30px; | |
| } | |
| .file-format { | |
| display: inline-block; | |
| margin: 0 10px; | |
| padding: 10px 20px; | |
| background: #ecf0f1; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .file-format:hover { | |
| background: #3498db; | |
| color: white; | |
| } | |
| @media (max-width: 768px) { | |
| .main-content { | |
| grid-template-columns: 1fr; | |
| } | |
| .form-row { | |
| grid-template-columns: 1fr; | |
| } | |
| .container { | |
| margin: 10px; | |
| } | |
| } | |
| .material-guide { | |
| background: #f8f9fa; | |
| border-left: 4px solid #3498db; | |
| padding: 15px; | |
| margin: 15px 0; | |
| } | |
| .material-guide h4 { | |
| color: #2c3e50; | |
| margin-bottom: 10px; | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); | |
| gap: 15px; | |
| margin-top: 20px; | |
| } | |
| .stat-card { | |
| background: white; | |
| padding: 15px; | |
| border-radius: 8px; | |
| border: 1px solid #e9ecef; | |
| text-align: center; | |
| } | |
| .stat-value { | |
| font-size: 1.5em; | |
| font-weight: bold; | |
| color: #3498db; | |
| } | |
| .stat-label { | |
| font-size: 0.9em; | |
| color: #7f8c8d; | |
| margin-top: 5px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>🔥 Advanced Laser-Cut Box Generator</h1> | |
| <p>Professional-grade parametric box design with precision kerf compensation and multiple joint types</p> | |
| </div> | |
| <div class="main-content"> | |
| <div class="controls"> | |
| <div class="section"> | |
| <h3>📐 Box Dimensions</h3> | |
| <div class="form-row"> | |
| <div class="form-group"> | |
| <label for="width">Width (mm)</label> | |
| <input type="number" id="width" value="100" min="10" max="1000" step="0.1"> | |
| <div class="help-text">Internal width of the box</div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="depth">Depth (mm)</label> | |
| <input type="number" id="depth" value="80" min="10" max="1000" step="0.1"> | |
| <div class="help-text">Internal depth of the box</div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="height">Height (mm)</label> | |
| <input type="number" id="height" value="60" min="10" max="1000" step="0.1"> | |
| <div class="help-text">Internal height of the box</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="section"> | |
| <h3>🔧 Material & Cutting Settings</h3> | |
| <div class="form-row"> | |
| <div class="form-group"> | |
| <label for="thickness">Material Thickness (mm)</label> | |
| <input type="number" id="thickness" value="3.0" min="0.1" max="20" step="0.1"> | |
| <div class="help-text">Actual measured thickness</div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="kerf">Laser Kerf (mm)</label> | |
| <input type="number" id="kerf" value="0.1" min="0" max="1" step="0.01"> | |
| <div class="help-text">Material removed by laser</div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="materialType">Material Type</label> | |
| <select id="materialType"> | |
| <option value="plywood">Plywood</option> | |
| <option value="mdf">MDF</option> | |
| <option value="acrylic">Acrylic</option> | |
| <option value="cardboard">Cardboard</option> | |
| <option value="custom">Custom</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="kerf-test"> | |
| <strong>🎯 Kerf Testing Tip:</strong> Cut a small test piece with interlocking tabs to verify your kerf setting before cutting the final box. | |
| </div> | |
| </div> | |
| <div class="section"> | |
| <h3>⚙️ Joint Configuration</h3> | |
| <div class="form-group"> | |
| <label for="jointType">Joint Type</label> | |
| <div class="joint-preview"> | |
| <div class="joint-option selected" data-joint="finger"> | |
| <div class="joint-diagram finger-joint"></div> | |
| <div>Finger Joints</div> | |
| </div> | |
| <div class="joint-option" data-joint="dovetail"> | |
| <div class="joint-diagram"></div> | |
| <div>Dovetail</div> | |
| </div> | |
| <div class="joint-option" data-joint="simple"> | |
| <div class="joint-diagram"></div> | |
| <div>Simple Tab</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="form-row"> | |
| <div class="form-group"> | |
| <label for="fingerWidth">Tab Width (mm)</label> | |
| <input type="number" id="fingerWidth" value="10" min="2" max="50" step="0.5"> | |
| <div class="help-text">Width of individual tabs</div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="fingerCount">Fingers per Edge</label> | |
| <select id="fingerCount"> | |
| <option value="auto">Auto Calculate</option> | |
| <option value="3">3</option> | |
| <option value="5">5</option> | |
| <option value="7">7</option> | |
| <option value="9">9</option> | |
| <option value="11">11</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="fingerOffset">Edge Offset (mm)</label> | |
| <input type="number" id="fingerOffset" value="0" min="0" max="20" step="0.5"> | |
| <div class="help-text">Distance from edge to first tab</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="section"> | |
| <h3>📦 Box Configuration</h3> | |
| <div class="form-row"> | |
| <div class="form-group"> | |
| <label for="boxType">Box Type</label> | |
| <select id="boxType"> | |
| <option value="open">Open Top</option> | |
| <option value="closed">Closed Box</option> | |
| <option value="lid">Box with Lid</option> | |
| <option value="sliding">Sliding Lid</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="bottomType">Bottom Type</label> | |
| <select id="bottomType"> | |
| <option value="attached">Attached</option> | |
| <option value="inset">Inset</option> | |
| <option value="none">No Bottom</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="cornerType">Corner Style</label> | |
| <select id="cornerType"> | |
| <option value="square">Square</option> | |
| <option value="rounded">Rounded</option> | |
| <option value="chamfered">Chamfered</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="section"> | |
| <h3>🎛️ Advanced Options</h3> | |
| <div class="form-group"> | |
| <label> | |
| <input type="checkbox" id="addHoles"> Add mounting holes | |
| </label> | |
| <label> | |
| <input type="checkbox" id="addDividers"> Include dividers | |
| </label> | |
| <label> | |
| <input type="checkbox" id="addVents"> Add ventilation holes | |
| </label> | |
| <label> | |
| <input type="checkbox" id="addLabels"> Add part labels | |
| </label> | |
| </div> | |
| </div> | |
| <button class="btn" onclick="generateBox()">🚀 Generate Box Design</button> | |
| </div> | |
| <div class="preview"> | |
| <canvas class="preview-canvas" id="previewCanvas"></canvas> | |
| <div class="dimensions-display" id="dimensionsDisplay"> | |
| <strong>📏 Calculated Dimensions:</strong><br> | |
| External: 106.0 × 86.0 × 66.0 mm<br> | |
| Material Usage: 520.0 cm²<br> | |
| Cutting Time: ~8 minutes | |
| </div> | |
| <div class="stats-grid"> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="partCount">6</div> | |
| <div class="stat-label">Parts</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="cutLength">2.4m</div> | |
| <div class="stat-label">Cut Length</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="materialUsage">520</div> | |
| <div class="stat-label">cm² Material</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="efficiency">85%</div> | |
| <div class="stat-label">Efficiency</div> | |
| </div> | |
| </div> | |
| <div class="download-section"> | |
| <h3>📥 Download Files</h3> | |
| <div class="file-format" onclick="downloadFile('svg')">SVG</div> | |
| <div class="file-format" onclick="downloadFile('dxf')">DXF</div> | |
| <div class="file-format" onclick="downloadFile('pdf')">PDF</div> | |
| <div class="file-format" onclick="downloadFile('eps')">EPS</div> | |
| </div> | |
| <div class="material-guide"> | |
| <h4>📋 Cutting Guidelines</h4> | |
| <p><strong>Recommended Settings:</strong></p> | |
| <ul> | |
| <li>3mm Plywood: 1000mm/min, 80% power</li> | |
| <li>Cut in this order: 1. Inner holes 2. Finger joints 3. Outer edges</li> | |
| <li>Use air assist for clean cuts</li> | |
| <li>Test fit corner pieces before full cut</li> | |
| </ul> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Global variables for box configuration | |
| let boxConfig = { | |
| width: 100, | |
| depth: 80, | |
| height: 60, | |
| thickness: 3.0, | |
| kerf: 0.1, | |
| jointType: 'finger', | |
| fingerWidth: 10, | |
| fingerCount: 'auto', | |
| boxType: 'open', | |
| bottomType: 'attached' | |
| }; | |
| // Material presets for common laser cutting materials | |
| const materialPresets = { | |
| plywood: { kerf: 0.1, density: 0.6 }, | |
| mdf: { kerf: 0.12, density: 0.75 }, | |
| acrylic: { kerf: 0.05, density: 1.18 }, | |
| cardboard: { kerf: 0.02, density: 0.7 } | |
| }; | |
| // Initialize the application | |
| function init() { | |
| setupEventListeners(); | |
| drawPreview(); | |
| updateDimensions(); | |
| } | |
| function setupEventListeners() { | |
| // Dimension inputs | |
| ['width', 'depth', 'height', 'thickness', 'kerf', 'fingerWidth'].forEach(id => { | |
| document.getElementById(id).addEventListener('input', function() { | |
| boxConfig[id] = parseFloat(this.value); | |
| updateDisplay(); | |
| }); | |
| }); | |
| // Select inputs | |
| ['fingerCount', 'boxType', 'bottomType', 'cornerType'].forEach(id => { | |
| document.getElementById(id).addEventListener('change', function() { | |
| boxConfig[id] = this.value; | |
| updateDisplay(); | |
| }); | |
| }); | |
| // Material type change | |
| document.getElementById('materialType').addEventListener('change', function() { | |
| if (materialPresets[this.value]) { | |
| document.getElementById('kerf').value = materialPresets[this.value].kerf; | |
| boxConfig.kerf = materialPresets[this.value].kerf; | |
| updateDisplay(); | |
| } | |
| }); | |
| // Joint type selection | |
| document.querySelectorAll('.joint-option').forEach(option => { | |
| option.addEventListener('click', function() { | |
| document.querySelectorAll('.joint-option').forEach(o => o.classList.remove('selected')); | |
| this.classList.add('selected'); | |
| boxConfig.jointType = this.dataset.joint; | |
| updateDisplay(); | |
| }); | |
| }); | |
| } | |
| function updateDisplay() { | |
| drawPreview(); | |
| updateDimensions(); | |
| updateStats(); | |
| } | |
| function drawPreview() { | |
| const canvas = document.getElementById('previewCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| // Set canvas size | |
| canvas.width = canvas.offsetWidth; | |
| canvas.height = canvas.offsetHeight; | |
| // Clear canvas | |
| ctx.fillStyle = '#fdfdfd'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| // Calculate scaling to fit the box in the canvas | |
| const margin = 40; | |
| const availableWidth = canvas.width - 2 * margin; | |
| const availableHeight = canvas.height - 2 * margin; | |
| const boxWidth = boxConfig.width + 2 * boxConfig.thickness; | |
| const boxHeight = boxConfig.height + boxConfig.thickness; | |
| const scale = Math.min(availableWidth / boxWidth, availableHeight / boxHeight); | |
| // Center the drawing | |
| const centerX = canvas.width / 2; | |
| const centerY = canvas.height / 2; | |
| ctx.save(); | |
| ctx.translate(centerX, centerY); | |
| ctx.scale(scale, scale); | |
| // Draw 3D isometric view of the box | |
| drawIsometricBox(ctx); | |
| ctx.restore(); | |
| } | |
| function drawIsometricBox(ctx) { | |
| const w = boxConfig.width; | |
| const h = boxConfig.height; | |
| const d = boxConfig.depth; | |
| const t = boxConfig.thickness; | |
| // Isometric transformation | |
| const isoX = (x, y) => x * 0.866 - y * 0.866; | |
| const isoY = (x, y, z) => x * 0.5 + y * 0.5 - z; | |
| // Colors for different faces | |
| const frontColor = '#3498db'; | |
| const topColor = '#2980b9'; | |
| const sideColor = '#34495e'; | |
| ctx.lineWidth = 2; | |
| ctx.strokeStyle = '#2c3e50'; | |
| // Draw front face | |
| ctx.fillStyle = frontColor; | |
| ctx.beginPath(); | |
| ctx.moveTo(isoX(-w/2, 0), isoY(-w/2, 0, h)); | |
| ctx.lineTo(isoX(w/2, 0), isoY(w/2, 0, h)); | |
| ctx.lineTo(isoX(w/2, 0), isoY(w/2, 0, 0)); | |
| ctx.lineTo(isoX(-w/2, 0), isoY(-w/2, 0, 0)); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| ctx.stroke(); | |
| // Draw finger joints on front face | |
| if (boxConfig.jointType === 'finger') { | |
| drawFingerJoints(ctx, isoX(-w/2, 0), isoY(-w/2, 0, h), isoX(w/2, 0), isoY(w/2, 0, h), 'horizontal'); | |
| } | |
| // Draw right side face | |
| ctx.fillStyle = sideColor; | |
| ctx.beginPath(); | |
| ctx.moveTo(isoX(w/2, 0), isoY(w/2, 0, h)); | |
| ctx.lineTo(isoX(w/2, -d), isoY(w/2, -d, h)); | |
| ctx.lineTo(isoX(w/2, -d), isoY(w/2, -d, 0)); | |
| ctx.lineTo(isoX(w/2, 0), isoY(w/2, 0, 0)); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| ctx.stroke(); | |
| // Draw top face (if closed box) | |
| if (boxConfig.boxType !== 'open') { | |
| ctx.fillStyle = topColor; | |
| ctx.beginPath(); | |
| ctx.moveTo(isoX(-w/2, 0), isoY(-w/2, 0, h)); | |
| ctx.lineTo(isoX(w/2, 0), isoY(w/2, 0, h)); | |
| ctx.lineTo(isoX(w/2, -d), isoY(w/2, -d, h)); | |
| ctx.lineTo(isoX(-w/2, -d), isoY(-w/2, -d, h)); | |
| ctx.closePath(); | |
| ctx.fill(); | |
| ctx.stroke(); | |
| } | |
| } | |
| function drawFingerJoints(ctx, x1, y1, x2, y2, orientation) { | |
| const fingerCount = calculateFingerCount(); | |
| const edgeLength = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); | |
| const fingerWidth = edgeLength / (fingerCount * 2 - 1); | |
| ctx.strokeStyle = '#e74c3c'; | |
| ctx.lineWidth = 1; | |
| for (let i = 0; i < fingerCount; i++) { | |
| const startRatio = (i * 2) / (fingerCount * 2 - 1); | |
| const endRatio = (i * 2 + 1) / (fingerCount * 2 - 1); | |
| const startX = x1 + (x2 - x1) * startRatio; | |
| const startY = y1 + (y2 - y1) * startRatio; | |
| const endX = x1 + (x2 - x1) * endRatio; | |
| const endY = y1 + (y2 - y1) * endRatio; | |
| ctx.beginPath(); | |
| ctx.moveTo(startX, startY); | |
| ctx.lineTo(endX, endY); | |
| ctx.stroke(); | |
| } | |
| } | |
| function calculateFingerCount() { | |
| if (boxConfig.fingerCount === 'auto') { | |
| // Auto-calculate based on edge length and finger width | |
| const edgeLength = Math.max(boxConfig.width, boxConfig.depth); | |
| const count = Math.floor(edgeLength / (boxConfig.fingerWidth * 2)) * 2 + 1; | |
| return Math.max(3, Math.min(11, count)); | |
| } | |
| return parseInt(boxConfig.fingerCount); | |
| } | |
| function updateDimensions() { | |
| const external = { | |
| width: boxConfig.width + 2 * boxConfig.thickness, | |
| depth: boxConfig.depth + 2 * boxConfig.thickness, | |
| height: boxConfig.height + boxConfig.thickness | |
| }; | |
| const materialUsage = calculateMaterialUsage(); | |
| const cuttingTime = estimateCuttingTime(); | |
| document.getElementById('dimensionsDisplay').innerHTML = ` | |
| <strong>📏 Calculated Dimensions:</strong><br> | |
| External: ${external.width.toFixed(1)} × ${external.depth.toFixed(1)} × ${external.height.toFixed(1)} mm<br> | |
| Material Usage: ${materialUsage.toFixed(0)} cm²<br> | |
| Cutting Time: ~${cuttingTime} minutes | |
| `; | |
| } | |
| function updateStats() { | |
| const partCount = calculatePartCount(); | |
| const cutLength = calculateCutLength(); | |
| const materialUsage = calculateMaterialUsage(); | |
| const efficiency = calculateEfficiency(); | |
| document.getElementById('partCount').textContent = partCount; | |
| document.getElementById('cutLength').textContent = (cutLength / 1000).toFixed(1) + 'm'; | |
| document.getElementById('materialUsage').textContent = Math.round(materialUsage); | |
| document.getElementById('efficiency').textContent = Math.round(efficiency) + '%'; | |
| } | |
| function calculatePartCount() { | |
| let count = 4; // Four sides | |
| if (boxConfig.bottomType !== 'none') count += 1; // Bottom | |
| if (boxConfig.boxType === 'closed' || boxConfig.boxType === 'lid') count += 1; // Top | |
| return count; | |
| } | |
| function calculateCutLength() { | |
| const perimeter = 2 * (boxConfig.width + boxConfig.depth + boxConfig.height + boxConfig.thickness); | |
| const fingerCount = calculateFingerCount(); | |
| const fingerLength = fingerCount * 8 * boxConfig.fingerWidth; // Approximate finger joint length | |
| return perimeter + fingerLength; | |
| } | |
| function calculateMaterialUsage() { | |
| const area1 = 2 * (boxConfig.width + 2 * boxConfig.thickness) * (boxConfig.height + boxConfig.thickness); | |
| const area2 = 2 * (boxConfig.depth + 2 * boxConfig.thickness) * (boxConfig.height + boxConfig.thickness); |