Skip to content
unibrongaPublic

About

Paint low-poly models by hand — with a brush, directly on the surface. Browser-based, three.js, desktop build included.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

3DPainter

Paint a low-poly model by hand — with a brush, directly on its surface.

Русская версия · MIT License

The tool with a painted demo cabin

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.

Why it exists

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.

Download

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.

Quick start

You need Node.js 18 or newer.

git clone https://github.com/unibronga/3Dpainter.git
cd 3Dpainter
npm install
npm run dev

Open 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.

Supported formats

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)

Controls

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

What it can do

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.

How it works

Three decisions carry the whole thing:

  1. 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.
  2. 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.
  3. 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).

Known limitation: overlapping UVs

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.

Status and roadmap

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.

Installing the desktop build

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.app

Windows 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.

Contributing

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).

License

MIT © 2026 Kostiantyn

About

Paint low-poly models by hand — with a brush, directly on the surface. Browser-based, three.js, desktop build included.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages