Feature - Add textured skin: SVG pattern displacement on outer perimeters
## Description
### Background
I watched video about adding textures to 3D prints [("CNCKitchen: Stop Using Fuzzy Skin - I made something better!"
)](https://www.youtube.com/watch?v=rTBkjR7JvzI) and my first thought was — why isn't this in PrusaSlicer already? So I tried to find out.
My first approach was implementing it "properly" — modifying the actual 3D mesh by projecting SVG patterns onto object surfaces using CGAL mesh intersection. After several attempts I kept hitting fundamental limitations: CGAL stack overflows on complex geometry, coplanarity crashes on axis-aligned objects, coordinate space mismatches, and the inherent impossibility of tiling a flat pattern onto a curved surface without distortion.
Then I realized the solution was actually in the name of the video. We don't need to make PrusaSlicer into yet another 3D modeling software. We need to make fuzzy skin. Just better. And that's where **textured skin** came to mind — the same perimeter displacement mechanism that fuzzy skin uses, but with a structured SVG pattern instead of random noise.
**Big kudos to Stefan** for the original idea (and in the end even for hint for the good implementation).
After I finished this and pushed it I found there is [PR 15335](https://github.com/prusa3d/PrusaSlicer/pull/15335) that wasn't there when I started. dave-hillier's approach is a way more complex and rigid - it is able to modify the object and paint the pattern on the object manually.
It's up on you to decide which approach makes more sense and is easier for maintenance. But in his approach I liked the dropdown menu, the option to use PNG and other modes for texture application (cylindrical and triplanar). All three of that looked like low hanging fruit. So I implemented them as well.
### Summary
Adds a new "Textured Skin" feature that creates structured surface textures by displacing the outer perimeter path based on an SVG or PNG pattern. Unlike fuzzy skin which applies random noise, textured skin produces repeatable, deterministic patterns (bricks, crosses, dimples, woodgrain, etc.) that are inherently surface-conforming.
### How it works
1. User provides an SVG or PNG file containing the texture pattern
2. SVG shapes are rasterized into a grayscale heightmap (fill brightness → displacement). PNG pixels are box-filter downsampled to prevent aliasing of fine features.
3. Both formats are auto-normalized so all-gray patterns produce full contrast
4. During perimeter generation, the outer perimeter is walked at regular intervals
5. At each point, the pattern is sampled at (u, v) coordinates determined by the mapping mode
6. The perimeter point is displaced outward by `thickness × pattern_value`
7. Bilinear interpolation ensures smooth transitions between grid cells
### Texture formats
- **SVG**: Fill color brightness controls displacement (black = raised). Painter's order respected.
- **PNG**: Standard heightmap convention (white = raised). Box-filter downsampled at load time.
- **Invert checkbox**: Swap raised/recessed for either format.
### Mapping modes
12 mapping modes (proper dropdown) control how the 2D pattern maps onto the 3D surface:
- **PaintedOn (0)**: u=arc-length, v=layer_z — consistent physical tile size, best for cylinders/boxes
- **Mercator (1)**: u=angle×R, v=z×(tile_w/circumference) — conformal, best for spheres
- **StretchFit (2)**: integer tile count per revolution — always seamless
- **Stamp Front/Back/Left/Right/Top/Bottom (3-8)**: XY/XZ/YZ projections from 6 directions
- **Adaptive (9)**: auto-blends PaintedOn and Mercator based on circumference
- **Cylindrical (10)**: angle-based seamless wrap without vertical scaling
- **Triplanar (11)**: auto-selects best projection per surface normal — works on any shape
### Config options (Print Settings > Textured Skin)
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| Enable textured skin | bool | false | On/off toggle |
| Texture file | string | "" | SVG or PNG path (with Browse button) |
| Texture mapping | enum | Painted on | Mapping mode dropdown (12 options) |
| Texture depth | float | 0.3 | Max displacement in mm |
| Tile width | float | 5.0 | Horizontal tile size in mm |
| Tile height | float | 0 | Vertical tile size (0 = auto from aspect) |
| Texture point distance | float | 0.3 | Sample spacing in mm |
| Invert pattern | bool | false | Swap raised/recessed areas |
### Testing
- Tested on box, sphere, cylinder, vase shapes with SVG and PNG patterns
- All 12 mapping modes verified via CLI slicing and G-code analysis
- Both Arachne (default) and Classic perimeter generators confirmed working
- Earth continents recognizable on sphere with Mercator mode
- No regressions in existing emboss tests (56 assertions, 11 test cases)
### Known limitations
- Sphere poles: very small perimeters are skipped (< 0.5 tiles circumference)
- Mercator on spheres: conformal v-scaling is a linear approximation
- Pattern is applied per-layer independently — no cross-layer coherence guarantee
- Using non-mercator style textures on rounded or sphere objects produces distorted shapes
- It's still "just" fuzzy skin implementation - there is nothing to be applied on top flat surfaces. For that Stefan's [BumpMesh.com](https://bumpmesh.com/) (or proper modelling software) has to be used.
### Final notes
After some initial playing on boxes with a simple brick or cobble stone patterns I felt this might be a big simplification for users who only knows of basics of tinnkercad etc. But after I used mercador and downloaded the map of Earth there I feel like this will have a use case even for pretty advanced designers as such a combination of 2d on 3d can make some complicated tasks so simple.
<img width="836" height="222" alt="Screenshot 2026-04-06 at 21 03 01" src="https://github.com/user-attachments/assets/f74628ef-9b9d-43a2-818b-3133fdbab0e1" />
<img width="488" height="510" alt="Screenshot 2026-04-06 at 22 18 23" src="https://github.com/user-attachments/assets/76297310-3592-4707-b423-9da5ee589970" />
<img width="446" height="513" alt="Screenshot 2026-04-06 at 22 18 44" src="https://github.com/user-attachments/assets/7b09504b-55c4-43ab-8f60-e7385b96bc6f" />
<img width="762" height="740" alt="Screenshot 2026-04-06 at 22 23 02" src="https://github.com/user-attachments/assets/3348b0ec-051d-4281-b0bb-a6b676a8124d" />
<img width="614" height="627" alt="Screenshot 2026-04-06 at 22 24 27" src="https://github.com/user-attachments/assets/11cd81a5-e9a0-4855-bd66-b134d9c43f46" />
<img width="680" height="679" alt="Screenshot 2026-04-06 at 22 40 07" src="https://github.com/user-attachments/assets/edce157e-308c-46b6-abb5-aadd6f5b6428" />
合并状态:未合并 2 条评论