diff --git a/TEXTURE_DISPLACEMENT.md b/TEXTURE_DISPLACEMENT.md index 6bbd5f085b..d1109644e2 100644 --- a/TEXTURE_DISPLACEMENT.md +++ b/TEXTURE_DISPLACEMENT.md @@ -1,4 +1,4 @@ -# Texture Displacement — Technical Notes +# Texture Displacement - Technical Notes Branch: `feature/texture_displacement`. This document is a knowledge dump of the whole feature as it stands: architecture, file map, algorithms, known bugs found and fixed (with root causes worth @@ -26,39 +26,18 @@ A paint-style gizmo (`GLGizmoTextureDisplacement`) that lets you: ### Data model (per `ModelVolume`) Each of up to `TEXTURE_DISPLACEMENT_MAX_LAYERS` (8) layers gets its **own independent -`FacetsAnnotation`** paint mask — the exact same `TriangleSelector`/`FacetsAnnotation` machinery +`FacetsAnnotation`** paint mask - the exact same `TriangleSelector`/`FacetsAnnotation` machinery every other paint gizmo (FdmSupports, Seam, MMU, FuzzySkin) already uses, just one full instance per layer slot instead of one per volume. This is what makes "layered/blended" painting work for free: the same triangle can be `ENFORCER` in layer 2's mask and layer 5's mask simultaneously, and at bake/preview time each layer displaces the surface left by the previous one (image-editor-layer semantics). -**Important gotcha**: `ModelVolume` does **not** hold `std::array`. It holds -8 individually-named fields (`texture_displacement_facets_0` .. `_7`) plus a -`texture_displacement_facet(int slot)` accessor. Reason: `FacetsAnnotation`'s ctor is private, -friended only to `ModelVolume`; `std::array`'s own implicitly-generated special member functions -are generated with **`std::array`'s** access rights, not the enclosing class's, so friendship does -not propagate through the array wrapper. This is a real MSVC C2280 if you try it — confirmed by -attempting it. `TextureDisplacementFacetsData` (a `std::array`, -used to carry paint-mask *data* around, e.g. into the bake job) is fine as a real `std::array` -since `TriangleSplittingData` has an ordinary public ctor — only the `FacetsAnnotation` object -itself has the friend-ctor problem. - -Plus `std::vector texture_displacement_layers;` — the plain-data layer -definitions (texture bytes + params), ordinary public ctor, safe in a vector. - -Touch points that had to mirror the existing `FacetsAnnotation` pattern (see `supported_facets` for -the template): all constructors' asserts, copy ctors' init lists, the `-1`-id deserialization ctor, -`set_new_unique_id()`, cereal `save`/`load`, `is_texture_displacement_painted()`, and -`reset_extra_facets()` (called whenever a topology-changing op like Simplify or subdivision -replaces the mesh — this is what drops any unbaked texture-displacement paint, since there's no -remap-across-topology-change support for it yet, see Limitations). - ### Bake algorithm (`libslic3r/TextureDisplacement.cpp`) `build_texture_displacement(base_mesh, layers, facets_data)` is **accumulate-then-displace, and topology-preserving**: the returned mesh has exactly the input's vertices and triangles, in the same -order — only the positions of displaced vertices differ. +order - only the positions of displaced vertices differ. 1. `its_compactify_vertices()` on a copy of the input. In practice a no-op (it only drops *unreferenced* vertices, and preserves the order and indices of the rest). It is there to @@ -70,36 +49,25 @@ order — only the positions of displaced vertices differ. the **base mesh** (never against a previous layer's output), then `selector.get_facets_strict(ENFORCER)` → the painted patch. Two facts are exploited: - `get_facets_strict()` returns the mesh's **entire** referenced vertex array regardless of which - state was asked for — only `.indices` is filtered by state. So `get_facets_strict(ENFORCER)` + state was asked for - only `.indices` is filtered by state. So `get_facets_strict(ENFORCER)` and `get_facets_strict(NONE)` share identical vertex indexing, which is what lets boundary detection be a plain index check instead of a position-hash lookup. - The selector's vertex array *starts with* the mesh's own vertices (extra ones created where a brush stroke split a triangle are appended after them), and `get_facets_strict()` emits the referenced ones in order. Combined with step 1, **selector vertex index `i` is our vertex `i`**. - Split vertices live past the end of our array and are simply skipped — they sit on the paint + Split vertices live past the end of our array and are simply skipped - they sit on the paint boundary anyway (splitting only happens at partial coverage), so they would be pinned regardless. -4. A vertex used by at least one **unpainted** triangle is a boundary vertex — pinned, never +4. A vertex used by at least one **unpainted** triangle is a boundary vertex - pinned, never displaced (its final position is ambiguous, it belongs to both regions). Only vertices used exclusively by painted triangles get displaced. This is what keeps bakes seamless with zero remeshing/hole-filling at the seam. 5. Per interior vertex: sample the height texture (`sample_layer_height()`, see Projection methods) and fold `height * depth_mm * (invert ? -1 : 1)` into that vertex's running total via the layer's `TextureBlendMode` (see Blend modes). A `visited` set makes each layer fold in exactly **once** - per vertex, no matter how many of the patch's triangles share it — otherwise a Multiply/Subtract + per vertex, no matter how many of the patch's triangles share it - otherwise a Multiply/Subtract layer would apply two or three times over depending on local triangle fan-out. 6. Finally, move each touched vertex along its (step 2) normal by its accumulated total. -**This replaced a sequential design** that re-meshed after each layer and carried the next layer's -paint mask onto the result with `TriangleSelector::remap_painting()`. That was the root cause of the -reported "the second texture is never applied" bug: remapping a mask onto a mesh whose vertices had -just been displaced out from under it routinely produced an empty bitstream, and the layer was then -silently `continue`d past. It was also what forced the per-layer vertex duplication and the final -`its_compactify_vertices()` weld. The current formulation has neither problem, is substantially -faster (no remap, no welding, one pass), makes blend modes possible at all (they need a shared -per-vertex accumulator, which sequential re-meshing cannot provide), and — because the output keeps -the input's exact vertex indexing — lets GUI code overlay a preview on the base mesh with no index -translation. - ### Blend modes `TextureBlendMode` {Add, Subtract, Multiply, Divide}, per layer, applied per vertex against the @@ -108,9 +76,9 @@ displacement in **mm**, not a pixel value. Add/Subtract are self-explanatory. Multiply/Divide are *scaling* operations and so need a unit convention: they treat the layer's own value as a **factor relative to 1 mm**. That makes `depth_mm` -a gain, and — the property that makes a Multiply layer usable as a mask — a layer with depth 1 mm +a gain, and - the property that makes a Multiply layer usable as a mask - a layer with depth 1 mm sampling a white (1.0) texel multiplies by exactly 1, i.e. leaves the layers below unchanged. -Divide floors its divisor's magnitude at 0.05 — a black texel samples to *exactly* zero, so the +Divide floors its divisor's magnitude at 0.05 - a black texel samples to *exactly* zero, so the divisor really does hit zero in ordinary use, and an unbounded `1/0` would fling vertices thousands of mm away and poison the mesh's bounding box (and every plate/print-volume check downstream). The floor doubles as a cap on how far Divide can amplify the relief beneath it: at most 20×. @@ -124,64 +92,64 @@ the UI, which labels that layer "Base layer" instead of offering a control that Four choices per layer (`TextureProjectionMethod`), all funneling through `apply_uv_transform()` (scale by `1/tiling_scale`, rotate by `rotation_deg`, add `offset`). They are dispatched by -`sample_layer_height()`, which returns a **height**, not a UV — because Triplanar takes three +`sample_layer_height()`, which returns a **height**, not a UV - because Triplanar takes three texture samples per vertex and so has no single UV that represents it. -- **Triplanar** (default) — samples the texture on all three world planes (`(y,z)`, `(x,z)`, `(x,y)`) +- **Triplanar** (default) - samples the texture on all three world planes (`(y,z)`, `(x,z)`, `(x,y)`) and blends the three by the vertex's own normal raised to `TRIPLANAR_BLEND_SHARPNESS` (4). This is the fix for a real, user-reported bug. The previous version *hard-picked* the single axis most aligned with the normal, which is discontinuous wherever that dominant axis flips: on a +X face the planar coordinate is `(y, z)`, on a −Y face it is `(x, z)`, so at the shared edge `u` jumps from `y_edge` to `x_edge`. On a box centred near the origin those two happen to **agree** at the (+,+) and (−,−) corners and **differ by the full corner width** at the (+,−) and (−,+) corners - — which is exactly the "two bad corners, two good ones" symmetry that was observed. A weighted + - which is exactly the "two bad corners, two good ones" symmetry that was observed. A weighted blend is continuous across the transition by construction, since the weight of the axis being left behind falls smoothly to zero. (Note this removes the hard *seam*; some cross-fade blurring in the band right at a 90° edge is inherent to triplanar mapping. A genuinely seam-free wrap around a box - needs a real unwrap — that is what the LSCM mode is for.) -- **Cylindrical** — wraps around an axis through the patch centroid, axis auto-picked as the world + needs a real unwrap - that is what the LSCM mode is for.) +- **Cylindrical** - wraps around an axis through the patch centroid, axis auto-picked as the world axis *least* aligned with the average normal (perpendicular to the outward radial normal, as a cylinder's own axis would be). `u = angle * local_radius` (arc length in mm), `v = distance along axis`. Approximation, not an exact fit for arbitrary geometry. -- **Spherical** — longitude/latitude around the centroid, scaled by local radius. Same caveat. -- **LSCM** — real UV unwrap via `MeshBoolean::cgal::parameterize_lscm()` (CGAL's +- **Spherical** - longitude/latitude around the centroid, scaled by local radius. Same caveat. +- **LSCM** - real UV unwrap via `MeshBoolean::cgal::parameterize_lscm()` (CGAL's `Surface_mesh_parameterization` package, LSCM algorithm). Computed **once per patch** (not - per-vertex like the others — it's a single global least-squares solve), then each vertex looks up + per-vertex like the others - it's a single global least-squares solve), then each vertex looks up its precomputed UV. Requires the patch to be a single topological disk (one connected component, - one boundary loop) — `compute_lscm_uvs()` returns empty and the layer silently falls back to + one boundary loop) - `compute_lscm_uvs()` returns empty and the layer silently falls back to Triplanar if not (e.g. multiple disconnected painted islands, or a fully closed patch). CGAL's parameterizer needs a mesh with no isolated/unreferenced vertices, but `get_facets_strict()` - returns the *whole* mesh's vertex array — so there's a compaction step + returns the *whole* mesh's vertex array - so there's a compaction step (`compact_patch_with_map()`) that builds a clean sub-mesh + an index map back to the original (uncompacted) vertex numbering, purely local to this file. -- **ViewProjected** ("From view") — a flat projection along a fixed direction captured from the 3D +- **ViewProjected** ("From view") - a flat projection along a fixed direction captured from the 3D camera, like a slide projector. `capture_view_projection()` takes the camera's right/up axes, transforms them into the volume's *local* frame (so the projection rides along if the part is later moved), and stores them as `TextureDisplacementLayer::view_project_right/up` (unit vectors, so the projected coordinate stays in mm and `tiling_scale` keeps meaning mm). `sample_layer_height()` - projects `Vec2f(dot(pos, right), dot(pos, up))`. Single-valued per point, so — like LSCM but unlike - blended Triplanar — the fast preview and UV-check overlay precompute it per vertex + projects `Vec2f(dot(pos, right), dot(pos, up))`. Single-valued per point, so - like LSCM but unlike + blended Triplanar - the fast preview and UV-check overlay precompute it per vertex (`compute_layer_vertex_uvs()`) and drive the shader's `use_vertex_uv` path. Faces angled away from the projector smear; that is inherent to view projection, not a bug. Two companions to this mode: - - **Projection frame overlay** (`TextureProjectorFrame`, see below) — a semi-transparent window + - **Projection frame overlay** (`TextureProjectorFrame`, see below) - a semi-transparent window dragged over the 3D view whose border becomes the projection's edge. Applying it stores an exact **projective** map in `view_project_matrix`, which supersedes the affine `right`/`up` axes above for that layer (`view_project_projective`). - - **"Project only on visible"** (`select_visible_faces()`) — repaints the layer with exactly the + - **"Project only on visible"** (`select_visible_faces()`) - repaints the layer with exactly the facets the camera can see, so the projected area matches the viewpoint the projector was captured - from. Two tests: a facing test (normal vs. view direction, per triangle — under perspective the + from. Two tests: a facing test (normal vs. view direction, per triangle - under perspective the view direction varies across the model, so it is taken from the eye to each centroid), then `MeshRaycaster::get_unobscured_idxs()` on the survivors to drop facets hidden behind other geometry, so a concave part's far inner wall is correctly excluded. One ray query per front-facing facet, hence click-driven (on the checkbox and on each "Capture current view"), never per frame. - It **replaces** the layer's paint rather than adding to it — "project onto what I can see" would + It **replaces** the layer's paint rather than adding to it - "project onto what I can see" would otherwise accumulate every angle the user had ever looked from. ### Manual seams and island cutting -`TextureDisplacementLayer::lscm_seam_edges` — undirected mesh-vertex-index edge pairs the unwrap is +`TextureDisplacementLayer::lscm_seam_edges` - undirected mesh-vertex-index edge pairs the unwrap is forced to cut along, on top of the dihedral-angle seams. `segment_into_charts()` takes a set of these (translated from mesh → compacted-patch numbering inside `compute_patch_unwrap()`) and refuses to union two triangles across a marked edge whatever their angle. Both the unwrap cache key and the @@ -190,15 +158,15 @@ untouched) still forces a re-solve. Like the paint masks, seams are mesh-index-s any topology change. Two ways to write to it: -- **Mark seam (manual, #9)** — a "Mark seams" click mode (`m_seam_edit_mode`) that suppresses +- **Mark seam (manual, #9)** - a "Mark seams" click mode (`m_seam_edit_mode`) that suppresses painting. A click raycasts the volume (`m_c->raycaster()->raycasters()[idx]->unproject_on_mesh()`, `idx` = the volume's slot among model-part volumes), finds the facet's edge nearest the hit point, and toggles it. Marked edges render as a red overlay (`render_seam_overlay()`), pulled toward the camera so they read on top. This is the Blender mark-seam workflow. -- **Cut island (auto, #17)** — `cut_island()` takes the selected chart's triangles (back-mapped from +- **Cut island (auto, #17)** - `cut_island()` takes the selected chart's triangles (back-mapped from the unwrap via `source_vertex`), finds their 3D bounding box, and marks every edge that straddles the mid-plane perpendicular to the longest axis. The re-unwrap then splits the chart across its - narrow waist — the "islands might be very long" case. Exposed as the UV pane's **Cut** button. + narrow waist - the "islands might be very long" case. Exposed as the UV pane's **Cut** button. ### UV-check overlays (checker / distortion) @@ -206,7 +174,7 @@ Two ways to write to it: drawn over the painted patch (`rebuild_uvcheck_mesh()`/`render_uvcheck_mesh()`, P3N3T2: `normal.x` = distortion, `tex_coord` = uv), pulled forward with a polygon offset. **Checker** (#13) samples a procedural checkerboard at the layer's uv (per-vertex for LSCM/ViewProjected, in-shader triplanar -otherwise) — squares that stay square mean low distortion. **Distortion** (#14) colours each triangle +otherwise) - squares that stay square mean low distortion. **Distortion** (#14) colours each triangle blue→green→red by `log2(uv_area / surface_area)` centred on the patch's *median* stretch (so a globally-scaled unwrap reads as uniformly ideal and only relative stretch shows), averaged to vertices. A separate **Show mesh wireframe** toggle (#8) draws the whole volume's triangle edges, @@ -216,7 +184,7 @@ rebuilt only when the vertex count changes (not per stroke). `DecodedHeightTexture::sample(uv, tile_enabled, tile_method)`. Two tile methods when enabled (Repeat, MirroredRepeat). **When `tile_enabled` is false, sampling outside `[0,1)` returns `0` -directly** — clamping the *coordinate* into range (what an earlier version did) instead smears the +directly** - clamping the *coordinate* into range (what an earlier version did) instead smears the border row/column of pixels outward to infinity in every direction, which is a real bug that was reported and fixed (visually: streaky lines radiating out from the painted patch). @@ -224,48 +192,26 @@ reported and fixed (visually: streaky lines radiating out from the painted patch Deliberately **whole-mesh and uniform**, not limited to the painted patch. A patch-only / adaptive subdivision would create a classic T-junction/cracking problem where the denser -(subdivided) and sparser (untouched) regions meet — the fine side has edge midpoints the coarse +(subdivided) and sparser (untouched) regions meet - the fine side has edge midpoints the coarse side doesn't know about, producing a real (non-manifold-looking) crack in the baked geometry. This was consciously scoped down from the original plan's "adaptive per-patch subdivider" idea to avoid that correctness risk (a subtly-cracked mesh is a much worse outcome than "not implemented yet"). Algorithm: recursive 1-to-4 triangle split via edge midpoints, with a shared per-pass midpoint cache -(keyed by sorted vertex-index pair) so triangles sharing an edge get the *same* new vertex — capped +(keyed by sorted vertex-index pair) so triangles sharing an edge get the *same* new vertex - capped at `max_iterations` (default 6) passes to bound worst-case triangle-count explosion. Wired as a "Subdivide steps" slider (**0–5**, where 0 means no subdivision and previews nothing) plus -Preview/Apply/Done in the gizmo panel. **Apply snaps the slider back to 0** — see bug #22: leaving it -at the count just committed made the panel immediately re-preview N more passes on top of a mesh 4^N -times denser, which is what the "subdivide lags" report was. A real, committed geometry change (like +Preview/Apply/Done in the gizmo panel. **Apply snaps the slider back to 0**. A real, committed geometry change (like Bake), using the same `save_painting()`/`set_mesh()`/`restore_painting()` dance `GLGizmoSimplify` uses: supported/seam/mmu/fuzzy-skin masks get remapped onto the new triangles, texture-displacement paint does not (no remap support yet) and is dropped rather than left pointing at now-meaningless triangle indices. -### Preview pipeline (perf) - -`rebuild_preview()` used to call `build_texture_displacement()` **synchronously on the UI thread** -on every stroke-end and every slider release. With multiple painted layers this got slow (each -layer's PNG sampling + vertex welding + `remap_painting()` stacks up). Fixed by moving the actual -computation into `TextureDisplacementPreviewJob` (mirrors `TextureDisplacementBakeJob`'s -process()/finalize() split), queued on the app's shared UI job worker via plain `queue_job()` (not -`replace_job()` — that worker is shared app-wide, including with Bake; `replace_job()` would cancel -an in-flight bake if one happened to be running). A `m_preview_generation` counter discards stale -results if a burst of edits queues several jobs in a row and an older one finishes after a newer one. - -Two other real perf fixes worth remembering: -- Sliders in ImGui report "changed" continuously on every drag frame, not just once on release — - gating the (then-synchronous) rebuild behind "mouse button not currently down" was necessary to - stop dozens of rebuilds per drag. -- `decode_height_texture()` used to re-decode the same PNG from scratch on every call. Now cached - in `TextureDisplacement.cpp`, keyed by a `weak_ptr` to the layer's `image_data` (not just the raw - pointer — a `weak_ptr` correctly detects a freed-then-reused address, where a raw-pointer key - would alias a stale cache entry onto an unrelated later texture). - ### Fast bump preview (GPU-only, no CPU meshing) `resources/shaders/{110,140}/texture_displacement_bump.{vs,fs}`, registered as `"texture_displacement_bump"`. Perturbs the *shading* normal from the height texture's local -gradient instead of moving geometry — active-layer-only, toggled via a "Fast preview (normal map)" +gradient instead of moving geometry - active-layer-only, toggled via a "Fast preview (normal map)" checkbox. Vertex format is `GLModel::Geometry::EVertexLayout::P3N3T2`: `normal.x` carries the per-vertex paint weight (0/1) and `tex_coord` carries a precomputed texture UV, so it can use `GLModel` normally instead of needing a hand-rolled VBO/VAO manager. Weight buffer is @@ -279,24 +225,24 @@ The perturbed normal is the analytic one for a height field `H = ±depth_mm · h N' = normalize(N − (dH/da)·T − (dH/db)·B), a = dot(p,T), b = dot(p,B) The two slopes have to be genuine **mm-per-mm** derivatives for the preview's apparent depth to -match the bake's — see bug #13. +match the bake's - see bug #13. **Two projection paths (`use_vertex_uv` uniform):** -- **Triplanar (`use_vertex_uv = 0`)** — `uv` and the `T`/`B` axes are both derived in-shader from +- **Triplanar (`use_vertex_uv = 0`)** - `uv` and the `T`/`B` axes are both derived in-shader from the dominant normal component, mirroring `project_planar()`/`apply_uv_transform()`, and the slope is formed analytically. `T`/`B` are the projection's axis-aligned pair, exact only when the face is axis-aligned; the shader drops the along-normal component to keep the gradient in the surface. Here one `uv` unit is exactly `tiling_scale` mm, so the `1/tiling_scale` gradient factor is right. -- **Precomputed UV (`use_vertex_uv = 1`, used for LSCM)** — `uv` comes per-vertex from the CPU +- **Precomputed UV (`use_vertex_uv = 1`, used for LSCM)** - `uv` comes per-vertex from the CPU (`compute_lscm_uvs(patch, layer)`, so island placement + tiling/rotation/offset are already folded in), and the perturbed normal is built with **Mikkelsen's method** ("Bump Mapping Unparametrized Surfaces on the GPU"): the surface gradient taken directly from the screen-space derivatives of the - *sampled height* and position. **This makes no uv→mm scale assumption**, which is essential — + *sampled height* and position. **This makes no uv→mm scale assumption**, which is essential - the first cut used the same global `1/tiling_scale` factor as triplanar and the depth came out visibly wrong, because an LSCM map is **conformal, not isometric**: it is globally area-scaled but the *local* mm-per-uv varies across the chart. `dFdx(h)` captures the true on-screen rate of change however the chart is stretched. **This path is also what makes the fast preview follow the UV - editor: move an island and its uv — hence its bump — moves with it** (the bump mesh rebuilds on + editor: move an island and its uv - hence its bump - moves with it** (the bump mesh rebuilds on drag-end, since `on_island_edited(finished)` → `rebuild_preview()` → `rebuild_bump_preview_mesh()`). The branch is uniform (`use_vertex_uv` is a uniform) and the paint weight gates by multiply, so the texture derivatives stay well defined. A triangle straddling a seam has a discontinuous uv → the @@ -306,49 +252,31 @@ Remaining deliberate approximation: the GPU sampler's wrap mode stands in for `tile_enabled`/`tile_method`, so with tiling *off* the GPU repeats where the CPU returns 0 outside `[0,1)`. -> **Known divergence from the CPU path (not yet reconciled).** The shader's `project_uv()` mirrors -> the *hard-axis* `project_planar()`, but the CPU's Triplanar mode is now a **blended** three-plane -> sample (see Projection methods — that change is what fixed the 90°-corner seam). The two therefore -> still agree on any face that is roughly axis-aligned (one blend weight ≈ 1 there, so the blend -> degenerates to exactly the hard-axis pick), and disagree in the cross-fade band around a sharp -> edge — precisely where the fast preview will still show the old hard seam that the true preview and -> the bake no longer have. Reconciling it means sampling all three planes in the shader and blending -> the three gradients by `pow(abs(N), TRIPLANAR_BLEND_SHARPNESS)`, the same weights -> `sample_layer_height()` uses. Also note the shader is still **active-layer-only** and knows nothing -> about `TextureBlendMode`, so a multi-layer stack cannot match the true preview by construction. - ### On-canvas "Adjust Texture" gizmo A per-active-layer toggle ("Adjust placement (drag on model)") that disables painting and shows a flat pan panel (free 2D drag on both axes) plus two arrows along the patch's own U/V axes (constrained single-axis drag). Anchored to the painted patch's centroid/average-normal (`compute_layer_paint_anchor()`). Hit-testing is screen-space distance/point-to-segment (not real -3D ray intersection against the handle geometry) — simple and good enough at this handle size. - -Known unverified detail: the **offset-drag direction/sign** is reasoning-based (increasing `offset` -shifts which texel is sampled at a fixed world position, which visually slides the pattern the -*opposite* way — so the code subtracts), not visually confirmed, since this environment can't -render pixels. May need a one-line sign flip once actually tested. The rotation-arrow-implied -direction should be reliable (it follows directly from a self-consistent 2D basis, no such -ambiguity). +3D ray intersection against the handle geometry) - simple and good enough at this handle size. ### Projection frame overlay (ViewProjected) -`src/slic3r/GUI/TextureProjectorFrame.hpp/.cpp` — a semi-transparent, resizable `wxFrame` the user +`src/slic3r/GUI/TextureProjectorFrame.hpp/.cpp` - a semi-transparent, resizable `wxFrame` the user drags **over the 3D view**, like a slide projector's gate. Whatever the model shows through it is what the texture is projected onto, and the window's border becomes the hard edge of the displacement. Press **Apply projection frame** and the gizmo reads the window's rectangle and commits it. The window is deliberately **dumb**: it owns no placement state and reports nothing continuously. Its -position and size *are* the placement, read on demand at Apply — which is also when the expensive +position and size *are* the placement, read on demand at Apply - which is also when the expensive visible-facet raycast runs. So dragging it is free and nothing recomputes until asked. Plain 2D (`wxPaintDC`), not a `wxGLCanvas`: a second GL canvas would have to share the app's one real `wxGLContext`, the cause of bugs #10 and #14 below. It only ever draws a bitmap and a border. -**The projective mapping (`apply_projection_frame()`)** is the substantive part. The frame defines a +**The projective mapping (`apply_projection_frame()`)**. The frame defines a **screen-space** rectangle, but the bake samples from a **local-space** position, so the two have to be -reconciled. `view_project_right/up` can only express an *affine* projection — exact under an +reconciled. `view_project_right/up` can only express an *affine* projection - exact under an orthographic camera, but wrong under perspective, where the near end of a part projects larger than the far end and no pair of axes reproduces that. So the layer instead stores a full projective map (`view_project_matrix`, row-major 3×4, `uv = (row0·p̃/row2·p̃, row1·p̃/row2·p̃)`), built like this: @@ -361,19 +289,19 @@ far end and no pair of axes reproduces that. So the layer instead stores a full - Window coordinates follow `igl::project`'s convention (as `CameraUtils::project` does), with y measured downward. Writing `uv = (win − rect_origin) / rect_size` makes u and v affine in `ndc = clip.xyz / clip.w`; multiplying through by `clip.w` leaves a plain linear combination of `K`'s - rows, which is exactly the 3×4 matrix — the perspective divide survives intact. + rows, which is exactly the 3×4 matrix - the perspective divide survives intact. - `w > 0` is checked rather than divided blindly. A point behind the projector has `w < 0` and divides - to a plausible-looking but **mirrored** uv — the classic way a projected decal reappears on the back + to a plausible-looking but **mirrored** uv - the classic way a projected decal reappears on the back of a model. `project_uv_projective()` returns false there and the caller treats it as no height. -The map already includes placement, so `apply_uv_transform()` is **not** applied on top of it — the +The map already includes placement, so `apply_uv_transform()` is **not** applied on top of it - the window's own position and size are the placement, and the tiling/rotation/offset sliders would shove the result off the frame the user just aligned. A "Clear" button drops back to the affine path where those controls mean something again. Apply also sets `tile_enabled = false`, so `DecodedHeightTexture::sample()` returns 0 outside `[0,1)` and the border is a hard edge rather than the first seam of an endless repeat, and repaints the layer -via `select_visible_faces(&matrix)` — the frame's uv square clips the selection, which both matches the +via `select_visible_faces(&matrix)` - the frame's uv square clips the selection, which both matches the paint to the border and keeps the ray queries proportional to the framed area instead of the model. Owned by the gizmo and **destroyed** (not just hidden) in `on_shutdown()`. Closing it only hides it, so @@ -381,10 +309,10 @@ reopening keeps it where it was left. ### UV Editor pane -`UVEditorCanvas` (`src/slic3r/GUI/UVEditorCanvas.hpp/.cpp`) — a standalone `wxGLCanvas` rendering the +`UVEditorCanvas` (`src/slic3r/GUI/UVEditorCanvas.hpp/.cpp`) - a standalone `wxGLCanvas` rendering the flattened LSCM islands (per-island wireframe + outline + fill) over the height texture (background quad tiled across the whole unwrap), with mouse pan/zoom. It is wrapped in a **`UVEditorPanel`** -(same file) that adds a button row (Frame / Snap / Average scale) and a Blender-style status line +(same file) that adds a button row (Frame / Snap / Average scale) and a status line along the bottom naming the current gesture and the shortcuts in play. The *panel* is what is registered as a `wxAuiPaneInfo` pane on `Plater`'s `m_aui_mgr`; `Plater::show_uv_editor(bool)` shows/hides it (deferred via `CallAfter`, since the gizmo calls it mid-3D-frame), and @@ -392,312 +320,102 @@ shows/hides it (deferred via `CallAfter`, since the gizmo calls it mid-3D-frame) Deliberately **shares the app's one real `wxGLContext`** (`wxGetApp().init_glcontext(*this)`, the same call `View3D`/`Preview`/`AssembleView` make) rather than creating an independent context like -`SkipPartCanvas` does elsewhere in this codebase — this is what lets it reuse the already-registered +`SkipPartCanvas` does elsewhere in this codebase - this is what lets it reuse the already-registered `"flat"`/`"flat_texture"` shaders and `GLModel` as-is, instead of needing its own shader compilation/VBO management. **Geometry is uploaded once, in the unwrap's own (raw, mm) coordinates**, one `GLModel` set per island; each island is then drawn through its own 2x3 affine (`island_transform_matrix()` composed -with the layer's tiling/rotation/offset) passed as the `flat` shader's `view_model_matrix`. This is -the fix for the ~200 ms-per-frame island-drag stall (#3): the old design pre-transformed every UV on -the CPU and re-uploaded the entire wireframe on every mouse-move event, which on a million-triangle -patch is exactly as slow as it sounds. Now a drag updates one matrix per island and touches no vertex -buffer — `on_island_edited(!finished)` calls only `set_island_transforms()`, and the full +with the layer's tiling/rotation/offset) passed as the `flat` shader's `view_model_matrix`. A +drag updates one matrix per island and touches no vertex +buffer - `on_island_edited(!finished)` calls only `set_island_transforms()`, and the full `set_islands()` rebuild happens solely when the unwrap itself changes (`unwrap_changed` in `update_uv_editor()`). **Gestures** (canvas-owned, reported to the gizmo as incremental deltas via `IslandEditFn`): left-drag -= move, right-drag or **R** = rotate (hold **Shift** to snap to 15° steps — quantised on the += move, right-drag or **R** = rotate (hold **Shift** to snap to 15° steps - quantised on the *cumulative* rotation, not each delta, so it doesn't judder, and accumulated incrementally so it survives crossing ±180°), **S** = scale (R/S modal, click/Enter to confirm, Esc to cancel), wheel = zoom about the cursor, middle-drag = pan, **Home**/**F** = frame all. Scale writes `TextureIsland::scale`; "Average scale" (`average_island_scales()`) sets every island to the mean, so one island scaled by hand can be matched back to its neighbours' texel density. **Snap** (canvas-owned `m_snap_enabled`, toggled from the toolbar) sticks a dragged island's nearest boundary vertex onto a -neighbouring island's at drag-*end* only — a magnet that re-applies mid-drag is very hard to pull out +neighbouring island's at drag-*end* only - a magnet that re-applies mid-drag is very hard to pull out of. Toolbar commands the canvas can't service itself (Average scale) are forwarded to the gizmo via `CommandFn`; view-only ones (Frame, Snap) it handles directly. -## Bugs found and fixed this session (worth remembering) - -These were all real, confirmed root causes (found by reading the actual code path, not guessed): - -1. **Cross-face projection distortion** — see "Triplanar" above. Fixed by projecting each vertex - with its own normal instead of one shared patch-average normal. -2. **Disabled-tile smearing to infinity** — clamping the UV *coordinate* into `[0,1]` instead of - returning 0 outside it, when tiling is off. Fixed in `DecodedHeightTexture::sample()`. -3. **Invisible checkbox/radio "checked" state in light mode** — `ImGuiWrapper::push_toolbar_style()` - sets `ImGuiCol_CheckMark` to white while the checkbox/radio frame background is fully transparent - (alpha 0) over a light window background — a white checkmark on an effectively-white background - is invisible by construction. This is a **pre-existing, general app-wide bug**, not specific to - this feature (every panel using `push_toolbar_style()` in light mode has it) — fixed by changing - just the light-mode branch's `CheckMark` color to the app's teal accent. -4. **Distorted (non-aspect-correct) texture thumbnails** — was forcing a square `ImGui::Image` size - regardless of the source image's actual aspect ratio. -5. **`std::array` compile error** — see Data model above (MSVC C2280, - `std::array`'s implicit special members don't inherit element-type friendship). -6. **Eigen ternary expression-template type mismatch** (MSVC C2446) — `cond ? (n / len) : - Vec3f::UnitZ()` fails because the two branches are different unevaluated Eigen expression - *types* with no common type; fixed by wrapping the non-`UnitZ()` branch in an explicit - `Vec3f(...)` to force a concrete common type. -7. **Post-bake stale preview** — `GLGizmoPainterBase::data_changed()`'s change-detection only - checks object id / volume count, neither of which changes when Bake replaces a volume's mesh - (same object, same volume count, just a new mesh/id on the volume itself) — so the gizmo kept - rendering/painting against the pre-bake `TriangleSelectorPatch` until manually deselected and - reselected. Fixed by explicitly calling `update_from_model_object()` in the bake-completion - callback. -8. **Bake job / crash-report `resources` junction going stale** — unrelated to this feature's code, - but hit during testing: `build/src/Release/resources` was a leftover **empty plain directory** - instead of the junction CMake's post-build step creates (`if not exist` skipped it because the - empty folder already "existed"), so the built exe couldn't find `resources/data/hints.ini`, - leaving `HintDatabase`'s hint list empty → `rand() % 0` divide-by-zero crash before the UI ever - opened. Fixed by deleting the empty folder and manually recreating the `mklink /J` junction. -9. **Fast bump preview showing a solid black object** — `GLModel::render()` **unconditionally** - re-sets the shader's `"uniform_color"` uniform from its own internal `Geometry::color` field - (defaulting to `ColorRGBA::BLACK()`) right before every draw call — so a manual - `shader->set_uniform("uniform_color", ...)` call made just before `.render()` gets silently - clobbered. Any `GLModel` that needs a specific flat color **must** call `.set_color(...)` on the - model itself, not set the shader uniform directly. Found by reading `GLModel::render()`'s actual - source rather than guessing at shader/lighting math. -10. **UV editor canvas rendering nothing / showing stale content on resize** — the canvas requested - a generic `wxGLAttributes().Defaults()` pixel format while sharing the app's one real - `wxGLContext` (which was originally created against `View3D`'s canvas, itself requesting a - specific RGBA/24-bit-depth/8-bit-stencil format). `wxGLCanvas::SetCurrent()` on WGL/GLX - generally requires the target window's pixel format to be compatible with the one the context - was created against; a mismatch can make `SetCurrent()` silently fail, leaving the canvas - showing whatever was last in its backbuffer (looks exactly like "blank" or "stale image on - resize"). Fixed by requesting the same explicit attribute list - `OpenGLManager::create_wxglcanvas()` uses for the main view canvases — but see bug #14: the - first attempt at this copied only *part* of that list and the symptom therefore survived. -11. **`` / `` include-order conflict** — `wx/glcanvas.h` pulls in the - platform's real `GL/gl.h`; if that happens before `` is processed in the same - translation unit, glad's own header errors out (`OpenGL (gl.h) header already included`). - Fixed by including `` first in `UVEditorCanvas.hpp`, before `` — any - file that includes this header (including `Plater.cpp`, transitively) needs glad to win that - race. -12. **Fast preview hidden behind the paint-highlight overlay** — `render_painter_gizmo()` always - drew the selection-highlight overlay on top with a depth-bias trick (`glPolygonOffset`) that - only makes sense for the *true*-displacement preview: real geometry moves in the painted area, - so the depth-biased overlay only wins the depth test in the *unpainted* (coincident) region. - The bump preview never moves geometry — its depth is identical to the overlay's *everywhere* — - so the overlay was winning the depth test across the whole surface and hiding the bump shading - entirely. Fixed by skipping the overlay draw entirely when the bump-preview path is active. -13. **Fast preview's apparent depth not matching the true preview's** — the bump shader built its - perturbed normal as `normalize(N + depth_mm * vec3(hL-hR, hD-hU, 0))`. Two things wrong with - that. (a) `hL-hR` is a height difference across *one texel step*, i.e. `dh/du` already scaled by - `2·texel`, and `du` is in uv units, not mm — the real surface slope needs the full chain rule - back through `uv = R(rotation) · planar_mm / tiling_scale`, i.e. a further `1/tiling_scale` and - a rotation of the gradient by `−rotation`. The missing `1/(2·texel·tiling_scale)` factor is - ~26× at a 1024px texture and a 20mm tile size, all in the flattening direction — which is - exactly what "fast preview has a different height from the real preview" looks like. (b) the - gradient was added to model-space `xy`, but the two axes the planar projection actually runs - along are `yz`/`xz`/`xy` depending on the dominant normal component, so on any face not - dominated by `z` the perturbation was applied to the wrong axes. Fixed by computing the real - mm-per-mm slope and rotating it into the projection's own `T`/`B` axes (see "Fast bump preview" - above for the derivation). -14. **UV editor pane still blank after bug #10** — three separate causes, all of them live at once: - - The bug-#10 fix copied `OpenGLManager::create_wxglcanvas()`'s attribute list but **dropped its - multisampling attributes** (`WX_GL_SAMPLE_BUFFERS`/`WX_GL_SAMPLES`, 4 samples by default), on - the reasoning that a flat 2D wireframe view doesn't need AA. But a differing sample count *is* - a differing pixel format, so this left exactly the `wglMakeCurrent()` mismatch bug #10 set out - to fix. It now mirrors the full list, AA included, reading `OpenGLManager::can_multisample()` - (already resolved by then — `View3D` is constructed first). - - `set_mesh()`/`set_background_texture()` each called `render()` **inline**, and both are reached - from `update_uv_editor()` → `rebuild_preview()` → the gizmo's ImGui panel — i.e. from the - middle of the *3D* canvas's GL frame, and (since `show_uv_editor(true)` is the last line of - `update_uv_editor()`) while this pane was still **hidden**. `wxGLCanvas::SetCurrent()` returns - false outright on a canvas that isn't shown on screen, and the old code ignored the return - value — so every GL call in `render()`, `glViewport`/`glClear` included, silently landed on the - 3D canvas instead. These now only mark dirty + `Refresh()`; `render()` bails unless - `IsShownOnScreen()` *and* `SetCurrent()` succeeds; and `Plater::show_uv_editor()` defers its - AUI relayout via `CallAfter` so the pane's first size/paint can't be delivered mid-frame either. - - Even once drawing, nothing would have been *visible*: the background quad spanned `[-1,1]²` - while LSCM UVs land around `[0,1]²`, and the view was centered on the origin at a half-extent - of 0.6. The quad is now the unit square in the same UV space the wireframe uses (which is also - where `sample()` maps the texture, regardless of its pixel aspect), the projection's Y is - negated so `v` runs down-screen (putting the texture's first pixel row at the top rather than - upside down), and the view auto-fits to the unwrap ∪ unit square the first time a patch shows up. -15. **A second texture layer was silently never applied** — the reported "multiple textures don't - work reliably". Root cause was the old sequential bake: layer N's paint mask was stored against - the volume's original mesh, so before layer N+1 could be deserialized the mask had to be carried - onto the mesh layer N had *just displaced*, via `TriangleSelector::remap_painting()`. Remapping a - mask onto geometry that has moved out from under it routinely returned an empty bitstream, and - the code then did `if (data.bitstream.empty()) continue;` — i.e. dropped the layer **without any - diagnostic**. Fixed structurally rather than patched: every layer is now evaluated against the - base mesh and merged per vertex (see Bake algorithm), so no remap happens at all. Covered by a - regression test. -16. **Hard-axis triplanar seam at exactly two of a box's four vertical corners** — see "Triplanar" - under Projection methods. Worth recording the *diagnostic* here, because the asymmetry is what - pinned it down: the user reported the seam at the (X+,Y−) and (X−,Y+) corners with the other two - clean. That is precisely what a dominant-axis switch predicts (`u = y` on an X face, `u = x` on a - Y face; those agree where `x == y` and differ by the corner width where `x == −y`) and it ruled - out every "the texture is wrong" hypothesis, since the texture itself is fine — the *mapping* is - discontinuous. Fixed by blending the three axis projections instead of picking one. -17. **Use-after-free when removing a texture layer** (latent, pre-existing — found while touching the - panel, not caused by it). The layer list's "Remove" button called `remove_texture_layer()` *in - the middle of rendering that layer's row*. That erases the layer from - `mv->texture_displacement_layers`, shifting every later element down — after which the loop - happily carried on dereferencing `layer` for the rest of the row's widgets (depth/tiling sliders, - `PopID`) and kept iterating `ordered`, a vector of pointers into the storage that had just moved. - Never crashed loudly because `vector::erase` doesn't reallocate, so the reads landed on a *valid* - but *wrong* (shifted) layer. Fixed by recording the slot and doing the removal after the loop. -18. **Every LSCM island collapsed to a point** (the UV editor was empty; the Tile-size slider did - nothing; an LSCM bake came out as a flat "single-face extrude"). One line in - `compute_patch_unwrap()`: `chart.indices = std::move(chart_mesh.indices);` ran *before* - `area_3d(chart_mesh)` was taken. `area_3d()` iterates those indices, so on the moved-from - (emptied) mesh it returned `0`, giving `scale = sqrt(0 / uv_area) = 0` — **every chart's UVs - multiplied by zero**. That one zero explained all three symptoms at once (no island extent to - draw; a zero-size unwrap is still zero after any tiling divide; the bake sampled ~one constant - texel per chart). Fixed by measuring the 3D area before the move. Found only by instrumenting the - actual island bbox into the panel — three rounds of reasoning from screenshots had each guessed - wrong, because a collapsed-to-a-point unwrap and an off-screen-framed one look identical. - -19. **Isotropic remeshing produced scrambled geometry with dropped triangles** — the "remeshing kinda - does not work properly" report, and a genuine one-line root cause. `isotropic_remeshing()` edits - the `Surface_mesh` **in place**, and its edge collapses only *mark* vertices and faces as removed; - the underlying arrays keep the holes until `collect_garbage()` is called. That matters because the - shared `cgal_to_indexed_triangle_set()` numbers its output vertices by **iteration order** (which - skips removed slots) while reading each face's corner as the **raw integer value of the vertex - descriptor** (which does not). The two agree only up to the first collapse; past that every - triangle indexes the wrong vertices, and any descriptor beyond the live vertex count hits the - converter's `iv >= vsize` guard and is silently dropped *together with its triangle*. Fixed by - compacting (`cgal_mesh.collect_garbage()`) before converting. Note the pre-existing callers were - unaffected: the boolean ops build their result into a fresh mesh, so only the in-place remesher - ever handed the converter a mesh with garbage in it. -20. **Remeshing rounded off every sharp edge** — the second half of the same report. CGAL's - `isotropic_remeshing` relaxes vertices tangentially along the surface, which erodes hard features - unless they are constrained: a cube came back with wobbly, eroded edges. Fixed by detecting sharp - edges (`PMP::detect_sharp_edges()` at a user-settable dihedral angle, default 40°) plus every open - border, constraining them, and passing `protect_constraints(true)`. That option additionally - requires each constrained edge to already be shorter than 4/3 · target, hence the - `PMP::split_long_edges()` pre-pass (given the same map, so the halves inherit the constraint) — - this mirrors CGAL's own isotropic_remeshing example. Also added a guard for the case where - `Surface_mesh::add_face()` refused faces on a non-manifold input: remeshing a mesh that silently - lost faces yields a **punctured** model, so it now bails out and reports instead. -21. **Remesh falsely reporting "did not change the model"** — the GUI decided success by comparing - *vertex counts*. A remesh that redistributes triangles at roughly the current density legitimately - lands on the same count, so a perfectly good result was discarded with an error. Now compared - structurally against the input (which is what `remesh_isotropic()` hands back on failure). -22. **Subdivide re-previewing at the same count after Apply** — Apply committed N passes and then - immediately rebuilt the *preview* at N passes again, now on top of a mesh up to 4^N times denser. - That is the single most expensive thing the panel can do, and it ran on every Apply. The slider - now starts at 0 (a real "no subdivision" value that previews nothing), and Apply snaps back to it. - ## Known limitations / deferred work - **No `.3mf` serialization** for texture-displacement paint data or texture assets. A background agent attempted this in an earlier session, hit its own usage limit mid-edit, and left `bbs_3mf.cpp` with an undefined forward-declared function; that partial edit was reverted rather than shipped broken. Practical impact: **baked** geometry round-trips fine (it's just an ordinary - part of the mesh via the existing mesh serialization path) — what does *not* survive a project + part of the mesh via the existing mesh serialization path) - what does *not* survive a project save/reload is any *unbaked* paint stroke and texture layer definition. - **No remap-across-topology-change** for texture-displacement paint (`ModelObject::split()`, mesh boolean ops, Simplify, and now `subdivide_mesh_uniform()` all drop it via `reset_extra_facets()`). The other four paint channels (supported/seam/mmu/fuzzy) do get remapped in these cases. -- **Cylindrical/Spherical axis/center are auto-picked heuristically**, not user-controllable — no +- **Cylindrical/Spherical axis/center are auto-picked heuristically**, not user-controllable - no UI to override the auto-detected wrap axis if it picks the "wrong" one for an odd shape. -- **Fast preview covers the active layer only**, while the true preview stacks every painted layer — - so with more than one layer painted the two will legitimately not agree, independently of bug #13. -- **Bump preview and true preview both only refresh at stroke-end**, not continuously during an - active drag (a deliberate scope cut for simplicity/consistency — the original plan's "instant - update mid-stroke" idea for the bump shader specifically was not carried through). -- **On-canvas Adjust-Texture gizmo's offset-drag direction is unverified** (see above). -- **The bump shader still uses hard-axis, single-layer projection** while the CPU path is now - blended-triplanar and blend-mode aware — see the callout under "Fast bump preview". +- **Fast preview covers the active layer only** - **Displacement resolution is capped by the mesh's own vertex density.** Baking only ever *moves* existing vertices (it never inserts any), so a coarse patch cannot show fine texture detail no - matter how high-resolution the height map is — that is what the "Subdivide model" button is for. + matter how high-resolution the height map is - that is what the "Subdivide model" button is for. Since the rewrite the bake is topology-preserving, so this is now a hard, explicit property rather than something partly papered over by the old per-layer re-meshing. -- **Placeholder toolbar icon** — reuses `toolbar_fuzzy_skin_paint.svg`, noted as a TODO in code. -- **Textures are matched to the picker by absolute path** (`TextureDisplacementLayer::path`), so the - picker's "which entry is selected" highlight goes blank if a project is moved between machines. - Harmless — the layer keeps its own embedded `image_data` and still bakes correctly. - -### Requested UV-editing features — status - -A user working through the feature end-to-end asked for a batch of UV-editing features. All of the -functional ones are now implemented (see the sections above): checker (#13), distortion heatmap -(#14), mesh wireframe overlay (#8), cut island (#17), project-from-view (#6), and Blender-style mark -seam (#9), plus per-island fills, the fast preview honouring the LSCM unwrap and island moves (#1), -the UV pane toolbar + status line (#18), Shift-snap rotation, midlevel/bidirectional displacement -(#19), and island scale/average/padding/snap (#15/#16/#2). - -Remaining cosmetic / known gaps: -- The UV pane toolbar has **text buttons, not icons** — no existing SVG reads cleanly as "average - island scale" / "snap islands", so real icons are deferred rather than mis-assigned. -- **Mark-seam edge picking** snaps to the nearest edge of the *hit facet* only; it does not - highlight the candidate edge on hover before you click (a hover-preview would be a nice refinement). -- **Cut island** always halves along the longest 3D axis; there is no UI to pick the cut line. ## File map **libslic3r (core, no GUI dependency):** -- `src/libslic3r/TextureDisplacement.hpp/.cpp` — data model, bake algorithm, projection methods, +- `src/libslic3r/TextureDisplacement.hpp/.cpp` - data model, bake algorithm, projection methods, tiling, subdivision. See doc comments throughout, they're kept accurate and up to date. -- `src/libslic3r/MeshBoolean.hpp/.cpp` — added `parameterize_lscm()` and `remesh_isotropic()` - (sharp-feature-preserving isotropic remeshing, see bugs #19–#21) in the `cgal` sub-namespace, +- `src/libslic3r/MeshBoolean.hpp/.cpp` - added `parameterize_lscm()` and `remesh_isotropic()` + in the `cgal` sub-namespace, reusing the existing `CGALMesh`/`_EpicMesh`/conversion-helper infrastructure already there for mesh boolean ops. New CGAL includes: `Polygon_mesh_processing/border.h`, `Polygon_mesh_processing/connected_components.h`, `Surface_mesh_parameterization/{Error_code, - LSCM_parameterizer_3, parameterize}.h`. No new dependency — CGAL 5.6.3 is already vendored and + LSCM_parameterizer_3, parameterize}.h`. No new dependency - CGAL 5.6.3 is already vendored and the `Surface_mesh_parameterization` package headers were already present, just unused before now. -- `src/libslic3r/Model.hpp/.cpp` — the 8 named `FacetsAnnotation` fields + accessor, +- `src/libslic3r/Model.hpp/.cpp` - the 8 named `FacetsAnnotation` fields + accessor, `texture_displacement_layers`, and all the mirrored touch points (see Data model above). **GUI:** -- `src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp/.cpp` — the gizmo. Panel controls: dock/ +- `src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp/.cpp` - the gizmo. Panel controls: dock/ undock toggle, brush/face/connected-area selection mode + "select whole model" button, per-layer texture picker + depth/tiling/rotation/invert/tile-mode/projection-mode/blend-mode controls, "Adjust placement" toggle (on-canvas gizmo), "Fast preview (normal map)" toggle, "Subdivide model" button, Add layer/Erase all/Bake. -- `src/slic3r/GUI/TextureLibrary.hpp/.cpp` — scans the shipped + user texture folders, imports an +- `src/slic3r/GUI/TextureLibrary.hpp/.cpp` - scans the shipped + user texture folders, imports an arbitrary image into the user folder (converting it to the 8-bit grayscale PNG libslic3r decodes), and loads a library file's bytes for a layer. The image→grayscale-PNG conversion lives here, on the GUI side, because libslic3r has no image toolkit; both the import path and the "pick a shipped texture" path go through the same one function. -- `resources/textures/displacement/*.png` — the 10 shipped height maps (Bricks, Grid, Hexagons, +- `resources/textures/displacement/*.png` - the 10 shipped height maps (Bricks, Grid, Hexagons, Knurl, Noise, Quilt, Studs, Waves, Weave, Wood Grain). All 512×512 8-bit grayscale and **seamless** (each is periodic over the full image in both axes, so tiling shows no seam). Generated procedurally; the whole `resources/` tree is installed recursively by CMake, so a new folder under it ships with no build-system change. -- `src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.hpp/.cpp` — background bake commit. -- `src/slic3r/GUI/Jobs/TextureDisplacementPreviewJob.hpp/.cpp` — background preview compute +- `src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.hpp/.cpp` - background bake commit. +- `src/slic3r/GUI/Jobs/TextureDisplacementPreviewJob.hpp/.cpp` - background preview compute (mirrors the bake job's shape but commits nothing to the Model). -- `src/slic3r/GUI/TextureProjectorFrame.hpp/.cpp` — the semi-transparent projection-frame overlay for - ViewProjected layers (plain 2D `wxPaintDC`, no GL context — see its section above). -- `src/slic3r/GUI/UVEditorCanvas.hpp/.cpp` — the 2D UV unwrap viewer widget. -- `src/slic3r/GUI/Plater.hpp/.cpp` — `uv_editor_canvas` member, AUI pane registration, +- `src/slic3r/GUI/TextureProjectorFrame.hpp/.cpp` - the semi-transparent projection-frame overlay for + ViewProjected layers (plain 2D `wxPaintDC`, no GL context - see its section above). +- `src/slic3r/GUI/UVEditorCanvas.hpp/.cpp` - the 2D UV unwrap viewer widget. +- `src/slic3r/GUI/Plater.hpp/.cpp` - `uv_editor_canvas` member, AUI pane registration, `get_uv_editor_canvas()`/`show_uv_editor()`. -- `src/slic3r/GUI/GLShadersManager.cpp` — registers `"texture_displacement_bump"`. -- `resources/shaders/{110,140}/texture_displacement_bump.{vs,fs}` — the bump-preview shader. -- `src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp` — `PainterGizmoType::TEXTURE_DISPLACEMENT`. -- `src/slic3r/GUI/Gizmos/GLGizmosManager.hpp/.cpp` — `EType::TextureDisplacement` registration. -- `src/slic3r/GUI/ImGuiWrapper.cpp` — the light-mode checkmark-color fix (bug #3 above; a - pre-existing, general bug, not scoped to this feature). +- `src/slic3r/GUI/GLShadersManager.cpp` - registers `"texture_displacement_bump"`. +- `resources/shaders/{110,140}/texture_displacement_bump.{vs,fs}` - the bump-preview shader. +- `src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp` - `PainterGizmoType::TEXTURE_DISPLACEMENT`. +- `src/slic3r/GUI/Gizmos/GLGizmosManager.hpp/.cpp` - `EType::TextureDisplacement` registration. +- `src/slic3r/GUI/ImGuiWrapper.cpp` - the light-mode checkmark-color fix -**CMake:** all new source files added to `src/libslic3r/CMakeLists.txt`, -`src/slic3r/CMakeLists.txt` (in roughly-alphabetical position matching each list's existing -convention), and `tests/libslic3r/CMakeLists.txt` for the unit test file. - -**Tests:** `tests/libslic3r/test_texture_displacement.cpp` — **run and passing** (7 cases, 116 +**Tests:** `tests/libslic3r/test_texture_displacement.cpp` - **run and passing** (7 cases, 116 assertions). Covers `decode_height_texture` round-trip, empty-layer no-op, full-cube uniform -displacement, boundary-vertex pinning on a hand-built fan mesh, and — added with the bake rewrite — -a regression test that a **second layer over the same area actually contributes** (the bug that -rewrite fixed), a table-driven check of all four blend modes, and that the lowest layer ignores its +displacement, boundary-vertex pinning on a hand-built fan mesh, and - added with the bake rewrite - +a regression test that a **second layer over the same area actually contributes**, +a table-driven check of all four blend modes, and that the lowest layer ignores its blend mode. `BUILD_TESTS` is `OFF` in the checked-in build cache; flip it on to run them: cmake -S . -B build -DBUILD_TESTS=ON cmake --build build --config Release --target libslic3r_tests -- -m - ./build/tests/libslic3r/Release/libslic3r_tests.exe "[TextureDisplacement]" --order rand - -## Build notes - -- Everything here lives in `libslic3r`/`libslic3r_gui`/`libslic3r_cgal` — no new external - dependency, no `deps/` rebuild needed. CGAL's parameterization package was already vendored. -- To build just this feature's code path fastest: `cmake --build build --config Release --target - libslic3r_gui -- -m` (pulls in `libslic3r` and `libslic3r_cgal` as needed). The full app target - is `OrcaSlicer_app_gui` (produces `build/src/Release/orca-slicer.exe`) — only needed to actually - run and visually test, not to verify compilation. -- `BUILD_TESTS` is `OFF` in the existing build cache; flip it on to actually run - `test_texture_displacement.cpp`. + ./build/tests/libslic3r/Release/libslic3r_tests.exe "[TextureDisplacement]" --order rand \ No newline at end of file diff --git a/resources/shaders/140/texture_displacement_bump.fs b/resources/shaders/140/texture_displacement_bump.fs index 5f762c4f32..3844e709f8 100644 --- a/resources/shaders/140/texture_displacement_bump.fs +++ b/resources/shaders/140/texture_displacement_bump.fs @@ -62,7 +62,7 @@ uniform bool use_vertex_uv; // true: sample at vertex_uv with a derived ta // A 2x3 affine (columns packed as lin = (m00, m01, m10, m11), tr = (m02, m12)) applied to the uv of // the island currently being dragged in the UV editor (active > 0.5). Identity when nothing is // dragged, so this whole path is a no-op then. Lets a UV island drag move the bump on the model with -// only a uniform update -- no mesh rebuild -- exactly the way Adjust placement moves the whole texture. +// only a uniform update uniform vec4 island_delta_lin; uniform vec2 island_delta_tr; @@ -76,7 +76,7 @@ in vec2 vertex_uv; out vec4 out_color; // The two model-space axes the triplanar planar coordinate is read off, per dominant normal -// component -- same choice libslic3r's project_planar() makes, so planar.x runs along t, planar.y +// component - same choice libslic3r's project_planar() makes, so planar.x runs along t, planar.y // along b. void projection_axes(vec3 n, out vec3 t, out vec3 b) { @@ -113,10 +113,10 @@ void main() triangle_normal = -triangle_normal; if (use_vertex_uv) { - // Precomputed-uv (LSCM) path -- Mikkelsen's surface-gradient bump ("Bump Mapping + // Precomputed-uv (LSCM) path - Mikkelsen's surface-gradient bump ("Bump Mapping // Unparametrized Surfaces on the GPU"). The perturbed normal is derived straight from the // screen-space derivatives of the *sampled height* and the position, so it is scale-exact - // with no uv->mm assumption at all -- which is the whole point here: an LSCM map is conformal, + // with no uv->mm assumption at all - which is the whole point here: an LSCM map is conformal, // not isometric, so the local mm-per-uv varies across the chart and the earlier "one global // 1/tiling factor" got the depth visibly wrong. dFdx(h) captures the true on-screen rate of // change however the chart is stretched or however fine the tiling is. diff --git a/resources/shaders/140/texture_displacement_uvcheck.fs b/resources/shaders/140/texture_displacement_uvcheck.fs index 323a49a4ba..8b430db52d 100644 --- a/resources/shaders/140/texture_displacement_uvcheck.fs +++ b/resources/shaders/140/texture_displacement_uvcheck.fs @@ -2,11 +2,11 @@ // UV-check overlay for the texture-displacement gizmo, drawn over the painted patch so the LSCM // unwrap can be sanity-checked on the real 3D surface (mode set by the `mode` uniform): -// mode 0 -- Checker: a procedural checkerboard sampled at the layer's uv. Even squares that stay +// mode 0 - Checker: a procedural checkerboard sampled at the layer's uv. Even squares that stay // square everywhere on the model mean the unwrap is low-distortion; squares that smear or // shear reveal exactly where it stretches. Same uv the bake samples, so what you see is // where the texture actually lands. -// mode 1 -- Distortion heatmap: the per-vertex area-distortion carried in `distortion`, blue +// mode 1 - Distortion heatmap: the per-vertex area-distortion carried in `distortion`, blue // (compressed) -> green (ideal) -> red (stretched). // Both are lit with the same cheap two-light diffuse the bump preview uses, so the surface still // reads as 3D. diff --git a/resources/shaders/140/texture_displacement_uvcheck.vs b/resources/shaders/140/texture_displacement_uvcheck.vs index 86a7dd13e9..79e858e0d7 100644 --- a/resources/shaders/140/texture_displacement_uvcheck.vs +++ b/resources/shaders/140/texture_displacement_uvcheck.vs @@ -2,9 +2,9 @@ // Vertex stage for the UV-check overlay (checker / distortion heatmap) drawn over the painted patch // by GLGizmoTextureDisplacement. Reuses GLModel's P3N3T2 layout so it needs no bespoke buffer: -// v_normal.x -- per-vertex UV distortion (uv-area / surface-area ratio, remapped so 0.5 = ideal); +// v_normal.x - per-vertex UV distortion (uv-area / surface-area ratio, remapped so 0.5 = ideal); // only the distortion mode reads it. -// v_tex_coord -- precomputed texture uv, valid only when use_vertex_uv is set (LSCM); the checker +// v_tex_coord - precomputed texture uv, valid only when use_vertex_uv is set (LSCM); the checker // mode reconstructs uv in the fragment shader otherwise. uniform mat4 view_model_matrix; diff --git a/src/libslic3r/TextureDisplacement.cpp b/src/libslic3r/TextureDisplacement.cpp index 5de11eafa6..0a6325cd92 100644 --- a/src/libslic3r/TextureDisplacement.cpp +++ b/src/libslic3r/TextureDisplacement.cpp @@ -38,7 +38,7 @@ float DecodedHeightTexture::sample(const Vec2f &uv, bool tile_enabled, TextureTi if (!tile_enabled && (uv.x() < 0.f || uv.x() >= 1.f || uv.y() < 0.f || uv.y() >= 1.f)) // Outside the single, non-repeating placement entirely: no texture there, not "smeared - // edge pixel" -- clamping the *coordinate* to [0, 1] would otherwise keep returning the + // edge pixel" - clamping the *coordinate* to [0, 1] would otherwise keep returning the // border row/column's height forever in every direction, stretching it out to infinity. return 0.f; @@ -136,7 +136,7 @@ DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer if (layer.empty()) return result; - // The raw (unsmoothed) decode is what gets cached, keyed by the image_data allocation -- decoding + // The raw (unsmoothed) decode is what gets cached, keyed by the image_data allocation - decoding // a PNG is the expensive part and never changes for a given image. Smoothing is applied afterwards // to a throwaway copy, so moving the smoothing slider never invalidates the decode cache. const void *key = layer.image_data.get(); @@ -192,7 +192,7 @@ Vec2f project_planar(const Vec3f &position, const Vec3f &normal) // tri-planar/cube projection; a patch spanning several differently-oriented faces gets each // face projected along its own best-fit axis instead of all faces sharing one axis picked // from a single averaged normal (which looks correct on one face but visibly distorts on any - // other face in the same patch -- exactly the bug an earlier version of this feature had). + // other face in the same patch - exactly the bug an earlier version of this feature had). const Vec3f n = normal.cwiseAbs(); if (n.x() >= n.y() && n.x() >= n.z()) return Vec2f(position.y(), position.z()); @@ -203,7 +203,7 @@ Vec2f project_planar(const Vec3f &position, const Vec3f &normal) namespace { // Wrapped around patch_axis, centered at patch_center. u is the arc length (mm) around the axis at -// this point's own radius, v is the signed distance along the axis -- a reasonable approximation +// this point's own radius, v is the signed distance along the axis - a reasonable approximation // for roughly cylindrical selections, not an exact fit for arbitrary geometry. Vec2f project_cylindrical(const Vec3f &position, const Vec3f &patch_center, const Vec3f &patch_axis) { @@ -225,7 +225,7 @@ Vec2f project_cylindrical(const Vec3f &position, const Vec3f &patch_center, cons // Longitude/latitude around patch_center. u/v are scaled by this point's own distance from the // center so the result is in roughly the same mm-ish units tiling_scale expects, rather than bare -// radians -- again an approximation, not an exact geodesic parametrization. +// radians - again an approximation, not an exact geodesic parametrization. Vec2f project_spherical(const Vec3f &position, const Vec3f &patch_center) { const Vec3f rel = position - patch_center; @@ -239,7 +239,7 @@ Vec2f project_spherical(const Vec3f &position, const Vec3f &patch_center) return Vec2f(longitude, latitude) * radius; } -// CGAL's LSCM parameterizer expects a clean mesh with no isolated (unreferenced) vertices -- but +// CGAL's LSCM parameterizer expects a clean mesh with no isolated (unreferenced) vertices - but // `patch` here (from TriangleSelector::get_facets_strict()) carries the *entire* mesh's vertex // array, only its `indices` filtered to the painted triangles. Build a compacted copy referencing // only the vertices `patch.indices` actually uses, plus a map back to the original vertex index so @@ -328,8 +328,8 @@ std::vector segment_into_charts(const indexed_triangle_set &mesh, const std } } - // The chart-join test compares a *neighbourhood-averaged* normal per face -- the face plus its - // edge-adjacent neighbours (~5 samples) -- rather than the single face normal. On a finely + // The chart-join test compares a *neighbourhood-averaged* normal per face - the face plus its + // edge-adjacent neighbours (~5 samples) - rather than the single face normal. On a finely // tessellated curved surface this stops one noisy triangle from spuriously cutting (or a lone // near-flat sliver from wrongly merging) a chart, while a genuine sharp crease, where the whole // neighbourhood on each side agrees, still cuts. This is the "use 5 points, not one" refinement. @@ -352,7 +352,7 @@ std::vector segment_into_charts(const indexed_triangle_set &mesh, const std for (const auto &[key, fp] : edge_faces) { if (fp.second < 0) continue; // a boundary edge of the patch, nothing on the far side to join - // A manually/auto marked seam always cuts, whatever the dihedral angle -- that is exactly + // A manually/auto marked seam always cuts, whatever the dihedral angle - that is exactly // what lets "mark seam" / "cut island" split a chart that is otherwise flat enough to merge. if (!seam_keys.empty() && seam_keys.count(key)) continue; @@ -373,7 +373,7 @@ std::vector segment_into_charts(const indexed_triangle_set &mesh, const std } // Projects a chart onto an orthonormal basis of its own average normal. This is *isometric* for a -// flat chart -- lengths and angles come out exactly right -- which is why a flat chart never needs a +// flat chart - lengths and angles come out exactly right - which is why a flat chart never needs a // solve at all, and why this also serves as the fallback for a chart LSCM cannot handle. std::vector project_to_tangent_plane(const indexed_triangle_set &chart, const Vec3f &normal) { @@ -407,7 +407,7 @@ float area_2d(const std::vector &uvs, const std::vector> &seam_edges) @@ -532,7 +532,7 @@ PatchUnwrap compute_patch_unwrap(const indexed_triangle_set &patch, float seam_a const Vec3f chart_normal = (normal_sum.norm() > 1e-12f) ? Vec3f(normal_sum.normalized()) : Vec3f::UnitZ(); // Is the chart flat? Charts are grown by a *pairwise* angle threshold, so a chart can still - // curve gradually across many triangles -- being merged is not the same as being planar. But + // curve gradually across many triangles - being merged is not the same as being planar. But // when it is planar (a cube face, and after seam-cutting that is the common case), the // tangent-plane projection is already the exact answer, and skipping the solve is the single // biggest speed-up here. @@ -541,14 +541,14 @@ PatchUnwrap compute_patch_unwrap(const indexed_triangle_set &patch, float seam_a if (chart_of[f] == c) planar = normals[f].dot(chart_normal) >= 0.9998f; // ~1 degree - // Measured before chart_mesh.indices is moved out from under it, below -- area_3d() iterates + // Measured before chart_mesh.indices is moved out from under it, below - area_3d() iterates // those indices, so taking it afterwards silently measures an empty mesh and returns 0. const float mesh_area_3d = area_3d(chart_mesh); std::optional> uvs; if (!planar) uvs = MeshBoolean::cgal::parameterize_lscm(chart_mesh); - // Flat chart, or one LSCM refused (not a topological disk -- closed, or holed). + // Flat chart, or one LSCM refused (not a topological disk - closed, or holed). chart.uvs = uvs ? std::move(*uvs) : project_to_tangent_plane(chart_mesh, chart_normal); chart.indices = std::move(chart_mesh.indices); @@ -871,7 +871,7 @@ std::vector compute_lscm_uvs(const indexed_triangle_set &patch, const Tex return {}; // Manual per-vertex UV edits (UV editor Vertex/Edge modes) override the automatic raw unwrap - // coordinate for a mesh vertex, before the island transform -- so the edit rides along with any + // coordinate for a mesh vertex, before the island transform - so the edit rides along with any // island move/rotate exactly like the rest of the island. See TextureDisplacementLayer:: // lscm_uv_overrides. Small (hand edits), so a plain map is ample. std::map overrides; @@ -880,7 +880,7 @@ std::vector compute_lscm_uvs(const indexed_triangle_set &patch, const Tex // One UV per patch vertex: a seam vertex has several (one per chart it touches) and has to // settle on one, since it can only be displaced to a single position. See compute_lscm_uvs()'s - // header comment -- the surface stays watertight regardless. + // header comment - the surface stays watertight regardless. std::vector per_vertex(patch.vertices.size(), Vec2f::Zero()); std::vector assigned(patch.vertices.size(), false); for (size_t i = 0; i < unwrap.uvs.size(); ++i) { @@ -918,12 +918,12 @@ float blend_displacement(float accumulated, float value, TextureBlendMode mode) // (see TextureBlendMode): a 1 mm-deep layer sampling a white texel is then exactly neutral. case TextureBlendMode::Multiply: return accumulated * value; case TextureBlendMode::Divide: { - // Every height map has black regions, and a black texel samples to *exactly* zero -- so this + // Every height map has black regions, and a black texel samples to *exactly* zero - so this // divisor really does hit zero in ordinary use, not just in some contrived edge case. Floor // its magnitude: an unbounded 1/0 would not merely look wrong, it would fling vertices // thousands of mm away and poison the mesh's bounding box (and with it every plate/print // volume check downstream). The floor doubles as a cap on how far Divide can ever amplify - // the relief beneath it -- at most 1/0.05 = 20x. + // the relief beneath it - at most 1/0.05 = 20x. constexpr float min_divisor = 0.05f; const float divisor = (std::abs(value) < min_divisor) ? std::copysign(min_divisor, value < 0.f ? -1.f : 1.f) : value; @@ -983,7 +983,7 @@ float sample_layer_height(const DecodedHeightTexture &texture, const TextureDisp // Flat projection onto the captured projector plane. Single-valued per point, so unlike // blended triplanar it is one sample, and it is what "project from view" places. return sample_at(Vec2f(position.dot(layer.view_project_right), position.dot(layer.view_project_up))); - case TextureProjectionMethod::LSCM: // no usable unwrap for this patch -- fall back to Triplanar + case TextureProjectionMethod::LSCM: // no usable unwrap for this patch - fall back to Triplanar case TextureProjectionMethod::Triplanar: default: break; } @@ -1048,7 +1048,7 @@ bool compute_layer_paint_anchor(const indexed_triangle_set &b } // Area-weighted vertex normals of the undisplaced mesh. build_texture_displacement() computes -// these once, up front, and every layer both projects and displaces along them -- so a vertex +// these once, up front, and every layer both projects and displaces along them - so a vertex // covered by several layers is pushed along one single, well-defined direction rather than along // whatever direction the surface happened to be pointing partway through the stack. static std::vector texture_displacement_vertex_normals(const indexed_triangle_set &its) @@ -1122,7 +1122,7 @@ indexed_triangle_set build_texture_displacement(const indexed_triangle_set indexed_triangle_set mesh = base_mesh; // TriangleSelector's vertex array starts with the mesh's own vertices (any extra ones, created // where a brush stroke split a triangle, are appended after them), and get_facets_strict() - // emits exactly the *referenced* ones, in order. So selector vertex index i is our vertex i -- + // emits exactly the *referenced* ones, in order. So selector vertex index i is our vertex i - // but only if every vertex of `mesh` is referenced by some triangle, which is precisely what // this call establishes. It is a no-op (indices untouched) for any mesh that already is, which // in practice is all of them; it exists so an input carrying stray unreferenced vertices can't @@ -1140,7 +1140,7 @@ indexed_triangle_set build_texture_displacement(const indexed_triangle_set std::sort(ordered_layers.begin(), ordered_layers.end(), [](const TextureDisplacementLayer *a, const TextureDisplacementLayer *b) { return a->slot < b->slot; }); - // Every layer measures its displacement against the *original* surface -- normals included -- + // Every layer measures its displacement against the *original* surface - normals included - // rather than against whatever the previous layer left behind. That is what lets all the layers // be evaluated independently and merged per vertex, instead of having to re-mesh and remap the // paint masks between them (see the header for why that earlier design was dropped). @@ -1167,7 +1167,7 @@ indexed_triangle_set build_texture_displacement(const indexed_triangle_set if (patch.indices.empty()) continue; // get_facets_strict() returns the same vertex array whichever state is asked for (only the - // triangles are filtered), so `patch` and `rest` share one indexing -- and, per the + // triangles are filtered), so `patch` and `rest` share one indexing - and, per the // compactify above, it is our own. const indexed_triangle_set rest = selector.get_facets_strict(EnforcerBlockerType::NONE); @@ -1208,7 +1208,7 @@ indexed_triangle_set build_texture_displacement(const indexed_triangle_set else if (an.y() <= an.x() && an.y() <= an.z()) patch_axis = Vec3f::UnitY(); - // A real unwrap of the whole patch, computed once here rather than per vertex -- it is a + // A real unwrap of the whole patch, computed once here rather than per vertex - it is a // per-chart solve over the whole patch, not a per-point formula. Cached, so repeating this // for every slider tweak costs a hash rather than a re-solve (see compute_patch_unwrap()). const std::vector lscm_uvs = (layer->projection_method == TextureProjectionMethod::LSCM) ? @@ -1255,14 +1255,14 @@ indexed_triangle_set build_texture_displacement(const indexed_triangle_set patch_centroid, patch_axis, lscm_uv); // midlevel is the height that means "stay put", so anything below it displaces - // *inwards* -- see TextureDisplacementLayer::midlevel. At the default of 0 this is + // *inwards* - see TextureDisplacementLayer::midlevel. At the default of 0 this is // exactly the old outward-only behaviour. const float edge_w = edge_weight.empty() ? 1.f : edge_weight[size_t(vi)]; const float signed_height = (h - layer->midlevel) * layer->depth_mm * sign * edge_w; displacement[size_t(vi)] = blend_displacement(displacement[size_t(vi)], signed_height, displaced[size_t(vi)] ? layer->blend_mode : TextureBlendMode::Add); // The first layer to reach a vertex has nothing underneath it to blend with, so it - // always starts the total off additively -- a Multiply/Divide against an implicit + // always starts the total off additively - a Multiply/Divide against an implicit // zero base would otherwise annihilate (or blow up) it, which is never what the // user means by putting a mask on the bottom of the stack. displaced[size_t(vi)] = true; diff --git a/src/libslic3r/TextureDisplacement.hpp b/src/libslic3r/TextureDisplacement.hpp index dc78be701c..d3698eaec1 100644 --- a/src/libslic3r/TextureDisplacement.hpp +++ b/src/libslic3r/TextureDisplacement.hpp @@ -51,13 +51,13 @@ enum class TextureProjectionMethod : int Triplanar = 0, // Wrapped around an axis running through the patch's centroid. The axis itself is picked // automatically as the world axis *least* aligned with the patch's average normal (since a - // cylinder's own axis is perpendicular to its outward radial normal) -- a reasonable default + // cylinder's own axis is perpendicular to its outward radial normal) - a reasonable default // for roughly cylindrical selections, not a precise fit for arbitrary geometry. Cylindrical = 1, - // Wrapped around the patch's centroid using longitude/latitude -- reasonable for roughly + // Wrapped around the patch's centroid using longitude/latitude - reasonable for roughly // spherical/rounded selections, again an approximation rather than an exact geodesic map. Spherical = 2, - // Real UV unwrap of the painted patch -- a proper low-distortion flattening rather than a + // Real UV unwrap of the painted patch - a proper low-distortion flattening rather than a // planar/cylindrical/spherical approximation. The patch is first cut into charts along its // sharp edges and each chart is flattened on its own (see compute_patch_unwrap()), so a patch // that is not a single developable surface still unwraps sensibly. Falls back to Triplanar for @@ -65,20 +65,20 @@ enum class TextureProjectionMethod : int LSCM = 3, // Flat projection along a fixed direction captured from the 3D camera ("project from view"): the // texture is laid onto the painted area as seen from that angle, like a decal projector. Single - // planar map (no per-face axis switch), so it can smear on faces turned away from the projector -- + // planar map (no per-face axis switch), so it can smear on faces turned away from the projector - // that is inherent to view projection and is the user's call, not a bug. The projector's two // in-plane axes live in TextureDisplacementLayer::view_project_right/up. ViewProjected = 4, }; // Dihedral angle (degrees) above which an edge between two painted triangles becomes a chart seam -// -- i.e. the unwrap is cut there rather than being forced to flatten across it. +// - i.e. the unwrap is cut there rather than being forced to flatten across it. // // The whole point of this being a threshold rather than "flatten everything as one piece": three // faces meeting at a cube corner are not developable, so a single-chart solve has to distort them // badly to lie flat (they splay out into a fan, which is what "it merges all the edges into a // triangle" describes). Cutting at the 90-degree edges instead lets each face flatten exactly. -// Meanwhile a smoothly curved surface -- a subdivided sphere, say -- has only small angles between +// Meanwhile a smoothly curved surface - a subdivided sphere, say - has only small angles between // neighbouring triangles, stays a single chart, and unwraps as one piece the way it should. static constexpr float LSCM_DEFAULT_SEAM_ANGLE_DEG = 30.f; @@ -95,8 +95,8 @@ static constexpr float TRIPLANAR_BLEND_SHARPNESS = 4.f; // // Add/Subtract are in mm and need no further explanation. Multiply/Divide are *scaling* operations // and therefore need a unit convention: they treat the layer's own value as a unitless factor -// relative to 1 mm. That makes `depth_mm` act as a gain -- a layer with depth 1 mm and a white -// (1.0) texel multiplies the accumulated relief by exactly 1, i.e. leaves it unchanged -- which is +// relative to 1 mm. That makes `depth_mm` act as a gain - a layer with depth 1 mm and a white +// (1.0) texel multiplies the accumulated relief by exactly 1, i.e. leaves it unchanged - which is // the behaviour that makes a Multiply layer usable as a mask over the layers beneath it. enum class TextureBlendMode : int { @@ -112,20 +112,20 @@ float blend_displacement(float accumulated, float value, TextureBlendMode mode); // Where one unwrap island (chart) sits in UV space, on top of wherever compute_patch_unwrap() first // packed it. This is what the UV editor's drag/rotate gestures write to, so a user can lay the -// islands out by hand -- move them, rotate them, overlap them -- rather than being stuck with the +// islands out by hand - move them, rotate them, overlap them - rather than being stuck with the // automatic packing. // // Indexed by chart id, which compute_patch_unwrap() assigns in first-encountered-triangle order. That // is stable for a given patch and seam angle, but *not* across a change to either: repainting the // patch, or moving the seam-angle slider, can renumber the charts and so leave a hand-placed island -// applied to a different one. Accepted deliberately -- the alternative is a persistent chart identity +// applied to a different one. Accepted deliberately - the alternative is a persistent chart identity // that survives arbitrary re-segmentation, which is a much larger problem than this feature warrants. struct TextureIsland { Vec2f offset = Vec2f::Zero(); // in the unwrap's own mm space float rotation_deg = 0.f; // about the island's own centroid // About the island's own centroid too. 1 = the size compute_patch_unwrap() gave it, which is - // already its true surface area in mm -- so scaling an island away from 1 deliberately makes its + // already its true surface area in mm - so scaling an island away from 1 deliberately makes its // texel density differ from its neighbours'. See average_island_scales(). float scale = 1.f; @@ -168,7 +168,7 @@ struct TextureDisplacementLayer // The height value that means "don't move this vertex". The sampled height (0..1) has this // subtracted before being scaled by depth_mm, so with the default of 0 the surface only ever // moves *outwards* (the classic height-map convention), while 0.5 makes mid-grey neutral and - // lets darker texels cut *into* the surface -- an engraved-and-embossed result from one map. + // lets darker texels cut *into* the surface - an engraved-and-embossed result from one map. // // Cutting inward is not free: vertices move along their own normals, which converge inside a // concave corner and inside a thin wall, so a large depth_mm against a small feature really can @@ -197,7 +197,7 @@ struct TextureDisplacementLayer bool auto_connect_islands = true; // When false, the texture is sampled once (clamped to its edge pixels outside [0, 1)) instead - // of being repeated -- useful for a single decal-like placement rather than a repeating tile. + // of being repeated - useful for a single decal-like placement rather than a repeating tile. bool tile_enabled = true; TextureTileMethod tile_method = TextureTileMethod::Repeat; @@ -222,19 +222,19 @@ struct TextureDisplacementLayer // Also ViewProjected, and takes precedence over the two axes above when set: an exact *projective* // map from a local-space position straight to a texture uv, written by the projection-frame overlay - // (the semi-transparent window dragged over the 3D view -- its border becomes the uv unit square). + // (the semi-transparent window dragged over the 3D view - its border becomes the uv unit square). // // Row-major 3x4, applied to the homogeneous point p~ = (x, y, z, 1): // uv = ( row0.p~ / row2.p~ , row1.p~ / row2.p~ ) // The perspective divide is the whole point. view_project_right/up can only express an *affine* - // projection, which matches an orthographic camera exactly but not a perspective one -- under + // projection, which matches an orthographic camera exactly but not a perspective one - under // perspective the near end of a part projects larger than the far end, and no pair of axes // reproduces that. Folding the camera's full projection*view*model product into one matrix does. // Because a point behind the projector has row2.p~ <= 0 and no meaningful uv, sampling must check // the sign rather than divide blindly; see project_uv_projective(). // // Note this map already includes placement, so the usual tiling/rotation/offset transform is NOT - // applied on top of it -- the window's own position and size are the placement. + // applied on top of it - the window's own position and size are the placement. bool view_project_projective = false; std::array view_project_matrix{}; // Only used by TextureProjectionMethod::LSCM: hand placement of the unwrap's islands, indexed by @@ -243,7 +243,7 @@ struct TextureDisplacementLayer std::vector islands; // Only used by TextureProjectionMethod::LSCM: persistent "join" groups, indexed by chart id. Charts - // that share a group id move together as one in the UV editor -- this is what the explicit "Join" + // that share a group id move together as one in the UV editor - this is what the explicit "Join" // command records (over and above placing the child next to its parent). An entry of -1, or an index // past the end of the vector, means the chart is its own singleton group (moves alone). Empty means // every chart is a singleton. Same chart-renumbering caveat as `islands`: a re-unwrap can reshuffle @@ -252,7 +252,7 @@ struct TextureDisplacementLayer // Only used by TextureProjectionMethod::LSCM: manual per-vertex UV edits made in the UV editor's // Vertex/Edge select modes. Each pair is (mesh vertex index, its overriding raw-unwrap coordinate in - // mm) -- the *raw* unwrap position, i.e. before the island transform, so the edited vertex still + // mm) - the *raw* unwrap position, i.e. before the island transform, so the edited vertex still // moves and rotates with its island. In compute_lscm_uvs() this replaces the automatic unwrap // coordinate for that vertex; in the editor it edits the displayed geometry directly. Keyed in mesh- // vertex space like lscm_seam_edges (dropped on a topology change). The raw coordinate is only @@ -304,7 +304,7 @@ struct DecodedHeightTexture bool empty() const { return width <= 0 || height <= 0 || pixels.empty(); } // Bilinearly sampled height in [0, 1] at a normalized uv coordinate. When tile_enabled is false, - // a uv outside [0, 1) samples as 0 -- the texture simply is not there, rather than its border + // a uv outside [0, 1) samples as 0 - the texture simply is not there, rather than its border // row/column being smeared outward forever (which is what clamping the coordinate would do, and // was a real reported bug). Callers rely on this to get a hard edge: it is how the projection // frame's border becomes the edge of the displacement. @@ -317,13 +317,13 @@ DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer // Raw dominant-axis planar projection of `position` (in mm, not yet scaled/rotated/offset by any // layer), dropping the axis position that best aligns with `normal`. Exposed on its own (rather -// than only inline inside project_texture_displacement_uv()) so GUI code -- the on-canvas -// "adjust texture placement" gizmo -- can map a dragged 3D point into the exact same 2D space +// than only inline inside project_texture_displacement_uv()) so GUI code - the on-canvas +// "adjust texture placement" gizmo - can map a dragged 3D point into the exact same 2D space // tiling_scale/rotation_deg/offset operate in, without duplicating the axis-selection logic. Vec2f project_planar(const Vec3f &position, const Vec3f &normal); // Applies a layer's tiling_scale/rotation_deg/offset to an already-projected planar coordinate -// (in mm, dominant-axis planar, cylindrical, spherical, or CGAL LSCM output -- any of them, all +// (in mm, dominant-axis planar, cylindrical, spherical, or CGAL LSCM output - any of them, all // share this same final step). Exposed separately so build_texture_displacement() can route CGAL // LSCM's per-patch UV solve through the same scale/rotate/offset controls as every other // projection method, without going through project_texture_displacement_uv()'s own dispatch @@ -331,7 +331,7 @@ Vec2f project_planar(const Vec3f &position, const Vec3f &normal); Vec2f apply_uv_transform(const Vec2f &planar, const TextureDisplacementLayer &layer); // Applies a row-major 3x4 projective matrix (see TextureDisplacementLayer::view_project_matrix) to a -// local-space point, writing the resulting texture uv. Returns false -- and leaves `uv` untouched -- +// local-space point, writing the resulting texture uv. Returns false - and leaves `uv` untouched - // when the point lies behind the projector or on its plane (w <= 0), where there is no meaningful uv // and dividing would produce a mirrored or infinite coordinate. Callers treat that as "no height". bool project_uv_projective(const std::array &m, const Vec3f &position, Vec2f &uv); @@ -340,11 +340,11 @@ bool project_uv_projective(const std::array &m, const Vec3f &position // method, tiling scale, rotation, offset and tiling mode. Returns a height in [0, 1]. // // This returns a *height* rather than a UV because TextureProjectionMethod::Triplanar is a blend -// of three separate axis projections and therefore takes three texture samples per vertex -- there +// of three separate axis projections and therefore takes three texture samples per vertex - there // is no single UV that represents it. The other methods do map to one UV internally. // - `normal` is this specific vertex's own normal; used only by Triplanar (for its blend weights). -// - `patch_center`/`patch_axis` describe the painted patch as a whole (its centroid, and -- for -// Cylindrical only -- the wrap axis); used only by the Cylindrical/Spherical methods. +// - `patch_center`/`patch_axis` describe the painted patch as a whole (its centroid, and - for +// Cylindrical only - the wrap axis); used only by the Cylindrical/Spherical methods. // - `lscm_uv`, when non-null, is this vertex's precomputed LSCM coordinate and takes precedence // over `layer.projection_method` (LSCM is a single per-patch solve, not a per-vertex formula, // so build_texture_displacement() computes it once up front and passes it in here). @@ -356,7 +356,7 @@ float sample_layer_height(const DecodedHeightTexture &texture, const TextureDisp const Vec2f *lscm_uv = nullptr); // Area-weighted centroid and average normal of a layer's currently painted patch, in mesh-local -// coordinates -- the same measurements build_texture_displacement() uses to pick its dominant +// coordinates - the same measurements build_texture_displacement() uses to pick its dominant // projection axis. Used by the GUI to anchor the on-canvas "adjust texture placement" gizmo to // wherever the layer is actually painted. Returns false (leaving the outputs untouched) if the // layer has nothing painted yet. @@ -365,7 +365,7 @@ bool compute_layer_paint_anchor(const indexed_triangle_set &b Vec3f &anchor_pos, Vec3f &anchor_normal); -// Extracts the currently painted patch from a volume's base mesh + stored facet data -- the same +// Extracts the currently painted patch from a volume's base mesh + stored facet data - the same // extraction build_texture_displacement() and compute_layer_paint_anchor() each do internally via // TriangleSelector::get_facets_strict(ENFORCER). Returns an empty mesh if nothing is painted. // Exposed so GUI code (the LSCM "UV editor" preview pane) can get the same patch build_texture_ @@ -388,7 +388,7 @@ struct PatchUnwrap std::vector source_vertex; // unwrapped vertex -> index into patch.vertices std::vector vertex_chart; // unwrapped vertex -> chart (island) id std::vector indices; // patch triangles, re-indexed into `uvs` - // Per chart, the centroid of its uvs -- the point a TextureIsland's rotation turns about. + // Per chart, the centroid of its uvs - the point a TextureIsland's rotation turns about. std::vector chart_centroid; // Edges belonging to exactly one triangle: the outline of each island. Indices into `uvs`. This // is what the UV editor draws highlighted, so the boundaries the seam angle cut are visible. @@ -424,13 +424,13 @@ bool join_chart_placement(const PatchUnwrap &unwrap, const std::vector> &seam_edges = {}); -// One UV per patch vertex, for displacement. Displacement is inherently per-vertex -- a vertex has -// exactly one position, so it can only be pushed out by one height -- which means a seam vertex has +// One UV per patch vertex, for displacement. Displacement is inherently per-vertex - a vertex has +// exactly one position, so it can only be pushed out by one height - which means a seam vertex has // to settle on a single one of its charts' UVs (the first, arbitrarily). That is not a compromise // in the result: the surface stays watertight either way, since neighbouring vertices each move // along their own normals and nothing depends on the UVs agreeing across the seam. It is only the @@ -457,7 +457,7 @@ using TextureDisplacementFacetsData = std::array data, unsigned int // codebase has historically considered unreliable on some graphics cards. // // Each level is a 2x2 box filter of the level above it. Note this used to re-upload the - // *level-0* buffer at every level instead, which does not downscale anything -- it just + // *level-0* buffer at every level instead, which does not downscale anything - it just // reinterprets the image's first lod_w * lod_h texels as the whole smaller level, i.e. every // level below 0 held a crop of the top-left corner. It went unnoticed for as long as every // caller drew these textures at roughly their native size (where only level 0 is ever @@ -660,8 +660,8 @@ bool GLTexture::generate_texture_from_text(const std::string& text_str, wxFont& found = false; src -= 3; - for (int h = m_height; h > 0; --h) { - for (int w = m_width; w > 0; --w) { + for (int h = m_height; h > 0; -h) { + for (int w = m_width; w > 0; -w) { if ((*src) != background.Red() && !found) { found = true; if (h < font.GetPointSize()) @@ -742,7 +742,7 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, static bool to_squared_power_of_two(const std::string& filename, int max_size_px, int& w, int& h) { auto is_power_of_two = [](int v) { return v != 0 && (v & (v - 1)) == 0; }; - auto upper_power_of_two = [](int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; }; + auto upper_power_of_two = [](int v) { v-; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; }; int new_w = std::max(w, h); if (!is_power_of_two(new_w)) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp index 4772c4e74d..d1bce40b6b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp @@ -637,10 +637,10 @@ void GLGizmoTextureDisplacement::render_seam_overlay() glsafe(::glEnable(GL_POLYGON_OFFSET_LINE)); glsafe(::glPolygonOffset(-2.0f, -2.0f)); // pull further forward than the wireframe so seams read on top // A seam edge is geometrically the same line as a wireframe edge, so a mere polygon offset is a - // fragile way to make the red seam beat the white wireframe -- drivers apply GL_POLYGON_OFFSET_LINE + // fragile way to make the red seam beat the white wireframe - drivers apply GL_POLYGON_OFFSET_LINE // inconsistently, and the two lines then z-fight and the wireframe wins. When the wireframe is on, // or while actively marking, just draw the seams with depth testing off so they are unconditionally - // on top -- being visible is the one thing this overlay has to guarantee. + // on top - being visible is the one thing this overlay has to guarantee. const bool seams_on_top = m_wireframe_overlay || m_seam_edit_mode; if (seams_on_top) glsafe(::glDisable(GL_DEPTH_TEST)); @@ -812,7 +812,7 @@ void GLGizmoTextureDisplacement::compute_bump_active_vertices(const std::vector< m_bump_active_vertex.assign(mv->mesh().its.vertices.size(), 0); // Flag the base vertices of every chart being moved. For a group/multi move that is more than one // chart, but since such a move is a pure translation the shader applies the same delta to them all - // (see on_island_edited) -- exactly the "joined islands move together" behaviour. + // (see on_island_edited) - exactly the "joined islands move together" behaviour. for (size_t i = 0; i < u.uvs.size(); ++i) { if (i >= u.vertex_chart.size() || std::find(charts.begin(), charts.end(), u.vertex_chart[i]) == charts.end()) @@ -893,7 +893,7 @@ void GLGizmoTextureDisplacement::render_bump_preview_mesh() // Reuses the layer-list panel's already-decoded, already-uploaded GPU thumbnail (smoothing-aware), // whose grayscale value lives in the R channel exactly as the shader samples it. Its width/height - // are read straight off the texture -- decoding the PNG here every frame would re-run the smoothing + // are read straight off the texture - decoding the PNG here every frame would re-run the smoothing // blur on every camera move, which is what tanked the frame rate at high smoothing. GLTexture *tex = get_layer_thumbnail(*layer); if (tex == nullptr || tex->get_width() <= 0 || tex->get_height() <= 0) @@ -1211,7 +1211,7 @@ void GLGizmoTextureDisplacement::update_uv_editor() // A vertex/edge edit committing (or an undo reverting one) changes the per-vertex UV overrides // without going through the Unwrap button. Detect that and force a re-solve, so the pane's geometry - // stays in step with what will bake -- the one exception to "only re-solve on Unwrap". + // stays in step with what will bake - the one exception to "only re-solve on Unwrap". { size_t sig = 1469598103934665603ull; // FNV-1a seed const auto mix = [&sig](uint64_t x) { sig = (sig ^ x) * 1099511628211ull; }; @@ -1236,7 +1236,7 @@ void GLGizmoTextureDisplacement::update_uv_editor() state.seam_edges = layer->lscm_seam_edges; // The re-solve happens only when the user pressed "Unwrap" (m_uv_unwrap_pending). Every other call - // into here -- a paint stroke ending, a slider release, the check mode changing -- must not pay for + // into here - a paint stroke ending, a slider release, the check mode changing - must not pay for // a fresh LSCM solve; it just re-applies the cheap affine transforms over whatever unwrap already // exists. If the paint changed underneath but the user hasn't asked to re-unwrap, the pane keeps // showing the last unwrap on purpose (that is the whole point of making it an explicit action). @@ -1255,7 +1255,7 @@ void GLGizmoTextureDisplacement::update_uv_editor() m_uv_editor_unwrap = compute_patch_unwrap(patch, layer->lscm_seam_angle_deg, 0.f, layer->lscm_seam_edges); // Re-apply any stored per-vertex UV edits onto the fresh unwrap, so the pane shows exactly what // compute_lscm_uvs() will bake (which applies the same overrides). Keyed by mesh vertex, so every - // unwrapped copy of that vertex gets it -- matching the bake's single-UV-per-vertex settle. + // unwrapped copy of that vertex gets it - matching the bake's single-UV-per-vertex settle. if (!layer->lscm_uv_overrides.empty()) { std::map ov; for (const auto &[mv2, uv] : layer->lscm_uv_overrides) @@ -1316,7 +1316,7 @@ void GLGizmoTextureDisplacement::update_uv_editor() // Connected-net layout (on by default): a *fresh* unwrap is unfolded so adjacent charts sit // edge-to-edge (cube -> a net), rather than as separately packed squares. Only when the user pressed - // Unwrap (m_uv_apply_connected_net) -- a re-segmentation renumbers charts anyway, so any hand + // Unwrap (m_uv_apply_connected_net) - a re-segmentation renumbers charts anyway, so any hand // placement from before is already meaningless. A *refresh* re-solve (a committed vertex edit, or an // undo) must NOT relayout, or it would throw away every island placement on every vertex edit. if (unwrap_changed && m_uv_apply_connected_net && layer->auto_connect_islands) { @@ -1459,7 +1459,7 @@ void GLGizmoTextureDisplacement::on_uv_command(int cmd) return; } // Join to whichever neighbouring island (one it shares an edge with) is currently placed - // nearest -- i.e. the one it was dragged up against. + // nearest - i.e. the one it was dragged up against. const Eigen::Matrix sel_m = island_transform_matrix(chart, m_uv_editor_unwrap, layer->islands); const Vec2f sel_c = sel_m.block<2, 2>(0, 0) * m_uv_editor_unwrap.chart_centroid[size_t(chart)] + sel_m.col(2); int best_parent = -1; @@ -1770,7 +1770,7 @@ void GLGizmoTextureDisplacement::on_island_edited(int island, const Vec2f &offse // just the primary for a rotate/scale. m_island_move_set = is_move ? build_island_move_set(*layer, island) : std::vector{ island }; // Set up the GPU drag: flag the moved islands' vertices and bake the mesh once (via the dirty - // flag). From then on the drag is a uniform update, no rebuild -- see render_bump_preview_mesh(). + // flag). From then on the drag is a uniform update, no rebuild - see render_bump_preview_mesh(). m_bump_active_chart = island; compute_bump_active_vertices(m_island_move_set); m_bump_island_delta = Eigen::Matrix::Identity(); @@ -1791,7 +1791,7 @@ void GLGizmoTextureDisplacement::on_island_edited(int island, const Vec2f &offse if (c == island) { target.rotation_deg += rotation_delta; // Guarded: a scale that reaches zero is unrecoverable (every subsequent factor multiplies it) - // and would collapse the island to a point -- exactly the failure this feature hit once already. + // and would collapse the island to a point - exactly the failure this feature hit once already. target.scale = std::clamp(target.scale * scale_factor, 0.001f, 1000.f); } } @@ -1811,7 +1811,7 @@ void GLGizmoTextureDisplacement::on_island_edited(int island, const Vec2f &offse // interactive on a patch with a million triangles. uv_canvas->set_island_transforms(xf); } - // Move the island on the model live through the shader's island_delta uniform -- no mesh + // Move the island on the model live through the shader's island_delta uniform - no mesh // rebuild. delta = F_current * F_baked^-1 in final-uv space (the bump mesh bakes F_baked; the // shader applies delta to the flagged island's uv). The one rebuild that bakes the flags is // scheduled at drag start above and consumed once per frame by render_painter_gizmo(). @@ -1893,7 +1893,7 @@ Vec3f GLGizmoTextureDisplacement::adjust_handle_center(const TextureDisplacement { // apply_uv_transform() maps a planar mm coordinate p to uv = R(p / tiling_scale) + offset, and // on_mouse_adjust_texture() drives offset by offset = offset_start - R(delta / tiling_scale). - // Inverting that, the handle's displacement from the anchor is - R^-1(offset) * tiling_scale -- + // Inverting that, the handle's displacement from the anchor is - R^-1(offset) * tiling_scale - // which, substituted into the drag equation, moves the handle by exactly `delta`. So the handle // follows the cursor precisely, and is back on the anchor exactly when offset is zero. const float rad = layer.rotation_deg * float(M_PI) / 180.f; @@ -2887,7 +2887,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float // them fit a narrow window, which it does by lowering the same toolbar_icon_scale() read here. const float icon_btn_sz = GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale() * m_parent.get_scale(); // The icon SVGs carry their own border, so the ImGui button's own frame border and idle fill are - // suppressed here (FrameBorderSize 0 + transparent ImGuiCol_Button) to avoid a doubled border -- the + // suppressed here (FrameBorderSize 0 + transparent ImGuiCol_Button) to avoid a doubled border - the // hover/active fill is left in place for feedback. Applied only around these gizmo icon buttons. const auto push_borderless_icon_style = []() { ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.f); @@ -2950,7 +2950,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float // Selection mode: which of TriangleSelector's existing click/brush mechanisms drives painting. // "Face" and "Connected area" reuse the exact same underlying selection machinery every other - // paint gizmo already has (single-facet click, and angle-limited flood fill respectively) -- + // paint gizmo already has (single-facet click, and angle-limited flood fill respectively) - // just exposed here as an alternative to brushing, one triangle/region at a time. m_imgui->text(_L("Selection mode")); const bool is_brush_mode = m_tool_type == ToolType::BRUSH && m_cursor_type != TriangleSelector::CursorType::POINTER; @@ -3059,7 +3059,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float // Add-layer affordance as an icon beside the heading. It is deliberately *not* right-aligned // against the window edge: this panel uses ImGuiWindowFlags_AlwaysAutoResize, and positioning // an item at GetWindowContentRegionMax().x - w feeds the window's own width back into its - // auto-fit, growing it by one item-spacing every frame -- which, with the panel docked and + // auto-fit, growing it by one item-spacing every frame - which, with the panel docked and // anchored by its right edge, walked it left off-screen on hover. A plain SameLine can't do that. const unsigned int add_icon = tool_icon_id(); const float sz = m_imgui->scaled(1.3f); @@ -3298,8 +3298,8 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float if (is_active) { // Explicit unwrap (#: "Add unwrap button so it does not recompute on every - // change"). The LSCM solve runs only when this is pressed -- painting, the seam - // angle slider and seam marking no longer trigger it -- and the pane opens right + // change"). The LSCM solve runs only when this is pressed - painting, the seam + // angle slider and seam marking no longer trigger it - and the pane opens right // afterwards. Re-press it to fold in any edits made since. if (m_imgui->button(_u8L("Unwrap"))) { m_uv_unwrap_pending = true; @@ -3309,7 +3309,7 @@ void GLGizmoTextureDisplacement::on_render_input_window(float x, float y, float } if (ImGui::IsItemHovered()) m_imgui->tooltip(_u8L("Flatten the painted area into UV islands and open the UV editor. The " - "unwrap is computed only when you press this, not on every edit -- so " + "unwrap is computed only when you press this, not on every edit - so " "paint, change the seam angle or mark seams first, then press Unwrap to " "see the result. Islands can then be moved, rotated and scaled."), m_imgui->scaled(20.f)); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp index f3b98d89fd..bbcd355ebe 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp @@ -22,7 +22,7 @@ class TextureProjectorFrame; // libslic3r/TextureDisplacement.hpp) to painted areas of a model, and can bake the result into // real mesh geometry. See the project plan for the overall architecture; in short: // - each layer owns its own independent paint mask (ModelVolume::texture_displacement_facets), -// reusing the same TriangleSelector/FacetsAnnotation machinery as every other paint gizmo -- +// reusing the same TriangleSelector/FacetsAnnotation machinery as every other paint gizmo - // only one layer is "active" (paintable) at a time, selected in the panel below; // - "Bake" runs build_texture_displacement() in a background job and commits the result exactly // like the Emboss/SVG "project on surface" gizmo does. @@ -69,7 +69,7 @@ private: void set_active_layer(int slot); // flushes the previous layer's edits, then reloads selectors void bake(); - // Marks every facet of every model-part volume as painted for the currently active layer -- + // Marks every facet of every model-part volume as painted for the currently active layer - // "whole model" as an alternative to brushing/clicking every triangle by hand. void select_whole_model(); @@ -124,7 +124,7 @@ private: // A texture from the picker's library (see slic3r/GUI/TextureLibrary.hpp), read and uploaded // once and then kept for the gizmo's lifetime. The decoded bytes are held alongside the GPU - // thumbnail so that picking the texture can hand the layer this very same image_data buffer -- + // thumbnail so that picking the texture can hand the layer this very same image_data buffer - // which both avoids re-reading the file and lets decode_height_texture()'s own cache (keyed by // exactly this pointer) hit immediately on the first bake/preview. struct LibraryTexture @@ -149,13 +149,13 @@ private: // "Adjust Texture" mode: instead of painting, dragging an on-canvas handle changes the active // layer's offset. The handle is a flat panel lying in the paint patch's own tangent plane - // (a "pan" -- drag anywhere on it for free 2D movement), plus two arrows along the patch's + // (a "pan" - drag anywhere on it for free 2D movement), plus two arrows along the patch's // own U/V axes that constrain the drag to just that one axis for precise nudging. Anchored to // the centroid/average-normal of the active layer's current paint patch (see // libslic3r::compute_layer_paint_anchor()), so nothing is drawn if it has nothing painted yet. // // NOTE: the drag direction/sign below is this session's best-effort reasoning about which way - // the texture should appear to move as the handle is dragged -- it could not be visually + // the texture should appear to move as the handle is dragged - it could not be visually // confirmed while writing it (no way to render/see pixels in this environment), so it may // need a one-line sign flip once actually tested. bool update_adjust_anchor(); // recomputes m_adjust_anchor_pos/normal; false if nothing painted @@ -170,13 +170,13 @@ private: void adjust_tangent_basis(Vec3f &u_axis, Vec3f &v_axis) const; // The plane a drag is measured against: the paint patch's anchor, lifted clear of the surface. - // Deliberately *fixed* -- independent of the layer's offset -- so that moving the handle cannot + // Deliberately *fixed* - independent of the layer's offset - so that moving the handle cannot // move the plane the handle's own motion is derived from, which would be a feedback loop. Vec3f adjust_plane_point() const; // Where the handle is actually drawn, in mesh-local coordinates. This is NOT just the patch's // centroid: the handle *represents the texture's placement*, so it has to travel as `offset` - // changes. Pinning it to the centroid is why dragging it looked broken -- the texture slid but + // changes. Pinning it to the centroid is why dragging it looked broken - the texture slid but // the handle stayed put. Undoing apply_uv_transform()'s scale and rotation turns the layer's // offset back into a displacement in mm within the patch's tangent plane, which is what gets // added to the anchor here. That is exactly consistent with the drag arithmetic in @@ -190,7 +190,7 @@ private: // Recomputes m_preview_glmodel from the volume's current (unbaked) paint state, using the same // build_texture_displacement() algorithm as Bake. Called whenever the paint mask changes - // (stroke end, layer switch, undo/redo reload, post-bake refresh) rather than every frame -- + // (stroke end, layer switch, undo/redo reload, post-bake refresh) rather than every frame - // this is real mesh work (PNG sampling, vertex welding), not something to redo per paint stroke // drag sample or idle repaint. With several painted layers this can be slow, so the actual // computation runs in a background TextureDisplacementPreviewJob; this function only queues @@ -201,9 +201,9 @@ private: // Alternate, GPU-only preview: perturbs shading normals from the active layer's height texture // (a classic bump map) instead of actually moving vertices, using the // resources/shaders/*/texture_displacement_bump.* shader. Faster than the true-displacement - // preview (no CPU meshing at all -- just a per-vertex paint-weight buffer built at the same + // preview (no CPU meshing at all - just a per-vertex paint-weight buffer built at the same // cadence as rebuild_preview()) but only shows the *active* layer, and any bump is a shading - // illusion, not real geometry -- "Bake" always produces the true, exact result either way. + // illusion, not real geometry - "Bake" always produces the true, exact result either way. void rebuild_bump_preview_mesh(); void render_bump_preview_mesh(); @@ -216,7 +216,7 @@ private: // Applies one island edit reported by the UV editor's drag/rotate gestures to the active layer. // Deltas are incremental (see UVEditorCanvas::IslandEditFn); `finished` ends the gesture, which - // is when -- and only when -- the 3D preview is rebuilt, since doing that per mouse-move would + // is when - and only when - the 3D preview is rebuilt, since doing that per mouse-move would // queue a mesh recompute for every pixel of a drag. void on_island_edited(int island, const Vec2f &offset_delta, float rotation_delta, float scale_factor, bool finished); // Applies a committed vertex/edge edit from the UV editor's Vertex/Edge modes: each entry is an @@ -227,7 +227,7 @@ private: // UV-editor sub-element select mode, mirrored into the canvas: 0 = Island, 1 = Vertex, 2 = Edge. int m_uv_select_mode = 0; // One affine per island (columns: x basis, y basis, translation), mapping the unwrap's raw mm - // coordinates to texture UVs -- the same type as UVEditorCanvas::IslandTransform, spelled out + // coordinates to texture UVs - the same type as UVEditorCanvas::IslandTransform, spelled out // here so this header needn't drag in wxGLCanvas/glad. Cheap to recompute (it is per *island*, // not per vertex), which is what lets an island drag update the pane without re-uploading a // single vertex. @@ -245,7 +245,7 @@ private: void capture_view_projection(TextureDisplacementLayer &layer); // Manual seam marking (#9): a mode where clicking the model toggles the nearest mesh edge in the - // active layer's lscm_seam_edges, so the unwrap can be cut exactly where the user wants -- the + // active layer's lscm_seam_edges, so the unwrap can be cut exactly where the user wants - the // Blender "mark seam" workflow. Painting is suppressed while it is on. bool m_seam_edit_mode = false; GLModel m_seam_glmodel; // the current seam edges, highlighted on the mesh @@ -255,7 +255,7 @@ private: void render_seam_overlay(); // The mesh edge nearest the mouse, in the volume's own vertex indices, or {-1,-1} if the ray misses. // Factored out of toggle_seam_at() so the same pick can drive a live hover highlight (below) that - // shows which edge a click would toggle -- the "I don't know how it works" feedback the user hit. + // shows which edge a click would toggle - the "I don't know how it works" feedback the user hit. std::pair seam_edge_at(const Vec2d &mouse_pos) const; std::pair m_seam_hover_edge{ -1, -1 }; // The vertex a click would pick in shortest-path mode, so the target is visible on hover the same @@ -280,7 +280,7 @@ private: // Which of the up to TEXTURE_DISPLACEMENT_MAX_LAYERS paint masks the brush currently writes // into. Always a valid slot index (0 by default) so the base class's per-volume selector - // machinery always has something to work with, even before any texture has been added -- + // machinery always has something to work with, even before any texture has been added - // painting into a slot with no texture assigned is harmless, it just has no visible/bake // effect until a texture is added to that slot. int m_active_layer_slot = 0; @@ -323,13 +323,13 @@ private: // back to the standard paint-mask overlay like every other painting gizmo. GLModel m_preview_glmodel; // Set while a layer parameter slider has changed since the last rebuild_preview() call but the - // mouse button driving the drag hasn't been released yet -- see on_render_input_window(). + // mouse button driving the drag hasn't been released yet - see on_render_input_window(). bool m_preview_params_dirty = false; // See rebuild_bump_preview_mesh()/render_bump_preview_mesh(). bool m_use_bump_preview = false; // Set from the UV editor's per-move island edits instead of rebuilding the (potentially large) bump - // mesh synchronously inside that mouse handler -- doing the rebuild there stalled both the UV pane + // mesh synchronously inside that mouse handler - doing the rebuild there stalled both the UV pane // and the 3D view. The rebuild is instead coalesced to once per 3D frame (render_painter_gizmo). bool m_bump_preview_dirty = false; GLModel m_bump_preview_glmodel; @@ -339,7 +339,7 @@ private: // GPU island drag: while an island is dragged in the UV editor, the bump mesh is baked once (with // the dragged island's vertices flagged, v_normal.y = 1) and then moved purely through the shader's - // island_delta uniform -- one uniform update per mouse move, no rebuild -- so it tracks the cursor + // island_delta uniform - one uniform update per mouse move, no rebuild - so it tracks the cursor // as smoothly as Adjust placement. m_bump_active_chart is the dragged island (or -1); // m_bump_active_vertex flags its base vertices; m_bump_baked_active_xf is that island's placement // baked into the current mesh, against which the live delta is measured; m_bump_island_delta is the @@ -363,7 +363,7 @@ private: static int island_group_of(const std::vector &groups, int c); // Merges chart `b`'s join group into chart `a`'s (materialising `groups` to `chart_count` first). static void join_island_groups(std::vector &groups, int a, int b, int chart_count); - // Final per-vertex texture uv for the projections the shader can't reconstruct itself -- LSCM (an + // Final per-vertex texture uv for the projections the shader can't reconstruct itself - LSCM (an // unwrap) and ViewProjected (a projector plane the shader doesn't know). One entry per patch/base // vertex, already through apply_uv_transform(). Empty for Triplanar/Cylindrical/Spherical, which // the shader projects on its own. Shared by the bump preview and the UV-check overlay. @@ -381,7 +381,7 @@ private: void render_uvcheck_mesh(); // The UV editor pane is opened only on the user's explicit request (this toggle in the panel), - // never automatically just because a patch exists -- auto-popping it whenever there was "a + // never automatically just because a patch exists - auto-popping it whenever there was "a // selection to process" is exactly what the user asked to stop. update_uv_editor() keeps the pane // hidden unless this is set. Reset on gizmo shutdown so reopening the gizmo doesn't reopen the pane. bool m_show_uv_editor = false; @@ -395,7 +395,7 @@ private: // leave island placements untouched. bool m_uv_apply_connected_net = false; // Signature of the per-vertex UV overrides last reflected in the pane. When it changes without the - // user pressing Unwrap -- a vertex/edge edit committing, or an undo/redo reverting one -- the pane + // user pressing Unwrap - a vertex/edge edit committing, or an undo/redo reverting one - the pane // is re-solved so its geometry follows, even though a plain edit otherwise never re-solves (#Feat2). size_t m_uv_overrides_sig = 0; // What the UV pane's background currently holds, so update_uv_editor() only re-uploads it when the @@ -437,7 +437,7 @@ private: std::map m_library_textures; // Everything the *unwrap* depends on. update_uv_editor() runs from rebuild_preview(), i.e. on - // every stroke end and every slider release -- but depth/tiling/rotation/offset/blend change + // every stroke end and every slider release - but depth/tiling/rotation/offset/blend change // none of this, so re-extracting the patch and re-solving on those edits would be pure waste. // Held as the real values rather than a hash: TriangleSplittingData has an exact operator==, so // there is no reason to accept a hash's (however unlikely) chance of showing a stale unwrap. @@ -460,7 +460,7 @@ private: }; UVEditorState m_uv_editor_state; // Bounds of the UVs last handed to the pane, purely so the panel can show where the unwrap - // actually landed -- it is packed in mm and then divided by the tile size, so it is easy for it + // actually landed - it is packed in mm and then divided by the tile size, so it is easy for it // to end up far outside the texture's first tile without any of that being visible. Vec2f m_uv_editor_bbox_min = Vec2f::Zero(); Vec2f m_uv_editor_bbox_max = Vec2f::Zero(); @@ -505,7 +505,7 @@ private: // Icons for the panel's selection-mode and view-mode button rows. Loaded through IconManager with // the same colour/monochrome variants the main toolbar uses, so an inactive button shows the icon in - // the theme's normal (grey) foreground colour and an active one shows it in its original colours -- + // the theme's normal (grey) foreground colour and an active one shows it in its original colours - // matching the toolbar's selected/unselected look. Uploaded once on first panel render. IconManager m_panel_icons; std::map m_panel_icon_map; // file name -> [normal, colour, disabled] diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 94083dacee..6fd025f6ed 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -1902,7 +1902,7 @@ void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, co scroll_up(); else { if (hovered_id > 0) - --hovered_id; + -hovered_id; scroll_y(hovered_id); } }); @@ -2293,7 +2293,7 @@ std::string ImGuiWrapper::trunc(const std::string &text, } else { // decrease letter count while (count_letter > 1) { - --count_letter; + -count_letter; result_text = text_.substr(0, count_letter); text_width = calc_text_size(result_text).x; if (text_width < allowed_width) break; @@ -2555,7 +2555,7 @@ void ImGuiWrapper::push_toolbar_style(const float scale) ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 0.00f)); // 11 ImGui::PushStyleColor(ImGuiCol_TextSelectedBg, COL_GREEN_LIGHT); // 12 // The checkbox/radio frame behind this is drawn fully transparent (see FrameBg above, - // alpha 0), showing the light window background through it -- a white check mark there is + // alpha 0), showing the light window background through it - a white check mark there is // invisible. Dark mode doesn't have this problem (its window background is dark), so only // this branch needs a check mark color with real contrast against a light background. ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(0.f, 156 / 255.f, 136 / 255.f, 1.00f));//13 diff --git a/src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.cpp b/src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.cpp index 9e76c55666..155760709d 100644 --- a/src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.cpp +++ b/src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.cpp @@ -22,8 +22,8 @@ void TextureDisplacementBakeJob::process(Ctl &ctl) { ctl.update_status(0, _u8L("Baking texture displacement")); - // Only ever touches m_input (captured by value before this job was queued) and local state -- - // never the live Model -- so this is safe to run concurrently with the UI thread. + // Only ever touches m_input (captured by value before this job was queued) and local state - + // never the live Model - so this is safe to run concurrently with the UI thread. m_result = TriangleMesh(build_texture_displacement(m_input.base_mesh, m_input.layers, m_input.facets_data)); } diff --git a/src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.hpp b/src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.hpp index 2d8a22c759..082fcfa976 100644 --- a/src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.hpp +++ b/src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.hpp @@ -24,7 +24,7 @@ struct TextureDisplacementBakeInput }; // Bakes a volume's painted texture-displacement layers into real mesh geometry in the background, -// then commits the result on the main thread -- mirrors EmbossJob's UpdateJob/update_volume() +// then commits the result on the main thread - mirrors EmbossJob's UpdateJob/update_volume() // bake-and-commit pattern (see EmbossJob.cpp). class TextureDisplacementBakeJob : public Job { diff --git a/src/slic3r/GUI/Jobs/TextureDisplacementPreviewJob.cpp b/src/slic3r/GUI/Jobs/TextureDisplacementPreviewJob.cpp index e4a7ca616c..48b031dcc3 100644 --- a/src/slic3r/GUI/Jobs/TextureDisplacementPreviewJob.cpp +++ b/src/slic3r/GUI/Jobs/TextureDisplacementPreviewJob.cpp @@ -14,8 +14,8 @@ void TextureDisplacementPreviewJob::process(Ctl &ctl) { ctl.update_status(0, _u8L("Computing texture displacement preview")); - // Only ever touches m_input (captured by value before this job was queued) and local state -- - // never the live Model -- so this is safe to run concurrently with the UI thread. + // Only ever touches m_input (captured by value before this job was queued) and local state - + // never the live Model - so this is safe to run concurrently with the UI thread. m_result = build_texture_displacement(m_input.base_mesh, m_input.layers, m_input.facets_data); } diff --git a/src/slic3r/GUI/Jobs/TextureDisplacementPreviewJob.hpp b/src/slic3r/GUI/Jobs/TextureDisplacementPreviewJob.hpp index d024fc2548..93d66bcfb2 100644 --- a/src/slic3r/GUI/Jobs/TextureDisplacementPreviewJob.hpp +++ b/src/slic3r/GUI/Jobs/TextureDisplacementPreviewJob.hpp @@ -12,7 +12,7 @@ namespace Slic3r::GUI { -// Everything process() needs, captured by value on the main thread when the job is queued -- +// Everything process() needs, captured by value on the main thread when the job is queued - // mirrors TextureDisplacementBakeInput, but a preview never writes back to the Model. struct TextureDisplacementPreviewInput { @@ -24,7 +24,7 @@ struct TextureDisplacementPreviewInput // Computes the true (unbaked) displaced-mesh preview in the background. With several painted // layers this is real, non-trivial CPU work (PNG sampling, per-layer vertex welding), which used // to run synchronously on every paint stroke and parameter tweak and made editing feel slow with -// more than one or two layers. Unlike Bake, this never touches the live Model -- a preview is +// more than one or two layers. Unlike Bake, this never touches the live Model - a preview is // purely informational, there is nothing to commit. class TextureDisplacementPreviewJob : public Job { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 8deede98ac..74d362e94d 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -695,7 +695,7 @@ void Sidebar::priv::flush_printer_sync(bool restart) } //btn_sync_printer->SetBackgroundColorNormal((*counter_sync_printer & 1) ? "#F8F8F8" :"#009688"); m_printer_bbl_sync->SetBitmap_((*counter_sync_printer & 1) ? "printer_sync_not" : "printer_sync_ok"); - if (--*counter_sync_printer <= 0) + if (-*counter_sync_printer <= 0) timer_sync_printer->Stop(); } @@ -1124,7 +1124,7 @@ ExtruderGroup::ExtruderGroup(wxWindow * parent, int index, wxString const &title btn_up->SetBackgroundColour(*wxWHITE); btn_up->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this, index](auto &evt) { if (page_cur > 0) - --page_cur; + -page_cur; update_ams(); }); btn_up->Hide(); @@ -1189,7 +1189,7 @@ void ExtruderGroup::update_ams() ams[index]->Refresh(); ams[index]->Open(); } - for (size_t i = i1; i < ams_n1 && left > 0; ++i, ++index, --left) { + for (size_t i = i1; i < ams_n1 && left > 0; ++i, ++index, -left) { ams[index]->Update(i < ams_1.size() ? ams_1[i] : info1); ams[index]->Refresh(); ams[index]->Open(); @@ -3781,7 +3781,7 @@ void Sidebar::sync_ams_list(bool is_from_big_sync_btn) for (size_t i = 0; i < merge_info.merges.size(); i++) { auto& cur = merge_info.merges[i]; - for (int j = cur.size() -1; j >= 1 ; j--) { + for (int j = cur.size() -1; j >= 1 ; j-) { auto last_index = cur[j]; change_filament(last_index, cur[0]); cur.erase(cur.begin() + j); @@ -4471,7 +4471,7 @@ struct Plater::priv AssembleView* assemble_view { nullptr }; // Docked/resizable 2D pane showing GLGizmoTextureDisplacement's LSCM unwrap of a painted // patch; a sibling AUI pane alongside "sidebar"/"main", not part of the view3D/preview/ - // assemble_view sizer -- see its registration below and Plater::get_uv_editor_canvas(). The + // assemble_view sizer - see its registration below and Plater::get_uv_editor_canvas(). The // pane hosts the panel (toolbar + canvas + status line); uv_editor_canvas is its inner canvas, // cached so the gizmo can reach it directly. UVEditorPanel* uv_editor_panel { nullptr }; @@ -4697,7 +4697,7 @@ struct Plater::priv bool up_to_date(bool saved, bool backup); void suppress_snapshots() { m_prevent_snapshots++; } - void allow_snapshots() { m_prevent_snapshots--; } + void allow_snapshots() { m_prevent_snapshots-; } // BBS: single snapshot void single_snapshots_enter(SingleSnapshot *single) { @@ -5141,7 +5141,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) .BottomDockable(false) .BestSize(wxSize(39 * wxGetApp().em_unit(), 90 * wxGetApp().em_unit()))); - // UV editor pane for GLGizmoTextureDisplacement's LSCM unwrap preview -- a resizable/dockable + // UV editor pane for GLGizmoTextureDisplacement's LSCM unwrap preview - a resizable/dockable // sibling of "sidebar"/"main" like everything else registered on this same AUI manager, not a // change to the view3D/preview/assemble_view sizer above. Hidden by default: only relevant // while that gizmo is active with a layer using the "Unwrap (LSCM)" projection method (see @@ -11808,7 +11808,7 @@ void Plater::priv::undo() const std::vector &snapshots = this->undo_redo_stack().snapshots(); auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack().active_snapshot_time())); // BBS: undo-redo until modify record - while (--it_current != snapshots.begin() && !snapshot_modifies_project(*it_current)); + while (-it_current != snapshots.begin() && !snapshot_modifies_project(*it_current)); if (it_current == snapshots.begin()) return; if (get_current_canvas3D()->get_canvas_type() == GLCanvas3D::CanvasAssembleView) { if (it_current->snapshot_data.snapshot_type != UndoRedo::SnapshotType::GizmoAction && @@ -11828,7 +11828,7 @@ void Plater::priv::redo() while (it_current != snapshots.end() && !snapshot_modifies_project(*it_current++)); if (it_current != snapshots.end()) { while (it_current != snapshots.end() && !snapshot_modifies_project(*it_current++)); - this->undo_redo_to(--it_current); + this->undo_redo_to(-it_current); } } @@ -13085,7 +13085,7 @@ void adjust_settings_for_flowrate_calib(ModelObjectPtrs& objects, bool linear, i auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config; auto filament_config = &wxGetApp().preset_bundle->filaments.get_edited_preset().config; - /// --- scale --- + /// -- scale -- // model is created for a 0.4 nozzle, scale z with nozzle size. const ConfigOptionFloats* nozzle_diameter_config = printer_config->option("nozzle_diameter"); std::vector extruder_types = printer_config->option("extruder_type")->values; @@ -16706,7 +16706,7 @@ void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int r for (auto& item : item->second.gcodes) { if (item.type == CustomGCode::Type::ToolChange && item.extruder > filament_id) - item.extruder--; + item.extruder-; } } } @@ -17191,7 +17191,7 @@ void Plater::show_uv_editor(bool show) if (!pane.IsOk() || pane.IsShown() == show) return; - // Deferred, because GLGizmoTextureDisplacement calls this from its ImGui panel -- that is, from + // Deferred, because GLGizmoTextureDisplacement calls this from its ImGui panel - that is, from // the middle of the 3D canvas's GL frame. Showing an AUI pane re-lays out the window and // delivers the resulting size/paint events synchronously, and the UV canvas painting itself // makes its own surface current in the app's *shared* GL context, which mid-frame is the one diff --git a/src/slic3r/GUI/TextureLibrary.cpp b/src/slic3r/GUI/TextureLibrary.cpp index 349d82a050..67df12c32b 100644 --- a/src/slic3r/GUI/TextureLibrary.cpp +++ b/src/slic3r/GUI/TextureLibrary.cpp @@ -157,7 +157,7 @@ std::optional import_texture_to_library(const std::string & return std::nullopt; // Never overwrite an existing texture (the user's or, if they picked the same name twice, their - // own earlier import) -- uniquify instead. + // own earlier import) - uniquify instead. const std::string stem = boost::filesystem::path(source_path).stem().string(); boost::filesystem::path dest = boost::filesystem::path(user_dir) / (stem + ".png"); for (int i = 2; boost::filesystem::exists(dest); ++i) diff --git a/src/slic3r/GUI/TextureLibrary.hpp b/src/slic3r/GUI/TextureLibrary.hpp index acef78d4d9..4140acaadc 100644 --- a/src/slic3r/GUI/TextureLibrary.hpp +++ b/src/slic3r/GUI/TextureLibrary.hpp @@ -40,8 +40,8 @@ std::optional import_texture_to_library(const std::string & // Encoded bytes of `path`, ready to hand to TextureDisplacementLayer::image_data. Files already in // the supported 8-bit grayscale PNG form (everything in the two library folders, by construction) -// are passed through verbatim; anything else -- e.g. a colour PNG the user copied into the folder -// by hand -- is converted on the fly, so a valid image never silently produces a blank layer. +// are passed through verbatim; anything else - e.g. a colour PNG the user copied into the folder +// by hand - is converted on the fly, so a valid image never silently produces a blank layer. // Returns nullptr with `error` set if the file cannot be read or decoded at all. std::shared_ptr> load_texture_image_data(const std::string &path, std::string &error); diff --git a/src/slic3r/GUI/UVEditorCanvas.cpp b/src/slic3r/GUI/UVEditorCanvas.cpp index 95281acb8b..25d737f2bb 100644 --- a/src/slic3r/GUI/UVEditorCanvas.cpp +++ b/src/slic3r/GUI/UVEditorCanvas.cpp @@ -851,7 +851,7 @@ void UVEditorCanvas::on_mouse(wxMouseEvent &evt) m_gesture_last_angle = angle; // With Shift, quantise to *global* 15-degree marks (0/15/30...), i.e. snap the island's - // absolute on-screen orientation, not 15 degrees relative to wherever it started (#10) -- + // absolute on-screen orientation, not 15 degrees relative to wherever it started (#10) - // snapping the target rather than each delta is what keeps it from juddering on a step. constexpr float STEP = 15.f; const float absolute = m_rot_base_deg + m_rot_raw_deg; @@ -1333,7 +1333,7 @@ void UVEditorCanvas::render() } // Add/remove hint next to the cursor in Vertex/Edge mode: a green '+' when a click will add to the - // selection (plain or Shift), a red '-' when Ctrl is held and a click will remove one -- the UV-side + // selection (plain or Shift), a red '-' when Ctrl is held and a click will remove one - the UV-side // twin of the 3D paint cursor's own sign. Rebuilt each frame at the pointer, sized in pixels. if (m_select_mode != SelectMode::Island && m_cursor_inside) { const float uv_per_px = 2.f * m_zoom / float(std::max(1, std::min(size.GetWidth(), size.GetHeight()))); @@ -1375,9 +1375,9 @@ void UVEditorCanvas::render() update_status(); } -// ---------------------------------------------------------------------------------------------- +// ----------------------------------------------- // UVEditorPanel -// ---------------------------------------------------------------------------------------------- +// ----------------------------------------------- namespace { enum : int { @@ -1394,8 +1394,8 @@ enum : int { UVEditorPanel::UVEditorPanel(wxWindow *parent) : wxPanel(parent, wxID_ANY) { // Icon + label buttons: each has its own dedicated SVG (see the map below), with the label kept - // alongside it. A missing SVG simply leaves the button showing only its label -- never bitmap-less - // garbage -- so the bar stays usable before the art lands. + // alongside it. A missing SVG simply leaves the button showing only its label - never bitmap-less + // garbage - so the bar stays usable before the art lands. auto *bar = new wxBoxSizer(wxHORIZONTAL); const auto set_icon = [this](wxAnyButton *b, const std::string &iconname) { const wxBitmap bmp = create_scaled_bitmap(iconname, this, 16); diff --git a/src/slic3r/GUI/UVEditorCanvas.hpp b/src/slic3r/GUI/UVEditorCanvas.hpp index 3e1047eeb9..40397470fd 100644 --- a/src/slic3r/GUI/UVEditorCanvas.hpp +++ b/src/slic3r/GUI/UVEditorCanvas.hpp @@ -23,26 +23,26 @@ namespace Slic3r::GUI { -// Standalone 2D viewer/editor for a flattened (UV-unwrapped) mesh patch -- shows the result of +// Standalone 2D viewer/editor for a flattened (UV-unwrapped) mesh patch - shows the result of // GLGizmoTextureDisplacement's LSCM projection method as its own resizable pane (see Plater's // "uv_editor" AUI pane) rather than folding 2D UV-space rendering into the main 3D viewport. // // Islands can be laid out by hand, roughly the way Blender's UV editor works: click one to select // it, drag to move it, right-drag or press R to rotate it, S to scale it, both about its own centre. -// Islands are free to overlap -- nothing re-packs them behind the user's back. The texture underneath +// Islands are free to overlap - nothing re-packs them behind the user's back. The texture underneath // is always drawn upright and axis-aligned, and it is the islands that move over it, which is what // makes "rotate this island" a meaningful gesture rather than just spinning the whole texture. // // **Geometry is uploaded in the unwrap's own (raw, mm) coordinates, once**, and each island is drawn // through its own affine matrix passed as a shader uniform. That matters: a patch can easily run to -// a million triangles, and the earlier design -- which pre-transformed every UV on the CPU and -// re-uploaded the whole wireframe on every mouse-move event -- made a drag cost a couple of hundred +// a million triangles, and the earlier design - which pre-transformed every UV on the CPU and +// re-uploaded the whole wireframe on every mouse-move event - made a drag cost a couple of hundred // milliseconds per frame. Moving an island now touches a 2x3 matrix and nothing else. // // Uses the app's single shared wxGLContext (via wxGetApp().init_glcontext(), the same call // View3D/Preview/AssembleView each make in GUI_Preview.cpp) rather than an independent context of // its own, specifically so it can reuse the app's already-registered "flat"/"flat_texture" -// shaders and GLModel as-is -- GLModel::render() looks up its shader via a GUI_App-wide "current +// shaders and GLModel as-is - GLModel::render() looks up its shader via a GUI_App-wide "current // shader", which only means anything for canvases sharing the app's one real GL context. class UVEditorCanvas : public wxGLCanvas { @@ -54,7 +54,7 @@ public: // y basis, translation). using IslandTransform = Eigen::Matrix; - // The unwrap to display, in the unwrap's own mm coordinates -- *not* texture UVs. Changing this + // The unwrap to display, in the unwrap's own mm coordinates - *not* texture UVs. Changing this // is the expensive path (it rebuilds every vertex buffer), so it must only be called when the // unwrap itself changes, never merely because an island moved. Pass an empty `indices` to show // nothing. @@ -71,13 +71,13 @@ public: // The cheap path: one transform per island. Safe to call on every mouse-move of a drag. void set_island_transforms(std::vector transforms); - // One fill colour per island, overriding the default light-green wash -- used to paint the UV + // One fill colour per island, overriding the default light-green wash - used to paint the UV // distortion heatmap over the islands when the gizmo's "Distortion" check mode is on (#7/#14). // Pass empty to go back to the default wash. Cheap: it never touches a vertex buffer. void set_island_fill_colors(std::vector colors); // The layer's own tiling scale and rotation. Needed to map a gesture, which happens in texture-UV - // space, back into the unwrap's mm space -- which is where a TextureIsland's offset actually + // space, back into the unwrap's mm space - which is where a TextureIsland's offset actually // lives (see apply_uv_transform()). The tile settings come along because the background has to // repeat exactly the way the height sampler does, or the pane would stop showing what gets baked. void set_uv_transform(float tiling_scale, float rotation_deg, bool tile_enabled, bool tile_mirrored); @@ -94,7 +94,7 @@ public: // High-level actions the pane toolbar triggers. The canvas handles the view-only ones (framing, // the snap toggle) itself and forwards the rest to whoever owns the island data (the gizmo), via - // the command callback -- the canvas has the selection and the view, the gizmo has the layer. + // the command callback - the canvas has the selection and the view, the gizmo has the layer. enum class Command { FrameAll, ToggleSnap, AverageScale, CutSelectedIsland, ProjectFromView, JoinSelected, UnjoinSelected }; void run_command(Command cmd); using CommandFn = std::function; @@ -106,7 +106,7 @@ public: void set_status_callback(StatusFn fn) { m_on_status = std::move(fn); } // Reports an island edit as it happens. The deltas are *incremental* (one mouse event's worth) - // and already converted into the units a TextureIsland stores -- unwrap mm, degrees, and a scale + // and already converted into the units a TextureIsland stores - unwrap mm, degrees, and a scale // *factor* to multiply the island's existing scale by. They are incremental on purpose: the owner // applies them and hands back fresh transforms, and if the gesture tracked geometry rather than // raw mouse motion that round trip would feed back into itself. `finished` marks the end of a @@ -116,7 +116,7 @@ public: void set_island_edit_callback(IslandEditFn fn) { m_on_island_edit = std::move(fn); } // What a click grabs: a whole island (move/rotate/scale, groups move together), a single vertex, or - // a single edge (both its endpoints). Vertex/Edge are free-form UV editing -- they move the actual + // a single edge (both its endpoints). Vertex/Edge are free-form UV editing - they move the actual // unwrap coordinates, which the owner then folds into the layer's per-vertex UV overrides so the // change is baked, not just shown (see set_vertex_edit_callback). enum class SelectMode { Island, Vertex, Edge }; @@ -177,7 +177,7 @@ private: void move_vertex_raw(int unwrapped_vertex, const Vec2f &delta_uv); Vec2f island_centroid(int island) const; // Converts a delta in texture-UV space into the unwrap's mm space, undoing the layer's scale and - // rotation -- the inverse of what apply_uv_transform() did on the way in. + // rotation - the inverse of what apply_uv_transform() did on the way in. Vec2f uv_delta_to_unwrap(const Vec2f &delta_uv) const; // The correction that would bring the selected island's nearest boundary vertex onto a boundary // vertex of some *other* island, in texture-UV space. Zero if nothing is within reach (#2). @@ -192,7 +192,7 @@ private: std::vector m_transforms; // Per-island fill colour override (distortion heatmap); empty means use the default wash (#7). std::vector m_island_fill_colors; - // Boundary vertices per island, for snapping -- a patch's boundary is a tiny fraction of it, and + // Boundary vertices per island, for snapping - a patch's boundary is a tiny fraction of it, and // rescanning the whole uv array on every snap test would not be. std::vector> m_island_boundary_verts; @@ -203,7 +203,7 @@ private: std::vector m_island_boundary; // outline std::vector m_island_fill; // filled, for the selected island's wash - GLModel m_tile_outline_glmodel; // the texture's first tile, [0,1]^2 -- the "you are here" + GLModel m_tile_outline_glmodel; // the texture's first tile, [0,1]^2 - the "you are here" GLModel m_grid_glmodel; float m_grid_step = 0.f; // the UV step m_grid_glmodel was built for; 0 = not built @@ -290,7 +290,7 @@ private: float m_gesture_last_dist = 0.f; // Rotation is tracked as two running totals over the gesture: the raw mouse rotation, and how much // has actually been applied. With Shift held the applied total is quantised to 15-degree steps - // (Blender-style angle snapping), so the two diverge -- and driving the applied total off the raw + // (Blender-style angle snapping), so the two diverge - and driving the applied total off the raw // one, rather than snapping each incremental delta, is what makes the snap stable instead of // juddering. The raw/applied split also survives crossing +/-180 degrees, which a single wrapped // angle would not. m_rot_applied doubles as the modal-rotate undo amount for Esc.