Skapa,一款类似宜家手册的参数化3D打印应用 (2025)
Skapa, a parametric 3D printing app like an IKEA manual (2025)

原始链接: https://nmattia.com/posts/2025-03-24-skapa-intro/

## Skapa:可3D打印的宜家Skadis配件 - 简短发布总结 Skapa(瑞典语意为“创造”)是一个网页应用程序,允许用户生成可3D打印的宜家Skadis洞板系统的定制盒子。网址为[skapa.build](https://skapa.build),用户只需输入所需的尺寸并下载模型进行打印即可。该项目完全在客户端构建,也是一次3D图形学习经验。 用户界面有意设计成类似于宜家手册,采用黑白美学和正投影。交互仅限于模型的垂直旋转和180度翻转,避免了复杂的相机控制。 技术上,Skapa利用`manifold-3d`库生成模型(通过Wasm编译为WebGL),并使用`Three.js`进行渲染。一种定制的基于着色的后期处理效果创造了独特的轮廓外观,模仿了宜家说明书的风格。最近的更新甚至消除了打印支撑的需要。 开发者计划扩展Skapa,增加更多模型——可能超出宜家产品范围——并完善现有的定制选项,例如壁厚和底厚。代码在GitHub上开源,Printables上提供打印说明。

Hacker News 新闻 | 过去 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 Skapa,一款类似宜家手册的参数化3D打印应用 (2025) (nmattia.com) 8 分,iamjackg 1小时前 | 隐藏 | 过去 | 收藏 | 1 条评论 mitchbob 17分钟前 [–] 顺便说一句,瑞典语中“创造”的动词是“skapa”,而不是“skåpa”。https://dictionary.cambridge.org/dictionary/swedish-english/... 建议您为应用名称使用正确的词。回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请YC | 联系 搜索:
相关文章

原文

March 24, 2025


This is a mini-release for Skapa, an app I made for generating 3D-printable models for IKEA Skadis pegboards. This post goes through some of the UI & UX decisions and gives an overview of the tech used for model generation and rendering (with Three.js).

My Skadis board at home

My Skadis board at home

First, let’s clarify. Skadis (skådis) is IKEA’s pegboard system, allowing different kinds of boxes and accessories to be mounted on the wall. Skapa (skåpa) is how I named my app, and comes from the Swedish word for “create” or “make” (or so I was led to believe).

With Skapa you can generate custom boxes for IKEA Skadis. The boxes can be downloaded and 3D-printed. The app is very opinionated and I reserve the right to break anything at any time! It’s a side project I’ve been playing with on & off for a few months — and also an excuse to learn more about 3D graphics.

To use it, head over to skapa.build, specify the dimensions (width, height & depth) and click the download button. Open the downloaded file in your slicer of choice (PrusaSlicer team here) and print. Voila!

User interface of skapa.build

The user interface of https://skapa.build

The code is on GitHub and there is also a Printables entry you can check out (and star).

I’ll briefly describe the interface (and some of the “thinking” behind it), then talk about the tech that makes it possible, and then briefly touch on where I’ll try to take the project.

Nu kör vi! (let’s go!)

UI/UX considerations

Big disclaimer: This is a side project where I decided to explore every possible rabbit hole and allowed myself to obsess over potentially irrelevant details. I’ve been learning a ton in the process. If something doesn’t look right, let me know!

The base idea (and hopefully most obvious design choice) was to make the app look like an IKEA manual. This was done by sticking to black and white, using big blocky letters with an Å, and showing strong, black outlines (more on how this is achieved later).

IKEA billy manual

The first page of an IKEA Billy shelf manual

There are a couple of notable differences between Skapa and an actual IKEA manual. First, the app is not paper. Then, the font is not the IKEA font (IKEA Sans) but “Kanit”, which is about as far as I’ll go to try and avoid being sued (IKEA, do reach out if you want to partner though). Another big visual difference is that IKEA uses perspective drawings (at least on the first page of their manuals) whereas Skapa uses orthographic projection (I just like it more).

Another goal was to have an interface as minimal as possible, so I just spent a lot of time removing buttons and features from the original implementation.

Evolution of the UI over time

The evolution of the UI over time

Camera Controls

At some point, someone I showed the app said “So cool! Is that an SVG?”. It’s terribly frustrating to hear someone ask which 2D graphics format you’re using when you poured hours into making a 3D rendering pipeline. So I decided to make sure the user could interact with the model a bit to see it was — actually — 3D. I wanted to avoid full control over the model (no zooming in/out, panning camera, etc). I find those sorts of controls confusing at best, dizzying at worst.

I settled on simple, vertical rotation when the user drags the part, with the part snapping between two different orientations (try it here) as well as the part rotating 180 degrees (π rads, for those in the know) when clicked.

This surfaced some issues on mobile, where the part can take up most of the real estate; by capturing the mouse & touch events to rotate the part, it sometimes became impossible to scroll the page up and down because the “click” would always land on the canvas element (where the part is rendered) and be captured by the rotation “gesture”!

The workaround here was to only capture events if they landed on the part itself, but not if the event was inside the canvas but not on the part. So satisfying.

Skapa touch controls

Satisfying camera controls

Behind the scenes with manifold-3d, Three.js and shaders

There were three major technical challenges: how to generate the model, how to render the model, and how to add the outlines.

The model generation is done with the manifold library. It is a C++ library compiled to Wasm which can be used from the browser (Skapa runs 100% client-side). The manifold library is also published on npm. The nice thing is that everything happens in the browser and Skapa can be distributed as a Single-Page Application.

Here’s some code that uses manifold to create the box base, i.e. the Skapa part without the clips:

// The box (without clips) with origin in the middle of the bottom face
export async function base(
  height: number,
  width: number,
  depth: number,
  radius: number,
  wall: number,
  bottom: number,
): Promise<Manifold> {
  const innerRadius = Math.max(0, radius - wall);
  const outer = (await roundedRectangle([width, depth], radius)).extrude(
    height,
  );
  const innerNeg = (
    await roundedRectangle([width - 2 * wall, depth - 2 * wall], innerRadius)
  )
    .extrude(height - bottom)
    .translate([0, 0, bottom]);

  return outer.subtract(innerNeg);
}

The part (with clips) can be exported as a list of vertices, but they still need to be rendered. For this I decided to go with the venerable Three.js library.

Unfortunately Three.js’ “outline” material didn’t allow me to render the part exactly as I wanted; in particular it gave me no control over the thickness of the outline and did not actually add outlines everywhere.

Three.js' native outline rendering

The model rendered with Three.js’ “outline material”, the EdgesGeometry with a LineMaterial.

Three.js does have pretty nice support for post-processing through its EffectComposer. I followed this great article by Roystan (originally written for Unity) explaining how to use edge detection on a 3D model to display outlines (it’s super well written with lots of cool examples).

My implementation uses two different passes, each implemented as a WebGL shader.

WebGL shaders are programs that the browser runs on the GPU. They are generally used for graphics […]. For more information I recommend https://webglfundamentals.org/ and the MDN WebGL tutorial.

From How to Set WebGL Shader Colors with CSS and JavaScript.

The first pass implements the edge detection algorithm explained in the article linked above. The edge detection is done by casting a ray for every pixel and figuring out where it lands on the model. It then calculates the depth of that point on the model (i.e. the distance between the camera & the point on the model) and the normal of the model at that point (i.e. the face’s orientation). The resulting depth and normal are then compared to the depth and normal of other pixels close by. If they differ too much from one pixel to the next, then an outline should be shown there and the pixel is given the color “black”. This would create a ~1 pixel outline.

Edge detection illustrated

The second pass goes through every pixel on screen again and checks if said pixel is within some distance (say, 5 pixels) of a black (outline) pixel from the first pass. If so, this new pixel is also marked as “outline” and rendered black. This effectively thickens the outline, and is implemented as a second pass for performance reasons.

Here’s an animation of the depth & normals (I seem to have misplaced the original video):

what goes into edge detection: 1. depth 2. normals 3. depth edges & normal edges 4. thickening #3D #3dgraphics #ThreeJS #webgl

[image or embed]

— Nicolas Mattia (@nmattia.bsky.social) 7 March 2025 at 22:57

That’s it for the tech used; bit of manifold-3d, a dab of Three.js and some shading goodness!

What’s next for Skapa

It’s been really fun to build and I’ve learned a ton. I didn’t understand how GPUs worked and hadn’t used graphics APIs in years, and I’ll say this: GPUs are crazy powerful (even on a smartphone and a 5 year-old MacBook Air) and graphics APIs have become fun to use (see also my other article on How to Set WebGL Shader Colors with CSS and JavaScript). I find that what’s happening when using WebGL is much more transparent than OpenGL 15 years ago, which makes it a bit slower to get started but more rewarding & sustainable in the long run. Maybe I also just have more experience than 15 years ago…

The Skapa project is not complete however and I’ll probably keep working on it every now and then. I’m happy with the UI and the rendering, but there are a couple of things I still want to improve.

For instance, I’d love to have more models, either for other IKEA products or potentially beyond IKEA. I have a couple of parts in mind but I need to figure out what the app will then look like: Will it be a single page still, with a dropdown to select the part? Or multi page app where each part gets its own page? Should it still be IKEA-themed if it’s not just for IKEA?

My Skadis collection on Printables

Some models on Printables I’d like to port to Skapa

I’d also like to add more controls to tweak the generated model’s bottom and wall thickness; all of this is already implemented in the code and previous versions of Skapa did allow to tweak those settings, but I haven’t found a nice way to present them.

Finally, there are some 3D printing-specific things that could be improved in the current model. One of them is that the generated model requires supports under the clips (most 3D printers, if not all, can’t print in thin air and need something to print on, and 3D printing experts on the internet will reach for the pitchforks when “supports” are mentioned). I have some ideas on how to solve this but changes take more time to implement in code than e.g. in WYSIWYG CAD software.

Update: The parts can now be printed without supports!

Again, code on GitHub, and printing instructions on Printables.

Let me know or join the discussion if you enjoyed this article! You can also subscribe to receive updates.


Here's more on the topic of JavaScript and WebGL:

联系我们 contact @ memedata.com