cube-8 / index.html
MarkTheArtist's picture
Add 2 files
e3cc6e9 verified
<!DOCTYPE html>
<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);