Skip to content

Feature - Add textured skin: SVG pattern displacement on outer perimeters#15336

Open
chocholom wants to merge 3 commits into
prusa3d:masterfrom
chocholom:feature/textured-skin
Open

Feature - Add textured skin: SVG pattern displacement on outer perimeters#15336
chocholom wants to merge 3 commits into
prusa3d:masterfrom
chocholom:feature/textured-skin

Conversation

@chocholom

@chocholom chocholom commented Apr 6, 2026

Copy link
Copy Markdown

Description

Background

I watched video about adding textures to 3D prints ("CNCKitchen: Stop Using Fuzzy Skin - I made something better!"
)
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 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 (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.

Screenshot 2026-04-06 at 21 03 01 Screenshot 2026-04-06 at 22 18 23 Screenshot 2026-04-06 at 22 18 44 Screenshot 2026-04-06 at 22 23 02 Screenshot 2026-04-06 at 22 24 27 Screenshot 2026-04-06 at 22 40 07

Mirek Chocholous added 2 commits April 6, 2026 22:50
- Thread.hpp: Respect caller's stack size instead of always overriding to 4MB.
  CGAL's GMP exact arithmetic requires deeper stack for complex intersections.
- deps/CMakeLists.txt: Add CMAKE_POLICY_VERSION_MINIMUM=3.5 for CMake 4.x compat.
Structured alternative to fuzzy skin that displaces the outer perimeter
path based on an SVG pattern, creating repeatable surface textures.

Core engine (Feature/TexturedSkin/):
- PatternSampler: loads SVG via NanoSVG, rasterizes shapes into a
  grayscale heightmap grid using point-in-polygon testing with
  painter's order (later SVG shapes override earlier ones).
  Fill color brightness controls displacement: black=max, white=none.
  Bilinear interpolation for smooth pattern edges.
- textured_polygon(): walks perimeter at configurable point intervals,
  samples pattern at (u,v) coordinates, displaces outward along normal.
  Rate-limited to prevent sharp spikes. Seam reference uses interpolated
  angle=0 crossing for layer-to-layer stability.

10 mapping modes for different object shapes:
- 0 PaintedOn: u=arc-length, v=layer_z. Consistent physical tile size.
- 1 Mercator: u=angle×R, v=z×(tile_w/circumference). Conformal on spheres.
- 2 StretchFit: arc-length rescaled for integer tiles per revolution.
- 3-8 Stamp: XY/XZ/YZ projections from 6 directions (Front/Back/Left/Right/Top/Bottom).
- 9 Adaptive: blends PaintedOn and Mercator based on circumference.

Integration:
- Hooks into both Arachne and Classic perimeter paths in PerimeterGenerator.
- Thread-local sampler cache with lazy loading.
- Skips perimeters smaller than half a tile (pole filtering).
- Updated has_compatible_perimeter_regions for region merging.

Config options (Print Settings > Textured Skin):
- textured_skin_enabled: on/off toggle
- textured_skin_svg: file path with Browse button
- textured_skin_mapping: mapping mode (0-9)
- textured_skin_thickness: max displacement in mm
- textured_skin_tile_size: horizontal tile width in mm
- textured_skin_tile_height: vertical tile height (0=auto from SVG aspect)
- textured_skin_point_dist: sample spacing along perimeter
@kurtgluck

Copy link
Copy Markdown

I have been playing with wood grain

https://www.printables.com/make/3331427

For sides I used fuzzy skin

For the top I used ironing.

PNG heightmap support:
- Load PNG files directly via PNGReadWrite (fast, no SVG rasterization)
- Box-filter downsampling to prevent aliasing on fine patterns (512px PNGs)
- Standard heightmap convention: white = raised, dark = recessed
- Browse button now accepts both SVG and PNG files

Proper enum dropdown for mapping mode:
- Changed textured_skin_mapping from coInt to coEnum with labeled dropdown
- 12 modes: PaintedOn, Mercator, StretchFit, 6 Stamps, Adaptive, Cylindrical, Triplanar
- Backward compatible: old integer values ("0", "1", ...) still parse correctly
- New modes 10 (Cylindrical) and 11 (Triplanar) added

Invert pattern option:
- New checkbox to swap raised/recessed areas
- Works for both SVG and PNG formats

Fixes:
- Grayscale auto-normalization: lightest shape=0, darkest=1
- Separate X/Y resolution in bilinear interpolation (non-square tiles render correctly)
- Removed rate limiter that made large thickness values invisible
- Cleaned up tooltips (removed Earth-specific hints)
- Fixed seam interpolation for stable layer alignment on spheres
@dave-hillier

dave-hillier commented Apr 12, 2026

Copy link
Copy Markdown

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.

I love how LLMs characterize these things when asked to justify a position 🤣

Instead of saying "complex and rigid" you could say "complex" is more fully featured and in addition to mentioned painting, UI widget, the skin displacement, you can also bake the geometry "rigidly" into the mesh.

@chocholom

Copy link
Copy Markdown
Author

Hi Dave,
I used claude and codex but not for the sentence you took :)
Sorry if it sounded harsh or something like that - that wasn't my intention.
The code and implementation being complex is not wrong at all. When I wrote rigid I meant it in the way. There is a missing feature and I wrote a MVP to have a discussion about if and how to to implement it. You wrote it complete from the start to the end but without the discussion with code owners and maintainers first.

I like what you did. If I wouldn't I wouldn't add the last commit to copy some of the features you had. But my intention is to write something that works, test it and have discussion if the maintainers want such a feature at all.

@kurtgluck

Copy link
Copy Markdown

Hey guys, stop fighting for a moment and read my previous comment. Perhaps this solution can be extended to top surfaces by looking at messing with the ironing instead of (in addition to) fuzzy skin....

By the way think of what you both could achieve together - unless its the frisson that motivates you both :-)

Keep up the great work

Kurt-a-3d

@dave-hillier

Copy link
Copy Markdown

Hi Dave,

I used claude and codex but not for the sentence you took :)

Sorry if it sounded harsh or something like that - that wasn't my intention.

The code and implementation being complex is not wrong at all. When I wrote rigid I meant it in the way. There is a missing feature and I wrote a MVP to have a discussion about if and how to to implement it. You wrote it complete from the start to the end but without the discussion with code owners and maintainers first.

I like what you did. If I wouldn't I wouldn't add the last commit to copy some of the features you had. But my intention is to write something that works, test it and have discussion if the maintainers want such a feature at all.

It did sound harsh, but don't worry, I completely realised how that came about! Don't worry about it 😀

I'm not wedded to my solution here. If you want to combine them feel free to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants