Refactor code structure for improved readability and maintainability
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 CNCKitchen (Stefan Elitz)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# STL Texturizer
|
||||||
|
|
||||||
|
A browser-based tool for applying surface displacement textures to STL files — no installation required.
|
||||||
|
|
||||||
|
Load any `.stl` file, pick a texture, tune the parameters, and export a new displaced STL ready for slicing.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Texture presets** — 12 built-in seamless textures (basket, brick, bubble, crystal, hexagon, knitting, knurling, leather, weave, wood, noise, and more)
|
||||||
|
- **Custom textures** — upload your own image as a displacement map
|
||||||
|
- **Projection modes** — Triplanar, Cubic (Box), Cylindrical, Spherical, Planar XY/XZ/YZ
|
||||||
|
- **UV transform** — independent U/V scale, offset, and rotation controls
|
||||||
|
- **Displacement amplitude** — fine-tune depth from subtle grain to deep relief
|
||||||
|
- **Surface mask** — skip horizontal top/bottom faces to keep flat surfaces clean
|
||||||
|
- **Surface exclusions / inclusions** — paint individual faces with a brush or bucket-fill to exclude or exclusively include them from displacement
|
||||||
|
- **Live preview** — real-time textured 3D preview with orbit/pan/zoom controls
|
||||||
|
- **Mesh subdivision** — auto-subdivides coarse geometry before displacement for smoother results
|
||||||
|
- **Export** — downloads a new binary STL with displacement baked in
|
||||||
|
- **Light / Dark theme** — persisted per browser
|
||||||
|
- **Multilingual** — English and German UI
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Open `index.html` in a modern browser (Chrome, Edge, Firefox, Safari).
|
||||||
|
2. Drop an STL file onto the viewport or click **Load STL…**.
|
||||||
|
3. Select a texture preset from the sidebar (or upload a custom image).
|
||||||
|
4. Adjust projection mode, UV scale, offset, rotation, and amplitude.
|
||||||
|
5. Optionally mask or exclude surfaces with the brush/fill tools.
|
||||||
|
6. Click **Export STL** to download the displaced mesh.
|
||||||
|
|
||||||
|
> **Note:** All processing runs entirely in the browser — no data is uploaded to any server.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
index.html # Main entry point
|
||||||
|
style.css # Styles (light/dark theme)
|
||||||
|
textures/ # Built-in JPG displacement map images
|
||||||
|
js/
|
||||||
|
main.js # App bootstrap & UI wiring
|
||||||
|
viewer.js # Three.js scene / camera / controls
|
||||||
|
stlLoader.js # Binary & ASCII STL parser
|
||||||
|
presetTextures.js # Built-in texture presets + custom upload
|
||||||
|
previewMaterial.js# Three.js material for live preview
|
||||||
|
mapping.js # UV projection logic
|
||||||
|
displacement.js # Vertex displacement baking
|
||||||
|
subdivision.js # Mesh subdivision
|
||||||
|
decimation.js # QEM mesh decimation
|
||||||
|
exclusion.js # Face exclusion / inclusion painting
|
||||||
|
exporter.js # Binary STL export
|
||||||
|
i18n.js # Translations (EN / DE)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Loaded via CDN — no build step needed:
|
||||||
|
|
||||||
|
- [Three.js](https://threejs.org/) v0.170.0
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT — see [LICENSE](LICENSE).
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
// Apply saved theme before first paint to avoid flash
|
// Apply saved theme before first paint to avoid flash
|
||||||
(function() {
|
(function() {
|
||||||
const t = localStorage.getItem('stlt-theme');
|
const t = localStorage.getItem('stlt-theme');
|
||||||
if (t === 'light') document.documentElement.setAttribute('data-theme', 'light');
|
if (t !== 'dark') document.documentElement.setAttribute('data-theme', 'light');
|
||||||
})();
|
})();
|
||||||
// Apply saved language before first paint to avoid flash
|
// Apply saved language before first paint to avoid flash
|
||||||
(function() {
|
(function() {
|
||||||
|
|||||||
@@ -102,8 +102,8 @@ export const TRANSLATIONS = {
|
|||||||
'progress.subdividing': 'Subdividing mesh\u2026',
|
'progress.subdividing': 'Subdividing mesh\u2026',
|
||||||
'progress.applyingDisplacement': 'Applying displacement to {n} triangles\u2026',
|
'progress.applyingDisplacement': 'Applying displacement to {n} triangles\u2026',
|
||||||
'progress.displacingVertices': 'Displacing vertices\u2026',
|
'progress.displacingVertices': 'Displacing vertices\u2026',
|
||||||
'progress.decimatingTo': 'Decimating {from} \u2192 {to} triangles\u2026',
|
'progress.decimatingTo': 'Simplifying {from} \u2192 {to} triangles\u2026',
|
||||||
'progress.decimating': 'Decimating: {cur} \u2192 {to} triangles',
|
'progress.decimating': 'Simplifying: {cur} \u2192 {to} triangles',
|
||||||
'progress.writingStl': 'Writing STL\u2026',
|
'progress.writingStl': 'Writing STL\u2026',
|
||||||
'progress.done': 'Done!',
|
'progress.done': 'Done!',
|
||||||
'progress.processing': 'Processing\u2026',
|
'progress.processing': 'Processing\u2026',
|
||||||
@@ -212,7 +212,7 @@ export const TRANSLATIONS = {
|
|||||||
|
|
||||||
// Export section
|
// Export section
|
||||||
'sections.export': 'Export \u24d8',
|
'sections.export': 'Export \u24d8',
|
||||||
'tooltips.export': 'Kleinere Kantenl\u00e4nge = feineres Verschiebungsdetail. Die Ausgabe wird dann auf das Dreieckslimit dezimiert.',
|
'tooltips.export': 'Kleinere Kantenl\u00e4nge = mehr Texturdetails. Die Ausgabe wird dann auf das Dreieckslimit vereinfacht.',
|
||||||
'labels.resolution': 'Aufl\u00f6sung',
|
'labels.resolution': 'Aufl\u00f6sung',
|
||||||
'tooltips.resolution': 'Kanten l\u00e4nger als dieser Wert werden beim Export unterteilt',
|
'tooltips.resolution': 'Kanten l\u00e4nger als dieser Wert werden beim Export unterteilt',
|
||||||
'labels.outputTriangles': 'Max Dreiecke',
|
'labels.outputTriangles': 'Max Dreiecke',
|
||||||
@@ -221,11 +221,11 @@ export const TRANSLATIONS = {
|
|||||||
'ui.exportStl': 'STL exportieren',
|
'ui.exportStl': 'STL exportieren',
|
||||||
|
|
||||||
// Export progress stages
|
// Export progress stages
|
||||||
'progress.subdividing': 'Netz wird unterteilt\u2026',
|
'progress.subdividing': 'Netz wird verfeinert\u2026',
|
||||||
'progress.applyingDisplacement': 'Verschiebung auf {n} Dreiecke anwenden\u2026',
|
'progress.applyingDisplacement': 'Textur auf {n} Dreiecke anwenden\u2026',
|
||||||
'progress.displacingVertices': 'Vertices werden verschoben\u2026',
|
'progress.displacingVertices': 'Punkte werden verschoben\u2026',
|
||||||
'progress.decimatingTo': '{from} \u2192 {to} Dreiecke dezimieren\u2026',
|
'progress.decimatingTo': '{from} \u2192 {to} Dreiecke vereinfachen\u2026',
|
||||||
'progress.decimating': 'Dezimieren: {cur} \u2192 {to} Dreiecke',
|
'progress.decimating': 'Vereinfachen: {cur} \u2192 {to} Dreiecke',
|
||||||
'progress.writingStl': 'STL schreiben\u2026',
|
'progress.writingStl': 'STL schreiben\u2026',
|
||||||
'progress.done': 'Fertig!',
|
'progress.done': 'Fertig!',
|
||||||
'progress.processing': 'Verarbeitung\u2026',
|
'progress.processing': 'Verarbeitung\u2026',
|
||||||
|
|||||||
@@ -14,18 +14,18 @@ function makeCanvas(size = SIZE) {
|
|||||||
// ── Image-based presets ───────────────────────────────────────────────────────
|
// ── Image-based presets ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
const IMAGE_PRESETS = [
|
const IMAGE_PRESETS = [
|
||||||
{ name: 'Basket', url: 'basket.jpg' },
|
{ name: 'Basket', url: 'textures/basket.jpg' },
|
||||||
{ name: 'Brick', url: 'brick.jpg' },
|
{ name: 'Brick', url: 'textures/brick.jpg' },
|
||||||
{ name: 'Bubble', url: 'bubble.jpg' },
|
{ name: 'Bubble', url: 'textures/bubble.jpg' },
|
||||||
{ name: 'Crystal', url: 'crystal.jpg' },
|
{ name: 'Crystal', url: 'textures/crystal.jpg' },
|
||||||
{ name: 'Hexagon', url: 'hexagon.jpg' },
|
{ name: 'Hexagon', url: 'textures/hexagon.jpg' },
|
||||||
{ name: 'Knitting', url: 'knitting.jpg' },
|
{ name: 'Knitting', url: 'textures/knitting.jpg' },
|
||||||
{ name: 'Knurling', url: 'knurling.jpg' },
|
{ name: 'Knurling', url: 'textures/knurling.jpg' },
|
||||||
{ name: 'Leather', url: 'leather.jpg' },
|
{ name: 'Leather', url: 'textures/leather.jpg' },
|
||||||
{ name: 'Leather 2', url: 'leather2.jpg' },
|
{ name: 'Leather 2', url: 'textures/leather2.jpg' },
|
||||||
{ name: 'Weave', url: 'weave.jpg' },
|
{ name: 'Weave', url: 'textures/weave.jpg' },
|
||||||
{ name: 'Wood', url: 'wood.jpg' },
|
{ name: 'Wood', url: 'textures/wood.jpg' },
|
||||||
{ name: 'Noise', url: 'noise.jpg' },
|
{ name: 'Noise', url: 'textures/noise.jpg' },
|
||||||
];
|
];
|
||||||
|
|
||||||
function loadImagePreset({ name, url }) {
|
function loadImagePreset({ name, url }) {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 512 KiB After Width: | Height: | Size: 512 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 175 KiB |