mirror of
https://github.com/CNCKitchen/stlTexturizer.git
synced 2026-04-07 22:11:32 +00:00
chore: clean up code structure and remove unused code blocks
This commit is contained in:
BIN
Binary file not shown.
|
After Width: | Height: | Size: 207 KiB |
+34
-11
@@ -3,8 +3,15 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>STL Texturizer</title>
|
||||
<title>CNC Kitchen STL Texturizer</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<script>
|
||||
// Apply saved theme before first paint to avoid flash
|
||||
(function() {
|
||||
const t = localStorage.getItem('stlt-theme');
|
||||
if (t === 'light') document.documentElement.setAttribute('data-theme', 'light');
|
||||
})();
|
||||
</script>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
@@ -22,9 +29,12 @@
|
||||
<path d="M2 17L12 22L22 17" stroke="#7c6aff" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
<path d="M2 12L12 17L22 12" stroke="#a08cff" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
<span>STL Texturizer</span>
|
||||
<span>CNC Kitchen STL Texturizer</span>
|
||||
</div>
|
||||
<div class="header-note">Units assumed to be <strong>mm</strong></div>
|
||||
<button id="theme-toggle" class="theme-toggle" title="Toggle light / dark mode" aria-label="Toggle light/dark mode" style="margin-left:auto">
|
||||
<span class="icon-moon">Dark</span>
|
||||
<span class="icon-sun">Light</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
@@ -153,7 +163,7 @@
|
||||
|
||||
<!-- Surface Mask -->
|
||||
<section class="panel-section">
|
||||
<h2>Surface Mask</h2>
|
||||
<h2 title="0° = no masking. Surfaces within this angle of horizontal will not be textured.">Surface Mask ⓘ</h2>
|
||||
<div class="form-row slider-row">
|
||||
<label for="bottom-angle-limit" title="Suppress texture on downward-facing surfaces within this angle of horizontal">Bottom faces</label>
|
||||
<input type="range" id="bottom-angle-limit" min="0" max="90" step="1" value="5" />
|
||||
@@ -164,12 +174,11 @@
|
||||
<input type="range" id="top-angle-limit" min="0" max="90" step="1" value="0" />
|
||||
<input type="number" class="val" id="top-angle-limit-val" value="0" min="0" max="90" step="1" />
|
||||
</div>
|
||||
<p class="hint">0° = no masking. Surfaces within this angle of horizontal will not be textured.</p>
|
||||
</section>
|
||||
|
||||
<!-- Surface Exclusions -->
|
||||
<section class="panel-section">
|
||||
<h2 id="excl-section-heading">Surface Exclusions</h2>
|
||||
<h2 id="excl-section-heading" title="Excluded surfaces appear orange and will not receive displacement during export.">Surface Exclusions ⓘ</h2>
|
||||
|
||||
<!-- Mode toggle: Exclude / Include Only -->
|
||||
<div class="excl-mode-row">
|
||||
@@ -235,12 +244,11 @@
|
||||
<span id="excl-count" class="excl-count">0 faces excluded</span>
|
||||
<button id="excl-clear-btn" class="excl-clear-btn">Clear All</button>
|
||||
</div>
|
||||
<p id="excl-hint" class="hint">Excluded surfaces appear orange and will not receive displacement during export.</p>
|
||||
</section>
|
||||
|
||||
<!-- Export -->
|
||||
<section class="panel-section">
|
||||
<h2>Export</h2>
|
||||
<h2 title="Smaller edge length = finer displacement detail. Output is then decimated to the triangle limit.">Export ⓘ</h2>
|
||||
<div class="form-row slider-row">
|
||||
<label for="refine-length" title="Edges longer than this value will be split during export">Max Edge Length</label>
|
||||
<input type="range" id="refine-length" min="0.1" max="5" step="0.1" value="1" />
|
||||
@@ -254,9 +262,7 @@
|
||||
<div id="tri-limit-warning" class="tri-limit-warning hidden">
|
||||
⚠ 5M-triangle safety cap hit during subdivision — result may still be coarser than requested edge length.
|
||||
</div>
|
||||
<p class="hint">
|
||||
Smaller edge length = finer displacement detail. Output is then decimated to the triangle limit.
|
||||
</p>
|
||||
|
||||
<div id="export-progress" class="export-progress hidden">
|
||||
<div class="export-progress-track">
|
||||
<div id="export-progress-bar" class="export-progress-bar"></div>
|
||||
@@ -270,6 +276,23 @@
|
||||
</aside>
|
||||
</main>
|
||||
|
||||
<!-- Sponsor popup -->
|
||||
<div id="sponsor-overlay" class="sponsor-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="sponsor-title">
|
||||
<div class="sponsor-modal">
|
||||
<h2 id="sponsor-title">Thanks for using CNC Kitchen STL Texturizer!</h2>
|
||||
<p>This tool is provided <strong>completely free</strong> by CNC Kitchen.<br>
|
||||
While your STL is being processed, why not check out the store that helps us keep making cool stuff for you?</p>
|
||||
<a href="https://geni.us/CNCStoreTexture" target="_blank" rel="noopener noreferrer" class="sponsor-link">
|
||||
🛒 Visit CNCKitchen.STORE
|
||||
</a>
|
||||
<label class="sponsor-no-show">
|
||||
<input type="checkbox" id="sponsor-dont-show" />
|
||||
Don't show this again
|
||||
</label>
|
||||
<button id="sponsor-close" class="sponsor-close-btn">Close & Continue</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+40
-2
@@ -1,7 +1,7 @@
|
||||
import * as THREE from 'three';
|
||||
import { initViewer, loadGeometry, setMeshMaterial, setWireframe,
|
||||
getControls, getCamera, getCurrentMesh,
|
||||
setExclusionOverlay, setHoverPreview } from './viewer.js';
|
||||
setExclusionOverlay, setHoverPreview, setViewerTheme } from './viewer.js';
|
||||
import { loadSTLFile, computeBounds, getTriangleCount } from './stlLoader.js';
|
||||
import { loadPresets, loadCustomTexture } from './presetTextures.js';
|
||||
import { createPreviewMaterial, updateMaterial } from './previewMaterial.js';
|
||||
@@ -128,6 +128,18 @@ const posToScale = p => parseFloat(Math.exp(_LOG_MIN + (p / 1000) * (_LOG_MAX -
|
||||
let PRESETS = [];
|
||||
|
||||
initViewer(canvas);
|
||||
|
||||
// Apply saved theme to 3D viewport on startup
|
||||
setViewerTheme(document.documentElement.getAttribute('data-theme') === 'light');
|
||||
|
||||
// Theme toggle
|
||||
document.getElementById('theme-toggle').addEventListener('click', () => {
|
||||
const isLight = document.documentElement.getAttribute('data-theme') !== 'light';
|
||||
document.documentElement.setAttribute('data-theme', isLight ? 'light' : 'dark');
|
||||
localStorage.setItem('stlt-theme', isLight ? 'light' : 'dark');
|
||||
setViewerTheme(isLight);
|
||||
});
|
||||
|
||||
wireEvents();
|
||||
// Sync scale number inputs with the slider's initial position
|
||||
scaleUVal.value = posToScale(parseFloat(scaleUSlider.value));
|
||||
@@ -136,6 +148,11 @@ scaleVVal.value = posToScale(parseFloat(scaleVSlider.value));
|
||||
loadPresets().then(presets => {
|
||||
PRESETS = presets;
|
||||
buildPresetGrid();
|
||||
// Select Noise as the default preset
|
||||
const noiseIdx = PRESETS.findIndex(p => p.name === 'Noise');
|
||||
const defaultIdx = noiseIdx !== -1 ? noiseIdx : 0;
|
||||
const swatches = presetGrid.querySelectorAll('.preset-swatch');
|
||||
if (swatches[defaultIdx]) selectPreset(defaultIdx, swatches[defaultIdx]);
|
||||
}).catch(err => console.error('Failed to load preset textures:', err));
|
||||
|
||||
// ── Preset grid ───────────────────────────────────────────────────────────────
|
||||
@@ -261,7 +278,28 @@ function wireEvents() {
|
||||
linkSlider(topAngleLimitSlider, topAngleLimitVal, v => { settings.topAngleLimit = v; return v; });
|
||||
|
||||
// ── Export ──
|
||||
exportBtn.addEventListener('click', handleExport);
|
||||
exportBtn.addEventListener('click', () => {
|
||||
if (localStorage.getItem('stlt-no-sponsor') === '1') {
|
||||
handleExport();
|
||||
return;
|
||||
}
|
||||
const overlay = document.getElementById('sponsor-overlay');
|
||||
const closeBtn = document.getElementById('sponsor-close');
|
||||
const storeLink = overlay.querySelector('.sponsor-link');
|
||||
overlay.classList.remove('hidden');
|
||||
|
||||
const dismiss = () => {
|
||||
if (document.getElementById('sponsor-dont-show').checked) {
|
||||
localStorage.setItem('stlt-no-sponsor', '1');
|
||||
}
|
||||
overlay.classList.add('hidden');
|
||||
handleExport();
|
||||
};
|
||||
|
||||
closeBtn.onclick = dismiss;
|
||||
// Also start processing when the user clicks through to the store
|
||||
storeLink.onclick = () => setTimeout(dismiss, 150);
|
||||
});
|
||||
|
||||
// ── Wireframe ──
|
||||
wireframeToggle.addEventListener('change', () => setWireframe(wireframeToggle.checked));
|
||||
|
||||
@@ -24,6 +24,7 @@ const IMAGE_PRESETS = [
|
||||
{ name: 'Leather 2', url: 'leather2.jpg' },
|
||||
{ name: 'Weave', url: 'weave.jpg' },
|
||||
{ name: 'Wood', url: 'wood.jpg' },
|
||||
{ name: 'Noise', url: 'noise.jpg' },
|
||||
];
|
||||
|
||||
function loadImagePreset({ name, url }) {
|
||||
|
||||
@@ -253,6 +253,28 @@ export function getScene() { return scene; }
|
||||
export function getControls() { return controls; }
|
||||
export function getCurrentMesh() { return currentMesh; }
|
||||
|
||||
export function setSceneBackground(hexColor) {
|
||||
if (scene) scene.background = new THREE.Color(hexColor);
|
||||
}
|
||||
|
||||
export function setViewerTheme(isLight) {
|
||||
if (!scene) return;
|
||||
scene.background = new THREE.Color(isLight ? 0xf0f0f5 : 0x111114);
|
||||
if (grid) {
|
||||
scene.remove(grid);
|
||||
grid.geometry.dispose();
|
||||
grid.material.dispose();
|
||||
}
|
||||
grid = new THREE.GridHelper(
|
||||
200, 40,
|
||||
isLight ? 0xb0b0c8 : 0x222228,
|
||||
isLight ? 0xd0d0e0 : 0x1e1e24
|
||||
);
|
||||
grid.rotation.x = Math.PI / 2;
|
||||
grid.position.z = 0;
|
||||
scene.add(grid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace (or clear) the flat orange exclusion overlay mesh.
|
||||
* overlayGeo must be a non-indexed BufferGeometry with a 'position' attribute,
|
||||
|
||||
@@ -17,6 +17,48 @@
|
||||
--header-h: 48px;
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--bg: #f0f0f5;
|
||||
--surface: #ffffff;
|
||||
--surface2: #eaeaf2;
|
||||
--border: #d0d0df;
|
||||
--accent: #6355e0;
|
||||
--accent-hover: #7c6aff;
|
||||
--text: #1a1a2e;
|
||||
--text-muted: #66667a;
|
||||
--danger: #d93025;
|
||||
--success: #1a7f3c;
|
||||
}
|
||||
|
||||
/* ── Theme toggle button ─────────────────────────────────────────────── */
|
||||
.theme-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
background: var(--surface2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text-muted);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border-color 0.15s, color 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.theme-toggle .icon-moon { display: block; }
|
||||
.theme-toggle .icon-sun { display: none; }
|
||||
[data-theme="light"] .theme-toggle .icon-moon { display: none; }
|
||||
[data-theme="light"] .theme-toggle .icon-sun { display: block; }
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||
@@ -153,7 +195,7 @@ main {
|
||||
.viewport-controls-hint {
|
||||
margin-left: auto;
|
||||
font-size: 11px;
|
||||
color: #555566;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.wireframe-toggle {
|
||||
@@ -198,8 +240,8 @@ main {
|
||||
/* ── Preset grid ─────────────────────────────────────────────────────── */
|
||||
.preset-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 4px;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 3px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -407,6 +449,12 @@ input[type="number"].val:focus { outline: none; border-color: var(--accent); }
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-theme="light"] .export-progress-pct {
|
||||
mix-blend-mode: normal;
|
||||
color: #fff;
|
||||
text-shadow: 0 0 4px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
/* ── Export button ───────────────────────────────────────────────────── */
|
||||
.export-btn {
|
||||
width: 100%;
|
||||
@@ -597,4 +645,92 @@ input[type="number"].val:focus { outline: none; border-color: var(--accent); }
|
||||
}
|
||||
|
||||
/* Hide utility (used by JS to show/hide exclusion sub-rows) */
|
||||
.form-row.hidden { display: none; }
|
||||
.form-row.hidden { display: none; }
|
||||
|
||||
/* ── Sponsor / popup overlay ─────────────────────────────────────────── */
|
||||
.sponsor-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
.sponsor-overlay.hidden { display: none; }
|
||||
|
||||
.sponsor-modal {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 28px 32px;
|
||||
max-width: 420px;
|
||||
width: calc(100% - 40px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.sponsor-modal h2 {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--text);
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sponsor-modal p {
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sponsor-link {
|
||||
display: block;
|
||||
text-align: center;
|
||||
padding: 10px 16px;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border-radius: var(--radius);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.sponsor-link:hover { background: var(--accent-hover); }
|
||||
|
||||
.sponsor-no-show {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sponsor-no-show input { cursor: pointer; accent-color: var(--accent); }
|
||||
|
||||
.sponsor-close-btn {
|
||||
padding: 8px 16px;
|
||||
background: var(--surface2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.sponsor-close-btn:hover {
|
||||
background: var(--border);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
Reference in New Issue
Block a user