Repository navigation
Paint by face concept - #21378
Paint by face concept#21378
Conversation
Introduce a FACE brush shape and implement painting/preview logic that targets whole visual faces instead of individual triangles. Adds Shape.FACE enum and two helpers: _getCoplanarConnectedFaces (flood-fill neighbor faces by normal similarity, default angle_threshold 0.99) and _getUvAreasForFace (collects UV polygons for all connected coplanar triangles, scales by texture dimensions). Integrates face-mode in event handling so the cursor and painting operations use the full face UV areas; falls back to existing stroke-based behavior for other brush shapes and handles missing UVs or mesh data gracefully.
Introduce a new BrushShapeButton for the Face brush (Cura.PaintToolBrush.FACE) with icon and localized label. Disable the "Brush Size" label and slider when the selected BrushShape is FACE, since size is not applicable for this brush.
Test Results24 955 tests 24 953 ✅ 53s ⏱️ Results for commit 7cd0e8d. ♻️ This comment has been updated with latest results. |
Replace list-based flood-fill with collections.deque for O(1) pops, using popleft() to traverse connected faces. Add a brush-size -> angle_threshold mapping and pass it into _getCoplanarConnectedFaces so brush size controls coplanarity. Optimize neighbor enqueueing with deque.extend and tighten docstrings. Simplify UV polygon collection by skipping None coords, building/scaling UV arrays, and collecting Polygons. In QML, remove the disabled condition on the shape size slider so it is always enabled. These changes improve traversal performance and make face selection responsive to brush size.
Introduces a dedicated `FaceAngle` setting in `PaintTool` with getter/setter and property exposure for UI binding. Face selection coplanarity now uses this explicit angle (converted from degrees to radians) instead of deriving the threshold from brush size, making face-mode behavior clearer and independently tunable.
Updates the PaintTool UI to switch the size control behavior when Face brush mode is active. The label now changes to “Angle Threshold,” a contextual tooltip explains the setting, and the slider uses degree semantics (0–90, step 1, indicator/unit enabled). The slider now reads/writes `FaceAngle` in face mode and continues using `BrushSize` for other brush shapes.
This will be especially useful if we add more brush types
| self._view.getUvTexDimensions()[0], | ||
| self._view.getUvTexDimensions()[1], |
There was a problem hiding this comment.
This is such a small thing, but, you could also get this before the call like:
tex_w, tex_h = self._view.getUvTexDimentions()
(I know it's the same as in .project and it won't make much difference, but still...)
| text: catalog.i18nc("@action:button", "Face") | ||
| toolItem: UM.ColorImage | ||
| { | ||
| source: UM.Theme.getIcon("LayFlatOnFace") |
| text: catalog.i18nc("@label", "Brush Size") | ||
| width: childrenRect.width | ||
| height: childrenRect.height | ||
| text: shapeSizeSlider.isFaceAngle ? catalog.i18nc("@tooltip", "Select neighboring faces whose angle differs by no more than this value.") : "" |
There was a problem hiding this comment.
Will this cause a small tooltip with no text to show up when isFaceAngle is off? If not then that's fine, but if so, we can also just write down what the 'Brush Size' does -- in fact, maybe we should in any case, because if a popup doesn't show up for that (and ppl try it), why would they (then also) try to hover over for the angle-version?
There was a problem hiding this comment.
Added the tooltip for brush size for consistency in 7cd0e8d
Store `getUvTexDimensions()` once per operation and pass `tex_w`/`tex_h` into `uvula.project` and `uvula.getConnectedFaces` instead of indexing repeated getter calls. This improves readability and avoids duplicate method invocations while preserving behavior.
Update the PaintTool tooltip fallback text so the slider shows a meaningful brush-size hint when face-angle mode is not active, instead of an empty tooltip.


Proof of concept for paint by face
Requires Ultimaker/libUvula#9 & Ultimaker/Uranium#1051