Paint a low-poly model by hand — with a brush, directly on its surface.
Open a model, spin it around, and paint. No UV wrangling, no texture editor, no round trip to another program: you click on the roof, and the roof gets painted. When you are done, the tool hands you a PNG you can drop straight back onto the model.
It runs in a browser, and ships as a desktop app built from the same code.
Low-poly assets are usually painted in flat colors — a roof, a wall, a door, each one solid. Doing that in a full texture painter is heavy: you fight the UV layout, you fight the brush size changing across the model, you fight the seam that cuts your stroke in half.
This tool is built for exactly that narrow job, and it makes three promises:
- The brush is the same size everywhere on the model — because it is a sphere in model space, not a circle on the texture.
- Seams do not exist for you. A stroke that crosses a UV seam simply continues; the tool paints both sides at once.
- A fill stops where the shape stops. "Fill the roof" does not leak onto the wall — it crosses an edge only when the bend is gentle enough.
Ready builds for macOS (Apple Silicon) and Windows are attached to each release. Neither is signed, so the system asks for confirmation on first launch — see Installing the desktop build.
To build from source instead, read on.
You need Node.js 18 or newer.
git clone https://github.com/unibronga/3Dpainter.git
cd 3Dpainter
npm install
npm run devOpen http://localhost:5273. A start screen offers two ways in: open your
own model, or begin with the demo cabin and paint right away. Below that are
the ten models you opened last: a row gets selected, its preview shows on the
right, and the Open button sits under it. You can also drop a file straight
onto the viewport.
| Reading | Writing |
|---|---|
| glTF / GLB, OBJ, FBX, Collada (DAE), 3MF, VRML | GLB, glTF, OBJ + MTL, PNG maps |
| STL, PLY, AMF — geometry only, see below |
Painting needs UVs, and they get built when a file has none. glTF, OBJ, FBX, DAE and 3MF carry UVs, but not equally: models downloaded from the web routinely dump a flat projection of the geometry into the UV channel, which is useless for painting. The program judges the UVs on open and, if there are none, if they fall outside the 0…1 square, or if they cover only a handful of texels, it builds its own — islands split on creases, packed into the atlas at one texel density. The status line says so. UVs that are fine are left alone.
A model can be opened together with the files next to it. The dialog takes
several files, and a folder can simply be dropped into the window; the desktop
app picks up the .mtl an .obj names (and its textures) by itself. For an
.obj this brings in the neighbouring .mtl — read in linear colour when it
comes from Blender, as its GLB would be: material colours are baked into
the first layer, so the model opens the way its author made it and is ready to
edit with the brush.
Saving offers the choice that matters: the maps alone, or the model
together with the paint — a file that opens in another editor already
painted. GLB packs everything into one file; OBJ comes with its .mtl and
the color map beside it.
Other commands:
| Command | What it does |
|---|---|
npm run dev |
Development server on port 5273 |
npm run desktop |
Build and launch as a desktop window |
npm run dist |
Build an installer into release/ |
npm run icon |
Redraw the app icon (it is generated by code) |
| Action | How |
|---|---|
| Paint | Left mouse button |
| Orbit | Right mouse button |
| Pan | Space + left button, or middle button |
| Zoom | Wheel |
| Brush size | [ and ] |
| Tools | V select object · H pan · O orbit · Z zoom · B brush · E eraser · I eyedropper · F fill faces · G fill UV island · L lasso (again for polygonal) |
| Views | 1–7 axis views · 0 three-quarter · 5 perspective ↔ orthographic |
| Orbit pivot | three icons in the column under the view cube: world, object, or whatever is in the centre of the frame |
| Display | the bar above the model: colors, clay, wireframe, normals; facets; frame |
| Light | icon in the column under the view cube: strength, rotation, spin the model, unlit |
| Fit to frame | Home |
| UV editor | U |
| Hide all panels | Tab |
| Undo / redo | Cmd+Z / Cmd+Shift+Z |
| Save project / save as | Cmd+S / Cmd+Shift+S |
| Select all / deselect / invert | Cmd+A / Cmd+D / Cmd+Shift+I |
Tools. Brush with 16 presets (grain, spacing, scatter), eraser, eyedropper, three kinds of fill, lasso, rectangle, ellipse and text. Shapes and text are printed by screen projection, so a rectangle stays a rectangle in frame no matter how the surface curves under it. Text comes with a choice of eight system fonts. The options for whichever tool is selected appear in a strip above the model, on the left. The orbit tool, for instance, has a rotation step there — tick it, set 30°, and the view turns in exact 30° steps — plus a button to reset the view.
Lasso selection, freehand and polygonal, as in Photoshop. While a selection exists, every tool paints only inside it — brush, eraser, fills, shapes — so a stroke can run right over the edge. Shift adds, Alt subtracts, Shift+Alt intersects. On the model the outline is projected from the screen, across all objects at once; in the UV editor it is drawn straight onto the texture. The edge shows as marching ants.
Material, not just color. A material here is color + pattern + roughness
- metalness + transparency, and all of it is painted per texel. "Paint this with iron" makes the painted area metallic — not the whole model. Ten procedural patterns are included, or you can load your own image.
Layers and history. Visibility, opacity, blend modes. The history panel lets you click any step and land in it, not just step back one at a time.
A UV editor on half the screen, where the whole toolset works directly on the unwrap — brush, fills, eyedropper, shapes, text and lasso. Handy when a face is too small or hidden on the model itself.
Output. A PNG of the color map, plus a second <name>_material.png when
you actually painted with surface properties (roughness in green, metalness
in blue — the packing three.js reads directly): the small PNG button under
the UV list saves the map that is open, File ▸ Save PNG saves them all. Or
the whole model with the paint baked in, via File ▸ Save as…. And
View to PNG renders the model exactly as framed in the viewport, on a
transparent background, at the size and edge smoothing you pick.
Effects. The Effects menu changes how the model is shown, not the paint itself. Pixel art draws the model in large square pixels, with an optional reduced palette and a dark outline around the silhouette. Anime is cel shading in the spirit of Genshin Impact: stepped light with a crisp, cool-tinted shadow, ink lines along the silhouette and creases, and a bright rim along the edges. The brush ring and floor grid stay sharp, so you can keep painting with the effect on, and View to PNG captures the image with the effect.
Project files. File ▸ Save project (Cmd+S) writes the whole session
to a native .3dpaint file — like a PSD: every layer with its colour,
roughness, metalness and transparency maps, layer settings, model orientation,
camera, material and brush. It is a plain zip inside. Undo history is not
stored.
AI painting (MCP). In the desktop app you can let Claude or any other MCP
client paint the open model from a prompt ("red tiled roof, beige walls, blue
door"). Turn it on in Settings, run the one-line claude mcp add … command
it shows, and the AI can describe the model's parts, fill them with colours and
surfaces on its own layer — by whole geometry piece (a boot, a wristband), by part, height, box or by pointing at pixels of
a rendered view — find small leftover patches and see them numbered on a wireframe render
or on the whole UV layout, undo its own steps, and render views to check its
work. The server
listens on 127.0.0.1 only, requires a key and is off by default; every AI
fill is an ordinary undo step.
Round trip. A model saved with its paint opens with it again — GLB, or
OBJ together with its .mtl and .png — and you keep painting where you
left off. Multi-file saves go into one folder, chosen once.
Model orientation. On opening, a model always stands on the floor at the world origin. Where its front is, no file says — so you look at the model's face and click This is the front (or This is the top for models lying on their side). The orientation is remembered per model; exported files keep the original coordinates.
Settings (File ▸ Settings…) hold the interface language, the interface scale (80–200%: text, icons, panels and dialogs grow together — for large screens), the default texture size and whether the start screen appears on launch.
Long operations — opening a model, saving, rebuilding textures — run under a busy indicator, so it is clear the program is working.
Languages. The whole interface speaks English, Russian, German, Spanish,
French, Dutch and Ukrainian, and switches
live — your paint is kept. Dictionaries are one file per language in
src/lang/, flat 'section.name' keys; adding a language means adding a
file and one line in LANGS, with no other code touched. Code comments
stay Russian.
Three decisions carry the whole thing:
- The brush is a sphere in model space, not a circle on the texture. For every triangle caught inside the sphere, the tool walks its texels and reconstructs each texel's point on the surface. That is why stroke size stays constant across the model and seams take care of themselves.
- Gaps between mouse events are filled by stepping across the screen, casting a fresh ray at each step. Interpolating through 3D space instead would push the brush through the model's interior and smear paint on the far walls.
- Accumulated paint is pushed to the texture once per frame. A single swipe lays down dozens of stamps, and each compositing pass costs a full texture upload to the GPU.
The full technical write-up is in docs/АРХИТЕКТУРА.md (Russian).
If two distant faces share the same texels, per-pixel painting is impossible —
a stroke on one wall bleeds through on another, and the brush leaves clipped
fragments instead of a round mark. Primitives are the usual culprit
(BoxGeometry packs all six faces into one square), as are mirrored unwraps.
The tool measures the overlap when a model loads and warns in red in the status bar and in the UV list. The Rebuild UVs button gives such objects a fresh overlap-free unwrap and carries everything already painted over to it. The tool never replaces a file's UVs on its own, since they may carry the author's painting. If you need the author's own unwrap, use Smart UV Project or a hand unwrap in Blender.
Working and usable, version 0.7.0. New in 0.7.0: AI painting grew from a first version into a full toolkit — the AI sees the model as separate geometry pieces, paints them whole or points at pixels of a render, previews a fill before painting, looks at close-ups and at parts hidden behind others, finds leftover patches, undoes its own steps and works from an inventory of the reference. Dense models open with a 2048 texture, thin slivers no longer keep their old colour, clothing is drawn double-sided, and the app has a new icon. Not yet done:
-
AI painting: brush strokes along surface points, brush and material presets.
-
Occlusion for shapes, text and the lasso: back-facing polygons are skipped, but a chimney does not cast a "shadow" onto the roof behind it.
-
Layer reordering and duplication; symmetry, straight-line strokes, stroke stabilization.
-
Island selection and seam highlighting in the UV editor.
-
The macOS build is not signed — see below.
npm run dist produces a .dmg in release/. The app is not signed with an
Apple developer certificate, so on first launch macOS will say it cannot
verify the developer. Either right-click the icon → Open → Open, or
clear the quarantine flag once:
xattr -dr com.apple.quarantine /Applications/3DPainter.appWindows and Linux targets are described in electron-builder.yml. A Windows
installer cannot be built on macOS — NSIS needs Wine there — so each system
builds itself: .github/workflows/release.yml runs the macOS and Windows
builds on their own machines and attaches the results to the release.
On Windows the first launch shows a SmartScreen warning: More info → Run anyway.
Issues and pull requests are welcome. A few things worth knowing:
- The codebase and its comments are in Russian. Contributions in either language are fine — please keep comments in the language of the file you are editing.
- The tool is deliberately narrow: it paints existing models. Modeling, rigging and animation are out of scope.
- This is a graphics tool, so "it should work" means little. Please check your change on screen, and read the numbers the status bar gives you (overlap ratio, frame composite time, brush size in centimeters).
MIT © 2026 Kostiantyn
