mirror of
https://github.com/CNCKitchen/stlTexturizer.git
synced 2026-04-07 22:11:32 +00:00
188 lines
8.7 KiB
HTML
188 lines
8.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>STL Texturizer</title>
|
|
<link rel="stylesheet" href="style.css" />
|
|
<script type="importmap">
|
|
{
|
|
"imports": {
|
|
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
|
|
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
|
|
}
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<div class="logo">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="#7c6aff" stroke-width="1.5" stroke-linejoin="round"/>
|
|
<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>
|
|
</div>
|
|
<div class="header-note">Units assumed to be <strong>mm</strong></div>
|
|
</header>
|
|
|
|
<main>
|
|
<!-- ─── 3-D Viewport ─────────────────────────────────────────── -->
|
|
<section id="viewport-section">
|
|
<div id="drop-zone" class="drop-zone">
|
|
<div id="drop-hint" class="drop-hint">
|
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M12 2L2 7L12 12L22 7L12 2Z" stroke="#555" stroke-width="1.2" stroke-linejoin="round"/>
|
|
<path d="M2 17L12 22L22 17" stroke="#555" stroke-width="1.2" stroke-linejoin="round"/>
|
|
<path d="M2 12L12 17L22 12" stroke="#555" stroke-width="1.2" stroke-linejoin="round"/>
|
|
</svg>
|
|
<p>Drop an <strong>.stl</strong> file here<br/>or <label for="stl-file-input" class="link-label">click to browse</label></p>
|
|
<input type="file" id="stl-file-input" accept=".stl" hidden />
|
|
</div>
|
|
<canvas id="viewport"></canvas>
|
|
</div>
|
|
<div id="viewport-footer">
|
|
<span id="mesh-info" class="mesh-info"></span>
|
|
<label class="wireframe-toggle">
|
|
<input type="checkbox" id="wireframe-toggle" />
|
|
Wireframe
|
|
</label>
|
|
<div class="viewport-controls-hint">Left drag: orbit · Right drag: pan · Scroll: zoom</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ─── Settings Panel ───────────────────────────────────────── -->
|
|
<aside id="settings-panel">
|
|
|
|
<!-- Displacement Map -->
|
|
<section class="panel-section">
|
|
<h2>Displacement Map</h2>
|
|
|
|
<!-- Preset grid -->
|
|
<div id="preset-grid" class="preset-grid">
|
|
<!-- filled by js/presetTextures.js -->
|
|
</div>
|
|
|
|
<!-- Custom upload -->
|
|
<label class="upload-btn" for="texture-file-input">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"><path d="M12 5v14M5 12l7-7 7 7" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
|
|
Upload custom map
|
|
</label>
|
|
<input type="file" id="texture-file-input" accept="image/*" hidden />
|
|
<div id="active-map-name" class="active-map-name">No map selected</div>
|
|
</section>
|
|
|
|
<!-- Projection -->
|
|
<section class="panel-section">
|
|
<h2>Projection</h2>
|
|
<div class="form-row">
|
|
<label for="mapping-mode">Mode</label>
|
|
<select id="mapping-mode">
|
|
<option value="0">Planar XY</option>
|
|
<option value="1">Planar XZ</option>
|
|
<option value="2">Planar YZ</option>
|
|
<option value="3">Cylindrical</option>
|
|
<option value="4">Spherical</option>
|
|
<option value="5">Triplanar</option>
|
|
<option value="6" selected>Cubic (Box)</option>
|
|
</select>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Transform -->
|
|
<section class="panel-section">
|
|
<h2>Transform</h2>
|
|
|
|
<div class="form-row slider-row">
|
|
<label for="scale-u">Scale U</label>
|
|
<input type="range" id="scale-u" min="0" max="1000" step="1" value="500" />
|
|
<input type="number" class="val" id="scale-u-val" value="1" min="0.1" max="10" step="0.1" />
|
|
</div>
|
|
<div class="lock-row">
|
|
<div class="lock-line"></div>
|
|
<button id="lock-scale" class="lock-btn active" title="Proportional scaling (U = V)" aria-pressed="true">
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none">
|
|
<rect x="3" y="11" width="18" height="11" rx="2" stroke="currentColor" stroke-width="2"/>
|
|
<path d="M7 11V7a5 5 0 0 1 10 0v4" stroke="currentColor" stroke-width="2"/>
|
|
</svg>
|
|
</button>
|
|
<div class="lock-line"></div>
|
|
</div>
|
|
|
|
<div class="form-row slider-row">
|
|
<label for="scale-v">Scale V</label>
|
|
<input type="range" id="scale-v" min="0" max="1000" step="1" value="500" />
|
|
<input type="number" class="val" id="scale-v-val" value="1" min="0.1" max="10" step="0.1" />
|
|
</div>
|
|
<div class="form-row slider-row">
|
|
<label for="offset-u">Offset U</label>
|
|
<input type="range" id="offset-u" min="-1" max="1" step="0.01" value="0" />
|
|
<input type="number" class="val" id="offset-u-val" value="0" min="-1" max="1" step="0.01" />
|
|
</div>
|
|
<div class="form-row slider-row">
|
|
<label for="offset-v">Offset V</label>
|
|
<input type="range" id="offset-v" min="-1" max="1" step="0.01" value="0" />
|
|
<input type="number" class="val" id="offset-v-val" value="0" min="-1" max="1" step="0.01" />
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Displacement -->
|
|
<section class="panel-section">
|
|
<h2>Displacement</h2>
|
|
<div class="form-row slider-row">
|
|
<label for="amplitude">Amplitude</label>
|
|
<input type="range" id="amplitude" min="-1" max="1" step="0.01" value="0.5" />
|
|
<input type="number" class="val" id="amplitude-val" value="0.5" min="-1" max="1" step="0.01" />
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Surface Mask -->
|
|
<section class="panel-section">
|
|
<h2>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" />
|
|
<input type="number" class="val" id="bottom-angle-limit-val" value="5" min="0" max="90" step="1" />
|
|
</div>
|
|
<div class="form-row slider-row">
|
|
<label for="top-angle-limit" title="Suppress texture on upward-facing surfaces within this angle of horizontal">Top faces</label>
|
|
<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>
|
|
|
|
<!-- Export -->
|
|
<section class="panel-section">
|
|
<h2>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" />
|
|
<input type="number" class="val" id="refine-length-val" value="1" min="0.1" max="5" step="0.1" />
|
|
</div>
|
|
<div class="form-row slider-row">
|
|
<label for="max-triangles" title="Mesh is fully subdivided first, then decimated down to this count">Output Triangles</label>
|
|
<input type="range" id="max-triangles" min="10000" max="5000000" step="10000" value="1000000" />
|
|
<span class="val" id="max-triangles-val">1.0 M</span>
|
|
</div>
|
|
<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 id="export-progress-bar" class="export-progress-bar"></div>
|
|
<span id="export-progress-label">Processing…</span>
|
|
</div>
|
|
<button id="export-btn" class="export-btn" disabled>Export STL</button>
|
|
</section>
|
|
|
|
</aside>
|
|
</main>
|
|
|
|
<script type="module" src="js/main.js"></script>
|
|
</body>
|
|
</html>
|