Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
assets/
dist/
node_modules/
package-lock.json
32 changes: 32 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: <http://localhost:3000>

## Features

### Textures
Expand All @@ -16,13 +32,15 @@ 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
- **Spherical** — maps texture spherically around the object
- **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
Expand All @@ -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
Expand All @@ -45,28 +65,33 @@ 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
- **Grid & axes indicator** — X = red, Y = green, Z = blue
- **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

Expand Down
34 changes: 34 additions & 0 deletions deploy/site.conf.template
Original file line number Diff line number Diff line change
@@ -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;
}
}
10 changes: 10 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
app:
build:
context: .
target: ${TARGET:-developer}
working_dir: /app
volumes:
- "${PWD:-./}:/${TARGET:-app}"
ports:
- "3000:3000"
33 changes: 33 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
File renamed without changes
35 changes: 35 additions & 0 deletions vite.config.js
Original file line number Diff line number Diff line change
@@ -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: '.'
}
]
})
],
});