diff --git a/.gitignore b/.gitignore index ee88966..253939d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ assets/ +dist/ +node_modules/ +package-lock.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5211bb7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM node:24-alpine AS base + + +FROM base AS developer +WORKDIR /app +RUN apk add shadow +CMD ["sh", "-c", \ + "groupmod -g $(stat -c '%u' /app) node; \ + usermod -u $(stat -c '%u' /app) -g $(stat -c '%u' /app) node; \ + su node -c 'npm install --loglevel=info; npm run dev'"] + + +FROM base AS builder + +WORKDIR /app + +COPY package*.json /app/ +RUN npm install + +COPY src /app/src +COPY vite.config.js /app/ +RUN npm run build + + +FROM nginx:alpine AS executor + +WORKDIR /app + +COPY --from=builder /app/dist /app +COPY deploy/site.conf.template /etc/nginx/templates/default.conf.template + +ENV SITE_ROOT=/app diff --git a/README.md b/README.md index 2cbd467..ee0c4a4 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,22 @@ A browser-based tool for applying surface displacement textures to 3D meshes — Load an STL, OBJ, or 3MF file, pick a texture, tune the parameters, and export a new displaced STL ready for slicing. +## Local development + +```sh +docker compose up --build +``` + +## Production build + +```sh +TARGET=executor docker compose up --build +``` + +## Usage + +Navigate the page: + ## Features ### Textures @@ -16,6 +32,7 @@ Load an STL, OBJ, or 3MF file, pick a texture, tune the parameters, and export a - **Texture smoothing** — configurable blur to soften the displacement map before applying ### Projection Modes + - **Triplanar** (default) — blends three planar projections based on surface normals; best for complex shapes - **Cubic (Box)** — projects from 6 box faces with edge-seam blending and smart axis dominance - **Cylindrical** — wraps texture around a cylindrical axis with configurable cap angle @@ -23,6 +40,7 @@ Load an STL, OBJ, or 3MF file, pick a texture, tune the parameters, and export a - **Planar XY / XZ / YZ** — flat axis-aligned projections ### UV & Transform Controls + - **Scale U/V** — independent or locked scaling (0.05–10×, logarithmic) - **Offset U/V** — position the texture on each axis - **Rotation** — rotate texture before projection @@ -31,12 +49,14 @@ Load an STL, OBJ, or 3MF file, pick a texture, tune the parameters, and export a - **Cap Angle** (Cylindrical) — threshold for switching to top/bottom cap projection ### Displacement + - **Amplitude** — scales displacement depth from 0 % to 100 % - **Symmetric displacement** — 50 % grey stays neutral, white pushes out, black pushes in (preserves volume) - **3D displacement preview** — real-time GPU-accelerated preview toggle showing actual vertex displacement - **Amplitude overlap warning** — alerts when depth exceeds 10 % of the smallest model dimension ### Surface Masking + - **Angle masking** — suppress texture on near-horizontal top and/or bottom faces (0°–90° threshold each) - **Face exclusion / inclusion painting** — paint individual faces to exclude (orange) or exclusively include (green) them - Brush tool — single-triangle click or adjustable-radius circle brush @@ -45,11 +65,13 @@ Load an STL, OBJ, or 3MF file, pick a texture, tune the parameters, and export a - Clear all — reset masking ### Mesh Processing + - **Adaptive subdivision** — subdivides edges until they are ≤ a target length; respects sharp creases (>30° dihedral) - **QEM decimation** — simplifies the result to a target triangle count using Quadric Error Metrics with boundary protection, link-condition checks, normal-flip rejection, and crease preservation - **Safety cap** — hard limit of 10 M triangles during subdivision to prevent out-of-memory ### 3D Viewer + - **Orbit / pan / zoom** controls - **Wireframe toggle** — visualise mesh topology - **Mesh info** — live triangle count, file size, bounding-box dimensions @@ -57,16 +79,19 @@ Load an STL, OBJ, or 3MF file, pick a texture, tune the parameters, and export a - **Place on Face** — click a face to orient it downward onto the print bed ### File Support + - **.STL** — binary and ASCII - **.OBJ** — via Three.js OBJLoader - **.3MF** — ZIP-based format (via fflate decompression) ### Export + - Downloads a **binary STL** with displacement baked in - Progress reporting through subdivision → displacement → decimation → writing stages - Configurable edge-length threshold and output triangle limit ### Other + - **Light / Dark theme** — respects OS preference, persisted per browser - **Multilingual** — English and German UI with auto-detection diff --git a/deploy/site.conf.template b/deploy/site.conf.template new file mode 100644 index 0000000..9a4b0d3 --- /dev/null +++ b/deploy/site.conf.template @@ -0,0 +1,34 @@ +server_tokens off; + +server { + listen 3000 default_server; + absolute_redirect off; + server_name _; + index index.html; + root ${SITE_ROOT}; + + gzip on; + gzip_static on; + gzip_disable msie6; + gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript; + + location /health { + return 200; + access_log off; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|webm|webp|svg|ico|woff|woff2|ttf)$ { + expires 30d; + log_not_found off; + } + + location / { + try_files $uri $uri.html $uri/ =404; + } + + error_page 404 /404.html; + + location = /404.html { + internal; + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6633107 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +services: + app: + build: + context: . + target: ${TARGET:-developer} + working_dir: /app + volumes: + - "${PWD:-./}:/${TARGET:-app}" + ports: + - "3000:3000" diff --git a/package.json b/package.json new file mode 100644 index 0000000..aae3686 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "stltexturizer", + "version": "1.0.0", + "description": "**Live demo:** https://cnckitchen.github.io/stlTexturizer/", + "main": "index.js", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/mikhail-shevtsov-wiregate/stlTexturizer.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/mikhail-shevtsov-wiregate/stlTexturizer/issues" + }, + "homepage": "https://github.com/mikhail-shevtsov-wiregate/stlTexturizer#readme", + "devDependencies": { + "@vitejs/plugin-legacy": "^8.0.1", + "vite": "^8.0.3", + "vite-plugin-static-copy": "^4.0.1" + }, + "dependencies": { + "fflate": "^0.8.2", + "three": "^0.183.2" + } +} diff --git a/index.html b/src/index.html similarity index 100% rename from index.html rename to src/index.html diff --git a/js/decimation.js b/src/js/decimation.js similarity index 100% rename from js/decimation.js rename to src/js/decimation.js diff --git a/js/displacement.js b/src/js/displacement.js similarity index 100% rename from js/displacement.js rename to src/js/displacement.js diff --git a/js/exclusion.js b/src/js/exclusion.js similarity index 100% rename from js/exclusion.js rename to src/js/exclusion.js diff --git a/js/exporter.js b/src/js/exporter.js similarity index 100% rename from js/exporter.js rename to src/js/exporter.js diff --git a/js/i18n.js b/src/js/i18n.js similarity index 100% rename from js/i18n.js rename to src/js/i18n.js diff --git a/js/i18n/de.js b/src/js/i18n/de.js similarity index 100% rename from js/i18n/de.js rename to src/js/i18n/de.js diff --git a/js/i18n/en.js b/src/js/i18n/en.js similarity index 100% rename from js/i18n/en.js rename to src/js/i18n/en.js diff --git a/js/i18n/es.js b/src/js/i18n/es.js similarity index 100% rename from js/i18n/es.js rename to src/js/i18n/es.js diff --git a/js/i18n/fr.js b/src/js/i18n/fr.js similarity index 100% rename from js/i18n/fr.js rename to src/js/i18n/fr.js diff --git a/js/i18n/it.js b/src/js/i18n/it.js similarity index 100% rename from js/i18n/it.js rename to src/js/i18n/it.js diff --git a/js/i18n/ja.js b/src/js/i18n/ja.js similarity index 100% rename from js/i18n/ja.js rename to src/js/i18n/ja.js diff --git a/js/i18n/pt.js b/src/js/i18n/pt.js similarity index 100% rename from js/i18n/pt.js rename to src/js/i18n/pt.js diff --git a/js/main.js b/src/js/main.js similarity index 100% rename from js/main.js rename to src/js/main.js diff --git a/js/mapping.js b/src/js/mapping.js similarity index 100% rename from js/mapping.js rename to src/js/mapping.js diff --git a/js/presetTextures.js b/src/js/presetTextures.js similarity index 100% rename from js/presetTextures.js rename to src/js/presetTextures.js diff --git a/js/previewMaterial.js b/src/js/previewMaterial.js similarity index 100% rename from js/previewMaterial.js rename to src/js/previewMaterial.js diff --git a/js/stlLoader.js b/src/js/stlLoader.js similarity index 100% rename from js/stlLoader.js rename to src/js/stlLoader.js diff --git a/js/subdivision.js b/src/js/subdivision.js similarity index 100% rename from js/subdivision.js rename to src/js/subdivision.js diff --git a/js/viewer.js b/src/js/viewer.js similarity index 100% rename from js/viewer.js rename to src/js/viewer.js diff --git a/logo.png b/src/logo.png similarity index 100% rename from logo.png rename to src/logo.png diff --git a/style.css b/src/style.css similarity index 100% rename from style.css rename to src/style.css diff --git a/textures/basket.jpg b/src/textures/basket.jpg similarity index 100% rename from textures/basket.jpg rename to src/textures/basket.jpg diff --git a/textures/brick.jpg b/src/textures/brick.jpg similarity index 100% rename from textures/brick.jpg rename to src/textures/brick.jpg diff --git a/textures/bubble.jpg b/src/textures/bubble.jpg similarity index 100% rename from textures/bubble.jpg rename to src/textures/bubble.jpg diff --git a/textures/carbonFiber.jpg b/src/textures/carbonFiber.jpg similarity index 100% rename from textures/carbonFiber.jpg rename to src/textures/carbonFiber.jpg diff --git a/textures/crystal.jpg b/src/textures/crystal.jpg similarity index 100% rename from textures/crystal.jpg rename to src/textures/crystal.jpg diff --git a/textures/dots.jpg b/src/textures/dots.jpg similarity index 100% rename from textures/dots.jpg rename to src/textures/dots.jpg diff --git a/textures/grid.png b/src/textures/grid.png similarity index 100% rename from textures/grid.png rename to src/textures/grid.png diff --git a/textures/gripSurface.jpg b/src/textures/gripSurface.jpg similarity index 100% rename from textures/gripSurface.jpg rename to src/textures/gripSurface.jpg diff --git a/textures/hexagon.jpg b/src/textures/hexagon.jpg similarity index 100% rename from textures/hexagon.jpg rename to src/textures/hexagon.jpg diff --git a/textures/hexagons.jpg b/src/textures/hexagons.jpg similarity index 100% rename from textures/hexagons.jpg rename to src/textures/hexagons.jpg diff --git a/textures/isogrid.png b/src/textures/isogrid.png similarity index 100% rename from textures/isogrid.png rename to src/textures/isogrid.png diff --git a/textures/knitting.jpg b/src/textures/knitting.jpg similarity index 100% rename from textures/knitting.jpg rename to src/textures/knitting.jpg diff --git a/textures/knurling.jpg b/src/textures/knurling.jpg similarity index 100% rename from textures/knurling.jpg rename to src/textures/knurling.jpg diff --git a/textures/leather.jpg b/src/textures/leather.jpg similarity index 100% rename from textures/leather.jpg rename to src/textures/leather.jpg diff --git a/textures/leather2.jpg b/src/textures/leather2.jpg similarity index 100% rename from textures/leather2.jpg rename to src/textures/leather2.jpg diff --git a/textures/noise.jpg b/src/textures/noise.jpg similarity index 100% rename from textures/noise.jpg rename to src/textures/noise.jpg diff --git a/textures/stripes.png b/src/textures/stripes.png similarity index 100% rename from textures/stripes.png rename to src/textures/stripes.png diff --git a/textures/stripes_02.png b/src/textures/stripes_02.png similarity index 100% rename from textures/stripes_02.png rename to src/textures/stripes_02.png diff --git a/textures/voronoi.jpg b/src/textures/voronoi.jpg similarity index 100% rename from textures/voronoi.jpg rename to src/textures/voronoi.jpg diff --git a/textures/weave.jpg b/src/textures/weave.jpg similarity index 100% rename from textures/weave.jpg rename to src/textures/weave.jpg diff --git a/textures/weave_02.jpg b/src/textures/weave_02.jpg similarity index 100% rename from textures/weave_02.jpg rename to src/textures/weave_02.jpg diff --git a/textures/weave_03.jpg b/src/textures/weave_03.jpg similarity index 100% rename from textures/weave_03.jpg rename to src/textures/weave_03.jpg diff --git a/textures/wood.jpg b/src/textures/wood.jpg similarity index 100% rename from textures/wood.jpg rename to src/textures/wood.jpg diff --git a/textures/woodgrain_02.jpg b/src/textures/woodgrain_02.jpg similarity index 100% rename from textures/woodgrain_02.jpg rename to src/textures/woodgrain_02.jpg diff --git a/textures/woodgrain_03.jpg b/src/textures/woodgrain_03.jpg similarity index 100% rename from textures/woodgrain_03.jpg rename to src/textures/woodgrain_03.jpg diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..6c5fdd2 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,35 @@ +import { defineConfig } from 'vite'; +import legacy from '@vitejs/plugin-legacy'; +import { viteStaticCopy } from 'vite-plugin-static-copy'; + +export default defineConfig({ + server: { + port: 3000, + host: '0.0.0.0' + }, + root: 'src', + publicDir: '../public', + build: { + outDir: '../dist', + emptyOutDir: true, + minify: 'terser', + rollupOptions: { + input: { + main: 'src/index.html', + }, + }, + }, + plugins: [ + legacy({ + targets: ['defaults', 'not IE 11'], + }), + viteStaticCopy({ + targets: [ + { + src: 'textures/*', + dest: '.' + } + ] + }) + ], +});