Compare commits
21 Commits
fix/plugin
...
feature/te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00f778e863 | ||
|
|
dc1e8b6dc3 | ||
|
|
753054974b | ||
|
|
eacc236ccb | ||
|
|
355a21626e | ||
|
|
345369e005 | ||
|
|
18c5d31fb2 | ||
|
|
61d2d4355a | ||
|
|
ab023f3f6d | ||
|
|
8de1ff37bd | ||
|
|
8b93cc5df3 | ||
|
|
b073aa4d23 | ||
|
|
8247514ae2 | ||
|
|
00f55639d3 | ||
|
|
68d754d946 | ||
|
|
dc5a48bdfd | ||
|
|
05083bb6ab | ||
|
|
a393b21642 | ||
|
|
a7c8dcc58d | ||
|
|
3514249197 | ||
|
|
7f2598d0d6 |
421
TEXTURE_DISPLACEMENT.md
Normal file
@@ -0,0 +1,421 @@
|
||||
# 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
|
||||
remembering), and what's still deferred. Written so a fresh session (or a fresh pair of eyes) can
|
||||
pick this up without re-deriving everything from scratch.
|
||||
|
||||
## What it does
|
||||
|
||||
A paint-style gizmo (`GLGizmoTextureDisplacement`) that lets you:
|
||||
- Paint one or more "layers" onto a model's surface, each a height-map texture with its own
|
||||
depth/tiling/rotation/offset/invert/tile-mode/projection-mode/blend-mode.
|
||||
- Pick a texture from a shipped library (`resources/textures/displacement/`) or import your own
|
||||
(saved into `<data_dir>/textures/displacement/`, kept separate so app updates can't clobber it).
|
||||
- Combine overlapping layers with image-editor-style blend modes (Add/Subtract/Multiply/Divide).
|
||||
- Preview the true displaced result live, before baking (background job, not on the UI thread).
|
||||
- Optionally preview via a fast GPU bump-map shader instead (no real geometry movement, just
|
||||
shading) for a lighter-weight alternative.
|
||||
- Bake into real mesh geometry on demand, restricted to the painted area only.
|
||||
- Subdivide a low-poly model first so there are enough vertices to show fine detail.
|
||||
- Unwrap a painted patch with a real CGAL LSCM parameterization and view it in a dedicated,
|
||||
dockable 2D "UV Editor" pane.
|
||||
|
||||
## Architecture
|
||||
|
||||
### 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
|
||||
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).
|
||||
|
||||
### 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.
|
||||
|
||||
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
|
||||
guarantee the index alignment step 3 depends on.
|
||||
2. Area-weighted vertex normals of the **undisplaced** mesh, computed once. Every layer both
|
||||
projects and displaces along these, so a vertex covered by several layers moves along one single
|
||||
well-defined direction.
|
||||
3. For each layer in slot order: deserialize its stored paint mask into a `TriangleSelector` against
|
||||
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)`
|
||||
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
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
### Blend modes
|
||||
|
||||
`TextureBlendMode` {Add, Subtract, Multiply, Divide}, per layer, applied per vertex against the
|
||||
total accumulated by the layers **below** it (lower slots). The quantity blended is a signed
|
||||
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
|
||||
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
|
||||
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×.
|
||||
|
||||
The **lowest painted layer ignores its blend mode**: it has nothing beneath it, and Multiply/Divide
|
||||
against an implicit zero base would annihilate (or blow up) it. Enforced in `build_texture_
|
||||
displacement()` (the first layer to reach a given vertex always folds in additively) and surfaced in
|
||||
the UI, which labels that layer "Base layer" instead of offering a control that silently does nothing.
|
||||
|
||||
### Projection methods
|
||||
|
||||
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
|
||||
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)`)
|
||||
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
|
||||
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
|
||||
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
|
||||
`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
|
||||
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
|
||||
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
|
||||
(`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
|
||||
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
|
||||
(`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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
gizmo's `UVEditorState` include the seam list, so marking a seam (which leaves the paint mask
|
||||
untouched) still forces a re-solve. Like the paint masks, seams are mesh-index-space and so dropped on
|
||||
any topology change.
|
||||
|
||||
Two ways to write to it:
|
||||
- **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
|
||||
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.
|
||||
|
||||
### UV-check overlays (checker / distortion)
|
||||
|
||||
`resources/shaders/{110,140}/texture_displacement_uvcheck.{vs,fs}`, one shader with a `mode` uniform,
|
||||
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
|
||||
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,
|
||||
rebuilt only when the vertex count changes (not per stroke).
|
||||
|
||||
### Tiling
|
||||
|
||||
`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
|
||||
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).
|
||||
|
||||
### Subdivision (`subdivide_mesh_uniform()`)
|
||||
|
||||
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
|
||||
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
|
||||
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**. 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.
|
||||
|
||||
### 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)"
|
||||
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
|
||||
rebuilt at the same cadence as the true-displacement preview (stroke-end/slider-release), using the
|
||||
**live** `TriangleSelector` state (not the flushed model facets), so it doesn't lag by a full model
|
||||
round-trip.
|
||||
|
||||
The perturbed normal is the analytic one for a height field `H = ±depth_mm · h(uv)` displaced along
|
||||
`N` over any orthonormal surface tangent pair `T`/`B`:
|
||||
|
||||
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.
|
||||
|
||||
**Two projection paths (`use_vertex_uv` uniform):**
|
||||
- **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
|
||||
(`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 -
|
||||
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
|
||||
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
|
||||
`det≈0` guard skips it (a localised preview-only artifact, never in the bake).
|
||||
|
||||
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)`.
|
||||
|
||||
### 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.
|
||||
|
||||
### Projection frame overlay (ViewProjected)
|
||||
|
||||
`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
|
||||
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()`)**. 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
|
||||
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:
|
||||
|
||||
- `K = projection · view · (instance · volume)`, i.e. local → clip, the same product the renderer uses.
|
||||
Note `Camera::get_projection_matrix()` is typed `Transform3d` (nominally affine) but its perspective
|
||||
form explicitly writes a `(0, 0, −1, 0)` bottom row into the underlying 4×4, so `clip.w = −z_eye` is
|
||||
genuinely carried. The build therefore multiplies **`.matrix()` products** (plain `Matrix4d`), never
|
||||
`Transform3d` products, which would not compose that row correctly.
|
||||
- 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.
|
||||
- `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
|
||||
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
|
||||
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
|
||||
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
|
||||
reopening keeps it where it was left.
|
||||
|
||||
### UV Editor pane
|
||||
|
||||
`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 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
|
||||
`get_uv_editor_canvas()` returns the inner canvas the gizmo talks to.
|
||||
|
||||
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
|
||||
`"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`. 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
|
||||
*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
|
||||
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.
|
||||
|
||||
## 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
|
||||
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
|
||||
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**
|
||||
- **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.
|
||||
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.
|
||||
|
||||
## File map
|
||||
|
||||
**libslic3r (core, no GUI dependency):**
|
||||
- `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()`
|
||||
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
|
||||
the `Surface_mesh_parameterization` package headers were already present, just unused before now.
|
||||
- `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/
|
||||
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
|
||||
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,
|
||||
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
|
||||
(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,
|
||||
`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
|
||||
|
||||
**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**,
|
||||
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
|
||||
326
TEXTURE_DISPLACEMENT_GUIDE.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# Texture Displacement — Feature & Controls Guide
|
||||
|
||||
Texture Displacement is a paint-style gizmo that stamps height-map textures onto a model's surface
|
||||
and turns them into real relief — engraved or embossed detail — either as a live preview or baked
|
||||
into actual mesh geometry. You paint where the texture applies, stack multiple textures as blended
|
||||
layers, choose how each is projected onto the surface, and (for the unwrap projection) lay the result
|
||||
out by hand in a dedicated 2D **UV Editor** pane.
|
||||
|
||||
This document describes every feature and control. For the internal architecture and algorithms, see
|
||||
`TEXTURE_DISPLACEMENT.md`.
|
||||
|
||||
---
|
||||
|
||||
## Table of contents
|
||||
|
||||
1. [Quick start](#quick-start)
|
||||
2. [Entering the tool](#entering-the-tool)
|
||||
3. [Selection modes](#selection-modes)
|
||||
4. [View modes](#view-modes)
|
||||
5. [Auto update](#auto-update)
|
||||
6. [Texture layers](#texture-layers)
|
||||
7. [Per-layer settings](#per-layer-settings)
|
||||
8. [Projection methods](#projection-methods)
|
||||
9. [The UV Editor](#the-uv-editor)
|
||||
10. [Seams](#seams)
|
||||
11. [Adjust placement (on-model)](#adjust-placement-on-model)
|
||||
12. [Preparing the mesh: Subdivide & Remesh](#preparing-the-mesh-subdivide--remesh)
|
||||
13. [Baking & resetting](#baking--resetting)
|
||||
14. [Controls reference](#controls-reference)
|
||||
15. [Tips & limitations](#tips--limitations)
|
||||
|
||||
---
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Select an object and open the **Texture displacement** gizmo from the left toolbar.
|
||||
2. A texture layer is added automatically. Pick a texture from the layer's picker, or import your own.
|
||||
3. **Paint** the area you want the texture to affect (or press **Select whole model**).
|
||||
4. The relief appears live on the model. Tune **Depth**, **Tile size**, **Rotation**, etc.
|
||||
5. If the model is low-poly, use **Subdivide** or **Remesh** so there are enough vertices for detail.
|
||||
6. Press **Bake** to convert the preview into real geometry, or leave it as a live preview.
|
||||
|
||||
> The tool only ever affects the **painted** area. Everything you don't paint keeps its original
|
||||
> surface, and bake blends the relief seamlessly into it.
|
||||
|
||||
---
|
||||
|
||||
## Entering the tool
|
||||
|
||||
The gizmo lives on the left gizmo toolbar (icon: `toolbar_texture_displacement.svg`). Its settings
|
||||
panel opens beside the toolbar. You can **Dock panel / Undock panel** (top of the panel) to pin it or
|
||||
float it freely over the 3D view, and **Close** at the bottom exits the gizmo.
|
||||
|
||||
When you first open the tool on a never-textured object it starts with **one texture layer already
|
||||
added**, so you can paint straight away.
|
||||
|
||||
---
|
||||
|
||||
## Selection modes
|
||||
|
||||
Choose *how* you paint. All three write into the **active layer's** mask.
|
||||
|
||||
| Mode | What it does |
|
||||
|------|--------------|
|
||||
| **Brush** | Free-hand painting with a round brush. Shows a **Brush size** slider and a **Circle / Sphere** choice (circle = surface disc, sphere = 3D ball that also paints around curves). |
|
||||
| **Face** | Click a single triangle to paint it. |
|
||||
| **Connected area** | Click to flood-fill a region; the **Angle threshold** slider limits how far the fill spreads across changes in surface angle. |
|
||||
|
||||
- **Select whole model** — marks the entire model as painted for the active layer, instead of
|
||||
brushing it by hand.
|
||||
|
||||
---
|
||||
|
||||
## View modes
|
||||
|
||||
A row of icon buttons labelled **View** controls how the painted area is shown. The first four are a
|
||||
radio group; **Wireframe** is an independent toggle. Hover any icon for its tooltip.
|
||||
|
||||
| View | Meaning |
|
||||
|------|---------|
|
||||
| **Normal** | The true displaced geometry — exactly what **Bake** produces. Rebuilt in the background. |
|
||||
| **Fast** | A GPU bump-shaded approximation of the *active layer only*. No real geometry movement — quick to update, not exact. Best while tuning or dragging islands. |
|
||||
| **Checker** | A test grid painted over the unwrap so you can see stretching (squares stay square where the map isn't distorted). |
|
||||
| **Distortion** | A blue→green→red heatmap of how much each area is compressed or stretched in UV space. Needs the **Unwrap (LSCM)** projection. |
|
||||
| **Wireframe** | Overlays the mesh edges (white). Independent of the view above; in **Normal** view it sits on the displaced surface. |
|
||||
|
||||
---
|
||||
|
||||
## Auto update
|
||||
|
||||
**Auto update** (on by default) rebuilds the true displaced geometry as soon as *anything* changes —
|
||||
painting, swapping textures, moving sliders. Turn it off on very heavy models to only rebuild when you
|
||||
release a slider (painting still updates on stroke end).
|
||||
|
||||
---
|
||||
|
||||
## Texture layers
|
||||
|
||||
You can stack up to **8** texture layers. Each has its own independent paint mask, its own texture,
|
||||
and its own parameters, and they combine in slot order like layers in an image editor.
|
||||
|
||||
- **Add a layer** — the **+ icon** to the right of the *Texture layers* heading (reuses the tool icon
|
||||
for now).
|
||||
- **Remove** — the button on each layer's header row.
|
||||
- **Active layer** — click a layer's header (or anywhere in its block) to make it active. The active
|
||||
layer is the one you paint into and the one whose block is tinted. Only one layer is active at a time.
|
||||
- **Erase all** — clears the active layer's paint.
|
||||
|
||||
Each layer shows a texture **picker** (large preview + name). Open it to choose from the shipped
|
||||
library or import your own image (any png/jpg/bmp; it's converted to an 8-bit grayscale height map and
|
||||
copied into your user texture folder so app updates can't overwrite it).
|
||||
|
||||
---
|
||||
|
||||
## Per-layer settings
|
||||
|
||||
| Control | Range / options | What it does |
|
||||
|---------|-----------------|--------------|
|
||||
| **Depth (mm)** | 0.01–10 (log) | Maximum displacement along the surface normal. |
|
||||
| **Tile size (mm)** | 0.2–200 (log) | Physical size of one texture tile on the surface. |
|
||||
| **Rotation** | 0–360° | Rotates the texture on the surface. |
|
||||
| **Midlevel** | 0–10 | The grey level that means "don't move". At 0 the texture only pushes outward; raise it and darker texels cut *inward* (one map both embosses and engraves). 0.5 makes mid-grey neutral. |
|
||||
| **Smoothing** | 0–1 | Blurs the height texture before it displaces — rounds hard edges and removes speckle without needing a softer source image. |
|
||||
| **Edge smoothing** | checkbox + **Edge amount** 0–1 | Fades the relief to flat toward the *edge of the painted area*, so it blends into the surrounding surface. A small amount softens only a thin band at the very edge; the maximum flattens the whole painted face. |
|
||||
| **Invert** | checkbox | Flips the height map (peaks become valleys). |
|
||||
| **Blend** | Add / Subtract / Multiply / Divide | How this layer combines with the layers **below** it where they overlap. Add/Subtract pile relief on or carve it away; Multiply/Divide scale the relief underneath (a mask). The lowest painted layer is the **Base** and always behaves additively. |
|
||||
| **Tile** | checkbox + **Repeat / Mirrored repeat** | When off, the texture is placed once (a decal) instead of repeating. Mirrored repeat flips every other tile to hide seams. |
|
||||
| **Projection** | see below | How the texture is mapped onto the painted surface. |
|
||||
|
||||
> **Midlevel warning:** cutting inward can fold the surface through itself in sharp concave corners or
|
||||
> thin walls. Keep Depth small relative to the feature you're cutting into; the panel warns when a deep
|
||||
> inward setting is risky.
|
||||
|
||||
---
|
||||
|
||||
## Projection methods
|
||||
|
||||
How the 2D texture is wrapped onto the 3D painted area.
|
||||
|
||||
| Method | Best for | Notes |
|
||||
|--------|----------|-------|
|
||||
| **Triplanar (blended)** | Patches wrapping around edges | Projects from all three axes at once and blends, so there's no seam across a sharp edge. |
|
||||
| **Cylindrical** | Round, tube-like selections | Wraps the texture around the patch's own centre/axis. |
|
||||
| **Spherical** | Ball-like selections | Longitude/latitude wrap around the patch centre. |
|
||||
| **Unwrap (LSCM)** | Flat, controlled layout | A real conformal unwrap. Cuts the area into pieces at sharp edges (see **Seam angle**), flattens each, and lets you lay them out by hand in the **UV Editor**. Unlocks Checker/Distortion, seams, and island editing. |
|
||||
| **From view** | Decals / slide-projector look | Projects straight onto the surface from the current camera direction. Use **Capture current view** to re-lay it from wherever you're looking. |
|
||||
|
||||
### LSCM-only controls
|
||||
|
||||
These appear when a layer uses **Unwrap (LSCM)**:
|
||||
|
||||
- **Seam angle** (5–90°) — edges sharper than this are cut so each piece lies flat. Lower cuts more
|
||||
(less stretching, more seams); raise to keep more in one piece. A box's 90° corners are cut by
|
||||
default. *Ignored once you've marked any seam by hand* (your seams then define the pieces).
|
||||
- **Connect islands** (on by default) — lays the unwrap out as a **connected net**: pieces that share
|
||||
an edge are unfolded next to each other (a cube becomes a joined net instead of six loose squares).
|
||||
They stay separate islands, so you can still move any of them by hand. Turn off for the classic
|
||||
packed-grid layout.
|
||||
- **Open UV editor** — shows the flattened unwrap in a side pane (see below). Opens *only* when you
|
||||
turn this on — it never pops up on its own.
|
||||
- **Mark seams** / **Path** / **Clear seams** — see [Seams](#seams).
|
||||
- An **Unwrap: N islands, F faces, V verts** read-out tells you what the unwrap actually produced.
|
||||
|
||||
---
|
||||
|
||||
## The UV Editor
|
||||
|
||||
A dockable 2D pane (enable **Open UV editor** on an LSCM layer) showing the flattened unwrap over the
|
||||
height texture. Islands are the flattened pieces; you can rearrange them freely — nothing re-packs them
|
||||
behind your back. Moving an island updates the model **live** (in Fast view it tracks the cursor
|
||||
smoothly, via a shader uniform — no rebuild until you release).
|
||||
|
||||
### Navigation
|
||||
|
||||
| Action | Control |
|
||||
|--------|---------|
|
||||
| Pan | Middle-drag |
|
||||
| Zoom | Mouse wheel (zooms about the cursor) |
|
||||
| Frame everything | **Home** or **F**, or the **Frame** toolbar button |
|
||||
|
||||
### Editing an island
|
||||
|
||||
| Action | Control |
|
||||
|--------|---------|
|
||||
| Select | Left-click an island |
|
||||
| Move | Left-drag |
|
||||
| Rotate | Right-drag, or press **R** then move the mouse (click/Enter to confirm, Esc to cancel) |
|
||||
| Rotate snapped | Hold **Shift** while rotating — snaps to **global** 15° marks (0/15/30…). A protractor dial with tick marks and the current angle is shown. |
|
||||
| Scale | Press **S** then move the mouse (click/Enter to confirm, Esc to cancel) |
|
||||
| Undo / Redo | **Ctrl+Z** / **Ctrl+Shift+Z** or **Ctrl+Y** |
|
||||
|
||||
The **selected** island gets a bold light-green outline and a brighter wireframe; unselected islands
|
||||
are a translucent light-green wash. The texture underneath repeats exactly as it will when baked.
|
||||
A **status line** along the bottom always names the current gesture and the shortcuts in play.
|
||||
|
||||
### Toolbar
|
||||
|
||||
| Button | Action |
|
||||
|--------|--------|
|
||||
| **Frame** | Frame all islands (same as Home). |
|
||||
| **Snap** | Toggle magnetic snapping — a dragged island sticks its boundary to a neighbour's when they come close. |
|
||||
| **Avg scale** | Give every island the same texel density (Blender's "Average Islands Scale"). |
|
||||
| **Cut** | Split the selected island across its long axis (useful for very long islands). |
|
||||
| **Join** | Unfold the selected island onto its nearest neighbour along their shared edge — keeps both as separate islands with their own borders. |
|
||||
| **Unjoin** | Send the selected island back to its own packed position. |
|
||||
|
||||
> **Checker / Distortion in the UV editor:** selecting those View modes also colours the UV pane — a
|
||||
> checker background, or a per-island distortion heatmap — so you can judge stretch in 2D as well as
|
||||
> on the model.
|
||||
|
||||
---
|
||||
|
||||
## Seams
|
||||
|
||||
Seams are edges the unwrap is forced to cut along, on top of whatever the Seam angle cuts — the
|
||||
Blender "mark seam" workflow. They let you control exactly where the unwrap splits.
|
||||
|
||||
Enable **Mark seams** on an active LSCM layer, then:
|
||||
|
||||
- **Click an edge** on the model to mark it (it turns **red**); click a red edge again to unmark it.
|
||||
The edge under the cursor is highlighted **yellow** so you can see what a click will toggle.
|
||||
- **Path mode** (the **Path** checkbox) — for dense meshes where clicking each edge is tedious: click a
|
||||
start point, then an end point, and the whole **shortest path** between them is seamed at once. It
|
||||
chains (each click extends from the last point); the start vertex is shown in **green**.
|
||||
- **Ctrl+drag** rotates/pans the camera while in seam mode.
|
||||
- **Clear seams** removes them all.
|
||||
|
||||
Once any seam is marked, the automatic Seam-angle cutting is disabled so *your* seams define the
|
||||
islands — pieces you leave un-seamed merge together.
|
||||
|
||||
---
|
||||
|
||||
## Adjust placement (on-model)
|
||||
|
||||
**Adjust placement** (on an active layer) lets you position the texture by dragging a handle on the
|
||||
model instead of nudging the Rotation/offset numbers. The handle is a flat panel in the patch's
|
||||
tangent plane (drag anywhere on it to move freely) plus U/V arrows for single-axis nudges. It's
|
||||
anchored to the painted patch, so paint something first.
|
||||
|
||||
---
|
||||
|
||||
## Preparing the mesh: Subdivide & Remesh
|
||||
|
||||
Displacement can only move vertices that exist, so a coarse model needs more of them first.
|
||||
|
||||
### Subdivide
|
||||
|
||||
Splits every triangle into four, **1–5 times** (each step roughly quadruples the triangle count).
|
||||
|
||||
- **Subdivide steps** (1–5) — how many times to split.
|
||||
- **Preview subdivision** — shows the result as a **cyan wireframe** without changing the model.
|
||||
- **Apply** — commits the subdivision to the geometry.
|
||||
- **Done** — ends the preview and leaves the model as it is.
|
||||
|
||||
### Remesh
|
||||
|
||||
Rebuilds the whole model with triangles close to a target edge length — evens out a mesh with wildly
|
||||
varying triangle sizes (CGAL isotropic remeshing).
|
||||
|
||||
- **Target edge (mm)** — desired triangle edge length (seeded to the model's current average).
|
||||
- **Remesh** — splits the big triangles and merges the small ones to that size.
|
||||
|
||||
> Both Subdivide-Apply and Remesh **replace the geometry** and clear any *not-yet-baked* paint on it
|
||||
> (already-baked relief is kept). If you had the mesh **Wireframe** on before, it stays on afterward.
|
||||
|
||||
---
|
||||
|
||||
## Baking & resetting
|
||||
|
||||
- **Bake** — converts the current preview into real, permanent mesh geometry, restricted to the
|
||||
painted area. Runs in the background; the button shows *Baking…* while it works.
|
||||
- **Erase all** — clears the active layer's paint.
|
||||
|
||||
Baking is the exact same algorithm as the **Normal** preview, so what you see is what you get.
|
||||
|
||||
---
|
||||
|
||||
## Controls reference
|
||||
|
||||
### Mouse — 3D view (while painting)
|
||||
|
||||
| Input | Action |
|
||||
|-------|--------|
|
||||
| Left-drag | Paint the active layer |
|
||||
| Ctrl + drag | Rotate / pan the camera (works in seam mode too) |
|
||||
| Wheel | Zoom |
|
||||
|
||||
### Mouse & keys — UV Editor
|
||||
|
||||
| Input | Action |
|
||||
|-------|--------|
|
||||
| Left-click | Select island |
|
||||
| Left-drag | Move island |
|
||||
| Right-drag | Rotate island |
|
||||
| **R** / **S** | Modal rotate / scale (mouse drives it, click or Enter confirms, Esc cancels) |
|
||||
| **Shift** (while rotating) | Snap to global 15° marks |
|
||||
| Middle-drag | Pan |
|
||||
| Wheel | Zoom about cursor |
|
||||
| **Home** / **F** | Frame all islands |
|
||||
| **Ctrl+Z** / **Ctrl+Shift+Z** / **Ctrl+Y** | Undo / redo |
|
||||
|
||||
### Seam mode
|
||||
|
||||
| Input | Action |
|
||||
|-------|--------|
|
||||
| Click edge | Mark / unmark a seam (yellow = hover, red = marked) |
|
||||
| Click (Path mode) | Set start, then seam the shortest path to the next click |
|
||||
| Ctrl + drag | Rotate / pan camera |
|
||||
|
||||
---
|
||||
|
||||
## Tips & limitations
|
||||
|
||||
- **Paint first, then bake.** The preview is free to explore; only Bake changes the real mesh.
|
||||
- **Not enough detail?** Subdivide or Remesh before painting fine textures.
|
||||
- **Inward cuts** (high Midlevel + big Depth) can self-intersect on thin walls or sharp concave
|
||||
corners — keep Depth modest there.
|
||||
- **Fast vs Normal:** Fast preview shades a bump and shows only the active layer; use it for quick
|
||||
tuning and smooth UV dragging, but trust **Normal**/**Bake** for the exact result.
|
||||
- **Topology changes drop unbaked paint.** Subdivide-Apply, Remesh, and Simplify replace the mesh, and
|
||||
texture-displacement paint isn't remapped across that change (already-baked relief is unaffected).
|
||||
- **Island placements** are tied to the current unwrap. Re-painting or changing the Seam angle can
|
||||
re-segment the charts and renumber them, so a re-unwrap re-lays the connected net and discards
|
||||
hand placements made before it.
|
||||
- **Connect islands** is on by default; turn it off (per layer) for the classic packed-grid layout, or
|
||||
if an unfold looks wrong on an unusual mesh.
|
||||
120
resources/images/texture_displacement_add.svg
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_add.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="1.625"
|
||||
inkscape:cy="10.84375"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" />
|
||||
<path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" />
|
||||
<path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" />
|
||||
<path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" />
|
||||
<path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" />
|
||||
<path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" />
|
||||
<path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" />
|
||||
<rect
|
||||
style="fill:#009688;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect1"
|
||||
width="22.449276"
|
||||
height="1.000026"
|
||||
x="8.7753611"
|
||||
y="19.499987"
|
||||
ry="0.50001299" />
|
||||
<path
|
||||
style="fill:#009688;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect2"
|
||||
width="22.449276"
|
||||
height="1.000026"
|
||||
x="8.775362"
|
||||
y="-20.500011"
|
||||
transform="rotate(90)"
|
||||
sodipodi:type="rect"
|
||||
inkscape:path-effect="#path-effect3"
|
||||
d="M 9.2741959,-20.500011 H 30.737917 a 0.48672135,0.48672135 45 0 1 0.486721,0.486721 0.51978097,0.51978097 135.70952 0 1 -0.526178,0.513305 H 9.2753766 A 0.50001454,0.50001454 45 0 1 8.775362,-20 v -0.0012 a 0.49883385,0.49883385 135 0 1 0.4988339,-0.498833 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
109
resources/images/texture_displacement_add_negative.svg
Normal file
@@ -0,0 +1,109 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_add_negative.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="45.254834"
|
||||
inkscape:cx="16.46233"
|
||||
inkscape:cy="19.301805"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" />
|
||||
<path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" />
|
||||
<path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" />
|
||||
<path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" />
|
||||
<path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" />
|
||||
<path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" />
|
||||
<path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" />
|
||||
<rect
|
||||
style="fill:#009688;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect1"
|
||||
width="22.449276"
|
||||
height="1.000026"
|
||||
x="8.7753611"
|
||||
y="19.499987"
|
||||
ry="0.50001299" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
330
resources/images/texture_displacement_avg_scale.svg
Normal file
@@ -0,0 +1,330 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_group.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect30"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.2615305,0,1 @ F,0,0,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect29"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect27"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect25"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect24"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect23"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9-1"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="25.28125"
|
||||
inkscape:cy="18.359375"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><g
|
||||
id="g31"
|
||||
transform="translate(-1.2926181,-1.328125)"><path
|
||||
d="m 11.472656,12.726388 v 6.047224 A 1.2263883,1.2263883 45 0 0 12.699044,20 h 6.074568 A 1.2263883,1.2263883 135 0 0 20,18.773612 V 12.726388 A 1.2263883,1.2263883 45 0 0 18.773612,11.5 h -6.074568 a 1.2263883,1.2263883 135 0 0 -1.226388,1.226388 z m 1.730947,-0.0096 h 5.065451 a 0.47508735,0.47508735 45 0 1 0.475087,0.475087 v 5.303732 a 0.47508735,0.47508735 135 0 1 -0.475087,0.475087 h -5.065451 a 0.47508735,0.47508735 45 0 1 -0.475087,-0.475087 l 0,-5.303732 a 0.47508735,0.47508735 135 0 1 0.475087,-0.475087 z"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
id="path30"
|
||||
inkscape:path-effect="#path-effect31"
|
||||
inkscape:original-d="M 11.472656,11.5 V 20 H 20 v -8.5 z m 1.25586,1.216797 h 6.015625 v 6.253906 h -6.015625 z" /><path
|
||||
d="m 11.472656,12.726388 v 6.047224 A 1.2263883,1.2263883 45 0 0 12.699044,20 h 6.074568 A 1.2263883,1.2263883 135 0 0 20,18.773612 V 12.726388 A 1.2263883,1.2263883 45 0 0 18.773612,11.5 h -6.074568 a 1.2263883,1.2263883 135 0 0 -1.226388,1.226388 z m 1.730947,-0.0096 h 5.065451 a 0.47508735,0.47508735 45 0 1 0.475087,0.475087 v 5.303732 a 0.47508735,0.47508735 135 0 1 -0.475087,0.475087 h -5.065451 a 0.47508735,0.47508735 45 0 1 -0.475087,-0.475087 l 0,-5.303732 a 0.47508735,0.47508735 135 0 1 0.475087,-0.475087 z"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
id="path30-4"
|
||||
inkscape:path-effect="#path-effect31-9"
|
||||
inkscape:original-d="M 11.472656,11.5 V 20 H 20 v -8.5 z m 1.25586,1.216797 h 6.015625 v 6.253906 h -6.015625 z"
|
||||
transform="translate(11.058594)" /><path
|
||||
d="m 11.472656,12.726388 v 6.047224 A 1.2263883,1.2263883 45 0 0 12.699044,20 h 6.074568 A 1.2263883,1.2263883 135 0 0 20,18.773612 V 12.726388 A 1.2263883,1.2263883 45 0 0 18.773612,11.5 h -6.074568 a 1.2263883,1.2263883 135 0 0 -1.226388,1.226388 z m 1.730947,-0.0096 h 5.065451 a 0.47508735,0.47508735 45 0 1 0.475087,0.475087 v 5.303732 a 0.47508735,0.47508735 135 0 1 -0.475087,0.475087 h -5.065451 a 0.47508735,0.47508735 45 0 1 -0.475087,-0.475087 l 0,-5.303732 a 0.47508735,0.47508735 135 0 1 0.475087,-0.475087 z"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
id="path30-4-3"
|
||||
inkscape:path-effect="#path-effect31-9-1"
|
||||
inkscape:original-d="M 11.472656,11.5 V 20 H 20 v -8.5 z m 1.25586,1.216797 h 6.015625 v 6.253906 h -6.015625 z"
|
||||
transform="translate(0,11.15625)" /></g></svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
165
resources/images/texture_displacement_checker.svg
Normal file
@@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_checker.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect251"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.896875,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect229"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,0.49844035,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.48399193,0,1 @ F,0,0,1,0,0.48399193,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49844035,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect206"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.0051443,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0058593,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0051442,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.003906,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect121"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.97194886,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.9540633,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.94238026,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect48"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.0055868,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.4911902,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0376412,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect47"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="22.627417"
|
||||
inkscape:cx="45.519999"
|
||||
inkscape:cy="5.4579805"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="M 19.973012,35.500013 H 5.9730109 a 1.5,1.5 0 0 1 -1.5,-1.500019 V 6.000006 a 1.5,1.5 0 0 1 1.5,-1.500019 H 33.973006 a 1.5,1.5 0 0 1 1.500019,1.500019 v 12.999987 a 0.5000126,0.5000126 0 0 1 -1.000025,0 V 6.000006 A 0.49999998,0.49999998 0 0 0 33.973006,5.500012 H 5.9730109 a 0.49999998,0.49999998 0 0 0 -0.499997,0.499994 v 27.999988 a 0.49999998,0.49999998 0 0 0 0.499997,0.499993 H 19.973012 a 0.500013,0.500013 0 0 1 0,1.000026 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999;fill-opacity:1"
|
||||
id="path4-6" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect40"
|
||||
width="15.363631"
|
||||
height="1.000026"
|
||||
x="18.609375"
|
||||
y="34.499989" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect41"
|
||||
width="1.000025"
|
||||
height="11.921875"
|
||||
x="34.473"
|
||||
y="18.546875" />
|
||||
<path
|
||||
id="path251"
|
||||
style="fill:#009688;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
d="m 7.8085938,8.3265625 v 0.103125 10.8320315 1 10.832031 a 1,1 45 0 0 1,1 h 10.8320312 1 10.832031 a 1,1 135 0 0 1,-1 v -10.832031 -1 -10.8320315 a 1,1 45 0 0 -1,-1 h -10.832031 -1 -10.8320312 -0.103125 a 0.896875,0.896875 135 0 0 -0.896875,0.896875 z M 20.640625,8.4296875 H 31.472656 V 19.261719 H 20.640625 Z M 8.8085938,20.261719 H 19.640625 V 31.09375 H 8.8085938 Z"
|
||||
inkscape:path-effect="#path-effect251"
|
||||
inkscape:original-d="m 7.8085938,7.4296875 v 1 10.8320315 1 10.832031 1 h 1 10.8320312 1 10.832031 1 v -1 -10.832031 -1 -10.8320315 -1 h -1 -10.832031 -1 -10.8320312 z m 12.8320312,1 H 31.472656 V 19.261719 H 20.640625 Z M 8.8085938,20.261719 H 19.640625 V 31.09375 H 8.8085938 Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
115
resources/images/texture_displacement_connected_area.svg
Normal file
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_connected_area.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect121"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.97194886,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.9540633,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.94238026,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect48"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.0055868,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.4911902,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0376412,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect47"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="5.90625"
|
||||
inkscape:cy="27.609375"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="M 19.973012,35.500013 H 5.9730109 a 1.5,1.5 0 0 1 -1.5,-1.500019 V 6.000006 a 1.5,1.5 0 0 1 1.5,-1.500019 H 33.973006 a 1.5,1.5 0 0 1 1.500019,1.500019 v 12.999987 a 0.5000126,0.5000126 0 0 1 -1.000025,0 V 6.000006 A 0.49999998,0.49999998 0 0 0 33.973006,5.500012 H 5.9730109 a 0.49999998,0.49999998 0 0 0 -0.499997,0.499994 v 27.999988 a 0.49999998,0.49999998 0 0 0 0.499997,0.499993 H 19.973012 a 0.500013,0.500013 0 0 1 0,1.000026 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999;fill-opacity:1"
|
||||
id="path4-6" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect40"
|
||||
width="15.363631"
|
||||
height="1.000026"
|
||||
x="18.609375"
|
||||
y="34.499989" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect41"
|
||||
width="1.000025"
|
||||
height="11.921875"
|
||||
x="34.473"
|
||||
y="18.546875" />
|
||||
<path
|
||||
id="path89"
|
||||
style="fill:#009688;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
d="m 7.8638887,10.084132 c 0.00101,0.01203 0.00272,0.02557 0.00526,0.04087 l 0.035156,0.203125 2.1367187,12.847656 0.214843,1.289063 0.982422,5.90039 0.0039,0.0293 0.002,0.04492 a 1.1174189,1.1174189 31.375688 0 0 1.274062,0.776949 l 1.309971,-0.08942 0.283199,-0.01758 0.265624,-0.01563 15.117188,-1.078125 a 1.107441,1.107441 135.10954 0 0 1.023438,-1.019532 l 0,0 0.427734,-5.447266 0.02539,-0.322266 0.06055,-0.755859 0.02344,-0.300781 0.789062,-10.052734 0.0059,-0.08203 c 5.2e-5,-6.47e-4 1.04e-4,-0.0013 1.56e-4,-0.0019 0.04173,-0.518787 -0.346547,-0.972695 -0.865395,-1.013686 L 20.908203,10.216797 19.798828,10.128906 8.6777344,9.2421875 C 8.1437829,9.2001186 7.8209269,9.5718807 7.8638887,10.084132 Z m 2.1575953,0.212743 8.246094,0.65625 0.863281,0.06836 -1.660156,2.21875 -0.113281,0.152344 -0.09766,0.128906 -0.128907,0.173828 -0.238281,-0.113281 -3.402344,-1.626953 z m -1.0800778,0.527344 6.3183598,3.021484 1.294922,0.619141 -0.109376,0.146484 -5.623046,7.519531 z m 11.2988278,0.285156 7.986328,0.636719 -9.626953,1.558594 z M 30.886719,12.271484 30.132812,21.875 25.191406,18.59375 19.574219,14.863281 18.654297,14.253906 Z m -12.990235,2.609375 1.208985,0.802735 10.603515,7.042968 -16.998046,7.029297 5.11914,-14.68164 z m -1.455078,1.310547 -4.472656,12.826172 -0.931641,-5.597656 z m 13.552735,7.4375 -0.425782,5.4375 -15.425781,1.119141 z m -17.699219,7.320313 -0.0039,0.0098 v -0.0078 z"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
|
||||
inkscape:original-d="M 7.7109375,9.1660156 C 8.1218019,10.255395 7.816209,9.8053782 7.8691443,10.125 l 0.035156,0.203125 2.1367187,12.847656 0.214843,1.289063 0.982422,5.90039 0.0039,0.0293 0.002,0.04492 0.152345,0.853515 2.431688,-0.165982 0.283199,-0.01758 0.265624,-0.01563 15.117188,-1.078125 0.949218,-0.06836 0.07422,-0.951172 0.427734,-5.447266 0.02539,-0.322266 0.06055,-0.755859 0.02344,-0.300781 0.789062,-10.052734 0.0059,-0.08203 c 0.02524,-0.313762 0.04951,-0.627607 0.07421,-0.941408 L 30.984458,11.019565 20.908203,10.216797 19.798828,10.128906 8.6777344,9.2421875 Z m 2.3105465,1.1308594 8.246094,0.65625 0.863281,0.06836 -1.660156,2.21875 -0.113281,0.152344 -0.09766,0.128906 -0.128907,0.173828 -0.238281,-0.113281 -3.402344,-1.626953 z m -1.0800778,0.527344 6.3183598,3.021484 1.294922,0.619141 -0.109376,0.146484 -5.623046,7.519531 z m 11.2988278,0.285156 7.986328,0.636719 -9.626953,1.558594 z M 30.886719,12.271484 30.132812,21.875 25.191406,18.59375 19.574219,14.863281 18.654297,14.253906 Z m -12.990235,2.609375 1.208985,0.802735 10.603515,7.042968 -16.998046,7.029297 5.11914,-14.68164 z m -1.455078,1.310547 -4.472656,12.826172 -0.931641,-5.597656 z m 13.552735,7.4375 -0.425782,5.4375 -15.425781,1.119141 z m -17.699219,7.320313 -0.0039,0.0098 v -0.0078 z"
|
||||
inkscape:path-effect="#path-effect121" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.0 KiB |
121
resources/images/texture_displacement_cross.svg
Normal file
@@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_cross.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="56.75"
|
||||
inkscape:cy="8.65625"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" />
|
||||
<path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" />
|
||||
<path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" />
|
||||
<path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" />
|
||||
<path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" />
|
||||
<path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" />
|
||||
<path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" />
|
||||
<rect
|
||||
style="fill:#009688;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect3"
|
||||
width="24.968962"
|
||||
height="0.96882033"
|
||||
x="15.762758"
|
||||
y="-0.50001258"
|
||||
ry="0"
|
||||
rx="0.50001299"
|
||||
transform="rotate(45)" />
|
||||
<rect
|
||||
style="fill:#009688;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect4"
|
||||
width="24.968962"
|
||||
height="0.96882033"
|
||||
x="-12.500083"
|
||||
y="-28.731649"
|
||||
ry="0.50001299"
|
||||
rx="0.50001299"
|
||||
transform="rotate(135)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.1 KiB |
359
resources/images/texture_displacement_cut.svg
Normal file
@@ -0,0 +1,359 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_cut.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect34"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 | F,0,0,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect33"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect30"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.2615305,0,1 @ F,0,0,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect29"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect27"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect25"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect24"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect23"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9-1"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="15.375"
|
||||
inkscape:cy="16.140625"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><path
|
||||
d="m 16.082031,8.7625 v 22.475 a 0.965625,0.965625 45 0 0 0.965625,0.965625 h 5.85 A 0.965625,0.965625 135 0 0 23.863281,31.2375 V 8.7625 A 0.965625,0.965625 45 0 0 22.897656,7.796875 h -5.85 A 0.965625,0.965625 135 0 0 16.082031,8.7625 Z m 1.325781,-0.032031 h 5.184376 a 0.3257813,0.3257813 45 0 1 0.325781,0.3257813 V 30.94375 a 0.3257813,0.3257813 135 0 1 -0.325781,0.325781 H 17.407812 A 0.3257813,0.3257813 45 0 1 17.082031,30.94375 V 9.0562501 a 0.3257813,0.3257813 135 0 1 0.325781,-0.3257813 z"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
id="path33"
|
||||
inkscape:path-effect="#path-effect34"
|
||||
inkscape:original-d="m 16.082031,7.796875 v 24.40625 h 7.78125 V 7.796875 Z m 1,0.9335938 h 5.835938 V 31.269531 h -5.835938 z" /><path
|
||||
style="fill:#009688;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect32"
|
||||
width="1.0000267"
|
||||
height="22.719467"
|
||||
x="27.527292"
|
||||
y="-12.430544"
|
||||
transform="rotate(47.337307)"
|
||||
inkscape:path-effect="#path-effect33"
|
||||
sodipodi:type="rect"
|
||||
d="m 28.027306,-12.430544 c 0.277007,0 0.500013,0.223006 0.500013,0.500013 V 9.7889099 c 0,0.2770071 -0.223006,0.5000131 -0.500013,0.5000131 -0.277008,0 -0.500014,-0.223006 -0.500014,-0.5000131 V -11.930531 c 0,-0.277007 0.223006,-0.500013 0.500014,-0.500013 z"
|
||||
ry="0.50001335" /></svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
197
resources/images/texture_displacement_distortion.svg
Normal file
@@ -0,0 +1,197 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_distortion.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect447"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.92418405,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.7580314,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.3849613,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5585937,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,0.39312043,0,1 @ F,0,0,1,0,0.39312043,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.39312043,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.53242312,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.32768312,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect446"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect445"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5585937,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect229"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,0.49844035,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.48399193,0,1 @ F,0,0,1,0,0.48399193,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49844035,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect206"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.0051443,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0058593,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0051442,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.003906,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect121"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.97194886,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.9540633,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.94238026,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect48"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.0055868,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.4911902,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0376412,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect47"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="45.254834"
|
||||
inkscape:cx="19.489631"
|
||||
inkscape:cy="18.329534"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="M 19.973012,35.500013 H 5.9730109 a 1.5,1.5 0 0 1 -1.5,-1.500019 V 6.000006 a 1.5,1.5 0 0 1 1.5,-1.500019 H 33.973006 a 1.5,1.5 0 0 1 1.500019,1.500019 v 12.999987 a 0.5000126,0.5000126 0 0 1 -1.000025,0 V 6.000006 A 0.49999998,0.49999998 0 0 0 33.973006,5.500012 H 5.9730109 a 0.49999998,0.49999998 0 0 0 -0.499997,0.499994 v 27.999988 a 0.49999998,0.49999998 0 0 0 0.499997,0.499993 H 19.973012 a 0.500013,0.500013 0 0 1 0,1.000026 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999;fill-opacity:1"
|
||||
id="path4-6" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect40"
|
||||
width="15.363631"
|
||||
height="1.000026"
|
||||
x="18.609375"
|
||||
y="34.499989" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect41"
|
||||
width="1.000025"
|
||||
height="11.921875"
|
||||
x="34.473"
|
||||
y="18.546875" />
|
||||
<path
|
||||
id="path392"
|
||||
style="fill:#009688;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
d="M 19.810547 7.4824219 A 4.0262158 4.0262158 0 0 0 18.550781 7.7421875 L 10.164062 10.806641 L 9.8007812 10.935547 A 1.2813952 1.2813952 0 0 0 8.9648438 11.9375 L 8.9023438 12.318359 L 8.3183594 15.894531 L 7.8710938 18.640625 A 4.753955 4.753955 0 0 0 7.8085938 19.40625 L 7.8085938 19.746094 L 7.8085938 19.761719 L 7.8085938 20.261719 L 7.8085938 20.642578 L 7.8085938 21.146484 L 7.8085938 26.261719 L 7.8085938 31.09375 L 7.8085938 31.236328 L 7.8085938 31.535156 A 0.5585937 0.5585937 0 0 0 8.3671875 32.09375 L 8.8085938 32.09375 L 18.650391 32.09375 L 19.640625 32.09375 L 20.064453 32.09375 L 20.640625 32.09375 L 31.472656 32.09375 A 1 1 0 0 0 32.472656 31.09375 L 32.472656 26.03125 L 32.472656 21.408203 A 4.849149 4.849149 0 0 0 32.410156 20.626953 L 32.349609 20.261719 L 32.347656 20.261719 L 32.283203 19.873047 L 32.183594 19.261719 L 32.15625 19.085938 L 32.085938 18.658203 L 31.472656 14.904297 L 31.050781 12.318359 L 30.953125 11.714844 A 0.90002024 0.90002024 0 0 0 30.365234 11.011719 L 29.816406 10.816406 L 29.789062 10.806641 L 21.207031 7.7480469 L 20.875 7.625 A 2.3640678 2.3640678 0 0 0 20.240234 7.4882812 A 4.0262158 4.0262158 0 0 0 19.810547 7.4824219 z M 20.640625 8.6074219 L 28.939453 11.564453 L 20.640625 18.652344 L 20.640625 8.6074219 z M 18.722656 8.8183594 L 8.8808594 18.660156 L 9.9785156 11.933594 L 18.722656 8.8183594 z M 19.640625 9.3144531 L 19.640625 19.261719 L 9.6933594 19.261719 L 19.640625 9.3144531 z M 29.984375 11.988281 L 31.171875 19.261719 L 31.173828 19.261719 L 31.216797 19.525391 L 31.335938 20.261719 L 31.333984 20.261719 L 31.214844 19.527344 L 31.171875 19.261719 L 21.466797 19.261719 L 29.984375 11.988281 z M 31.472656 19.261719 L 31.480469 19.261719 L 31.472656 19.269531 L 31.472656 19.261719 z M 8.8085938 20.261719 L 18.783203 20.261719 L 8.8085938 30.236328 L 8.8085938 25.404297 L 8.8085938 20.261719 z M 20.640625 20.261719 L 30.480469 20.261719 L 20.640625 30.103516 L 20.640625 20.261719 z M 31.414062 20.742188 L 31.472656 21.09375 L 31.472656 31.09375 L 21.064453 31.09375 L 31.412109 20.744141 L 31.414062 20.742188 z M 19.640625 20.818359 L 19.640625 31.09375 L 19.650391 31.09375 L 19.640625 31.103516 L 19.640625 31.09375 L 9.3671875 31.09375 L 19.640625 20.818359 z " />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 17 KiB |
132
resources/images/texture_displacement_fast_preview.svg
Normal file
@@ -0,0 +1,132 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_fast_preview.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect206"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.0051443,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0058593,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0051442,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.003906,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect121"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.97194886,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.9540633,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.94238026,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect48"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.0055868,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.4911902,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0376412,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect47"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="18.875"
|
||||
inkscape:cy="8.734375"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="M 19.973012,35.500013 H 5.9730109 a 1.5,1.5 0 0 1 -1.5,-1.500019 V 6.000006 a 1.5,1.5 0 0 1 1.5,-1.500019 H 33.973006 a 1.5,1.5 0 0 1 1.500019,1.500019 v 12.999987 a 0.5000126,0.5000126 0 0 1 -1.000025,0 V 6.000006 A 0.49999998,0.49999998 0 0 0 33.973006,5.500012 H 5.9730109 a 0.49999998,0.49999998 0 0 0 -0.499997,0.499994 v 27.999988 a 0.49999998,0.49999998 0 0 0 0.499997,0.499993 H 19.973012 a 0.500013,0.500013 0 0 1 0,1.000026 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999;fill-opacity:1"
|
||||
id="path4-6" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect40"
|
||||
width="15.363631"
|
||||
height="1.000026"
|
||||
x="18.609375"
|
||||
y="34.499989" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect41"
|
||||
width="1.000025"
|
||||
height="11.921875"
|
||||
x="34.473"
|
||||
y="18.546875" />
|
||||
<path
|
||||
id="path150"
|
||||
style="fill:#009688;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
d="m 10.849609,11.300781 -0.208984,2.06836 -0.101563,1 -0.259765,2.546875 -0.101563,1 -0.2695309,2.664062 -0.1015625,1 -0.2714844,2.66211 -0.1015624,1 -0.2578126,2.546874 -0.1015624,1 -0.2558594,2.507813 a 0.90855311,0.90855311 47.877062 0 0 0.9042968,1 h 0 3.9492188 1.001953 4.632813 1 4.964843 1.001954 4.021484 l 7.15e-4,0 a 0.90823039,0.90823039 132.10041 0 0 0.903582,-1 l -0.25586,-2.507813 -0.101562,-1 -0.257813,-2.546874 -0.101562,-1 -0.271484,-2.66211 -0.101563,-1 -0.269531,-2.664062 -0.101563,-1 -0.259765,-2.546875 -0.101563,-1 -0.210937,-2.06836 -1.25e-4,-0.0012 a 1.1110301,1.1110301 42.100377 0 0 -1.105344,-0.998768 h -3.390625 -1.003906 -3.359375 -1 -3.027344 -1.001953 -3.320313 -7.15e-4 a 1.1124005,1.1124005 137.89962 0 0 -1.106707,1 z m 1.00586,0 h 3.349609 l -0.152344,2.06836 h -3.40625 z m 4.351562,0 h 3.09961 1 3.43164 l 0.152344,2.06836 h -3.583984 -1 -3.251953 z m 8.535157,0 h 3.419921 l 0.210938,2.06836 h -3.480469 z m -13.197266,3.06836 h 3.435547 1.001953 3.324219 v 2.546875 h -3.509766 -1.001953 -3.509766 z m 8.761719,0 h 3.65625 1.003906 3.507812 l 0.257813,2.546875 h -3.580078 -1.003906 -3.841797 z m -9.123047,3.546875 h 3.537109 l -0.193359,2.664062 h -3.615235 z m 4.541015,0 h 3.582032 1 3.914062 l 0.195313,2.664062 h -4.109375 -1 -3.777344 z m 9.5,0 h 3.609375 l 0.271485,2.664062 h -3.685547 z m -14.414062,3.664062 h 3.642578 1.003906 3.84961 v 2.66211 h -4.044922 -1.001953 -3.71875 z m 9.496094,0 h 4.18164 1.003907 3.714843 l 0.269531,2.66211 h -3.791015 -1.001953 -4.376953 z m -9.867188,3.66211 h 3.746094 L 14,27.789062 h -3.820312 z m 4.75,0 h 4.117188 1 4.449218 l 0.185547,2.546874 h -4.634765 -1 -4.302735 z m 10.570313,0 h 3.818359 l 0.257813,2.546874 h -3.890626 z m -15.681641,3.546874 h 3.849609 1.001954 4.376953 v 2.507813 H 14.748047 13.744141 9.8242188 Z m 10.228516,0 h 4.708984 1.001953 3.919922 l 0.255859,2.507813 h -3.992187 -1.003906 -4.890625 z"
|
||||
inkscape:path-effect="#path-effect206"
|
||||
inkscape:original-d="m 10.951172,10.300781 -0.101563,1 -0.208984,2.06836 -0.101563,1 -0.259765,2.546875 -0.101563,1 -0.2695309,2.664062 -0.1015625,1 -0.2714844,2.66211 -0.1015624,1 -0.2578126,2.546874 -0.1015624,1 -0.2558594,2.507813 -0.1015625,1 h 1.0058593 3.9492188 1.001953 4.632813 1 4.964843 1.001954 4.021484 1.005859 l -0.101562,-1 -0.25586,-2.507813 -0.101562,-1 -0.257813,-2.546874 -0.101562,-1 -0.271484,-2.66211 -0.101563,-1 -0.269531,-2.664062 -0.101563,-1 -0.259765,-2.546875 -0.101563,-1 -0.210937,-2.06836 -0.101563,-1 h -1.003906 -3.390625 -1.003906 -3.359375 -1 -3.027344 -1.001953 -3.320313 z m 0.904297,1 h 3.349609 l -0.152344,2.06836 h -3.40625 z m 4.351562,0 h 3.09961 1 3.43164 l 0.152344,2.06836 h -3.583984 -1 -3.251953 z m 8.535157,0 h 3.419921 l 0.210938,2.06836 h -3.480469 z m -13.197266,3.06836 h 3.435547 1.001953 3.324219 v 2.546875 h -3.509766 -1.001953 -3.509766 z m 8.761719,0 h 3.65625 1.003906 3.507812 l 0.257813,2.546875 h -3.580078 -1.003906 -3.841797 z m -9.123047,3.546875 h 3.537109 l -0.193359,2.664062 h -3.615235 z m 4.541015,0 h 3.582032 1 3.914062 l 0.195313,2.664062 h -4.109375 -1 -3.777344 z m 9.5,0 h 3.609375 l 0.271485,2.664062 h -3.685547 z m -14.414062,3.664062 h 3.642578 1.003906 3.84961 v 2.66211 h -4.044922 -1.001953 -3.71875 z m 9.496094,0 h 4.18164 1.003907 3.714843 l 0.269531,2.66211 h -3.791015 -1.001953 -4.376953 z m -9.867188,3.66211 h 3.746094 L 14,27.789062 h -3.820312 z m 4.75,0 h 4.117188 1 4.449218 l 0.185547,2.546874 h -4.634765 -1 -4.302735 z m 10.570313,0 h 3.818359 l 0.257813,2.546874 h -3.890626 z m -15.681641,3.546874 h 3.849609 1.001954 4.376953 v 2.507813 H 14.748047 13.744141 9.8242188 Z m 10.228516,0 h 4.708984 1.001953 3.919922 l 0.255859,2.507813 h -3.992187 -1.003906 -4.890625 z"
|
||||
transform="translate(-0.03578843,-1.298844)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
242
resources/images/texture_displacement_frame.svg
Normal file
@@ -0,0 +1,242 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_frame.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="11.6875"
|
||||
inkscape:cy="6.28125"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><g
|
||||
id="g5-0"
|
||||
transform="translate(6.4998844,6.0000382)"
|
||||
style="fill:#ff2323;fill-opacity:1"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4-1"
|
||||
style="fill:#ff2323;fill-opacity:1" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5-2"
|
||||
style="fill:#4e2ca6;fill-opacity:1;fill-rule:nonzero" /></g><g
|
||||
id="g5-0-8"
|
||||
transform="matrix(-1,0,0,1,33.44613,6.0000382)"
|
||||
style="fill:#ff2323;fill-opacity:1"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4-1-3"
|
||||
style="fill:#ff2323;fill-opacity:1" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5-2-5"
|
||||
style="fill:#ff2323;fill-opacity:1" /></g><g
|
||||
id="g5-0-8-2"
|
||||
transform="rotate(180,16.723064,16.999981)"
|
||||
style="fill:#ff2323;fill-opacity:1"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4-1-3-0"
|
||||
style="fill:#ff2323;fill-opacity:1" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5-2-5-1"
|
||||
style="fill:#ff2323;fill-opacity:1" /></g><g
|
||||
id="g5-0-8-2-4"
|
||||
transform="matrix(1,0,0,-1,6.4998854,33.999963)"
|
||||
style="fill:#ff2323;fill-opacity:1"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4-1-3-0-0"
|
||||
style="fill:#ff2323;fill-opacity:1" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5-2-5-1-6"
|
||||
style="fill:#ff2323;fill-opacity:1" /></g><path
|
||||
id="path13"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
d="m 10.972656,11.5 v 4.101562 1.407813 A 0.490625,0.490625 45 0 0 11.463281,17.5 h 0.0094 a 0.4999428,0.4999428 135 0 0 0.499943,-0.499943 V 15.601562 12.000236 A 0.5002363,0.5002363 135 0 1 12.472892,11.5 h 0.241952 0.503906 4.255468 a 0.4984375,0.4984375 135 0 0 0.498438,-0.498437 v -0.0018 A 0.4997516,0.4997516 45 0 0 17.472904,10.5 H 13.21875 12.714844 11.972656 a 1,1 135 0 0 -1,1 z"
|
||||
inkscape:original-d="m 10.972656,10.5 v 1 4.101562 V 17.5 h 1 V 15.601562 11.5 h 0.742188 0.503906 4.753906 v -1 H 13.21875 12.714844 11.972656 Z"
|
||||
inkscape:path-effect="#path-effect15" /><path
|
||||
id="path16"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
d="m 10.972656,11.5 v 4.101562 1.439063 A 0.459375,0.459375 45 0 0 11.432031,17.5 h 0.04066 a 0.499968,0.499968 135 0 0 0.499968,-0.499968 l 0,-1.39847 V 12.000236 A 0.5002363,0.5002363 135 0 1 12.472892,11.5 h 0.241952 0.503906 4.271094 a 0.4828125,0.4828125 135 0 0 0.482812,-0.482812 V 11.00584 A 0.50584,0.50584 45 0 0 17.466816,10.5 H 13.21875 12.714844 11.972656 a 1,1 135 0 0 -1,1 z"
|
||||
inkscape:original-d="m 10.972656,10.5 v 1 4.101562 V 17.5 h 1 V 15.601562 11.5 h 0.742188 0.503906 4.753906 v -1 H 13.21875 12.714844 11.972656 Z"
|
||||
inkscape:path-effect="#path-effect17"
|
||||
transform="matrix(-1,0,0,1,39.939926,2.52e-5)" /><path
|
||||
id="path17"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
d="m 10.972656,11.5 v 4.101562 1.439063 A 0.459375,0.459375 45 0 0 11.432031,17.5 h 0.04066 a 0.499968,0.499968 135 0 0 0.499968,-0.499968 l 0,-1.39847 V 12.000236 A 0.5002363,0.5002363 135 0 1 12.472892,11.5 h 0.241952 0.503906 4.271094 a 0.4828125,0.4828125 135 0 0 0.482812,-0.482812 V 11.00584 A 0.50584,0.50584 45 0 0 17.466816,10.5 H 13.21875 12.714844 11.972656 a 1,1 135 0 0 -1,1 z"
|
||||
inkscape:original-d="m 10.972656,10.5 v 1 4.101562 V 17.5 h 1 V 15.601562 11.5 h 0.742188 0.503906 4.753906 v -1 H 13.21875 12.714844 11.972656 Z"
|
||||
inkscape:path-effect="#path-effect19"
|
||||
transform="rotate(180,19.972885,20.000096)" /><path
|
||||
id="path19"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
d="m 10.972656,11.5 v 4.101562 1.439063 A 0.459375,0.459375 45 0 0 11.432031,17.5 h 0.04066 a 0.499968,0.499968 135 0 0 0.499968,-0.499968 l 0,-1.39847 V 12.000236 A 0.5002363,0.5002363 135 0 1 12.472892,11.5 h 0.241952 0.503906 4.271094 a 0.4828125,0.4828125 135 0 0 0.482812,-0.482812 V 11.00584 A 0.50584,0.50584 45 0 0 17.466816,10.5 H 13.21875 12.714844 11.972656 a 1,1 135 0 0 -1,1 z"
|
||||
inkscape:original-d="m 10.972656,10.5 v 1 4.101562 V 17.5 h 1 V 15.601562 11.5 h 0.742188 0.503906 4.753906 v -1 H 13.21875 12.714844 11.972656 Z"
|
||||
inkscape:path-effect="#path-effect21"
|
||||
transform="matrix(1,0,0,-1,0.0060898,39.99995)" /></svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
387
resources/images/texture_displacement_join.svg
Normal file
@@ -0,0 +1,387 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_join.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect34"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 | F,0,0,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect33"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect30"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.2615305,0,1 @ F,0,0,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect29"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect27"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect25"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect24"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect23"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9-1"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35-7"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="18.3125"
|
||||
inkscape:cy="22.328125"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><path
|
||||
id="rect35"
|
||||
style="fill:#009688;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.74375,1 h 7.563281 a 0.24375,0.24375 45 0 1 0.24375,0.24375 v 7.575 a 0.24375,0.24375 135 0 1 -0.24375,0.24375 h -7.563281 a 0.24375,0.24375 45 0 1 -0.24375,-0.24375 v -7.575 a 0.24375,0.24375 135 0 1 0.24375,-0.24375 z"
|
||||
inkscape:path-effect="#path-effect35"
|
||||
inkscape:original-d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.5,1 h 8.050781 v 8.0625 h -8.050781 z"
|
||||
transform="translate(0.294922,2.953125)" /><path
|
||||
id="rect35-3"
|
||||
style="fill:#009688;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.74375,1 h 7.563281 a 0.24375,0.24375 45 0 1 0.24375,0.24375 v 7.575 a 0.24375,0.24375 135 0 1 -0.24375,0.24375 h -7.563281 a 0.24375,0.24375 45 0 1 -0.24375,-0.24375 v -7.575 a 0.24375,0.24375 135 0 1 0.24375,-0.24375 z"
|
||||
inkscape:path-effect="#path-effect35-7"
|
||||
inkscape:original-d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.5,1 h 8.050781 v 8.0625 h -8.050781 z"
|
||||
transform="translate(9.345703,2.953125)" /></svg>
|
||||
|
After Width: | Height: | Size: 18 KiB |
150
resources/images/texture_displacement_real_preview.svg
Normal file
@@ -0,0 +1,150 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_real_preview.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect229"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,0.49844035,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.48399193,0,1 @ F,0,0,1,0,0.48399193,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49844035,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect206"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.0051443,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0058593,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0051442,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.003906,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect121"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.97194886,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.9540633,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.94238026,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect48"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.0055868,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.4911902,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0376412,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect47"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="22.627417"
|
||||
inkscape:cx="25.279067"
|
||||
inkscape:cy="19.202368"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="M 19.973012,35.500013 H 5.9730109 a 1.5,1.5 0 0 1 -1.5,-1.500019 V 6.000006 a 1.5,1.5 0 0 1 1.5,-1.500019 H 33.973006 a 1.5,1.5 0 0 1 1.500019,1.500019 v 12.999987 a 0.5000126,0.5000126 0 0 1 -1.000025,0 V 6.000006 A 0.49999998,0.49999998 0 0 0 33.973006,5.500012 H 5.9730109 a 0.49999998,0.49999998 0 0 0 -0.499997,0.499994 v 27.999988 a 0.49999998,0.49999998 0 0 0 0.499997,0.499993 H 19.973012 a 0.500013,0.500013 0 0 1 0,1.000026 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999;fill-opacity:1"
|
||||
id="path4-6" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect40"
|
||||
width="15.363631"
|
||||
height="1.000026"
|
||||
x="18.609375"
|
||||
y="34.499989" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect41"
|
||||
width="1.000025"
|
||||
height="11.921875"
|
||||
x="34.473"
|
||||
y="18.546875" />
|
||||
<path
|
||||
id="path227"
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
d="m 9.0234375,12.297268 v 0.499607 0.002 0.002 V 28.5 v 1 h 1.0000005 19.925781 1 v -1 -14.027344 -1.673828 -0.516008 c 0,-0.267301 -0.216678,-0.487988 -0.483974,-0.487257 -0.01082,3e-5 -0.02151,6.6e-5 -0.03209,1.08e-4 -0.267272,0.0011 -0.483932,0.219848 -0.483932,0.487149 v 0.516008 1.359375 c -0.414107,0.519735 -1.042494,0.822425 -1.707031,0.822266 -1.124527,-0.0023 -2.063124,-0.858885 -2.167969,-1.978516 -0.0077,-0.06746 -0.01228,-0.135241 -0.01367,-0.203125 1.13e-4,-0.08617 0.0053,-0.172259 0.01563,-0.257812 -0.0105,-0.134093 -0.02949,-0.267265 -0.0568,-0.398724 -0.03994,-0.192221 -0.250872,-0.343464 -0.4473,-0.343464 -0.192979,0 -0.401264,0.151193 -0.44139,0.343374 -0.02852,0.136589 -0.04805,0.275032 -0.05841,0.414439 0.0097,0.08038 0.01491,0.161231 0.01563,0.242187 -0.001,0.0718 -0.0056,0.143498 -0.01367,0.214844 -0.110484,1.114992 -1.047518,1.965076 -2.167969,1.966797 -1.204815,-1.73e-4 -2.181468,-0.976826 -2.181641,-2.181641 -1.87e-4,-0.221094 -0.02342,-0.441241 -0.06912,-0.656906 -0.04069,-0.192058 -0.25223,-0.343094 -0.448658,-0.343094 -0.177877,0 -0.374076,0.151139 -0.414397,0.34328 -0.04525,0.215649 -0.06805,0.435732 -0.06783,0.65672 -1.73e-4,1.204815 -0.976825,2.181468 -2.18164,2.181641 -1.120451,-0.0017 -2.057485,-0.851805 -2.167969,-1.966797 -0.0081,-0.07135 -0.01265,-0.143048 -0.01367,-0.214844 1.13e-4,-0.08617 0.0053,-0.172259 0.01563,-0.257812 -0.01085,-0.134196 -0.03019,-0.267448 -0.05787,-0.398962 -0.04043,-0.192117 -0.251758,-0.343226 -0.448186,-0.343226 -0.192979,0 -0.400882,0.151323 -0.440519,0.343606 -0.02815,0.136537 -0.04732,0.274902 -0.05733,0.414207 0.0097,0.08038 0.01491,0.161231 0.01563,0.242187 -0.0014,0.06788 -0.006,0.135666 -0.01367,0.203125 -0.104845,1.119631 -1.043442,1.976207 -2.167969,1.978516 -1.205578,9.06e-4 -2.18342,-0.976063 -2.183593,-2.181641 v -0.50156 a 0.49844035,0.49844035 45 0 0 -0.4984403,-0.49844 h -0.00315 a 0.49844035,0.49844035 135 0 0 -0.4984403,0.49844 z m 11.2011715,2.204685 c 0.582376,0.918834 1.593793,1.476477 2.681641,1.478516 1.079194,-0.0024 2.083602,-0.551691 2.667969,-1.458985 0.584367,0.907294 1.588775,1.456557 2.667969,1.458985 0.604585,1.64e-4 1.196705,-0.171917 1.707031,-0.496094 V 28.5 H 10.023438 V 15.115234 c 0.590891,0.556323 1.372022,0.865842 2.183593,0.865235 1.079194,-0.0024 2.083602,-0.551691 2.667969,-1.458985 0.584367,0.907294 1.588775,1.456557 2.667969,1.458985 1.087848,-0.002 2.099265,-0.559682 2.68164,-1.478516 z"
|
||||
sodipodi:nodetypes="ccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
|
||||
inkscape:path-effect="#path-effect229"
|
||||
inkscape:original-d="m 9.0234375,11.798828 v 0.998047 0.002 0.002 V 28.5 v 1 h 1.0000005 19.925781 1 v -1 -14.027344 -1.673828 -1 c -0.473966,-0.0071 -0.666658,-8.7e-4 -1,0 v 1 1.359375 c -0.414107,0.519735 -1.042494,0.822425 -1.707031,0.822266 -1.124527,-0.0023 -2.063124,-0.858885 -2.167969,-1.978516 -0.0077,-0.06746 -0.01228,-0.135241 -0.01367,-0.203125 1.13e-4,-0.08617 0.0053,-0.172259 0.01563,-0.257812 -0.01977,-0.252421 -0.06961,-0.501579 -0.148438,-0.742188 h -0.705078 c -0.08072,0.245558 -0.131226,0.500038 -0.15039,0.757813 0.0097,0.08038 0.01491,0.161231 0.01563,0.242187 -0.001,0.0718 -0.0056,0.143498 -0.01367,0.214844 -0.110484,1.114992 -1.047518,1.965076 -2.167969,1.966797 -1.204815,-1.73e-4 -2.181468,-0.976826 -2.181641,-2.181641 -2.87e-4,-0.339851 -0.05502,-0.677463 -0.162109,-1 H 19.88478 c -0.106429,0.322646 -0.1605,0.660254 -0.160157,1 -1.73e-4,1.204815 -0.976825,2.181468 -2.18164,2.181641 -1.120451,-0.0017 -2.057485,-0.851805 -2.167969,-1.966797 -0.0081,-0.07135 -0.01265,-0.143048 -0.01367,-0.214844 1.13e-4,-0.08617 0.0053,-0.172259 0.01563,-0.257812 -0.02041,-0.2525 -0.0709,-0.50166 -0.150391,-0.742188 h -0.705078 c -0.08006,0.245641 -0.129907,0.500119 -0.148437,0.757813 0.0097,0.08038 0.01491,0.161231 0.01563,0.242187 -0.0014,0.06788 -0.006,0.135666 -0.01367,0.203125 -0.104845,1.119631 -1.043442,1.976207 -2.167969,1.978516 -1.205578,9.06e-4 -2.18342,-0.976063 -2.183593,-2.181641 v -1 z m 11.2011715,2.703125 c 0.582376,0.918834 1.593793,1.476477 2.681641,1.478516 1.079194,-0.0024 2.083602,-0.551691 2.667969,-1.458985 0.584367,0.907294 1.588775,1.456557 2.667969,1.458985 0.604585,1.64e-4 1.196705,-0.171917 1.707031,-0.496094 V 28.5 H 10.023438 V 15.115234 c 0.590891,0.556323 1.372022,0.865842 2.183593,0.865235 1.079194,-0.0024 2.083602,-0.551691 2.667969,-1.458985 0.584367,0.907294 1.588775,1.456557 2.667969,1.458985 1.087848,-0.002 2.099265,-0.559682 2.68164,-1.478516 z"
|
||||
transform="translate(0.26516504,0.08838835)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
260
resources/images/texture_displacement_snap.svg
Normal file
@@ -0,0 +1,260 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_snap.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect29"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect27"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect25"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect24"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect23"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="45.254834"
|
||||
inkscape:cx="14.363106"
|
||||
inkscape:cy="12.827359"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><path
|
||||
d="M 20.422985,9.2501548 V 29.968905 H 28.78236 A 1.015625,1.015625 135 0 0 29.797985,28.95328 V 9.2501548 A 1.015625,1.015625 45 0 0 28.78236,8.2345298 h -7.34375 a 1.015625,1.015625 135 0 0 -1.015625,1.015625 z m 1.015625,-0.015625 h 7.34375 V 28.968905 h -7.34375 z"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
id="path23"
|
||||
inkscape:path-effect="#path-effect24"
|
||||
inkscape:original-d="M 20.422985,8.2345298 V 29.968905 h 9.375 V 8.2345298 Z m 1.015625,1 h 7.34375 V 28.968905 h -7.34375 z" /><path
|
||||
d="m 11.047985,11.324808 v 19.616307 a 1.059034,1.059034 45 0 0 1.059034,1.059034 h 7.256932 a 1.059034,1.059034 135 0 0 1.059034,-1.059034 V 10.265774 h -8.315966 a 1.059034,1.059034 135 0 0 -1.059034,1.059034 z m 1.015625,-0.05903 h 7.34375 v 19.734375 h -7.34375 z"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
id="path23-8"
|
||||
inkscape:path-effect="#path-effect25"
|
||||
inkscape:original-d="m 11.047985,10.265774 v 21.734375 h 9.375 V 10.265774 Z m 1.015625,1 h 7.34375 v 19.734375 h -7.34375 z" /><path
|
||||
id="use25"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
d="M 21.439453 8.234375 A 1.015625 1.015625 0 0 0 20.423828 9.25 L 20.423828 10.265625 L 12.107422 10.265625 A 1.059034 1.059034 0 0 0 11.048828 11.324219 L 11.048828 30.941406 A 1.059034 1.059034 0 0 0 12.107422 32 L 19.363281 32 A 1.059034 1.059034 0 0 0 20.423828 30.941406 L 20.423828 29.96875 L 28.783203 29.96875 A 1.015625 1.015625 0 0 0 29.798828 28.953125 L 29.798828 9.25 A 1.015625 1.015625 0 0 0 28.783203 8.234375 L 21.439453 8.234375 z M 21.439453 9.234375 L 28.783203 9.234375 L 28.783203 28.96875 L 21.439453 28.96875 L 21.439453 9.234375 z M 12.064453 11.265625 L 19.408203 11.265625 L 19.408203 31 L 12.064453 31 L 12.064453 11.265625 z " /></svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
387
resources/images/texture_displacement_unjoin.svg
Normal file
@@ -0,0 +1,387 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_unjoin.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect34"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 | F,0,0,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect33"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect30"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.2615305,0,1 @ F,0,0,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect29"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect27"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect25"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect24"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect23"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9-1"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35-7"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="18.3125"
|
||||
inkscape:cy="22.328125"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><path
|
||||
id="rect35"
|
||||
style="fill:#009688;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.74375,1 h 7.563281 a 0.24375,0.24375 45 0 1 0.24375,0.24375 v 7.575 a 0.24375,0.24375 135 0 1 -0.24375,0.24375 h -7.563281 a 0.24375,0.24375 45 0 1 -0.24375,-0.24375 v -7.575 a 0.24375,0.24375 135 0 1 0.24375,-0.24375 z"
|
||||
inkscape:path-effect="#path-effect35"
|
||||
inkscape:original-d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.5,1 h 8.050781 v 8.0625 h -8.050781 z"
|
||||
transform="translate(-1.142612,2.9530855)" /><path
|
||||
id="rect35-3"
|
||||
style="fill:#009688;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.74375,1 h 7.563281 a 0.24375,0.24375 45 0 1 0.24375,0.24375 v 7.575 a 0.24375,0.24375 135 0 1 -0.24375,0.24375 h -7.563281 a 0.24375,0.24375 45 0 1 -0.24375,-0.24375 v -7.575 a 0.24375,0.24375 135 0 1 0.24375,-0.24375 z"
|
||||
inkscape:path-effect="#path-effect35-7"
|
||||
inkscape:original-d="m 11.154297,12.015625 c -0.277007,0 -0.5,0.222993 -0.5,0.5 v 9.050781 c 0,0.0019 -2.2e-5,0.0039 0,0.0059 -2.3e-5,0.002 0,0.0039 0,0.0059 3.45e-4,0.01513 0.0023,0.03016 0.0039,0.04492 0.02282,0.255546 0.234414,0.455078 0.496094,0.455078 h 9.050781 c 0.277007,0 0.5,-0.222993 0.5,-0.5 v -0.002 -9.048828 c 9e-5,-0.0039 0,-0.0078 0,-0.01172 0,-0.277007 -0.222993,-0.5 -0.5,-0.5 z m 0.5,1 h 8.050781 v 8.0625 h -8.050781 z"
|
||||
transform="translate(10.783203,2.9530855)" /></svg>
|
||||
|
After Width: | Height: | Size: 18 KiB |
383
resources/images/texture_displacement_uv_select_edge.svg
Normal file
@@ -0,0 +1,383 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_uv_select_edge.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect34"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 | F,0,0,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect33"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect30"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.2615305,0,1 @ F,0,0,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect29"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect27"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect25"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect24"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect23"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9-1"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35-7"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="13.65625"
|
||||
inkscape:cy="21.984375"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><rect
|
||||
style="fill:#009688;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect35"
|
||||
width="1.0000261"
|
||||
height="23.343758"
|
||||
x="19.972986"
|
||||
y="8.3281212"
|
||||
rx="0.50001299"
|
||||
ry="0.50001299" /></svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
413
resources/images/texture_displacement_uv_select_island.svg
Normal file
@@ -0,0 +1,413 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_uv_select_island.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect37"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.22141448,0,1 @ F,0,1,1,0,0.22141448,0,1 @ F,0,1,1,0,0.22141448,0,1 @ F,0,0,1,0,0.22141448,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect36"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect34"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 | F,0,0,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect33"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect30"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.2615305,0,1 @ F,0,0,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect29"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect27"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect25"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect24"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect23"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9-1"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35-7"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="13.65625"
|
||||
inkscape:cy="21.984375"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><path
|
||||
id="rect35"
|
||||
style="fill:#009688;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.1;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 9.5039062,8.65625 c -0.2770071,0 -0.5,0.2229928 -0.5,0.5 V 31.484375 31.5 c 3.682e-4,0.0119 7.748e-4,0.02348 0.00195,0.03516 C 9.0240314,31.795393 9.2388928,32 9.5039062,32 c 0.043378,0 0.08492,-0.0053 0.125,-0.01563 H 31.722656 c 0.04008,0.01029 0.08162,0.01563 0.125,0.01563 0.265014,0 0.479875,-0.204607 0.498047,-0.464844 0.0017,-0.01685 0.002,-0.03347 0.002,-0.05078 V 9.15625 c 0,-0.2770072 -0.222993,-0.5 -0.5,-0.5 z m 0.7214138,1 20.900922,0 a 0.22141448,0.22141448 45 0 1 0.221414,0.2214145 l 0,20.8852965 a 0.22141448,0.22141448 135 0 1 -0.221414,0.221414 l -20.900922,0 a 0.22141448,0.22141448 45 0 1 -0.221414,-0.221414 l 0,-20.8852965 A 0.22141448,0.22141448 135 0 1 10.22532,9.65625 Z"
|
||||
inkscape:path-effect="#path-effect37"
|
||||
inkscape:original-d="m 9.5039062,8.65625 c -0.2770071,0 -0.5,0.2229928 -0.5,0.5 V 31.484375 31.5 c 3.682e-4,0.0119 7.748e-4,0.02348 0.00195,0.03516 C 9.0240314,31.795393 9.2388928,32 9.5039062,32 c 0.043378,0 0.08492,-0.0053 0.125,-0.01563 H 31.722656 c 0.04008,0.01029 0.08162,0.01563 0.125,0.01563 0.265014,0 0.479875,-0.204607 0.498047,-0.464844 0.0017,-0.01685 0.002,-0.03347 0.002,-0.05078 V 9.15625 c 0,-0.2770072 -0.222993,-0.5 -0.5,-0.5 z m 0.4999998,1 h 21.34375 v 21.328125 h -21.34375 z"
|
||||
transform="translate(-0.5,-0.32812498)" /></svg>
|
||||
|
After Width: | Height: | Size: 19 KiB |
378
resources/images/texture_displacement_uv_select_vertex.svg
Normal file
@@ -0,0 +1,378 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_uv_select_vertex.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs17"><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect34"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 @ F,0,0,1,0,0.965625,0,1 | F,0,0,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1 @ F,0,1,1,0,0.3257813,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect33"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect30"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.2615305,0,1 @ F,0,0,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 @ F,0,1,1,0,1.2615305,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect29"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect27"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect25"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,1.059034,0,1 @ F,0,1,1,0,1.059034,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect24"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,1.015625,0,1 @ F,0,0,1,0,1.015625,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect23"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect21"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect19"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect17"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.459375,0,1 @ F,0,0,1,0,0.499968,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.4828125,0,1 @ F,0,0,1,0,0.50584,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect15"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.5002363,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><linearGradient
|
||||
id="swatch7"
|
||||
inkscape:swatch="solid"><stop
|
||||
style="stop-color:#77eaa3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop7" /></linearGradient><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.49883385,0,1 @ F,0,0,1,0,0.48672135,0,1 @ F,0,0,1,0,0.5261776,0,1 @ F,0,0,1,0,0.50001454,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect2"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect31-9-1"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 @ F,0,0,1,0,1.2263883,0,1 | F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1 @ F,0,1,1,0,0.47508735,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /><inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect35-7"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,1,1,0,0.24375,0,1 @ F,0,0,1,0,0.24375,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" /></defs><sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="16.6875"
|
||||
inkscape:cy="29.109375"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" /><path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" /><path
|
||||
d="m 35.473026,33.999994 v -0.999987 a 0.500013,0.500013 0 0 0 -1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 1 -0.499993,0.499994 h -5.000013 a 0.5000126,0.5000126 0 0 0 0,1.000025 h 5.000013 a 1.5,1.5 0 0 0 1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7" /><path
|
||||
d="m 5.9730079,35.500013 h 0.999987 a 0.500013,0.500013 0 0 0 0,-1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 1 -0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 0 -1.000025,0 v 5.000013 a 1.5,1.5 0 0 0 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3" /><path
|
||||
d="m 4.4729879,33.999994 v -0.999987 a 0.500013,0.500013 0 0 1 1.000026,0 v 0.999987 a 0.49999998,0.49999998 0 0 0 0.499993,0.499994 H 10.97302 a 0.5000126,0.5000126 0 0 1 0,1.000025 H 5.9730069 a 1.5,1.5 0 0 1 -1.500019,-1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5" /><path
|
||||
d="m 5.9730079,4.499987 h 0.999987 a 0.50001302,0.50001302 0 0 1 0,1.000026 h -0.999987 a 0.49999998,0.49999998 0 0 0 -0.499994,0.499993 v 5.000013 a 0.5000126,0.5000126 0 0 1 -1.0000251,0 V 6.000006 A 1.5,1.5 0 0 1 5.9730079,4.499987 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9" /><path
|
||||
d="m 4.4729878,6.000006 v 0.999987 a 0.50001302,0.50001302 0 0 0 1.0000261,0 V 6.000006 A 0.49999998,0.49999998 0 0 1 5.9730068,5.500012 H 10.97302 a 0.5000126,0.5000126 0 0 0 0,-1.000025 H 5.9730068 a 1.5,1.5 0 0 0 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8" /><path
|
||||
d="m 33.973006,4.499986 h -0.999987 a 0.50001302,0.50001302 0 0 0 0,1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 1 34.473,6.000005 v 5.000013 a 0.5000126,0.5000126 0 0 0 1.000025,0 V 6.000005 A 1.5,1.5 0 0 0 33.973006,4.499986 Z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-3-9-8" /><path
|
||||
d="m 35.473026,6.000005 v 0.999987 a 0.50001305,0.50001305 0 0 1 -1.000026,0 V 6.000005 A 0.49999998,0.49999998 0 0 0 33.973007,5.500011 h -5.000013 a 0.5000126,0.5000126 0 0 1 0,-1.000025 h 5.000013 a 1.5,1.5 0 0 1 1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6-7-5-8-2" /><g
|
||||
id="g5"><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-3-9"
|
||||
id="use4" /><use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#path3-6-7-5-8"
|
||||
id="use5" /></g><path
|
||||
d="M 20.472999,14.75 A 5.2386374,5.25 0 0 0 15.234718,20 5.2386374,5.25 0 0 0 20.472999,25.25 5.2386374,5.25 0 0 0 25.71128,20 5.2386374,5.25 0 0 0 20.472999,14.75 Z m 0,0.974609 A 4.265625,4.2748771 0 0 1 24.738624,20 4.265625,4.2748771 0 0 1 20.472999,24.275391 4.265625,4.2748771 0 0 1 16.207374,20 4.265625,4.2748771 0 0 1 20.472999,15.724609 Z"
|
||||
style="fill:#009688;stroke-width:0.1;paint-order:stroke fill markers"
|
||||
id="path39" /></svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
163
resources/images/texture_displacement_wireframe.svg
Normal file
@@ -0,0 +1,163 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="texture_displacement_wireframe.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect294"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.636718,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect229"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,1,1,0,0.49844035,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.48399193,0,1 @ F,0,0,1,0,0.48399193,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0.35566405,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.49844035,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect206"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.0051443,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0058593,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0051442,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.003906,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect121"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0.97194886,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.9540633,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.94238026,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect48"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.0055868,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.4911902,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0376412,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect47"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="26.09375"
|
||||
inkscape:cy="31.421875"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="M 19.973012,35.500013 H 5.9730109 a 1.5,1.5 0 0 1 -1.5,-1.500019 V 6.000006 a 1.5,1.5 0 0 1 1.5,-1.500019 H 33.973006 a 1.5,1.5 0 0 1 1.500019,1.500019 v 12.999987 a 0.5000126,0.5000126 0 0 1 -1.000025,0 V 6.000006 A 0.49999998,0.49999998 0 0 0 33.973006,5.500012 H 5.9730109 a 0.49999998,0.49999998 0 0 0 -0.499997,0.499994 v 27.999988 a 0.49999998,0.49999998 0 0 0 0.499997,0.499993 H 19.973012 a 0.500013,0.500013 0 0 1 0,1.000026 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999;fill-opacity:1"
|
||||
id="path4-6" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect40"
|
||||
width="15.363631"
|
||||
height="1.000026"
|
||||
x="18.609375"
|
||||
y="34.499989" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect41"
|
||||
width="1.000025"
|
||||
height="11.921875"
|
||||
x="34.473"
|
||||
y="18.546875" />
|
||||
<path
|
||||
id="path291"
|
||||
style="fill:#009688;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
d="M 8.8085938 7.4296875 A 1 1 0 0 0 7.8085938 8.4296875 L 7.8085938 19.261719 L 7.8085938 20.261719 L 7.8085938 31.09375 A 1 1 0 0 0 8.8085938 32.09375 L 18.796875 32.09375 L 19.640625 32.09375 L 20.210938 32.09375 L 20.640625 32.09375 L 31.472656 32.09375 A 1 1 0 0 0 32.472656 31.09375 L 32.472656 20.261719 L 32.472656 19.830078 L 32.472656 19.261719 L 32.472656 18.416016 L 32.472656 8.4296875 L 32.472656 8.2070312 L 32.472656 8.0664062 A 0.636718 0.636718 0 0 0 31.835938 7.4296875 L 31.472656 7.4296875 L 20.640625 7.4296875 L 19.640625 7.4296875 L 8.8085938 7.4296875 z M 20.640625 8.4296875 L 30.835938 8.4296875 L 20.640625 18.626953 L 20.640625 8.4296875 z M 31.472656 9.2070312 L 31.472656 19.261719 L 21.419922 19.261719 L 31.472656 9.2070312 z M 20.640625 20.261719 L 30.626953 20.261719 L 20.640625 30.25 L 20.640625 20.261719 z M 31.472656 20.830078 L 31.472656 31.09375 L 21.210938 31.09375 L 31.472656 20.830078 z " />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
60
resources/images/toolbar_big_brush.svg
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="toolbar_big_brush.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17" />
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="5.6568543"
|
||||
inkscape:cx="-13.081475"
|
||||
inkscape:cy="98.994949"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="M 19.973012,35.500013 H 5.9730109 a 1.5,1.5 0 0 1 -1.5,-1.500019 V 6.000006 a 1.5,1.5 0 0 1 1.5,-1.500019 H 33.973006 a 1.5,1.5 0 0 1 1.500019,1.500019 v 12.999987 a 0.5000126,0.5000126 0 0 1 -1.000025,0 V 6.000006 A 0.49999998,0.49999998 0 0 0 33.973006,5.500012 H 5.9730109 a 0.49999998,0.49999998 0 0 0 -0.499997,0.499994 v 27.999988 a 0.49999998,0.49999998 0 0 0 0.499997,0.499993 H 19.973012 a 0.500013,0.500013 0 0 1 0,1.000026 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999;fill-opacity:1"
|
||||
id="path4-6" />
|
||||
<path
|
||||
id="path30"
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
d="M 30.109375 6.9648438 A 1.8710937 1.8710937 0 0 0 28.705078 7.5996094 A 1.8710937 1.8710937 0 0 0 28.591797 7.7421875 L 18.103516 20.615234 A 6.1070809 6.1070809 0 0 0 17.226562 20.552734 A 6.1070809 6.1070809 0 0 0 11.513672 24.503906 A 6.1070809 6.1070809 0 0 0 11.121094 26.658203 A 6.1070809 6.1070809 0 0 0 11.121094 26.757812 A 4.4931102 4.4931102 0 0 1 11.15625 27.3125 A 4.4931102 4.4931102 0 0 1 7.7890625 31.662109 A 4.4931102 4.4931102 0 0 1 7.5839844 31.708984 A 0.55609465 0.55242634 0 0 0 7.2558594 32.212891 A 0.55609465 0.55242634 0 0 0 7.8105469 32.765625 A 0.55609465 0.55242634 0 0 0 7.8125 32.765625 L 7.9570312 32.765625 L 17.224609 32.765625 L 17.226562 32.765625 A 6.1070809 6.1070809 0 0 0 23.333984 26.658203 A 6.1070809 6.1070809 0 0 0 22.792969 24.144531 L 31.666016 9.875 A 1.8710937 1.8710937 0 0 1 31.638672 9.9101562 A 1.8710937 1.8710937 0 0 0 31.699219 9.8242188 A 1.8710937 1.8710937 0 0 0 31.980469 8.8359375 A 1.8710937 1.8710937 0 0 0 31.724609 7.890625 A 1.8710937 1.8710937 0 0 0 30.480469 7.0019531 A 1.8710937 1.8710937 0 0 0 30.109375 6.9648438 z M 28.525391 7.8476562 A 1.8710937 1.8710937 0 0 0 28.472656 7.9335938 A 1.8710937 1.8710937 0 0 1 28.478516 7.9199219 A 1.8710937 1.8710937 0 0 1 28.525391 7.8476562 z M 30.109375 7.9570312 A 0.87817276 0.87817276 0 0 1 30.988281 8.8359375 A 0.87817276 0.87817276 0 0 1 30.90625 9.2050781 A 0.87817276 0.87817276 0 0 1 30.791016 9.390625 L 29.974609 10.703125 L 22.230469 23.15625 A 6.1070809 6.1070809 0 0 0 19.183594 20.873047 L 28.388672 9.5703125 L 29.359375 8.3808594 A 0.87817276 0.87817276 0 0 1 29.511719 8.1933594 A 0.87817276 0.87817276 0 0 1 30.109375 7.9570312 z M 28.457031 7.9628906 A 1.8710937 1.8710937 0 0 0 28.373047 8.1425781 A 1.8710937 1.8710937 0 0 1 28.457031 7.9628906 z M 28.300781 8.359375 A 1.8710937 1.8710937 0 0 0 28.263672 8.5507812 A 1.8710937 1.8710937 0 0 1 28.298828 8.3613281 A 1.8710937 1.8710937 0 0 1 28.300781 8.359375 z M 17.226562 21.65625 A 5.0022264 5.0022264 0 0 1 22.230469 26.658203 A 5.0022264 5.0022264 0 0 1 17.226562 31.662109 L 13.724609 31.662109 L 10.199219 31.662109 A 5.6057596 5.6057596 0 0 0 11.822266 29.501953 A 5.6057596 5.6057596 0 0 0 12.267578 27.3125 A 5.0022264 5.0022264 0 0 1 12.224609 26.658203 A 5.0022264 5.0022264 0 0 1 12.224609 26.621094 A 5.0022264 5.0022264 0 0 1 17.226562 21.65625 z " />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect40"
|
||||
width="15.363631"
|
||||
height="1.000026"
|
||||
x="18.609375"
|
||||
y="34.499989" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect41"
|
||||
width="1.000025"
|
||||
height="11.921875"
|
||||
x="34.473"
|
||||
y="18.546875" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
97
resources/images/toolbar_face.svg
Normal file
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="toolbar_face.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17">
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect48"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,1.0055868,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.4911902,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,1.0376412,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
<inkscape:path-effect
|
||||
effect="fillet_chamfer"
|
||||
id="path-effect47"
|
||||
is_visible="true"
|
||||
lpeversion="1"
|
||||
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
|
||||
radius="0"
|
||||
unit="px"
|
||||
method="auto"
|
||||
mode="F"
|
||||
chamfer_steps="1"
|
||||
flexible="false"
|
||||
use_knot_distance="true"
|
||||
apply_no_radius="true"
|
||||
apply_with_radius="true"
|
||||
only_selected="false"
|
||||
hide_knots="false" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="22.627417"
|
||||
inkscape:cx="23.113553"
|
||||
inkscape:cy="18.318485"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<path
|
||||
d="m 33.973006,35.500013 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 A 0.49999998,0.49999998 0 0 0 34.473,33.999994 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="M 19.973012,35.500013 H 5.9730109 a 1.5,1.5 0 0 1 -1.5,-1.500019 V 6.000006 a 1.5,1.5 0 0 1 1.5,-1.500019 H 33.973006 a 1.5,1.5 0 0 1 1.500019,1.500019 v 12.999987 a 0.5000126,0.5000126 0 0 1 -1.000025,0 V 6.000006 A 0.49999998,0.49999998 0 0 0 33.973006,5.500012 H 5.9730109 a 0.49999998,0.49999998 0 0 0 -0.499997,0.499994 v 27.999988 a 0.49999998,0.49999998 0 0 0 0.499997,0.499993 H 19.973012 a 0.500013,0.500013 0 0 1 0,1.000026 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999;fill-opacity:1"
|
||||
id="path4-6" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect40"
|
||||
width="15.363631"
|
||||
height="1.000026"
|
||||
x="18.609375"
|
||||
y="34.499989" />
|
||||
<rect
|
||||
style="fill:#b6b6b6;fill-opacity:1;stroke:none;stroke-width:0;stroke-linejoin:miter;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0;paint-order:stroke fill markers"
|
||||
id="rect41"
|
||||
width="1.000025"
|
||||
height="11.921875"
|
||||
x="34.473"
|
||||
y="18.546875" />
|
||||
<path
|
||||
id="path42"
|
||||
style="fill:#009688;stroke-width:0;stroke-opacity:0;paint-order:stroke fill markers;fill-opacity:1"
|
||||
d="M 19.58431,7.9401469 19.519531,8.0820312 9.1699219,30.736328 a 0.61329334,0.61329334 42.827026 0 0 0.828125,0.767578 l 0,0 20.7832031,-7.320312 0.0019,-6.61e-4 a 0.7874119,0.7874119 107.77086 0 0 0.382891,-1.194652 L 20.714844,8.0449219 20.578195,7.8494958 A 0.57476577,0.57476577 174.78854 0 0 19.58431,7.9401469 Z M 20.232422,9.1015625 30.1875,23.332031 10.576172,30.240234 Z"
|
||||
inkscape:path-effect="#path-effect48"
|
||||
inkscape:original-d="M 20.001953,7.0253906 19.519531,8.0820312 9.1699219,30.736328 8.5917969,32 9.9980469,31.503906 30.78125,24.183594 31.761719,23.837891 31.166016,22.988281 20.714844,8.0449219 Z M 20.232422,9.1015625 30.1875,23.332031 10.576172,30.240234 Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
240
resources/images/toolbar_texture_displacement.svg
Normal file
@@ -0,0 +1,240 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
version="1.1"
|
||||
id="svg17"
|
||||
sodipodi:docname="toolbar_texture_displacement.svg"
|
||||
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs17" />
|
||||
<sodipodi:namedview
|
||||
id="namedview17"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="27.578125"
|
||||
inkscape:cy="27.671875"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2126"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg17" />
|
||||
<g
|
||||
id="g17"
|
||||
transform="translate(0.69176086,0.249987)">
|
||||
<path
|
||||
d="m 27.659236,35.176022 h -4.399987 c -0.508996,0 -0.844996,0 -0.954999,-0.394015 a 0.59299998,0.59299998 0 0 1 0.36,-0.669997 2.3,2.3 0 0 0 1.2,-2.188989 3.253,3.253 0 1 1 3.800012,3.205984 z M 24.12125,34.175997 h 2.988 a 2.253,2.253 0 1 0 -2.252999,-2.252976 3.445,3.445 0 0 1 -0.735001,2.252976 z"
|
||||
style="fill:#009688;stroke-width:0.999999"
|
||||
id="path1-6" />
|
||||
<path
|
||||
d="m 30.281245,30.538996 -0.846009,-0.534993 4.364031,-6.899981 A 0.222,0.222 0 0 0 33.439456,22.84588 l -5.4,6.644032 -0.776013,-0.63103 5.400001,-6.643994 a 1.222,1.222 0 0 1 1.981984,1.422992 z"
|
||||
style="fill:#009688;stroke-width:0.999999"
|
||||
id="path2-0" />
|
||||
<path
|
||||
d="m 33.281245,35.250026 h -0.999987 a 0.500013,0.500013 0 0 1 0,-1.000026 h 0.999987 a 0.49999998,0.49999998 0 0 0 0.499994,-0.499993 v -5.000013 a 0.5000126,0.5000126 0 0 1 1.000025,0 v 5.000013 a 1.5,1.5 0 0 1 -1.500019,1.500019 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path3-6" />
|
||||
<path
|
||||
d="M 19.281251,35.250026 H 5.28125 a 1.5,1.5 0 0 1 -1.5,-1.500019 V 5.750019 A 1.5,1.5 0 0 1 5.28125,4.25 h 27.999995 a 1.5,1.5 0 0 1 1.500019,1.500019 v 12.999987 a 0.5000126,0.5000126 0 0 1 -1.000025,0 V 5.750019 A 0.49999998,0.49999998 0 0 0 33.281245,5.250025 H 5.28125 A 0.49999998,0.49999998 0 0 0 4.781253,5.750019 V 33.750007 A 0.49999998,0.49999998 0 0 0 5.28125,34.25 h 14.000001 a 0.500013,0.500013 0 0 1 0,1.000026 z"
|
||||
style="fill:#b6b6b6;stroke-width:0.999999"
|
||||
id="path4-6" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-1"
|
||||
width="39.379108"
|
||||
height="0.99609375"
|
||||
x="-19.909107"
|
||||
y="27.094706"
|
||||
ry="0.49804688"
|
||||
transform="rotate(-45)" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2"
|
||||
width="27.566595"
|
||||
height="0.99609375"
|
||||
x="7.9031763"
|
||||
y="-0.27849278"
|
||||
ry="0.49487707"
|
||||
transform="rotate(45)" />
|
||||
<g
|
||||
id="g7"
|
||||
transform="matrix(3.7795276,0,0,3.7795276,-350.69182,-427.25001)">
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2"
|
||||
width="2.8453603"
|
||||
height="0.2635498"
|
||||
x="150.08304"
|
||||
y="13.289762"
|
||||
ry="0.1317749"
|
||||
transform="rotate(45)"
|
||||
rx="0.1317749" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-2"
|
||||
width="1.7498204"
|
||||
height="0.2635498"
|
||||
x="12.763216"
|
||||
y="-149.44461"
|
||||
ry="0.1317749"
|
||||
transform="rotate(135)"
|
||||
rx="0.1317749" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-2-2"
|
||||
width="0.82876241"
|
||||
height="0.27337581"
|
||||
x="13.654455"
|
||||
y="-148.49495"
|
||||
ry="0.1317749"
|
||||
transform="rotate(135)"
|
||||
rx="0.1317749" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-6"
|
||||
width="2.5927844"
|
||||
height="0.25224927"
|
||||
x="150.38367"
|
||||
y="12.286384"
|
||||
ry="0.12612464"
|
||||
transform="rotate(45)"
|
||||
rx="0.12612464" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-6-1"
|
||||
width="1.9647908"
|
||||
height="0.2635498"
|
||||
x="151.01166"
|
||||
y="11.21043"
|
||||
ry="0.1317749"
|
||||
transform="rotate(45)"
|
||||
rx="0.1317749" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2-2-8"
|
||||
width="10.754118"
|
||||
height="0.99609375"
|
||||
x="-10.211728"
|
||||
y="-31.661213"
|
||||
ry="0.49804688"
|
||||
transform="rotate(135)"
|
||||
rx="0.49804688" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2-2-2-9"
|
||||
width="6.6134944"
|
||||
height="0.99609375"
|
||||
x="-33.794979"
|
||||
y="12.679931"
|
||||
ry="0.49804688"
|
||||
transform="rotate(-135)"
|
||||
rx="0.49804688" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2-2-2-2-3"
|
||||
width="3.1323304"
|
||||
height="1.0332314"
|
||||
x="-30.282875"
|
||||
y="16.213961"
|
||||
ry="0.49804688"
|
||||
transform="rotate(-135)"
|
||||
rx="0.49804688" />
|
||||
<g
|
||||
id="g7-1"
|
||||
transform="matrix(0,-3.7795276,3.7795276,0,-427.55829,389.70233)">
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-9"
|
||||
width="2.8453603"
|
||||
height="0.2635498"
|
||||
x="150.08304"
|
||||
y="13.289762"
|
||||
ry="0.1317749"
|
||||
transform="rotate(45)"
|
||||
rx="0.1317749" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-2-8"
|
||||
width="1.7498204"
|
||||
height="0.2635498"
|
||||
x="12.763216"
|
||||
y="-149.44461"
|
||||
ry="0.1317749"
|
||||
transform="rotate(135)"
|
||||
rx="0.1317749" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-2-2-6"
|
||||
width="0.82876241"
|
||||
height="0.27337581"
|
||||
x="13.654455"
|
||||
y="-148.49495"
|
||||
ry="0.1317749"
|
||||
transform="rotate(135)"
|
||||
rx="0.1317749" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-6-5"
|
||||
width="2.5927844"
|
||||
height="0.25224927"
|
||||
x="150.38367"
|
||||
y="12.286384"
|
||||
ry="0.12612464"
|
||||
transform="rotate(45)"
|
||||
rx="0.12612464" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:0.264999;stroke-dasharray:none"
|
||||
id="rect6-2-2-6-1-0"
|
||||
width="1.9647908"
|
||||
height="0.2635498"
|
||||
x="151.01166"
|
||||
y="11.21043"
|
||||
ry="0.1317749"
|
||||
transform="rotate(45)"
|
||||
rx="0.1317749" />
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2-2-9-4"
|
||||
width="9.0822706"
|
||||
height="0.99609375"
|
||||
x="-36.379971"
|
||||
y="-4.5927153"
|
||||
ry="0.49804688"
|
||||
transform="rotate(-135)"
|
||||
rx="0.49804688" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2-2-6-5-5"
|
||||
width="9.7995005"
|
||||
height="0.95338309"
|
||||
x="-36.915619"
|
||||
y="-8.385005"
|
||||
ry="0.47669154"
|
||||
transform="rotate(-135)"
|
||||
rx="0.47669154" />
|
||||
<rect
|
||||
style="fill:#009688;fill-opacity:1;stroke-width:1.00157;stroke-dasharray:none"
|
||||
id="rect6-2-2-6-1-0-0"
|
||||
width="7.425981"
|
||||
height="0.99609375"
|
||||
x="-34.54211"
|
||||
y="-12.451604"
|
||||
ry="0.49804688"
|
||||
transform="rotate(-135)"
|
||||
rx="0.49804688" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
133
resources/shaders/110/texture_displacement_bump.fs
Normal file
@@ -0,0 +1,133 @@
|
||||
#version 110
|
||||
|
||||
// See resources/shaders/140/texture_displacement_bump.fs for full documentation; this is the
|
||||
// GLSL 1.10 compatibility variant (same logic, older syntax).
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
uniform bool volume_mirrored;
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
|
||||
uniform sampler2D height_tex;
|
||||
uniform vec2 height_tex_texel;
|
||||
uniform float depth_mm;
|
||||
uniform float tiling_scale;
|
||||
uniform float rotation_rad;
|
||||
uniform vec2 uv_offset;
|
||||
uniform bool invert;
|
||||
uniform bool use_vertex_uv;
|
||||
// 2x3 affine (lin = (m00, m01, m10, m11), tr = (m02, m12)) applied to the dragged island's uv; see the
|
||||
// 140 variant. Identity when nothing is dragged.
|
||||
uniform vec4 island_delta_lin;
|
||||
uniform vec2 island_delta_tr;
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 model_pos;
|
||||
varying vec4 world_pos;
|
||||
varying float weight;
|
||||
varying float island_active;
|
||||
varying vec2 vertex_uv;
|
||||
|
||||
void projection_axes(vec3 n, out vec3 t, out vec3 b)
|
||||
{
|
||||
vec3 an = abs(n);
|
||||
if (an.x >= an.y && an.x >= an.z) { // planar = p.yz
|
||||
t = vec3(0.0, 1.0, 0.0);
|
||||
b = vec3(0.0, 0.0, 1.0);
|
||||
} else if (an.y >= an.x && an.y >= an.z) { // planar = p.xz
|
||||
t = vec3(1.0, 0.0, 0.0);
|
||||
b = vec3(0.0, 0.0, 1.0);
|
||||
} else { // planar = p.xy
|
||||
t = vec3(1.0, 0.0, 0.0);
|
||||
b = vec3(0.0, 1.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
vec2 project_uv(vec3 p, vec3 n)
|
||||
{
|
||||
vec3 an = abs(n);
|
||||
vec2 planar = (an.x >= an.y && an.x >= an.z) ? p.yz : ((an.y >= an.x && an.y >= an.z) ? p.xz : p.xy);
|
||||
planar *= (tiling_scale > 1e-6) ? (1.0 / tiling_scale) : 1.0;
|
||||
float cs = cos(rotation_rad);
|
||||
float sn = sin(rotation_rad);
|
||||
return vec2(planar.x * cs - planar.y * sn, planar.x * sn + planar.y * cs) + uv_offset;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
discard;
|
||||
|
||||
vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz)));
|
||||
if (volume_mirrored)
|
||||
triangle_normal = -triangle_normal;
|
||||
|
||||
if (use_vertex_uv) {
|
||||
// Mikkelsen surface-gradient bump; see the 140 variant for the full rationale. Scale-exact
|
||||
// for a conformal LSCM map (no global 1/tiling assumption), and gated by the paint weight
|
||||
// via a multiply so the branch stays uniform (use_vertex_uv is a uniform).
|
||||
vec2 uv = (island_active > 0.5)
|
||||
? vec2(dot(island_delta_lin.xy, vertex_uv), dot(island_delta_lin.zw, vertex_uv)) + island_delta_tr
|
||||
: vertex_uv;
|
||||
float h = texture2D(height_tex, uv).r;
|
||||
float k = (invert ? -1.0 : 1.0) * depth_mm * clamp(weight, 0.0, 1.0);
|
||||
vec3 sigmaS = dFdx(model_pos.xyz);
|
||||
vec3 sigmaT = dFdy(model_pos.xyz);
|
||||
vec3 R1 = cross(sigmaT, triangle_normal);
|
||||
vec3 R2 = cross(triangle_normal, sigmaS);
|
||||
float det = dot(sigmaS, R1);
|
||||
float dHdx = k * dFdx(h);
|
||||
float dHdy = k * dFdy(h);
|
||||
if (abs(det) > 1e-12)
|
||||
triangle_normal = normalize(triangle_normal - (dHdx * R1 + dHdy * R2) / det);
|
||||
} else if (weight > 0.0) {
|
||||
vec2 uv = project_uv(model_pos.xyz, triangle_normal);
|
||||
vec3 t, b;
|
||||
projection_axes(triangle_normal, t, b);
|
||||
|
||||
float hL = texture2D(height_tex, uv - vec2(height_tex_texel.x, 0.0)).r;
|
||||
float hR = texture2D(height_tex, uv + vec2(height_tex_texel.x, 0.0)).r;
|
||||
float hD = texture2D(height_tex, uv - vec2(0.0, height_tex_texel.y)).r;
|
||||
float hU = texture2D(height_tex, uv + vec2(0.0, height_tex_texel.y)).r;
|
||||
|
||||
vec2 dh_duv = vec2((hR - hL) / (2.0 * height_tex_texel.x), (hU - hD) / (2.0 * height_tex_texel.y));
|
||||
float inv_tiling = (tiling_scale > 1e-6) ? (1.0 / tiling_scale) : 1.0;
|
||||
float amplitude = (invert ? -1.0 : 1.0) * depth_mm * inv_tiling * clamp(weight, 0.0, 1.0);
|
||||
|
||||
float cs = cos(rotation_rad);
|
||||
float sn = sin(rotation_rad);
|
||||
vec2 slope = amplitude * vec2(dh_duv.x * cs + dh_duv.y * sn, -dh_duv.x * sn + dh_duv.y * cs);
|
||||
|
||||
vec3 gradient = slope.x * t + slope.y * b;
|
||||
gradient -= triangle_normal * dot(triangle_normal, gradient);
|
||||
triangle_normal = normalize(triangle_normal - gradient);
|
||||
}
|
||||
|
||||
vec3 eye_normal = normalize(view_normal_matrix * triangle_normal);
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
vec2 intensity = vec2(0.0);
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec3 position = (view_model_matrix * model_pos).xyz;
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
gl_FragColor = vec4(vec3(intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a);
|
||||
}
|
||||
34
resources/shaders/110/texture_displacement_bump.vs
Normal file
@@ -0,0 +1,34 @@
|
||||
#version 110
|
||||
|
||||
// See resources/shaders/140/texture_displacement_bump.vs for full documentation; this is the
|
||||
// GLSL 1.10 compatibility variant.
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat4 volume_world_matrix;
|
||||
uniform vec2 z_range;
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal; // .x = paint weight (0/1); .y = 1 for the dragged island's vertices
|
||||
attribute vec2 v_tex_coord; // precomputed texture uv, used only when use_vertex_uv is set
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 model_pos;
|
||||
varying vec4 world_pos;
|
||||
varying float weight;
|
||||
varying float island_active;
|
||||
varying vec2 vertex_uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
model_pos = vec4(v_position, 1.0);
|
||||
world_pos = volume_world_matrix * model_pos;
|
||||
|
||||
gl_Position = projection_matrix * view_model_matrix * model_pos;
|
||||
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
|
||||
|
||||
weight = v_normal.x;
|
||||
island_active = v_normal.y;
|
||||
vertex_uv = v_tex_coord;
|
||||
}
|
||||
70
resources/shaders/110/texture_displacement_uvcheck.fs
Normal file
@@ -0,0 +1,70 @@
|
||||
#version 110
|
||||
|
||||
// See resources/shaders/140/texture_displacement_uvcheck.fs; GLSL 1.10 compatibility variant.
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
uniform mat3 view_normal_matrix;
|
||||
uniform bool volume_mirrored;
|
||||
|
||||
uniform int mode;
|
||||
uniform float checker_freq;
|
||||
uniform float tiling_scale;
|
||||
uniform float rotation_rad;
|
||||
uniform vec2 uv_offset;
|
||||
uniform bool use_vertex_uv;
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 model_pos;
|
||||
varying vec4 world_pos;
|
||||
varying float distortion;
|
||||
varying vec2 vertex_uv;
|
||||
|
||||
vec2 project_uv(vec3 p, vec3 n)
|
||||
{
|
||||
vec3 an = abs(n);
|
||||
vec2 planar = (an.x >= an.y && an.x >= an.z) ? p.yz : ((an.y >= an.x && an.y >= an.z) ? p.xz : p.xy);
|
||||
planar *= (tiling_scale > 1e-6) ? (1.0 / tiling_scale) : 1.0;
|
||||
float cs = cos(rotation_rad);
|
||||
float sn = sin(rotation_rad);
|
||||
return vec2(planar.x * cs - planar.y * sn, planar.x * sn + planar.y * cs) + uv_offset;
|
||||
}
|
||||
|
||||
vec3 heatmap(float t)
|
||||
{
|
||||
t = clamp(t, 0.0, 1.0);
|
||||
return clamp(vec3(1.5 - abs(4.0 * t - 3.0),
|
||||
1.5 - abs(4.0 * t - 2.0),
|
||||
1.5 - abs(4.0 * t - 1.0)), 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
discard;
|
||||
|
||||
vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz)));
|
||||
if (volume_mirrored)
|
||||
triangle_normal = -triangle_normal;
|
||||
|
||||
vec3 base;
|
||||
if (mode == 1) {
|
||||
base = heatmap(distortion);
|
||||
} else {
|
||||
vec2 uv = use_vertex_uv ? vertex_uv : project_uv(model_pos.xyz, triangle_normal);
|
||||
vec2 c = floor(uv * checker_freq);
|
||||
float check = mod(c.x + c.y, 2.0);
|
||||
base = (check < 0.5) ? vec3(0.22, 0.23, 0.26) : vec3(0.82, 0.83, 0.86);
|
||||
}
|
||||
|
||||
vec3 eye_normal = normalize(view_normal_matrix * triangle_normal);
|
||||
float intensity = INTENSITY_AMBIENT + max(dot(eye_normal, LIGHT_TOP_DIR), 0.0) * LIGHT_TOP_DIFFUSE
|
||||
+ max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0) * LIGHT_FRONT_DIFFUSE;
|
||||
gl_FragColor = vec4(base * intensity, 1.0);
|
||||
}
|
||||
31
resources/shaders/110/texture_displacement_uvcheck.vs
Normal file
@@ -0,0 +1,31 @@
|
||||
#version 110
|
||||
|
||||
// See resources/shaders/140/texture_displacement_uvcheck.vs; GLSL 1.10 compatibility variant.
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat4 volume_world_matrix;
|
||||
uniform vec2 z_range;
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
attribute vec3 v_position;
|
||||
attribute vec3 v_normal; // .x = per-vertex uv distortion
|
||||
attribute vec2 v_tex_coord; // precomputed texture uv, used only when use_vertex_uv is set
|
||||
|
||||
varying vec3 clipping_planes_dots;
|
||||
varying vec4 model_pos;
|
||||
varying vec4 world_pos;
|
||||
varying float distortion;
|
||||
varying vec2 vertex_uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
model_pos = vec4(v_position, 1.0);
|
||||
world_pos = volume_world_matrix * model_pos;
|
||||
|
||||
gl_Position = projection_matrix * view_model_matrix * model_pos;
|
||||
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
|
||||
|
||||
distortion = v_normal.x;
|
||||
vertex_uv = v_tex_coord;
|
||||
}
|
||||
186
resources/shaders/140/texture_displacement_bump.fs
Normal file
@@ -0,0 +1,186 @@
|
||||
#version 140
|
||||
|
||||
// Fast, geometry-free preview of texture displacement: perturbs the *shading* normal from the
|
||||
// height texture's local gradient (a bump map), faded out by the per-vertex paint weight. The
|
||||
// true, exact result is what "Bake" produces via libslic3r/TextureDisplacement.cpp on the CPU.
|
||||
//
|
||||
// The bake displaces each surface point along its normal by H = +/- depth_mm * (h(uv) - midlevel),
|
||||
// with uv from the layer's projection. The perturbed normal is the analytic
|
||||
//
|
||||
// N' = normalize(N - (dH/da) * T - (dH/db) * B)
|
||||
//
|
||||
// over any orthonormal surface tangent pair (T, B), where the two slopes are real mm-per-mm
|
||||
// derivatives. Two things have to be right for the preview's apparent depth to match the bake's:
|
||||
// the tangent frame the gradient is expressed in, and the uv->mm scale that turns a texel
|
||||
// difference into a slope. Getting the scale wrong is a uniform flattening (a raw texel difference
|
||||
// is dh over one texel step, not over one mm); getting the frame wrong tilts the bump along the
|
||||
// wrong axes.
|
||||
//
|
||||
// Two projection paths:
|
||||
// * Triplanar (use_vertex_uv = 0): uv and the tangent axes are derived in-shader from the dominant
|
||||
// normal axis, mirroring libslic3r's project_planar()/apply_uv_transform(), and the slope is
|
||||
// formed analytically (there is a closed-form uv, so 1 uv unit is exactly tiling_scale mm).
|
||||
// * Precomputed uv (use_vertex_uv = 1, used for LSCM): uv comes per-vertex from the CPU (the LSCM
|
||||
// unwrap with island placement + tiling/rotation/offset already folded in), and the perturbed
|
||||
// normal is built with Mikkelsen's method -- the surface gradient taken straight from the
|
||||
// screen-space derivatives of the sampled height and position. This makes no uv->mm scale
|
||||
// assumption, which matters because an LSCM map is conformal, not isometric: the local mm-per-uv
|
||||
// varies across the chart, so a single global 1/tiling factor (what an earlier version used) got
|
||||
// the apparent depth wrong. 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.
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
uniform vec4 uniform_color;
|
||||
uniform bool volume_mirrored;
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat3 view_normal_matrix;
|
||||
|
||||
uniform sampler2D height_tex;
|
||||
uniform vec2 height_tex_texel; // (1/width, 1/height) of height_tex
|
||||
uniform float depth_mm;
|
||||
uniform float tiling_scale;
|
||||
uniform float rotation_rad;
|
||||
uniform vec2 uv_offset;
|
||||
uniform bool invert;
|
||||
uniform bool use_vertex_uv; // true: sample at vertex_uv with a derived tangent frame (LSCM)
|
||||
// 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 (island_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
|
||||
uniform vec4 island_delta_lin;
|
||||
uniform vec2 island_delta_tr;
|
||||
|
||||
in vec3 clipping_planes_dots;
|
||||
in vec4 model_pos;
|
||||
in vec4 world_pos;
|
||||
in float weight;
|
||||
in float island_active;
|
||||
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
|
||||
// along b.
|
||||
void projection_axes(vec3 n, out vec3 t, out vec3 b)
|
||||
{
|
||||
vec3 an = abs(n);
|
||||
if (an.x >= an.y && an.x >= an.z) { // planar = p.yz
|
||||
t = vec3(0.0, 1.0, 0.0);
|
||||
b = vec3(0.0, 0.0, 1.0);
|
||||
} else if (an.y >= an.x && an.y >= an.z) { // planar = p.xz
|
||||
t = vec3(1.0, 0.0, 0.0);
|
||||
b = vec3(0.0, 0.0, 1.0);
|
||||
} else { // planar = p.xy
|
||||
t = vec3(1.0, 0.0, 0.0);
|
||||
b = vec3(0.0, 1.0, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
vec2 project_uv(vec3 p, vec3 n)
|
||||
{
|
||||
vec3 an = abs(n);
|
||||
vec2 planar = (an.x >= an.y && an.x >= an.z) ? p.yz : ((an.y >= an.x && an.y >= an.z) ? p.xz : p.xy);
|
||||
planar *= (tiling_scale > 1e-6) ? (1.0 / tiling_scale) : 1.0;
|
||||
float cs = cos(rotation_rad);
|
||||
float sn = sin(rotation_rad);
|
||||
return vec2(planar.x * cs - planar.y * sn, planar.x * sn + planar.y * cs) + uv_offset;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
discard;
|
||||
|
||||
vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz)));
|
||||
if (volume_mirrored)
|
||||
triangle_normal = -triangle_normal;
|
||||
|
||||
if (use_vertex_uv) {
|
||||
// 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,
|
||||
// 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.
|
||||
//
|
||||
// use_vertex_uv is a uniform, so this whole branch is uniform control flow and the texture
|
||||
// derivatives are well defined; the paint weight gates the result by a plain multiply (k)
|
||||
// rather than a per-fragment branch, keeping it that way.
|
||||
// The dragged island's uv rides a uniform affine so its bump moves without a rebuild; every
|
||||
// other vertex (island_active == 0) samples its baked uv unchanged.
|
||||
vec2 uv = (island_active > 0.5)
|
||||
? vec2(dot(island_delta_lin.xy, vertex_uv), dot(island_delta_lin.zw, vertex_uv)) + island_delta_tr
|
||||
: vertex_uv;
|
||||
float h = texture(height_tex, uv).r;
|
||||
float k = (invert ? -1.0 : 1.0) * depth_mm * clamp(weight, 0.0, 1.0);
|
||||
vec3 sigmaS = dFdx(model_pos.xyz);
|
||||
vec3 sigmaT = dFdy(model_pos.xyz);
|
||||
vec3 R1 = cross(sigmaT, triangle_normal);
|
||||
vec3 R2 = cross(triangle_normal, sigmaS);
|
||||
float det = dot(sigmaS, R1);
|
||||
float dHdx = k * dFdx(h);
|
||||
float dHdy = k * dFdy(h);
|
||||
if (abs(det) > 1e-12)
|
||||
triangle_normal = normalize(triangle_normal - (dHdx * R1 + dHdy * R2) / det);
|
||||
} else if (weight > 0.0) {
|
||||
// Triplanar path: uv and the tangent axes are reconstructed in-shader from the dominant
|
||||
// normal component (see header). The gradient is expressed analytically because there is a
|
||||
// closed-form uv here, unlike the LSCM case.
|
||||
vec2 uv = project_uv(model_pos.xyz, triangle_normal);
|
||||
vec3 t, b;
|
||||
projection_axes(triangle_normal, t, b);
|
||||
|
||||
float hL = texture(height_tex, uv - vec2(height_tex_texel.x, 0.0)).r;
|
||||
float hR = texture(height_tex, uv + vec2(height_tex_texel.x, 0.0)).r;
|
||||
float hD = texture(height_tex, uv - vec2(0.0, height_tex_texel.y)).r;
|
||||
float hU = texture(height_tex, uv + vec2(0.0, height_tex_texel.y)).r;
|
||||
|
||||
// Central difference, per uv unit (not per texel).
|
||||
vec2 dh_duv = vec2((hR - hL) / (2.0 * height_tex_texel.x), (hU - hD) / (2.0 * height_tex_texel.y));
|
||||
|
||||
// uv -> mm is 1/tiling_scale for the triplanar projection, so this turns the uv-space
|
||||
// gradient into a real surface slope.
|
||||
float inv_tiling = (tiling_scale > 1e-6) ? (1.0 / tiling_scale) : 1.0;
|
||||
float amplitude = (invert ? -1.0 : 1.0) * depth_mm * inv_tiling * clamp(weight, 0.0, 1.0);
|
||||
// uv was rotated by project_uv() while t/b are the unrotated model axes, so rotate the
|
||||
// gradient back into the axes' frame.
|
||||
float cs = cos(rotation_rad);
|
||||
float sn = sin(rotation_rad);
|
||||
vec2 slope = amplitude * vec2(dh_duv.x * cs + dh_duv.y * sn, -dh_duv.x * sn + dh_duv.y * cs);
|
||||
|
||||
vec3 gradient = slope.x * t + slope.y * b;
|
||||
gradient -= triangle_normal * dot(triangle_normal, gradient);
|
||||
triangle_normal = normalize(triangle_normal - gradient);
|
||||
}
|
||||
|
||||
vec3 eye_normal = normalize(view_normal_matrix * triangle_normal);
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
vec2 intensity = vec2(0.0);
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec3 position = (view_model_matrix * model_pos).xyz;
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
out_color = vec4(vec3(intensity.y) + uniform_color.rgb * intensity.x, uniform_color.a);
|
||||
}
|
||||
44
resources/shaders/140/texture_displacement_bump.vs
Normal file
@@ -0,0 +1,44 @@
|
||||
#version 140
|
||||
|
||||
uniform mat4 view_model_matrix;
|
||||
uniform mat4 projection_matrix;
|
||||
|
||||
uniform mat4 volume_world_matrix;
|
||||
// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane.
|
||||
uniform vec2 z_range;
|
||||
// Clipping plane - general orientation. Used by the SLA gizmo.
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
in vec3 v_position;
|
||||
// GLModel's P3N3T2 layout (position + normal + texcoord), reused so this mesh builds and renders
|
||||
// like any other GLModel rather than needing a bespoke vertex buffer. The two spare channels carry
|
||||
// what the bump preview actually needs per vertex:
|
||||
// v_normal.x -- the active layer's paint weight, 0 (untouched) or 1 (painted).
|
||||
// v_normal.y -- 1 for a vertex of the island currently being dragged in the UV editor, else 0.
|
||||
// The fragment shader applies island_delta to those vertices' uv, so a UV drag is a
|
||||
// single uniform update rather than a whole-mesh rebuild (like Adjust placement).
|
||||
// v_tex_coord -- the precomputed texture uv for this vertex, valid only when use_vertex_uv is set
|
||||
// (i.e. the LSCM projection, where uv can't be reconstructed in the shader). The
|
||||
// triplanar path ignores it and projects in the fragment shader instead.
|
||||
in vec3 v_normal;
|
||||
in vec2 v_tex_coord;
|
||||
|
||||
out vec3 clipping_planes_dots;
|
||||
out vec4 model_pos;
|
||||
out vec4 world_pos;
|
||||
out float weight;
|
||||
out float island_active;
|
||||
out vec2 vertex_uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
model_pos = vec4(v_position, 1.0);
|
||||
world_pos = volume_world_matrix * model_pos;
|
||||
|
||||
gl_Position = projection_matrix * view_model_matrix * model_pos;
|
||||
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
|
||||
|
||||
weight = v_normal.x;
|
||||
island_active = v_normal.y;
|
||||
vertex_uv = v_tex_coord;
|
||||
}
|
||||
83
resources/shaders/140/texture_displacement_uvcheck.fs
Normal file
@@ -0,0 +1,83 @@
|
||||
#version 140
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// (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.
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
uniform mat3 view_normal_matrix;
|
||||
uniform bool volume_mirrored;
|
||||
|
||||
uniform int mode; // 0 checker, 1 distortion
|
||||
uniform float checker_freq; // checker squares per uv unit (one uv unit == one texture tile)
|
||||
uniform float tiling_scale;
|
||||
uniform float rotation_rad;
|
||||
uniform vec2 uv_offset;
|
||||
uniform bool use_vertex_uv;
|
||||
|
||||
in vec3 clipping_planes_dots;
|
||||
in vec4 model_pos;
|
||||
in vec4 world_pos;
|
||||
in float distortion;
|
||||
in vec2 vertex_uv;
|
||||
|
||||
out vec4 out_color;
|
||||
|
||||
vec2 project_uv(vec3 p, vec3 n)
|
||||
{
|
||||
vec3 an = abs(n);
|
||||
vec2 planar = (an.x >= an.y && an.x >= an.z) ? p.yz : ((an.y >= an.x && an.y >= an.z) ? p.xz : p.xy);
|
||||
planar *= (tiling_scale > 1e-6) ? (1.0 / tiling_scale) : 1.0;
|
||||
float cs = cos(rotation_rad);
|
||||
float sn = sin(rotation_rad);
|
||||
return vec2(planar.x * cs - planar.y * sn, planar.x * sn + planar.y * cs) + uv_offset;
|
||||
}
|
||||
|
||||
// Blue -> cyan -> green -> yellow -> red over t in [0,1].
|
||||
vec3 heatmap(float t)
|
||||
{
|
||||
t = clamp(t, 0.0, 1.0);
|
||||
return clamp(vec3(1.5 - abs(4.0 * t - 3.0),
|
||||
1.5 - abs(4.0 * t - 2.0),
|
||||
1.5 - abs(4.0 * t - 1.0)), 0.0, 1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
discard;
|
||||
|
||||
vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz)));
|
||||
if (volume_mirrored)
|
||||
triangle_normal = -triangle_normal;
|
||||
|
||||
vec3 base;
|
||||
if (mode == 1) {
|
||||
base = heatmap(distortion);
|
||||
} else {
|
||||
vec2 uv = use_vertex_uv ? vertex_uv : project_uv(model_pos.xyz, triangle_normal);
|
||||
vec2 c = floor(uv * checker_freq);
|
||||
float check = mod(c.x + c.y, 2.0);
|
||||
// Two distinct greys, plus a faint tint on one set so orientation is readable at a glance.
|
||||
base = (check < 0.5) ? vec3(0.22, 0.23, 0.26) : vec3(0.82, 0.83, 0.86);
|
||||
}
|
||||
|
||||
vec3 eye_normal = normalize(view_normal_matrix * triangle_normal);
|
||||
float intensity = INTENSITY_AMBIENT + max(dot(eye_normal, LIGHT_TOP_DIR), 0.0) * LIGHT_TOP_DIFFUSE
|
||||
+ max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0) * LIGHT_FRONT_DIFFUSE;
|
||||
out_color = vec4(base * intensity, 1.0);
|
||||
}
|
||||
36
resources/shaders/140/texture_displacement_uvcheck.vs
Normal file
@@ -0,0 +1,36 @@
|
||||
#version 140
|
||||
|
||||
// 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);
|
||||
// only the distortion mode reads it.
|
||||
// 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;
|
||||
uniform mat4 projection_matrix;
|
||||
uniform mat4 volume_world_matrix;
|
||||
uniform vec2 z_range;
|
||||
uniform vec4 clipping_plane;
|
||||
|
||||
in vec3 v_position;
|
||||
in vec3 v_normal;
|
||||
in vec2 v_tex_coord;
|
||||
|
||||
out vec3 clipping_planes_dots;
|
||||
out vec4 model_pos;
|
||||
out vec4 world_pos;
|
||||
out float distortion;
|
||||
out vec2 vertex_uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
model_pos = vec4(v_position, 1.0);
|
||||
world_pos = volume_world_matrix * model_pos;
|
||||
|
||||
gl_Position = projection_matrix * view_model_matrix * model_pos;
|
||||
clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z);
|
||||
|
||||
distortion = v_normal.x;
|
||||
vertex_uv = v_tex_coord;
|
||||
}
|
||||
BIN
resources/textures/displacement/Bricks.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
resources/textures/displacement/Grid.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
resources/textures/displacement/Hexagons.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
resources/textures/displacement/Knurl.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
resources/textures/displacement/Noise.png
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
resources/textures/displacement/Quilt.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
resources/textures/displacement/Studs.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
resources/textures/displacement/Waves.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
resources/textures/displacement/Weave.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
resources/textures/displacement/Wood Grain.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
@@ -441,6 +441,8 @@ set(lisbslic3r_sources
|
||||
Tesselate.cpp
|
||||
Tesselate.hpp
|
||||
TextConfiguration.hpp
|
||||
TextureDisplacement.cpp
|
||||
TextureDisplacement.hpp
|
||||
Thread.cpp
|
||||
Thread.hpp
|
||||
Time.cpp
|
||||
|
||||
@@ -26,6 +26,13 @@
|
||||
#include <CGAL/property_map.h>
|
||||
#include <CGAL/boost/graph/copy_face_graph.h>
|
||||
#include <CGAL/boost/graph/Face_filtered_graph.h>
|
||||
// For parameterize_lscm()
|
||||
#include <CGAL/Polygon_mesh_processing/border.h>
|
||||
#include <CGAL/Polygon_mesh_processing/connected_components.h>
|
||||
#include <CGAL/Polygon_mesh_processing/detect_features.h>
|
||||
#include <CGAL/Surface_mesh_parameterization/Error_code.h>
|
||||
#include <CGAL/Surface_mesh_parameterization/LSCM_parameterizer_3.h>
|
||||
#include <CGAL/Surface_mesh_parameterization/parameterize.h>
|
||||
// BBS: for boolean using mcut
|
||||
#include "mcut/include/mcut/mcut.h"
|
||||
|
||||
@@ -249,6 +256,130 @@ indexed_triangle_set cgal_to_indexed_triangle_set(const CGALMesh &cgalmesh)
|
||||
return cgal_to_indexed_triangle_set(cgalmesh.m);
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Isotropic remeshing
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
indexed_triangle_set remesh_isotropic(const indexed_triangle_set &mesh, double target_edge_length,
|
||||
unsigned n_iterations, double sharp_angle_deg)
|
||||
{
|
||||
if (mesh.indices.empty() || target_edge_length <= 0.0)
|
||||
return mesh;
|
||||
|
||||
_EpicMesh cgal_mesh;
|
||||
triangle_mesh_to_cgal(mesh.vertices, mesh.indices, cgal_mesh);
|
||||
if (cgal_mesh.is_empty() || cgal_mesh.number_of_faces() == 0)
|
||||
return mesh;
|
||||
|
||||
// Surface_mesh::add_face() refuses any face that would make the mesh non-manifold and returns a
|
||||
// null descriptor instead. Remeshing a mesh that silently lost faces that way produces holes in
|
||||
// the output, so bail out and let the caller report it rather than hand back a punctured model.
|
||||
if (cgal_mesh.number_of_faces() != mesh.indices.size())
|
||||
return mesh;
|
||||
|
||||
using edge_descriptor = boost::graph_traits<_EpicMesh>::edge_descriptor;
|
||||
try {
|
||||
// Sharp edges and open borders are pinned before remeshing. Without that, the tangential
|
||||
// relaxation pass slides vertices along the surface and rounds every hard feature off - a
|
||||
// cube comes back with wobbly, eroded edges, which is the most visible way "remeshing does
|
||||
// not work properly". protect_constraints() forbids splitting or collapsing them, but it
|
||||
// requires each constrained edge to already be shorter than 4/3 * target, hence the split
|
||||
// first (passing the map so the halves inherit the constraint). This mirrors CGAL's own
|
||||
// isotropic_remeshing example.
|
||||
auto ecm = cgal_mesh.add_property_map<edge_descriptor, bool>("e:is_constrained", false).first;
|
||||
if (sharp_angle_deg > 0.0)
|
||||
CGALProc::detect_sharp_edges(cgal_mesh, sharp_angle_deg, ecm);
|
||||
for (edge_descriptor e : edges(cgal_mesh)) {
|
||||
const auto h = halfedge(e, cgal_mesh);
|
||||
if (is_border(h, cgal_mesh) || is_border(opposite(h, cgal_mesh), cgal_mesh))
|
||||
put(ecm, e, true);
|
||||
}
|
||||
|
||||
std::vector<edge_descriptor> constrained;
|
||||
for (edge_descriptor e : edges(cgal_mesh))
|
||||
if (get(ecm, e))
|
||||
constrained.push_back(e);
|
||||
if (!constrained.empty())
|
||||
CGALProc::split_long_edges(constrained, target_edge_length, cgal_mesh,
|
||||
CGALParams::edge_is_constrained_map(ecm));
|
||||
|
||||
CGALProc::isotropic_remeshing(faces(cgal_mesh), target_edge_length, cgal_mesh,
|
||||
CGALParams::number_of_iterations(n_iterations)
|
||||
.edge_is_constrained_map(ecm)
|
||||
.protect_constraints(true));
|
||||
} catch (const std::exception &) {
|
||||
return mesh; // CGAL throws on some non-manifold / degenerate inputs; leave the mesh untouched
|
||||
}
|
||||
if (cgal_mesh.number_of_faces() == 0)
|
||||
return mesh;
|
||||
|
||||
// isotropic_remeshing edits in place, and its edge collapses only *mark* vertices and faces as
|
||||
// removed - the underlying arrays keep the holes until the garbage is collected. That matters
|
||||
// because 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). Past the first collapse the two disagree, so every triangle
|
||||
// points at the wrong vertices, and any descriptor beyond the live vertex count is dropped
|
||||
// together with its triangle. Compacting first makes descriptor == iteration order again.
|
||||
cgal_mesh.collect_garbage();
|
||||
return cgal_to_indexed_triangle_set(cgal_mesh);
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// UV parameterization
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
std::optional<std::vector<Vec2f>> parameterize_lscm(const indexed_triangle_set &mesh)
|
||||
{
|
||||
namespace SMP = CGAL::Surface_mesh_parameterization;
|
||||
|
||||
if (mesh.indices.empty())
|
||||
return std::nullopt;
|
||||
|
||||
_EpicMesh cgal_mesh;
|
||||
triangle_mesh_to_cgal(mesh.vertices, mesh.indices, cgal_mesh);
|
||||
|
||||
using vertex_descriptor = boost::graph_traits<_EpicMesh>::vertex_descriptor;
|
||||
using halfedge_descriptor = boost::graph_traits<_EpicMesh>::halfedge_descriptor;
|
||||
|
||||
// LSCM assumes a single topological disk: one connected component, one boundary loop. A patch
|
||||
// with several disconnected painted islands, or with a hole in it, doesn't qualify -- bail out
|
||||
// rather than silently parameterizing just one arbitrary piece of it.
|
||||
{
|
||||
std::vector<std::size_t> component_id(num_faces(cgal_mesh));
|
||||
const std::size_t num_components = CGAL::Polygon_mesh_processing::connected_components(
|
||||
cgal_mesh, CGAL::make_property_map(component_id));
|
||||
if (num_components != 1)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const halfedge_descriptor border = CGAL::Polygon_mesh_processing::longest_border(cgal_mesh).first;
|
||||
if (border == halfedge_descriptor())
|
||||
return std::nullopt; // no boundary at all -- a closed patch, which isn't a disk either
|
||||
|
||||
using Point_2 = EpicKernel::Point_2;
|
||||
using UV_pmap = _EpicMesh::Property_map<vertex_descriptor, Point_2>;
|
||||
UV_pmap uv_map = cgal_mesh.add_property_map<vertex_descriptor, Point_2>("h:uv", Point_2(0, 0)).first;
|
||||
|
||||
using Parameterizer = SMP::LSCM_parameterizer_3<_EpicMesh>;
|
||||
const SMP::Error_code err = SMP::parameterize(cgal_mesh, Parameterizer(), border, uv_map);
|
||||
if (err != SMP::OK)
|
||||
return std::nullopt;
|
||||
|
||||
// triangle_mesh_to_cgal() adds vertices in the exact same order as mesh.vertices (see above),
|
||||
// and Surface_mesh assigns indices sequentially on insertion into a fresh mesh, so a
|
||||
// vertex_descriptor's index here is guaranteed to match the original input vertex index --
|
||||
// the same assumption cgal_to_indexed_triangle_set() above already relies on.
|
||||
std::vector<Vec2f> result(mesh.vertices.size(), Vec2f::Zero());
|
||||
for (vertex_descriptor vd : vertices(cgal_mesh)) {
|
||||
const std::size_t idx = std::size_t(vd);
|
||||
if (idx < result.size()) {
|
||||
const Point_2 &uv = uv_map[vd];
|
||||
result[idx] = Vec2f(float(uv.x()), float(uv.y()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
// Boolean operations for CGAL meshes
|
||||
// /////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <memory>
|
||||
#include <exception>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <Eigen/Geometry>
|
||||
@@ -73,6 +75,22 @@ bool empty(const CGALMesh &mesh);
|
||||
|
||||
// Repair a mesh using CGAL. Returns true on success. Optionally returns a summary of repairs and an error string.
|
||||
bool repair(TriangleMesh &mesh, RepairedMeshErrors *repaired_errors = nullptr, std::string *error = nullptr);
|
||||
|
||||
// Real UV unwrap of an open mesh patch via CGAL's LSCM (Least Squares Conformal Maps) surface
|
||||
// parameterization. Returns one UV coordinate per input vertex (same indexing as `mesh.vertices`),
|
||||
// or nullopt if `mesh` isn't a single topological disk -- LSCM needs exactly one connected
|
||||
// component with exactly one boundary loop, true for a typical single brush stroke/patch but not
|
||||
// guaranteed for multiple disconnected painted islands merged into one mesh.
|
||||
std::optional<std::vector<Vec2f>> parameterize_lscm(const indexed_triangle_set &mesh);
|
||||
|
||||
// Isotropic remeshing (CGAL): rebuilds the mesh so its triangles are close to a uniform target edge
|
||||
// length, splitting oversized triangles and collapsing undersized ones. Used to even out a model with
|
||||
// wildly varying triangle sizes so texture displacement has a consistent vertex density to work with.
|
||||
// Edges whose dihedral angle exceeds `sharp_angle_deg`, and any open border, are held fixed so hard
|
||||
// features survive instead of being eroded by the relaxation pass; pass 0 to remesh everything.
|
||||
// Returns the input unchanged if remeshing fails (e.g. a non-manifold or self-intersecting input).
|
||||
indexed_triangle_set remesh_isotropic(const indexed_triangle_set &mesh, double target_edge_length,
|
||||
unsigned n_iterations = 3, double sharp_angle_deg = 40.0);
|
||||
}
|
||||
|
||||
namespace mcut {
|
||||
|
||||
@@ -1977,6 +1977,11 @@ void ModelVolume::reset_extra_facets()
|
||||
this->seam_facets.reset();
|
||||
this->mmu_segmentation_facets.reset();
|
||||
this->fuzzy_skin_facets.reset();
|
||||
// Texture-displacement paint data has no remap-across-topology-change support yet (see
|
||||
// build_texture_displacement()'s documented limitation), so it must be dropped here rather
|
||||
// than left referring to a mesh that no longer matches it.
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
this->texture_displacement_facet(i).reset();
|
||||
}
|
||||
|
||||
std::optional<TriangleSelector::SavedPainting> ModelVolume::save_painting() const
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "TextConfiguration.hpp"
|
||||
#include "EmbossShape.hpp"
|
||||
#include "TriangleSelector.hpp"
|
||||
#include "TextureDisplacement.hpp"
|
||||
|
||||
//BBS: add bbs 3mf
|
||||
#include "Format/bbs_3mf.hpp"
|
||||
@@ -28,6 +29,7 @@
|
||||
#include "Format/STL.hpp"
|
||||
#include "Format/OBJ.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -878,6 +880,64 @@ public:
|
||||
// List of mesh facets painted for fuzzy skin.
|
||||
FacetsAnnotation fuzzy_skin_facets;
|
||||
|
||||
// One independent paint mask per texture-displacement layer slot (see texture_displacement_layers
|
||||
// below). Unlike the other facets fields above, a triangle may be painted (ENFORCER) in more
|
||||
// than one of these simultaneously -- that overlap is what makes the layers "blend".
|
||||
//
|
||||
// These are 8 plain named fields rather than a std::array<FacetsAnnotation, N>: FacetsAnnotation's
|
||||
// default/copy constructors are private and friended only to ModelVolume, but std::array's own
|
||||
// implicitly-defined default/copy constructors are generated with std::array's access rights,
|
||||
// not ModelVolume's -- so an array of FacetsAnnotation ends up with its default/copy
|
||||
// constructors implicitly deleted regardless of the friend declaration. Use
|
||||
// texture_displacement_facet(slot) below for array-like indexed access.
|
||||
FacetsAnnotation texture_displacement_facets_0;
|
||||
FacetsAnnotation texture_displacement_facets_1;
|
||||
FacetsAnnotation texture_displacement_facets_2;
|
||||
FacetsAnnotation texture_displacement_facets_3;
|
||||
FacetsAnnotation texture_displacement_facets_4;
|
||||
FacetsAnnotation texture_displacement_facets_5;
|
||||
FacetsAnnotation texture_displacement_facets_6;
|
||||
FacetsAnnotation texture_displacement_facets_7;
|
||||
|
||||
FacetsAnnotation& texture_displacement_facet(int slot) {
|
||||
switch (slot) {
|
||||
case 0: return texture_displacement_facets_0;
|
||||
case 1: return texture_displacement_facets_1;
|
||||
case 2: return texture_displacement_facets_2;
|
||||
case 3: return texture_displacement_facets_3;
|
||||
case 4: return texture_displacement_facets_4;
|
||||
case 5: return texture_displacement_facets_5;
|
||||
case 6: return texture_displacement_facets_6;
|
||||
default: assert(slot == 7); return texture_displacement_facets_7;
|
||||
}
|
||||
}
|
||||
const FacetsAnnotation& texture_displacement_facet(int slot) const { return const_cast<ModelVolume*>(this)->texture_displacement_facet(slot); }
|
||||
|
||||
// Small helpers for the constructor asserts below (kept out of line-noise at each call site).
|
||||
bool texture_displacement_facets_ids_valid() const {
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
if (!texture_displacement_facet(i).id().valid() || texture_displacement_facet(i).id() == this->id())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
bool texture_displacement_facets_ids_invalid() const {
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
if (texture_displacement_facet(i).id().valid())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
bool texture_displacement_facets_all_empty() const {
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
if (!texture_displacement_facet(i).empty())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Texture assets (height maps) and their projection/displacement parameters. Element order
|
||||
// is not meaningful for baking (layers are applied in TextureDisplacementLayer::slot order,
|
||||
// see build_texture_displacement()); it only reflects UI insertion order.
|
||||
std::vector<TextureDisplacementLayer> texture_displacement_layers;
|
||||
|
||||
// Save painting data before reset_extra_facets() discards it.
|
||||
// Used for replacing mesh without losing painting data.
|
||||
// Only for model parts (not modifiers/connectors).
|
||||
@@ -1014,13 +1074,18 @@ public:
|
||||
this->seam_facets.set_new_unique_id();
|
||||
this->mmu_segmentation_facets.set_new_unique_id();
|
||||
this->fuzzy_skin_facets.set_new_unique_id();
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
this->texture_displacement_facet(i).set_new_unique_id();
|
||||
}
|
||||
|
||||
bool is_fdm_support_painted() const { return !this->supported_facets.empty(); }
|
||||
bool is_seam_painted() const { return !this->seam_facets.empty(); }
|
||||
bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); }
|
||||
bool is_fuzzy_skin_painted() const { return !this->fuzzy_skin_facets.empty(); }
|
||||
bool is_any_painted() const { return is_fdm_support_painted() || is_seam_painted() || is_mm_painted() || is_fuzzy_skin_painted(); }
|
||||
bool is_texture_displacement_painted() const { return !this->texture_displacement_facets_all_empty(); }
|
||||
bool is_any_painted() const {
|
||||
return is_fdm_support_painted() || is_seam_painted() || is_mm_painted() || is_fuzzy_skin_painted() || is_texture_displacement_painted();
|
||||
}
|
||||
|
||||
// Orca: Implement prusa's filament shrink compensation approach
|
||||
// Returns 0-based indices of extruders painted by multi-material painting gizmo.
|
||||
@@ -1073,6 +1138,7 @@ private:
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->mmu_segmentation_facets.id().valid());
|
||||
assert(this->fuzzy_skin_facets.id().valid());
|
||||
assert(this->texture_displacement_facets_ids_valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->supported_facets.id());
|
||||
assert(this->id() != this->seam_facets.id());
|
||||
@@ -1089,6 +1155,7 @@ private:
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->mmu_segmentation_facets.id().valid());
|
||||
assert(this->fuzzy_skin_facets.id().valid());
|
||||
assert(this->texture_displacement_facets_ids_valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->supported_facets.id());
|
||||
assert(this->id() != this->seam_facets.id());
|
||||
@@ -1103,6 +1170,7 @@ private:
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->mmu_segmentation_facets.id().valid());
|
||||
assert(this->fuzzy_skin_facets.id().valid());
|
||||
assert(this->texture_displacement_facets_ids_valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->supported_facets.id());
|
||||
assert(this->id() != this->seam_facets.id());
|
||||
@@ -1116,10 +1184,16 @@ private:
|
||||
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
|
||||
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
|
||||
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets),
|
||||
fuzzy_skin_facets(other.fuzzy_skin_facets), cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
|
||||
fuzzy_skin_facets(other.fuzzy_skin_facets),
|
||||
texture_displacement_facets_0(other.texture_displacement_facets_0), texture_displacement_facets_1(other.texture_displacement_facets_1),
|
||||
texture_displacement_facets_2(other.texture_displacement_facets_2), texture_displacement_facets_3(other.texture_displacement_facets_3),
|
||||
texture_displacement_facets_4(other.texture_displacement_facets_4), texture_displacement_facets_5(other.texture_displacement_facets_5),
|
||||
texture_displacement_facets_6(other.texture_displacement_facets_6), texture_displacement_facets_7(other.texture_displacement_facets_7),
|
||||
texture_displacement_layers(other.texture_displacement_layers),
|
||||
cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
|
||||
{
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->supported_facets.id().valid());
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->mmu_segmentation_facets.id().valid());
|
||||
@@ -1169,6 +1243,8 @@ private:
|
||||
assert(this->seam_facets.empty());
|
||||
assert(this->mmu_segmentation_facets.empty());
|
||||
assert(this->fuzzy_skin_facets.empty());
|
||||
assert(this->texture_displacement_facets_all_empty());
|
||||
assert(this->texture_displacement_layers.empty());
|
||||
}
|
||||
|
||||
ModelVolume& operator=(ModelVolume &rhs) = delete;
|
||||
@@ -1176,13 +1252,17 @@ private:
|
||||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
// Used for deserialization, therefore no IDs are allocated.
|
||||
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), fuzzy_skin_facets(-1), object(nullptr) {
|
||||
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), fuzzy_skin_facets(-1),
|
||||
texture_displacement_facets_0(-1), texture_displacement_facets_1(-1), texture_displacement_facets_2(-1), texture_displacement_facets_3(-1),
|
||||
texture_displacement_facets_4(-1), texture_displacement_facets_5(-1), texture_displacement_facets_6(-1), texture_displacement_facets_7(-1),
|
||||
object(nullptr) {
|
||||
assert(this->id().invalid());
|
||||
assert(this->config.id().invalid());
|
||||
assert(this->supported_facets.id().invalid());
|
||||
assert(this->seam_facets.id().invalid());
|
||||
assert(this->mmu_segmentation_facets.id().invalid());
|
||||
assert(this->fuzzy_skin_facets.id().invalid());
|
||||
assert(this->texture_displacement_facets_ids_invalid());
|
||||
}
|
||||
template<class Archive> void load(Archive &ar) {
|
||||
bool has_convex_hull;
|
||||
@@ -1202,6 +1282,13 @@ private:
|
||||
mesh_changed |= t != mmu_segmentation_facets.timestamp();
|
||||
cereal::load_by_value(ar, fuzzy_skin_facets);
|
||||
mesh_changed |= t != fuzzy_skin_facets.timestamp();
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i) {
|
||||
FacetsAnnotation &f = texture_displacement_facet(i);
|
||||
Timestamp tf = f.timestamp();
|
||||
cereal::load_by_value(ar, f);
|
||||
mesh_changed |= tf != f.timestamp();
|
||||
}
|
||||
ar(texture_displacement_layers);
|
||||
cereal::load_by_value(ar, config);
|
||||
cereal::load(ar, text_configuration);
|
||||
cereal::load(ar, emboss_shape);
|
||||
@@ -1223,6 +1310,9 @@ private:
|
||||
cereal::save_by_value(ar, seam_facets);
|
||||
cereal::save_by_value(ar, mmu_segmentation_facets);
|
||||
cereal::save_by_value(ar, fuzzy_skin_facets);
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
cereal::save_by_value(ar, texture_displacement_facet(i));
|
||||
ar(texture_displacement_layers);
|
||||
cereal::save_by_value(ar, config);
|
||||
cereal::save(ar, text_configuration);
|
||||
cereal::save(ar, emboss_shape);
|
||||
|
||||
@@ -3504,7 +3504,7 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi
|
||||
if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt)
|
||||
{
|
||||
//BBS: add bed_exclude_area
|
||||
if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area") {
|
||||
if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area" || opt_key == "slicing_pipeline_plugin") {
|
||||
// Scalar variable, or a vector variable, which is independent from number of extruders,
|
||||
// thus the vector is presented to the user as a single input.
|
||||
diff.emplace_back(opt_key);
|
||||
|
||||
1349
src/libslic3r/TextureDisplacement.cpp
Normal file
516
src/libslic3r/TextureDisplacement.hpp
Normal file
@@ -0,0 +1,516 @@
|
||||
#ifndef slic3r_TextureDisplacement_hpp_
|
||||
#define slic3r_TextureDisplacement_hpp_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <cereal/types/array.hpp> // view_project_matrix is a std::array<float, 12>
|
||||
#include <cereal/types/string.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "TriangleSelector.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ModelVolume;
|
||||
|
||||
// Maximum number of simultaneous texture-displacement layers a single ModelVolume can hold.
|
||||
// Each layer owns its own paint mask (ModelVolume::texture_displacement_facet(slot)), so this
|
||||
// is also the number of independent EnforcerBlockerType selectors kept per volume.
|
||||
static constexpr size_t TEXTURE_DISPLACEMENT_MAX_LAYERS = 8;
|
||||
|
||||
// How a layer's height texture is sampled outside its [0, 1) tile when tiling is enabled. Ignored
|
||||
// (always clamp) when TextureDisplacementLayer::tile_enabled is false.
|
||||
enum class TextureTileMethod : int
|
||||
{
|
||||
Repeat = 0, // wrap around, tile i and tile i+1 are identical (default)
|
||||
MirroredRepeat = 1, // wrap around, every other tile is mirrored (no visible seam at tile edges)
|
||||
};
|
||||
|
||||
// How a layer's texture is mapped onto the mesh.
|
||||
enum class TextureProjectionMethod : int
|
||||
{
|
||||
// Standard *blended* tri-planar projection: the texture is sampled once per world axis (the
|
||||
// XY, XZ and YZ planes) and the three samples are blended per vertex, weighted by that
|
||||
// vertex's own normal raised to TRIPLANAR_BLEND_SHARPNESS.
|
||||
//
|
||||
// Earlier versions instead *hard-picked* the single axis most aligned with the normal. That
|
||||
// has a real, visible failure mode at any edge where the 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 agree at the (+,+) and (-,-)
|
||||
// corners (making them look fine) but differ by the full corner width at the (+,-) and (-,+)
|
||||
// corners, which is exactly the "two bad corners, two good ones" seam that was reported.
|
||||
// Blending across the transition removes that hard discontinuity by construction.
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// any chart that cannot be flattened at all.
|
||||
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 -
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
// 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;
|
||||
|
||||
// Exponent the tri-planar blend weights are raised to (see TextureProjectionMethod::Triplanar).
|
||||
// Higher means a tighter, more "hard-edged" transition between the three axis projections; lower
|
||||
// means a wider cross-fade. 4 is the usual default: tight enough that a flat face is sampled
|
||||
// almost purely along its own axis, wide enough that a 90-degree edge has no visible hard seam.
|
||||
static constexpr float TRIPLANAR_BLEND_SHARPNESS = 4.f;
|
||||
|
||||
// How a layer's displacement combines with the displacement accumulated by the layers below it
|
||||
// (i.e. those in lower slots), evaluated per vertex. Analogous to an image editor's layer blend
|
||||
// modes, except the quantity being blended is a signed displacement distance in mm rather than a
|
||||
// pixel value.
|
||||
//
|
||||
// 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
|
||||
// the behaviour that makes a Multiply layer usable as a mask over the layers beneath it.
|
||||
enum class TextureBlendMode : int
|
||||
{
|
||||
Add = 0, // acc + value (default; several layers pile their relief up together)
|
||||
Subtract = 1, // acc - value (carve this layer's relief out of the layers below)
|
||||
Multiply = 2, // acc * (value / 1mm) (mask/modulate the layers below by this layer)
|
||||
Divide = 3, // acc / (value / 1mm) (inverse mask; guarded against a zero/near-zero divisor)
|
||||
};
|
||||
|
||||
// Combines one layer's signed displacement `value` (mm) into `accumulated` (mm) per `mode`.
|
||||
// Shared by the bake/preview path and exposed for tests.
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
// texel density differ from its neighbours'. See average_island_scales().
|
||||
float scale = 1.f;
|
||||
|
||||
template<class Archive> void serialize(Archive &ar) { ar(offset, rotation_deg, scale); }
|
||||
};
|
||||
|
||||
// Sets every island's scale to the mean of the current ones (Blender's "Average Islands Scale").
|
||||
// Only meaningful after islands have been scaled by hand: compute_patch_unwrap() already sizes every
|
||||
// chart to its true mm area, so a freshly unwrapped patch has uniform texel density to begin with.
|
||||
void average_island_scales(std::vector<TextureIsland> &islands);
|
||||
|
||||
// One texture asset plus its projection/displacement parameters. Several layers may be painted
|
||||
// onto overlapping areas of the same volume: their displacements are combined per vertex, in slot
|
||||
// order, each layer folding into the total via its own TextureBlendMode (see
|
||||
// build_texture_displacement()). This is what "layered/blended" texture displacement means here.
|
||||
struct TextureDisplacementLayer
|
||||
{
|
||||
// Index into ModelVolume::texture_displacement_facets, assigned once when the layer is
|
||||
// created. Not reused for the lifetime of the ModelVolume, so a deleted layer's slot simply
|
||||
// becomes unused rather than being handed to a different layer.
|
||||
int slot = -1;
|
||||
|
||||
std::string name;
|
||||
// Path on the local filesystem the image was loaded from (informational; may be stale or
|
||||
// empty, e.g. after loading a .3mf on a different machine).
|
||||
std::string path;
|
||||
// Path inside the .3mf archive once saved (empty until the project is saved once).
|
||||
std::string path_in_3mf;
|
||||
// Raw encoded image bytes. Only 8-bit grayscale PNG is understood by decode_height_texture()
|
||||
// (libslic3r has no GUI image toolkit available); the GUI converts any imported image to that
|
||||
// format before storing it here, so the baking code never needs to depend on wxWidgets.
|
||||
std::shared_ptr<std::vector<unsigned char>> image_data;
|
||||
|
||||
float depth_mm = 0.4f; // maximum displacement along the surface normal, in mm
|
||||
float tiling_scale = 10.f; // size of one texture tile, in mm
|
||||
float rotation_deg = 0.f;
|
||||
Vec2f offset = Vec2f::Zero();
|
||||
bool invert = false;
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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
|
||||
// fold the surface through itself. There is no cheap way to detect that here (it needs a full
|
||||
// self-intersection test on the displaced mesh), so the GUI warns rather than promising safety.
|
||||
float midlevel = 0.f;
|
||||
|
||||
// Optional blur applied to the decoded height map before it is sampled, in [0, 1]: 0 is the raw
|
||||
// texture, 1 the strongest blur. Softens the relief (rounds hard edges, removes speckle) without
|
||||
// needing a pre-blurred source image. Applied in decode_height_texture(), so it feeds the true
|
||||
// preview, the UV editor backdrop and the bake identically.
|
||||
float smoothing = 0.f;
|
||||
|
||||
// Optional feathering of the displacement toward the edge of the painted patch. When enabled, the
|
||||
// displacement is scaled down as a vertex approaches the patch boundary, so the relief blends
|
||||
// smoothly into the surrounding surface instead of ending abruptly. `edge_smoothing_amount` in
|
||||
// (0, 1] sets how far the fade reaches into the patch: small values only soften a thin band at the
|
||||
// very edge, 1 fades the whole patch to nothing (the painted face comes out flat). Off by default.
|
||||
bool edge_smoothing = false;
|
||||
float edge_smoothing_amount = 0.5f;
|
||||
|
||||
// Only used by TextureProjectionMethod::LSCM: when set, a fresh unwrap is laid out as a connected
|
||||
// net (adjacent charts unfolded edge-to-edge along a spanning tree, see compute_connected_net())
|
||||
// rather than as separately packed islands. On by default. Hand-moving an island overrides its
|
||||
// placement until the next re-unwrap.
|
||||
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.
|
||||
bool tile_enabled = true;
|
||||
TextureTileMethod tile_method = TextureTileMethod::Repeat;
|
||||
|
||||
TextureProjectionMethod projection_method = TextureProjectionMethod::Triplanar;
|
||||
// Only used by TextureProjectionMethod::LSCM. See LSCM_DEFAULT_SEAM_ANGLE_DEG.
|
||||
float lscm_seam_angle_deg = LSCM_DEFAULT_SEAM_ANGLE_DEG;
|
||||
// Only used by TextureProjectionMethod::LSCM: gap left between islands by the automatic packing,
|
||||
// in the unwrap's mm space. Negative means "auto" (a small fraction of the packed size), which is
|
||||
// what a patch that has never had the slider touched gets.
|
||||
float island_padding_mm = -1.f;
|
||||
// Only used by TextureProjectionMethod::LSCM: edges the unwrap is forced to cut along, on top of
|
||||
// whatever the seam angle already cuts. Each pair is an undirected edge in *mesh vertex index*
|
||||
// space (first < second). This is what "mark seam" (manual) and "cut island" (auto) both write to.
|
||||
// Mesh-index space, so like the paint masks these are dropped on any topology change.
|
||||
std::vector<std::pair<int, int>> lscm_seam_edges;
|
||||
|
||||
// Only used by TextureProjectionMethod::ViewProjected: the projector's in-plane axes, in the
|
||||
// volume's *local* space, captured from the camera when the user hits "Project from view". A point
|
||||
// projects to Vec2f(dot(pos, right), dot(pos, up)) before the usual tiling/rotation/offset.
|
||||
Vec3f view_project_right = Vec3f::UnitX();
|
||||
Vec3f view_project_up = Vec3f::UnitY();
|
||||
|
||||
// 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).
|
||||
//
|
||||
// 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
|
||||
// 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.
|
||||
bool view_project_projective = false;
|
||||
std::array<float, 12> view_project_matrix{};
|
||||
// Only used by TextureProjectionMethod::LSCM: hand placement of the unwrap's islands, indexed by
|
||||
// chart id (see TextureIsland). Shorter than the chart count simply means the missing ones are
|
||||
// still where the automatic packing put them.
|
||||
std::vector<TextureIsland> 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"
|
||||
// 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
|
||||
// chart ids, so this is meaningful only against the unwrap it was made on.
|
||||
std::vector<int> island_groups;
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// meaningful against the current unwrap, so a re-unwrap clears these. A mesh vertex shared by several
|
||||
// charts (a seam vertex) settles on one, matching compute_lscm_uvs()'s single-UV-per-vertex rule.
|
||||
std::vector<std::pair<int, Vec2f>> lscm_uv_overrides;
|
||||
|
||||
// How this layer folds into the displacement accumulated by the layers below it. Ignored for
|
||||
// the lowest-slot painted layer, which has nothing beneath it to combine with (the GUI shows
|
||||
// it as the "Base" layer and hides the control).
|
||||
TextureBlendMode blend_mode = TextureBlendMode::Add;
|
||||
|
||||
bool empty() const { return !image_data || image_data->empty(); }
|
||||
|
||||
template<class Archive> void save(Archive &ar) const
|
||||
{
|
||||
std::string blob = image_data ? std::string(image_data->begin(), image_data->end()) : std::string();
|
||||
ar(slot, name, path, path_in_3mf, blob, depth_mm, tiling_scale, rotation_deg, offset, invert, tile_enabled,
|
||||
static_cast<int>(tile_method), static_cast<int>(projection_method), lscm_seam_angle_deg, islands,
|
||||
static_cast<int>(blend_mode), midlevel, island_padding_mm, lscm_seam_edges, view_project_right,
|
||||
view_project_up, smoothing, edge_smoothing, edge_smoothing_amount, auto_connect_islands, island_groups,
|
||||
lscm_uv_overrides, view_project_projective, view_project_matrix);
|
||||
}
|
||||
template<class Archive> void load(Archive &ar)
|
||||
{
|
||||
std::string blob;
|
||||
int tile_method_int = 0;
|
||||
int projection_method_int = 0;
|
||||
int blend_mode_int = 0;
|
||||
ar(slot, name, path, path_in_3mf, blob, depth_mm, tiling_scale, rotation_deg, offset, invert, tile_enabled,
|
||||
tile_method_int, projection_method_int, lscm_seam_angle_deg, islands, blend_mode_int, midlevel,
|
||||
island_padding_mm, lscm_seam_edges, view_project_right, view_project_up, smoothing, edge_smoothing,
|
||||
edge_smoothing_amount, auto_connect_islands, island_groups, lscm_uv_overrides, view_project_projective,
|
||||
view_project_matrix);
|
||||
image_data = blob.empty() ? nullptr : std::make_shared<std::vector<unsigned char>>(blob.begin(), blob.end());
|
||||
tile_method = static_cast<TextureTileMethod>(tile_method_int);
|
||||
projection_method = static_cast<TextureProjectionMethod>(projection_method_int);
|
||||
blend_mode = static_cast<TextureBlendMode>(blend_mode_int);
|
||||
}
|
||||
};
|
||||
|
||||
// Decoded 8-bit grayscale height sample, independent of any GUI/OpenGL texture object so it can
|
||||
// be evaluated from a background bake Job as well as from GUI-side preview code.
|
||||
struct DecodedHeightTexture
|
||||
{
|
||||
std::vector<uint8_t> pixels; // row-major, top-to-bottom, one byte per pixel
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
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
|
||||
// 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.
|
||||
float sample(const Vec2f &uv, bool tile_enabled = true, TextureTileMethod tile_method = TextureTileMethod::Repeat) const;
|
||||
};
|
||||
|
||||
// Decode a layer's raw image bytes into sampleable grayscale height data. Returns an empty
|
||||
// DecodedHeightTexture if image_data is empty or is not an 8-bit grayscale PNG.
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
// (which only knows how to compute the *analytic* methods from a single vertex + 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 -
|
||||
// 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<float, 12> &m, const Vec3f &position, Vec2f &uv);
|
||||
|
||||
// Sample a layer's height texture at a mesh-local position, honouring the layer's projection
|
||||
// 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
|
||||
// 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.
|
||||
// - `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).
|
||||
// patch_center/patch_axis are cheap to compute once per patch and passed through unchanged for
|
||||
// every vertex rather than being re-derived per call.
|
||||
float sample_layer_height(const DecodedHeightTexture &texture, const TextureDisplacementLayer &layer,
|
||||
const Vec3f &position, const Vec3f &normal,
|
||||
const Vec3f &patch_center = Vec3f::Zero(), const Vec3f &patch_axis = Vec3f::UnitZ(),
|
||||
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
|
||||
// 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.
|
||||
bool compute_layer_paint_anchor(const indexed_triangle_set &base_mesh,
|
||||
const TriangleSelector::TriangleSplittingData &facet_data,
|
||||
Vec3f &anchor_pos,
|
||||
Vec3f &anchor_normal);
|
||||
|
||||
// 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_
|
||||
// displacement() would act on, without duplicating the deserialize/get_facets_strict boilerplate.
|
||||
indexed_triangle_set extract_painted_patch(const indexed_triangle_set &base_mesh,
|
||||
const TriangleSelector::TriangleSplittingData &facet_data);
|
||||
|
||||
// A patch flattened into 2D. The patch is first split into charts along edges sharper than
|
||||
// `seam_angle_deg` (see LSCM_DEFAULT_SEAM_ANGLE_DEG), each chart is flattened independently, the
|
||||
// charts are scaled to their true mm size and packed side by side.
|
||||
//
|
||||
// A vertex sitting on a seam belongs to several charts at once and therefore has a *different* UV
|
||||
// in each of them, so this cannot be a plain "one UV per patch vertex" array: seam vertices are
|
||||
// duplicated, once per chart touching them. `indices` is the patch's own triangle list re-indexed
|
||||
// onto that duplicated vertex set, and `source_vertex` maps each duplicate back to the patch vertex
|
||||
// it came from.
|
||||
struct PatchUnwrap
|
||||
{
|
||||
std::vector<Vec2f> uvs; // one per unwrapped vertex, in mm
|
||||
std::vector<int> source_vertex; // unwrapped vertex -> index into patch.vertices
|
||||
std::vector<int> vertex_chart; // unwrapped vertex -> chart (island) id
|
||||
std::vector<stl_triangle_vertex_indices> indices; // patch triangles, re-indexed into `uvs`
|
||||
// Per chart, the centroid of its uvs - the point a TextureIsland's rotation turns about.
|
||||
std::vector<Vec2f> 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.
|
||||
std::vector<std::pair<int, int>> boundary_edges;
|
||||
int chart_count = 0;
|
||||
|
||||
bool empty() const { return indices.empty(); }
|
||||
};
|
||||
|
||||
// Applies an island's hand placement (scale + rotation about its own centroid, then offset) to one
|
||||
// unwrapped UV. A chart with no entry in `islands` is left exactly where the packing put it.
|
||||
Vec2f apply_island_transform(const Vec2f &uv, int chart, const PatchUnwrap &unwrap, const std::vector<TextureIsland> &islands);
|
||||
|
||||
// The same transform as a 2x3 affine matrix (columns: x basis, y basis, translation), for callers
|
||||
// that would otherwise apply it to every vertex of an island one at a time. The UV editor renders
|
||||
// each island through this as a uniform, which is what lets a drag move an island without touching
|
||||
// its vertex buffer at all.
|
||||
Eigen::Matrix<float, 2, 3> island_transform_matrix(int chart, const PatchUnwrap &unwrap, const std::vector<TextureIsland> &islands);
|
||||
|
||||
// Lays the unwrap's charts out as a connected net: charts that share a mesh edge are unfolded so
|
||||
// their shared edge coincides (a cube -> its six faces joined along a spanning tree of edges, the rest
|
||||
// left as free borders). Charts stay separate islands, so their borders still show and any of them can
|
||||
// still be moved by hand afterwards. A chart whose unfold would overlap one already placed is left
|
||||
// where the packing put it. Returns one placement per chart. See the gizmo's auto-connect option.
|
||||
std::vector<TextureIsland> compute_connected_net(const PatchUnwrap &unwrap);
|
||||
|
||||
// The placement that unfolds `child` onto `parent` along their shared mesh edge, honouring `parent`'s
|
||||
// current placement in `islands`. Returns false if the two charts share no edge. Backs the manual
|
||||
// "Join" command; compute_connected_net() does the same thing across a whole spanning tree.
|
||||
bool join_chart_placement(const PatchUnwrap &unwrap, const std::vector<TextureIsland> &islands,
|
||||
int child, int parent, TextureIsland &out_child);
|
||||
|
||||
// Unwraps `patch` as described above. Charts that are flat (within a degree) are projected onto
|
||||
// their own tangent plane directly, which is both exact and far cheaper than a solve; only genuinely
|
||||
// curved charts go through CGAL's LSCM parameterizer (MeshBoolean::cgal::parameterize_lscm()). A
|
||||
// chart that LSCM cannot flatten at all (it is not a topological disk - closed, or with a hole)
|
||||
// falls back to that same tangent-plane projection.
|
||||
//
|
||||
// `padding_mm` is the gap the packing leaves between islands; negative means auto (see
|
||||
// TextureDisplacementLayer::island_padding_mm). `seam_edges` are extra edges to cut along regardless
|
||||
// of angle (manual/auto seams), in the patch's own vertex-index space (which is the mesh's, since the
|
||||
// patch carries the whole vertex array - see get_facets_strict()).
|
||||
//
|
||||
// Results are cached, keyed on the patch's geometry, the seam angle, the padding and the seam edges:
|
||||
// nothing else about a layer (depth, tiling, rotation, offset, texture, island placement) changes the
|
||||
// unwrap, so dragging any of those sliders must not pay for a re-solve.
|
||||
PatchUnwrap compute_patch_unwrap(const indexed_triangle_set &patch, float seam_angle_deg = LSCM_DEFAULT_SEAM_ANGLE_DEG,
|
||||
float padding_mm = -1.f, const std::vector<std::pair<int, int>> &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
|
||||
// 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
|
||||
// *display* in the UV editor that needs the duplicated-vertex form above.
|
||||
//
|
||||
// Returns an empty vector if the patch has no triangles. Takes the whole layer because it applies
|
||||
// both the layer's seam angle and its hand-placed islands.
|
||||
std::vector<Vec2f> compute_lscm_uvs(const indexed_triangle_set &patch, const TextureDisplacementLayer &layer);
|
||||
|
||||
// One paint mask (as stored by ModelVolume::texture_displacement_facets) per possible layer slot.
|
||||
using TextureDisplacementFacetsData = std::array<TriangleSelector::TriangleSplittingData, TEXTURE_DISPLACEMENT_MAX_LAYERS>;
|
||||
|
||||
// Bake all painted texture-displacement layers into `base_mesh`'s geometry, restricted to the
|
||||
// painted area(s) only (the rest of the mesh is left untouched). Returns the mesh unchanged if
|
||||
// nothing is painted or no layer has a usable texture.
|
||||
//
|
||||
// **Topology-preserving**: the returned mesh has exactly `base_mesh`'s vertices and triangles, in
|
||||
// the same order - only the positions of displaced vertices differ. Every layer's paint mask is
|
||||
// evaluated against `base_mesh` directly, and each vertex accumulates a single signed displacement
|
||||
// (in mm) that all the layers covering it fold into, in slot order, via their TextureBlendMode.
|
||||
// The vertex is then moved once, along its base-mesh normal, by that accumulated total.
|
||||
//
|
||||
// This replaced an earlier design that instead applied the layers *sequentially*, re-meshing after
|
||||
// each one and carrying the next layer's paint mask onto the result with
|
||||
// TriangleSelector::remap_painting(). That was the cause of a real "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 skipped. It is also
|
||||
// what forced the per-layer vertex duplication and the final its_compactify_vertices() pass. The
|
||||
// accumulate-then-displace formulation has neither problem, is substantially faster (no remap, no
|
||||
// welding, one pass over the mesh), and - because the output keeps the input's exact vertex
|
||||
// indexing - lets the GUI overlay a preview on the base mesh without any index translation.
|
||||
//
|
||||
// A vertex used by even one *unpainted* triangle of a layer's mask is that layer's boundary: its
|
||||
// displacement is pinned to zero, so the patch never tears away from the surrounding surface. Only
|
||||
// vertices used exclusively by painted triangles move.
|
||||
//
|
||||
// Takes plain copied data rather than a ModelVolume reference so it is safe to call from a
|
||||
// background thread (e.g. a bake Job's process() method) on a snapshot captured on the main
|
||||
// thread, without touching the live Model concurrently with the UI.
|
||||
//
|
||||
// Known limitation: this does not attempt to remap texture-displacement paint data across
|
||||
// topology-changing operations performed outside this gizmo (e.g. ModelObject::split(),
|
||||
// mesh-boolean ops) the way TriangleSelector::remap_painting() does for the other paint channels.
|
||||
// Such operations will silently drop any unbaked texture-displacement paint on the affected
|
||||
// volume. This is an explicit extension point for a later phase, not an oversight.
|
||||
indexed_triangle_set build_texture_displacement(const indexed_triangle_set &base_mesh,
|
||||
const std::vector<TextureDisplacementLayer> &layers,
|
||||
const TextureDisplacementFacetsData &facets_data);
|
||||
|
||||
// Convenience overload for main-thread callers: extracts the mesh/layers/paint data from `volume`
|
||||
// and forwards to the overload above.
|
||||
indexed_triangle_set build_texture_displacement(const ModelVolume &volume);
|
||||
|
||||
// Uniformly subdivides `mesh` (every triangle recursively split into 4 via edge midpoints, using a
|
||||
// shared cache so a midpoint is computed once and reused by both triangles on either side of that
|
||||
// edge) until every edge is at or below max_edge_length_mm, or max_iterations passes have run,
|
||||
// whichever comes first (bounding the worst-case triangle-count explosion on a very fine target).
|
||||
//
|
||||
// This exists so a low-poly input model can still get fine-grained texture displacement detail -
|
||||
// build_texture_displacement() can only ever move existing vertices, so a patch with only a
|
||||
// handful of vertices to begin with cannot show much detail no matter the texture's resolution.
|
||||
//
|
||||
// Deliberately whole-mesh and uniform, not limited to a painted patch: subdividing only part of a
|
||||
// mesh while leaving the rest untouched creates a classic T-junction/cracking problem where the
|
||||
// denser and sparser regions meet (the finer side has edge midpoints the coarser side doesn't
|
||||
// know about). Uniform, whole-mesh subdivision has no such seam and stays manifold, at the cost of
|
||||
// applying everywhere rather than just where texture detail is actually wanted - meant to be run
|
||||
// once, deliberately, before painting (see the gizmo's "Subdivide model" button), not automatically
|
||||
// during baking.
|
||||
indexed_triangle_set subdivide_mesh_uniform(const indexed_triangle_set &mesh, float max_edge_length_mm, int max_iterations = 6);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_TextureDisplacement_hpp_
|
||||
@@ -198,6 +198,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Gizmos/GLGizmosManager.hpp
|
||||
GUI/Gizmos/GLGizmoSVG.cpp
|
||||
GUI/Gizmos/GLGizmoSVG.hpp
|
||||
GUI/Gizmos/GLGizmoTextureDisplacement.cpp
|
||||
GUI/Gizmos/GLGizmoTextureDisplacement.hpp
|
||||
GUI/Gizmos/GLGizmoUtils.cpp
|
||||
GUI/Gizmos/GLGizmoUtils.hpp
|
||||
#GUI/Gizmos/GLGizmoText.cpp
|
||||
@@ -323,6 +325,10 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Jobs/SLAImportDialog.hpp
|
||||
GUI/Jobs/SLAImportJob.cpp
|
||||
GUI/Jobs/SLAImportJob.hpp
|
||||
GUI/Jobs/TextureDisplacementBakeJob.cpp
|
||||
GUI/Jobs/TextureDisplacementBakeJob.hpp
|
||||
GUI/Jobs/TextureDisplacementPreviewJob.cpp
|
||||
GUI/Jobs/TextureDisplacementPreviewJob.hpp
|
||||
GUI/Jobs/ThreadSafeQueue.hpp
|
||||
GUI/Jobs/UpgradeNetworkJob.cpp
|
||||
GUI/Jobs/UpgradeNetworkJob.hpp
|
||||
@@ -494,6 +500,10 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/TaskManager.hpp
|
||||
GUI/TextLines.cpp
|
||||
GUI/TextLines.hpp
|
||||
GUI/TextureLibrary.cpp
|
||||
GUI/TextureLibrary.hpp
|
||||
GUI/TextureProjectorFrame.cpp
|
||||
GUI/TextureProjectorFrame.hpp
|
||||
GUI/TickCode.cpp
|
||||
GUI/TickCode.hpp
|
||||
GUI/TroubleshootDialog.cpp
|
||||
@@ -510,6 +520,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/UserManager.hpp
|
||||
GUI/UserNotification.cpp
|
||||
GUI/UserNotification.hpp
|
||||
GUI/UVEditorCanvas.cpp
|
||||
GUI/UVEditorCanvas.hpp
|
||||
GUI/WebDownPluginDlg.cpp
|
||||
GUI/WebDownPluginDlg.hpp
|
||||
GUI/WebGuideDialog.cpp
|
||||
|
||||
@@ -104,6 +104,11 @@ std::pair<bool, std::string> GLShadersManager::init()
|
||||
valid &= append_shader("mm_gouraud", { prefix + "mm_gouraud.vs", prefix + "mm_gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv });
|
||||
else
|
||||
valid &= append_shader("mm_gouraud", { prefix + "mm_gouraud.vs", prefix + "mm_gouraud.fs" });
|
||||
// Fast bump-map preview for the texture displacement gizmo (see libslic3r/TextureDisplacement.hpp).
|
||||
valid &= append_shader("texture_displacement_bump", { prefix + "texture_displacement_bump.vs", prefix + "texture_displacement_bump.fs" });
|
||||
// UV-check overlay for the same gizmo: a procedural checker or a distortion heatmap over the
|
||||
// painted patch, to sanity-check the unwrap.
|
||||
valid &= append_shader("texture_displacement_uvcheck", { prefix + "texture_displacement_uvcheck.vs", prefix + "texture_displacement_uvcheck.fs" });
|
||||
|
||||
return { valid, error };
|
||||
}
|
||||
|
||||
@@ -171,7 +171,8 @@ bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GLTexture::load_from_raw_data(std::vector<unsigned char> data, unsigned int w, unsigned int h, bool apply_anisotropy)
|
||||
bool GLTexture::load_from_raw_data(std::vector<unsigned char> data, unsigned int w, unsigned int h, bool apply_anisotropy,
|
||||
bool use_mipmaps)
|
||||
{
|
||||
m_width = w;
|
||||
m_height = h;
|
||||
@@ -195,18 +196,51 @@ bool GLTexture::load_from_raw_data(std::vector<unsigned char> data, unsigned int
|
||||
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
|
||||
|
||||
bool use_mipmaps = true;
|
||||
if (use_mipmaps) {
|
||||
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
|
||||
int lod_w = m_width;
|
||||
int lod_h = m_height;
|
||||
// We generate the mipmap chain ourselves rather than calling glGenerateMipmap(), which this
|
||||
// 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
|
||||
// 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
|
||||
// sampled); it shows up the moment one is drawn small enough to select a lower level, as a
|
||||
// texture that visibly turns into something else as it shrinks.
|
||||
std::vector<unsigned char> scratch;
|
||||
const std::vector<unsigned char> *src = &data;
|
||||
int src_w = m_width;
|
||||
int src_h = m_height;
|
||||
GLint level = 0;
|
||||
while (lod_w > 1 || lod_h > 1) {
|
||||
while (src_w > 1 || src_h > 1) {
|
||||
++level;
|
||||
lod_w = std::max(lod_w / 2, 1);
|
||||
lod_h = std::max(lod_h / 2, 1);
|
||||
n_pixels = lod_w * lod_h;
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
|
||||
const int lod_w = std::max(src_w / 2, 1);
|
||||
const int lod_h = std::max(src_h / 2, 1);
|
||||
|
||||
std::vector<unsigned char> lod(size_t(lod_w) * size_t(lod_h) * 4);
|
||||
for (int y = 0; y < lod_h; ++y) {
|
||||
// min() rather than a plain 2*y+1: an odd source extent leaves the last output texel
|
||||
// with only one source row/column to average, not two.
|
||||
const int y0 = std::min(2 * y, src_h - 1);
|
||||
const int y1 = std::min(2 * y + 1, src_h - 1);
|
||||
for (int x = 0; x < lod_w; ++x) {
|
||||
const int x0 = std::min(2 * x, src_w - 1);
|
||||
const int x1 = std::min(2 * x + 1, src_w - 1);
|
||||
for (int c = 0; c < 4; ++c) {
|
||||
const unsigned int sum = (*src)[(size_t(y0) * size_t(src_w) + size_t(x0)) * 4 + size_t(c)] +
|
||||
(*src)[(size_t(y0) * size_t(src_w) + size_t(x1)) * 4 + size_t(c)] +
|
||||
(*src)[(size_t(y1) * size_t(src_w) + size_t(x0)) * 4 + size_t(c)] +
|
||||
(*src)[(size_t(y1) * size_t(src_w) + size_t(x1)) * 4 + size_t(c)];
|
||||
lod[(size_t(y) * size_t(lod_w) + size_t(x)) * 4 + size_t(c)] = (unsigned char)(sum / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)lod.data()));
|
||||
|
||||
scratch = std::move(lod);
|
||||
src = &scratch;
|
||||
src_w = lod_w;
|
||||
src_h = lod_h;
|
||||
}
|
||||
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level));
|
||||
|
||||
@@ -100,7 +100,10 @@ namespace GUI {
|
||||
bool load_from_file(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy);
|
||||
bool load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px);
|
||||
//BBS load GLTexture from raw pixel data
|
||||
bool load_from_raw_data(std::vector<unsigned char> data, unsigned int w, unsigned int h, bool apply_anisotropy = false);
|
||||
// `data` is RGBA, w * h * 4 bytes. With use_mipmaps, a real box-filtered mipmap chain is
|
||||
// built, so the texture may safely be drawn smaller than its pixel size.
|
||||
bool load_from_raw_data(std::vector<unsigned char> data, unsigned int w, unsigned int h, bool apply_anisotropy = false,
|
||||
bool use_mipmaps = true);
|
||||
// meanings of states: (std::pair<int, bool>)
|
||||
// first field (int):
|
||||
// 0 -> no changes
|
||||
|
||||
@@ -27,7 +27,8 @@ enum class PainterGizmoType {
|
||||
FDM_SUPPORTS,
|
||||
SEAM,
|
||||
MM_SEGMENTATION,
|
||||
FUZZY_SKIN
|
||||
FUZZY_SKIN,
|
||||
TEXTURE_DISPLACEMENT
|
||||
};
|
||||
|
||||
class TriangleSelectorGUI : public TriangleSelector {
|
||||
|
||||
3691
src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp
Normal file
518
src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp
Normal file
@@ -0,0 +1,518 @@
|
||||
#ifndef slic3r_GLGizmoTextureDisplacement_hpp_
|
||||
#define slic3r_GLGizmoTextureDisplacement_hpp_
|
||||
|
||||
#include "GLGizmoPainterBase.hpp"
|
||||
#include "libslic3r/TextureDisplacement.hpp"
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
#include "slic3r/GUI/GLTexture.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/IconManager.hpp"
|
||||
#include "slic3r/GUI/TextureLibrary.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
class TextureProjectorFrame;
|
||||
|
||||
// Paint-style gizmo that assigns one or more texture-displacement "layers" (see
|
||||
// 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 -
|
||||
// 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.
|
||||
class GLGizmoTextureDisplacement : public GLGizmoPainterBase
|
||||
{
|
||||
public:
|
||||
GLGizmoTextureDisplacement(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
|
||||
void render_painter_gizmo() override;
|
||||
|
||||
// Intercepts mouse input while "Adjust Texture" mode is on (dragging the on-canvas offset/
|
||||
// rotation handles instead of painting); otherwise forwards to the normal painting handling.
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
protected:
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
std::string on_get_name() const override;
|
||||
|
||||
wxString handle_snapshot_action_name(bool shift_down, Button button_down) const override;
|
||||
|
||||
std::string get_gizmo_entering_text() const override { return _u8L("Entering Texture displacement painting"); }
|
||||
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Texture displacement painting"); }
|
||||
std::string get_action_snapshot_name() const override { return _u8L("Texture displacement editing"); }
|
||||
|
||||
EnforcerBlockerType get_left_button_state_type() const override { return EnforcerBlockerType::ENFORCER; }
|
||||
EnforcerBlockerType get_right_button_state_type() const override { return EnforcerBlockerType::NONE; }
|
||||
|
||||
private:
|
||||
bool on_init() override;
|
||||
void update_model_object() override;
|
||||
void update_from_model_object(bool first_update) override;
|
||||
void on_opening() override {}
|
||||
void on_shutdown() override;
|
||||
PainterGizmoType get_painter_type() const override;
|
||||
|
||||
// Phase 1 restricts the texture layer list to the first model-part volume of the current
|
||||
// object (the common single-volume case); multi-part objects only get texture layers on
|
||||
// their first part until a later phase. Returns nullptr if there is no model part.
|
||||
ModelVolume* texture_volume();
|
||||
const ModelVolume* texture_volume() const;
|
||||
|
||||
void add_texture_layer();
|
||||
void remove_texture_layer(int slot);
|
||||
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 -
|
||||
// "whole model" as an alternative to brushing/clicking every triangle by hand.
|
||||
void select_whole_model();
|
||||
|
||||
// The mesh raycasters are built one per model-part volume, in that order; this is the texture
|
||||
// volume's slot among them, or -1 if it has none (no selection, or the lists disagree).
|
||||
int texture_volume_raycaster_index() const;
|
||||
|
||||
// Paints exactly the facets currently visible from the camera onto the active layer, replacing
|
||||
// whatever that layer had painted. "Visible" is two tests: the facet faces the camera, and its
|
||||
// centroid is not hidden behind other geometry (a real raycast, so a concave part's far inner
|
||||
// wall is correctly excluded). When `uv_clip` is given (the projection frame's matrix), facets
|
||||
// whose centroid falls outside the frame's uv unit square are skipped first - which both clips
|
||||
// the selection to the frame and spares the raycast for everything outside it. Costs one ray
|
||||
// query per surviving facet, so it is a one-shot action, never a per-frame one. Returns the
|
||||
// number of facets selected.
|
||||
int select_visible_faces(const std::array<float, 12> *uv_clip = nullptr);
|
||||
|
||||
// When set, "Capture current view" also re-selects the visible faces, so the viewpoint the
|
||||
// projector was captured from and the area it projects onto stay the same. Independent of the
|
||||
// projection frame below: this takes every visible facet, the frame clips to its rectangle.
|
||||
// Off by default, because turning it on replaces whatever the layer had painted.
|
||||
bool m_project_only_visible = false;
|
||||
|
||||
// The projection-frame overlay for a ViewProjected layer: a semi-transparent window dragged over
|
||||
// the 3D view whose border becomes the projection's edge. Created lazily and owned here; hidden
|
||||
// rather than destroyed when closed, so reopening keeps it where the user left it.
|
||||
TextureProjectorFrame *m_projector_frame = nullptr;
|
||||
int m_projector_opacity = 140;
|
||||
// What the overlay's texture was last built from, so repeated updates don't rebuild the bitmap
|
||||
// from unchanged pixels. Same shape as the m_thumbnail_source/m_thumbnail_smoothing pair above.
|
||||
const void *m_projector_tex_source = nullptr;
|
||||
float m_projector_tex_smoothing = -1.f;
|
||||
void show_projector(bool show);
|
||||
// Pushes the active layer's texture into the overlay. Cheap, and a no-op while it is hidden.
|
||||
void update_projector();
|
||||
// Reads the overlay's rectangle and commits it as the layer's projection: builds the exact
|
||||
// projective local->uv matrix from the camera and that rectangle, turns tiling off so the border
|
||||
// is a hard edge, and repaints the layer with the visible facets inside the frame. Returns the
|
||||
// number of facets selected, or -1 if the frame could not be used at all.
|
||||
int apply_projection_frame();
|
||||
|
||||
// Uniformly subdivides the volume's mesh (see libslic3r::subdivide_mesh_uniform()) so a
|
||||
// low-poly input model has enough vertices to actually show texture-displacement detail.
|
||||
// A real, committed geometry change (like Bake), so it needs its own snapshot; unlike Bake it
|
||||
// has no target region, so any not-yet-baked paint on the volume is dropped rather than
|
||||
// remapped (texture-displacement paint has no remap-across-topology-change support yet).
|
||||
void subdivide_model();
|
||||
|
||||
// Returns a cached GPU thumbnail of layer's texture (decoding + uploading it the first time it
|
||||
// is requested, or whenever its image_data changes), or nullptr if it has no usable texture.
|
||||
GLTexture *get_layer_thumbnail(const TextureDisplacementLayer &layer);
|
||||
|
||||
// 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 -
|
||||
// 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
|
||||
{
|
||||
std::shared_ptr<std::vector<unsigned char>> image_data;
|
||||
std::unique_ptr<GLTexture> thumbnail;
|
||||
};
|
||||
const LibraryTexture *get_library_texture(const std::string &path);
|
||||
|
||||
// The layer's texture chooser: a drop-down whose closed state and every one of whose entries
|
||||
// shows a large preview image on the left and the texture's name on the right, plus an adjacent
|
||||
// button that imports an image file from disk into the user texture folder. Shipped and
|
||||
// user-imported textures are listed under separate headings.
|
||||
void render_texture_picker(TextureDisplacementLayer &layer);
|
||||
void set_layer_texture(TextureDisplacementLayer &layer, const TextureLibraryEntry &entry);
|
||||
void import_custom_texture(TextureDisplacementLayer &layer);
|
||||
// Draws a picker row (image left, name right) on top of a full-width Selectable, and leaves the
|
||||
// cursor below it. Shared by the drop-down's closed state and its individual entries so the two
|
||||
// cannot drift apart. Returns true when the row is clicked.
|
||||
bool texture_row(const char *id, const std::string &name, GLTexture *thumbnail, bool selected, float width);
|
||||
float texture_row_height() const;
|
||||
|
||||
// "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
|
||||
// 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
|
||||
// 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
|
||||
bool on_mouse_adjust_texture(const wxMouseEvent &mouse_event);
|
||||
void render_adjust_texture_gizmo();
|
||||
// Draws a small '+'/'-' next to the mouse over the 3D view while painting/selecting, so it is
|
||||
// obvious whether the next stroke adds paint (default) or erases it (Shift). Uses ImGui's
|
||||
// foreground draw list, so it must be called from inside the gizmo's ImGui frame.
|
||||
void render_paint_cursor_hint();
|
||||
// Mesh-local tangent-plane basis at m_adjust_anchor_normal, matching project_planar()'s
|
||||
// dominant-axis convention so dragging on-canvas maps consistently onto offset.
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
// on_mouse_adjust_texture(): the handle then tracks the cursor 1:1, and sits back on the anchor
|
||||
// precisely when offset is zero.
|
||||
Vec3f adjust_handle_center(const TextureDisplacementLayer &layer) const;
|
||||
|
||||
// The layer painted by the active slot, or nullptr if that slot has no layer yet.
|
||||
TextureDisplacementLayer *active_layer();
|
||||
const TextureDisplacementLayer *active_layer() const;
|
||||
|
||||
// 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 -
|
||||
// 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
|
||||
// it and returns immediately, and m_preview_glmodel is updated later when it completes.
|
||||
void rebuild_preview();
|
||||
void render_preview_mesh();
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
void rebuild_bump_preview_mesh();
|
||||
void render_bump_preview_mesh();
|
||||
|
||||
// Feeds the active layer's painted patch + LSCM unwrap (if it's using that projection method)
|
||||
// into Plater's docked UV-editor pane and shows it, or hides the pane if the active layer
|
||||
// isn't using LSCM (or nothing is painted). Called whenever something that could change what
|
||||
// the pane should show happens: paint changes, layer switch, projection method change, bake,
|
||||
// and on shutdown (to hide it).
|
||||
void update_uv_editor();
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// unwrapped-vertex index and its new raw-unwrap coordinate. Maps the unwrapped index to a mesh
|
||||
// vertex and stores a per-vertex UV override on the layer (see lscm_uv_overrides), then rebuilds the
|
||||
// preview so the baked geometry follows.
|
||||
void on_uv_vertex_edited(const std::vector<std::pair<int, Vec2f>> &edits);
|
||||
// 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
|
||||
// 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.
|
||||
std::vector<Eigen::Matrix<float, 2, 3>> uv_editor_island_transforms(const TextureDisplacementLayer &layer);
|
||||
// Handles a toolbar command forwarded from the UV pane that needs the layer data the canvas
|
||||
// doesn't hold (average island scale, cut island). Takes the command as an int (a cast of
|
||||
// UVEditorCanvas::Command) so this header needn't pull in glad/wxGLCanvas via the canvas header.
|
||||
void on_uv_command(int cmd);
|
||||
// Splits one unwrap chart in two by marking the mesh edges that straddle the plane through its
|
||||
// 3D centroid, perpendicular to its longest axis, as seams (#17). The re-unwrap then separates it.
|
||||
void cut_island(TextureDisplacementLayer &layer, int chart);
|
||||
|
||||
// Captures the current camera's right/up axes into the layer's projector (#6), transformed into
|
||||
// the volume's local space so the projection is stable as the object is later moved/rotated.
|
||||
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
|
||||
// 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
|
||||
bool on_mouse_seam(const wxMouseEvent &mouse_event);
|
||||
void toggle_seam_at(const Vec2d &mouse_pos);
|
||||
void rebuild_seam_overlay();
|
||||
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.
|
||||
std::pair<int, int> seam_edge_at(const Vec2d &mouse_pos) const;
|
||||
std::pair<int, int> 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
|
||||
// way the edge is in normal mode. -1 when nothing is under the cursor (or not in path mode).
|
||||
int m_seam_hover_vertex = -1;
|
||||
GLModel m_seam_hover_glmodel;
|
||||
void rebuild_seam_hover_overlay();
|
||||
|
||||
// Shortest-path seam marking, for dense meshes where clicking every single triangle edge is
|
||||
// tedious: in this sub-mode a click picks the nearest vertex, and the next click marks every edge
|
||||
// on the shortest surface path between the two as a seam - so a whole seam line is drawn with two
|
||||
// clicks. The end vertex becomes the next start, so a multi-segment seam chains click by click.
|
||||
bool m_seam_path_mode = false;
|
||||
int m_seam_path_anchor = -1; // mesh vertex the path starts from, or -1
|
||||
GLModel m_seam_anchor_glmodel; // the anchor's incident edges, highlighted
|
||||
int seam_vertex_at(const Vec2d &mouse_pos) const; // nearest mesh vertex under the cursor
|
||||
void mark_seam_path(int v_from, int v_to); // seam every edge on the shortest path
|
||||
void rebuild_seam_anchor_overlay();
|
||||
// Set while an island gesture is in flight, so the undo snapshot is taken once at the start of
|
||||
// the drag (capturing the state *before* it) rather than on every motion event.
|
||||
bool m_island_drag_active = false;
|
||||
|
||||
// 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 -
|
||||
// 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;
|
||||
bool m_bake_in_progress = false;
|
||||
|
||||
// When set, the true-displacement geometry is rebuilt on every parameter change (live), instead of
|
||||
// only once the slider being dragged is released. On by default so painting/added textures show
|
||||
// straight away without needing to nudge a slider first.
|
||||
bool m_auto_update = true;
|
||||
|
||||
// Subdivision is now count-based (split the whole mesh 1..5 times) rather than a target edge
|
||||
// length, and is previewed as a wireframe before it is committed: nothing is written to the model
|
||||
// until "Apply". While previewing, the would-be subdivided mesh is drawn as a wireframe overlay so
|
||||
// the added density is visible; "Done" ends the preview without touching the model. The normal
|
||||
// "Show mesh wireframe" toggle is left alone, so a wireframe the user already had on stays on.
|
||||
// 0 is a real value meaning "no subdivision": it previews nothing and Apply is a no-op. Apply
|
||||
// snaps the slider back to it, because each pass quadruples the triangle count - leaving the
|
||||
// count where it was would immediately re-preview N more passes on top of the mesh that was just
|
||||
// committed, i.e. the most expensive thing the panel can do, on every Apply.
|
||||
int m_subdivide_count = 1;
|
||||
bool m_subdivide_editing = false;
|
||||
int m_subdivide_preview_count = -1; // the count m_subdivide_preview_glmodel was built for
|
||||
GLModel m_subdivide_preview_glmodel;
|
||||
void rebuild_subdivide_preview();
|
||||
void render_subdivide_preview();
|
||||
|
||||
// Isotropic remeshing (CGAL) to even out wildly varying triangle sizes so displacement has a
|
||||
// consistent density to work with. Target edge length in mm; 0 means "not yet initialised", filled
|
||||
// with the mesh's mean edge length the first time the control is shown. Like subdivide, it replaces
|
||||
// the geometry and drops not-yet-baked paint (no remap across a topology change).
|
||||
float m_remesh_target_edge_mm = 0.f;
|
||||
// Dihedral angle above which an edge counts as a hard feature and is held fixed by the remesher.
|
||||
// Off by default would round every sharp edge off, so this is on; 0 disables the protection.
|
||||
float m_remesh_sharp_angle_deg = 40.f;
|
||||
bool m_remesh_keep_sharp_edges = true;
|
||||
void remesh_model();
|
||||
|
||||
// Live, pre-bake preview of the true displaced geometry (built by the same algorithm Bake
|
||||
// uses). Empty/uninitialized whenever nothing is painted yet, in which case the gizmo falls
|
||||
// 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().
|
||||
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
|
||||
// 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;
|
||||
// Whether the current bump mesh carries a precomputed per-vertex uv (LSCM) that the shader
|
||||
// should sample at directly, rather than projecting in-shader. Set by rebuild_bump_preview_mesh().
|
||||
bool m_bump_preview_uses_vertex_uv = false;
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// resulting final-uv-space affine handed to the shader (identity except mid-drag).
|
||||
int m_bump_active_chart = -1;
|
||||
std::vector<uint8_t> m_bump_active_vertex;
|
||||
Eigen::Matrix<float, 2, 3> m_bump_baked_active_xf = Eigen::Matrix<float, 2, 3>::Identity();
|
||||
Eigen::Matrix<float, 2, 3> m_bump_island_delta = Eigen::Matrix<float, 2, 3>::Identity();
|
||||
void compute_bump_active_vertices(const std::vector<int> &charts);
|
||||
|
||||
// The set of islands the current UV-editor drag moves together: the pane's multi-selection unioned
|
||||
// with each selected island's join group (see build_island_move_set()). Populated at drag start and
|
||||
// cleared when it finishes. A move applies the same offset to every island in it; rotate/scale act
|
||||
// only on the primary. Empty when no move drag is in flight.
|
||||
std::vector<int> m_island_move_set;
|
||||
// All islands that must move with `primary`: the pane's multi-selection plus, for each of those, the
|
||||
// charts sharing its join group in `layer`. Always contains `primary`.
|
||||
std::vector<int> build_island_move_set(const TextureDisplacementLayer &layer, int primary) const;
|
||||
// The join-group id of chart `c`: its explicit entry in `groups`, or `c` itself (its own singleton)
|
||||
// when unset. Two charts move together iff this matches.
|
||||
static int island_group_of(const std::vector<int> &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<int> &groups, int a, int b, int chart_count);
|
||||
// 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.
|
||||
std::vector<Vec2f> compute_layer_vertex_uvs(const indexed_triangle_set &patch,
|
||||
const TextureDisplacementLayer &layer) const;
|
||||
|
||||
// UV-check overlay drawn over the painted patch to sanity-check the unwrap (#13/#14). Built by
|
||||
// rebuild_uvcheck_mesh(), drawn by render_uvcheck_mesh() with the "texture_displacement_uvcheck"
|
||||
// shader. Checker works for any projection; Distortion needs the per-vertex LSCM uv.
|
||||
enum class UVCheckMode { None, Checker, Distortion };
|
||||
UVCheckMode m_uv_check_mode = UVCheckMode::None;
|
||||
GLModel m_uvcheck_glmodel;
|
||||
bool m_uvcheck_uses_vertex_uv = false;
|
||||
void rebuild_uvcheck_mesh();
|
||||
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
|
||||
// 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;
|
||||
// The unwrap is expensive, so it is recomputed only when the user explicitly asks for it (the
|
||||
// "Unwrap" button), not on every paint stroke or slider nudge. This is set by that button and
|
||||
// consumed by the next update_uv_editor() call, which is the only path that re-solves the unwrap;
|
||||
// every other call merely refreshes the cheap per-island affine transforms over the existing one.
|
||||
bool m_uv_unwrap_pending = false;
|
||||
// Set alongside m_uv_unwrap_pending only by the Unwrap button, so the connected-net auto-layout runs
|
||||
// on a genuine re-unwrap but not on a refresh re-solve (a vertex-edit commit or undo), which must
|
||||
// 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
|
||||
// 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
|
||||
// choice actually changes (the height texture is large; re-sending it every stroke would be waste).
|
||||
enum class UVBackground { None, Height, Checker };
|
||||
UVBackground m_uv_editor_bg = UVBackground::None;
|
||||
float m_uv_editor_bg_smoothing = -1.f; // smoothing the height backdrop was uploaded at
|
||||
// Per-chart distortion heatmap colour for the UV pane (#7/#14), computed once when the unwrap is
|
||||
// re-solved (relative stretch doesn't change when islands are merely moved), fed to the canvas only
|
||||
// while the Distortion check mode is on. Empty otherwise.
|
||||
std::vector<ColorRGBA> m_uv_editor_distortion_colors;
|
||||
void compute_uv_editor_distortion_colors(const indexed_triangle_set &patch);
|
||||
|
||||
// Plain triangle-edge overlay on the mesh (#8), toggled independently of the check modes.
|
||||
bool m_wireframe_overlay = false;
|
||||
GLModel m_wireframe_overlay_glmodel;
|
||||
size_t m_wireframe_overlay_vcount = 0; // topology signature, so it rebuilds only on a real change
|
||||
void rebuild_wireframe_overlay(); // from the base mesh (bump/paint mode)
|
||||
void build_wireframe_from_its(const indexed_triangle_set &its); // from an explicit mesh, no early-out
|
||||
void refresh_wireframe(); // pick base vs displaced source for the current view
|
||||
void render_wireframe_overlay();
|
||||
// The displaced preview geometry the last preview job produced, kept so the wireframe overlay can be
|
||||
// drawn on the raised surface actually shown in the true-displacement view (#: "wireframe in real mode").
|
||||
indexed_triangle_set m_preview_its;
|
||||
// Bumped on every rebuild_preview() call; a background TextureDisplacementPreviewJob's result
|
||||
// is only applied if this hasn't moved on since the job was queued (see rebuild_preview()),
|
||||
// so a burst of edits can't have an earlier, now-stale job clobber a later one's result.
|
||||
uint64_t m_preview_generation = 0;
|
||||
|
||||
// Per-slot GPU thumbnail cache for the layer list panel, keyed by the image_data pointer that
|
||||
// was current the last time each thumbnail was built (see get_layer_thumbnail()).
|
||||
std::array<std::unique_ptr<GLTexture>, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnails;
|
||||
std::array<const void *, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_source{};
|
||||
// The smoothing each cached thumbnail was built at, so a smoothing change re-uploads it (and the
|
||||
// fast/bump preview, which samples this texture, actually shows the blur).
|
||||
std::array<float, TEXTURE_DISPLACEMENT_MAX_LAYERS> m_thumbnail_smoothing{};
|
||||
|
||||
// Library textures the picker has shown at least once, keyed by file path (see LibraryTexture).
|
||||
std::map<std::string, LibraryTexture> 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
|
||||
// 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.
|
||||
struct UVEditorState
|
||||
{
|
||||
int slot = -1;
|
||||
const void *image_data = nullptr;
|
||||
float seam_angle = -1.f;
|
||||
float padding = -2.f;
|
||||
TriangleSelector::TriangleSplittingData facets;
|
||||
// Manual/auto seam edges also change the unwrap, so a change here must force a re-solve just
|
||||
// like the facets do (marking a seam leaves the paint mask untouched).
|
||||
std::vector<std::pair<int, int>> seam_edges;
|
||||
|
||||
bool operator==(const UVEditorState &other) const
|
||||
{
|
||||
return slot == other.slot && image_data == other.image_data && seam_angle == other.seam_angle &&
|
||||
padding == other.padding && facets == other.facets && seam_edges == other.seam_edges;
|
||||
}
|
||||
};
|
||||
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
|
||||
// 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();
|
||||
// The unwrap m_uv_editor_state produced, kept so that changing tiling/rotation/offset only costs
|
||||
// re-running apply_uv_transform() over it, not another extraction and solve.
|
||||
PatchUnwrap m_uv_editor_unwrap;
|
||||
|
||||
// When set, the panel is a free-floating window the user can drag anywhere (with a title bar to
|
||||
// grab), instead of being pinned to the right of the gizmo toolbar. Persisted across gizmo
|
||||
// open/close within a session, so the choice sticks while working.
|
||||
bool m_undocked = false;
|
||||
|
||||
// See the "Adjust Texture" block of private methods above.
|
||||
bool m_adjust_texture_mode = false;
|
||||
bool m_adjust_anchor_valid = false;
|
||||
Vec3f m_adjust_anchor_pos = Vec3f::Zero(); // mesh-local
|
||||
Vec3f m_adjust_anchor_normal = Vec3f::UnitZ(); // mesh-local
|
||||
|
||||
// Pan: free drag anywhere on the flat panel, moves offset along both axes. AxisU/AxisV: drag
|
||||
// the corresponding arrow, moves offset along only that one axis.
|
||||
enum class AdjustHandle { None, Pan, AxisU, AxisV };
|
||||
AdjustHandle m_adjust_drag_handle = AdjustHandle::None;
|
||||
Vec2f m_adjust_drag_start_offset = Vec2f::Zero();
|
||||
// Anchor-relative planar position (see project_planar()) of the point under the mouse at the
|
||||
// moment the current drag started; every subsequent frame's delta is measured against this,
|
||||
// rather than accumulated frame-to-frame, to avoid drift.
|
||||
Vec2f m_adjust_drag_start_planar = Vec2f::Zero();
|
||||
|
||||
// Lazily-built unit quad (the pan panel) and unit line-with-arrowhead (reused, rotated, for
|
||||
// both the U and V axis arrows), transformed into place at render time.
|
||||
GLModel m_adjust_panel_glmodel;
|
||||
GLModel m_adjust_arrow_glmodel;
|
||||
|
||||
std::map<std::string, wxString> m_desc;
|
||||
|
||||
// The tool's SVG (toolbar_texture_displacement.svg) uploaded once as a GL texture, so it can be
|
||||
// used as an ImGui image button in the panel (currently the "add layer" affordance next to the
|
||||
// Texture layers heading). Lazily loaded on first use, when a GL context is guaranteed current.
|
||||
GLTexture m_tool_icon;
|
||||
bool m_tool_icon_tried = false;
|
||||
unsigned int tool_icon_id(); // 0 if the icon could not be loaded
|
||||
|
||||
// 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 -
|
||||
// matching the toolbar's selected/unselected look. Uploaded once on first panel render.
|
||||
IconManager m_panel_icons;
|
||||
std::map<std::string, IconManager::Icons> m_panel_icon_map; // file name -> [normal, colour, disabled]
|
||||
bool m_panel_icons_tried = false;
|
||||
void ensure_panel_icons();
|
||||
};
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_GLGizmoTextureDisplacement_hpp_
|
||||
@@ -22,6 +22,7 @@
|
||||
//#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoEmboss.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSVG.hpp"
|
||||
@@ -164,6 +165,9 @@ void GLGizmosManager::switch_gizmos_icon_filename()
|
||||
case(EType::FuzzySkin):
|
||||
gizmo->set_icon_filename(m_is_dark ? "toolbar_fuzzy_skin_paint_dark.svg" : "toolbar_fuzzy_skin_paint.svg");
|
||||
break;
|
||||
case(EType::TextureDisplacement):
|
||||
gizmo->set_icon_filename(m_is_dark ? "toolbar_fuzzy_skin_paint_dark.svg" : "toolbar_fuzzy_skin_paint.svg");
|
||||
break;
|
||||
case(EType::MeshBoolean):
|
||||
gizmo->set_icon_filename(m_is_dark ? "toolbar_meshboolean_dark.svg" : "toolbar_meshboolean.svg");
|
||||
break;
|
||||
@@ -213,6 +217,8 @@ bool GLGizmosManager::init()
|
||||
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, m_is_dark ? "toolbar_seam_dark.svg" : "toolbar_seam.svg", EType::Seam));
|
||||
m_gizmos.emplace_back(new GLGizmoFuzzySkin(m_parent, m_is_dark ? "toolbar_fuzzy_skin_paint_dark.svg" : "toolbar_fuzzy_skin_paint.svg", EType::FuzzySkin));
|
||||
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, m_is_dark ? "mmu_segmentation_dark.svg" : "mmu_segmentation.svg", EType::MmSegmentation));
|
||||
// One shared icon (no dedicated dark variant yet); it recolours acceptably in both themes.
|
||||
m_gizmos.emplace_back(new GLGizmoTextureDisplacement(m_parent, "toolbar_texture_displacement.svg", EType::TextureDisplacement));
|
||||
m_gizmos.emplace_back(new GLGizmoEmboss(m_parent, m_is_dark ? "toolbar_text_dark.svg" : "toolbar_text.svg", EType::Emboss));
|
||||
m_gizmos.emplace_back(new GLGizmoSVG(m_parent));
|
||||
m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, m_is_dark ? "toolbar_measure_dark.svg" : "toolbar_measure.svg", EType::Measure));
|
||||
@@ -524,6 +530,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
|
||||
return dynamic_cast<GLGizmoCut3D*>(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == FuzzySkin)
|
||||
return dynamic_cast<GLGizmoFuzzySkin*>(m_gizmos[FuzzySkin].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == TextureDisplacement)
|
||||
return dynamic_cast<GLGizmoTextureDisplacement*>(m_gizmos[TextureDisplacement].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == MeshBoolean)
|
||||
return dynamic_cast<GLGizmoMeshBoolean*>(m_gizmos[MeshBoolean].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
else if (m_current == BrimEars)
|
||||
@@ -537,6 +545,7 @@ bool GLGizmosManager::is_paint_gizmo()
|
||||
return m_current == EType::FdmSupports ||
|
||||
m_current == EType::MmSegmentation ||
|
||||
m_current == EType::FuzzySkin ||
|
||||
m_current == EType::TextureDisplacement ||
|
||||
m_current == EType::Seam;
|
||||
}
|
||||
|
||||
@@ -1486,6 +1495,8 @@ std::string get_name_from_gizmo_etype(GLGizmosManager::EType type)
|
||||
return "Color Painting";
|
||||
case GLGizmosManager::EType::FuzzySkin:
|
||||
return "Fuzzy Skin Painting";
|
||||
case GLGizmosManager::EType::TextureDisplacement:
|
||||
return "Texture Displacement";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ public:
|
||||
Seam,
|
||||
FuzzySkin,
|
||||
MmSegmentation,
|
||||
TextureDisplacement,
|
||||
Emboss,
|
||||
Svg,
|
||||
Measure,
|
||||
|
||||
@@ -2554,7 +2554,11 @@ void ImGuiWrapper::push_toolbar_style(const float scale)
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 1.00f)); // 10
|
||||
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
|
||||
ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.00f, 1.00f, 1.00f, 1.00f));//13
|
||||
// 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
|
||||
// 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
|
||||
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, ImVec4(0.42f, 0.42f, 0.42f, 1.00f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, ImVec4(0.93f, 0.93f, 0.93f, 1.00f));
|
||||
|
||||
88
src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "TextureDisplacementBakeJob.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
TextureDisplacementBakeJob::TextureDisplacementBakeJob(TextureDisplacementBakeInput &&input, std::function<void()> on_finished)
|
||||
: m_input(std::move(input)), m_on_finished(std::move(on_finished))
|
||||
{
|
||||
}
|
||||
|
||||
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.
|
||||
m_result = TriangleMesh(build_texture_displacement(m_input.base_mesh, m_input.layers, m_input.facets_data));
|
||||
}
|
||||
|
||||
void TextureDisplacementBakeJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
{
|
||||
struct OnExit
|
||||
{
|
||||
std::function<void()> fn;
|
||||
~OnExit() { if (fn) fn(); }
|
||||
} on_exit{m_on_finished};
|
||||
|
||||
if (canceled || eptr || m_result.empty())
|
||||
return;
|
||||
|
||||
Plater *plater = wxGetApp().plater();
|
||||
|
||||
Plater::TakeSnapshot snapshot(plater, _u8L("Bake texture displacement"), UndoRedo::SnapshotType::GizmoAction);
|
||||
|
||||
ModelVolume *volume = get_model_volume(m_input.volume_id, plater->model().objects);
|
||||
if (volume == nullptr)
|
||||
return;
|
||||
|
||||
volume->set_mesh(std::move(m_result));
|
||||
volume->set_new_unique_id();
|
||||
volume->calculate_convex_hull();
|
||||
|
||||
// Clear the paint mask of every layer that was actually baked so a repeat bake (or the paint
|
||||
// overlay) doesn't act on triangles that no longer represent the same unbaked surface. The
|
||||
// texture layer definitions themselves (and paint outside the baked area, if any) are left
|
||||
// untouched so the user can keep sculpting with the same textures.
|
||||
for (const TextureDisplacementLayer &layer : m_input.layers)
|
||||
if (!layer.empty() && layer.slot >= 0 && layer.slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS))
|
||||
volume->texture_displacement_facet(layer.slot).reset();
|
||||
|
||||
ModelObject *object = volume->get_object();
|
||||
if (object == nullptr)
|
||||
return;
|
||||
|
||||
if (ObjectList *obj_list = wxGetApp().obj_list()) {
|
||||
const ModelObjectPtrs &objs = plater->model().objects;
|
||||
auto it = std::find(objs.begin(), objs.end(), object);
|
||||
if (it != objs.end())
|
||||
obj_list->update_info_items(size_t(it - objs.begin()));
|
||||
}
|
||||
|
||||
plater->changed_object(*object);
|
||||
}
|
||||
|
||||
void queue_texture_displacement_bake(const ModelVolume &volume, std::function<void()> on_finished)
|
||||
{
|
||||
TextureDisplacementBakeInput input;
|
||||
input.volume_id = volume.id();
|
||||
input.base_mesh = volume.mesh().its;
|
||||
input.layers = volume.texture_displacement_layers;
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
input.facets_data[size_t(i)] = volume.texture_displacement_facet(i).get_data();
|
||||
|
||||
auto &worker = wxGetApp().plater()->get_ui_job_worker();
|
||||
queue_job(worker, std::make_unique<TextureDisplacementBakeJob>(std::move(input), std::move(on_finished)));
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
51
src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef slic3r_TextureDisplacementBakeJob_hpp_
|
||||
#define slic3r_TextureDisplacementBakeJob_hpp_
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#include "libslic3r/TextureDisplacement.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
#include "Job.hpp"
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
// Everything process() needs, captured by value on the main thread when the job is queued so the
|
||||
// worker thread never touches the live Model concurrently with the UI (mirrors how EmbossJob's
|
||||
// DataBase is captured before process() runs).
|
||||
struct TextureDisplacementBakeInput
|
||||
{
|
||||
ObjectID volume_id;
|
||||
indexed_triangle_set base_mesh;
|
||||
std::vector<TextureDisplacementLayer> layers;
|
||||
TextureDisplacementFacetsData facets_data;
|
||||
};
|
||||
|
||||
// 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()
|
||||
// bake-and-commit pattern (see EmbossJob.cpp).
|
||||
class TextureDisplacementBakeJob : public Job
|
||||
{
|
||||
public:
|
||||
TextureDisplacementBakeJob(TextureDisplacementBakeInput &&input, std::function<void()> on_finished);
|
||||
|
||||
void process(Ctl &ctl) override;
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
||||
|
||||
private:
|
||||
TextureDisplacementBakeInput m_input;
|
||||
TriangleMesh m_result;
|
||||
std::function<void()> m_on_finished;
|
||||
};
|
||||
|
||||
// Captures `volume`'s current mesh/layers/paint data and queues a TextureDisplacementBakeJob on
|
||||
// the app's UI job worker. `on_finished` is always called once the job settles (success, failure,
|
||||
// or cancellation), so the caller can clear its own "bake in progress" UI state. Must be called
|
||||
// from the main thread.
|
||||
void queue_texture_displacement_bake(const ModelVolume &volume, std::function<void()> on_finished);
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_TextureDisplacementBakeJob_hpp_
|
||||
29
src/slic3r/GUI/Jobs/TextureDisplacementPreviewJob.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "TextureDisplacementPreviewJob.hpp"
|
||||
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
TextureDisplacementPreviewJob::TextureDisplacementPreviewJob(TextureDisplacementPreviewInput &&input, uint64_t generation,
|
||||
std::function<void(indexed_triangle_set, uint64_t)> on_finished)
|
||||
: m_input(std::move(input)), m_generation(generation), m_on_finished(std::move(on_finished))
|
||||
{
|
||||
}
|
||||
|
||||
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.
|
||||
m_result = build_texture_displacement(m_input.base_mesh, m_input.layers, m_input.facets_data);
|
||||
}
|
||||
|
||||
void TextureDisplacementPreviewJob::finalize(bool canceled, std::exception_ptr &eptr)
|
||||
{
|
||||
if (canceled || eptr || !m_on_finished)
|
||||
return;
|
||||
m_on_finished(std::move(m_result), m_generation);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
52
src/slic3r/GUI/Jobs/TextureDisplacementPreviewJob.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#ifndef slic3r_TextureDisplacementPreviewJob_hpp_
|
||||
#define slic3r_TextureDisplacementPreviewJob_hpp_
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "libslic3r/TextureDisplacement.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
#include "Job.hpp"
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
// 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
|
||||
{
|
||||
indexed_triangle_set base_mesh;
|
||||
std::vector<TextureDisplacementLayer> layers;
|
||||
TextureDisplacementFacetsData facets_data;
|
||||
};
|
||||
|
||||
// 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
|
||||
// purely informational, there is nothing to commit.
|
||||
class TextureDisplacementPreviewJob : public Job
|
||||
{
|
||||
public:
|
||||
// `generation` is an opaque token the caller controls (typically an incrementing counter):
|
||||
// on_finished should only actually be applied by the caller if it still matches the caller's
|
||||
// current generation when the job completes, so that a burst of edits queuing several of
|
||||
// these jobs in a row can't have an earlier, now-stale result clobber a later one that
|
||||
// finishes first.
|
||||
TextureDisplacementPreviewJob(TextureDisplacementPreviewInput &&input, uint64_t generation,
|
||||
std::function<void(indexed_triangle_set, uint64_t)> on_finished);
|
||||
|
||||
void process(Ctl &ctl) override;
|
||||
void finalize(bool canceled, std::exception_ptr &eptr) override;
|
||||
|
||||
private:
|
||||
TextureDisplacementPreviewInput m_input;
|
||||
uint64_t m_generation;
|
||||
indexed_triangle_set m_result;
|
||||
std::function<void(indexed_triangle_set, uint64_t)> m_on_finished;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_TextureDisplacementPreviewJob_hpp_
|
||||
@@ -103,6 +103,7 @@
|
||||
#include "Selection.hpp"
|
||||
#include "GLToolbar.hpp"
|
||||
#include "GUI_Preview.hpp"
|
||||
#include "UVEditorCanvas.hpp"
|
||||
#include "3DBed.hpp"
|
||||
#include "PartPlate.hpp"
|
||||
#include "Camera.hpp"
|
||||
@@ -5283,6 +5284,13 @@ struct Plater::priv
|
||||
GLToolbar collapse_toolbar;
|
||||
Preview *preview;
|
||||
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
|
||||
// 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 };
|
||||
UVEditorCanvas* uv_editor_canvas { nullptr };
|
||||
bool first_enter_assemble{ true };
|
||||
std::unique_ptr<NotificationManager> notification_manager;
|
||||
|
||||
@@ -5948,6 +5956,20 @@ 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
|
||||
// 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
|
||||
// Plater::show_uv_editor()), so it stays out of the way of everyone else's window layout.
|
||||
uv_editor_panel = new UVEditorPanel(q);
|
||||
uv_editor_canvas = uv_editor_panel->canvas();
|
||||
m_aui_mgr.AddPane(uv_editor_panel, wxAuiPaneInfo()
|
||||
.Name("uv_editor")
|
||||
.Caption(_L("UV Editor"))
|
||||
.Right()
|
||||
.Hide()
|
||||
.BestSize(wxSize(40 * wxGetApp().em_unit(), 40 * wxGetApp().em_unit())));
|
||||
|
||||
auto* panel_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
panel_sizer->Add(view3D, 1, wxEXPAND | wxALL, 0);
|
||||
panel_sizer->Add(preview, 1, wxEXPAND | wxALL, 0);
|
||||
@@ -5977,6 +5999,13 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
BOOST_LOG_TRIVIAL(info) << "Removed floating AUI state from saved window layout for Wayland";
|
||||
}
|
||||
|
||||
// The UV editor is a transient, gizmo-driven pane (see show_uv_editor()); a saved layout
|
||||
// from a session that happened to close with it open would otherwise restore it visible on
|
||||
// startup, with nothing painted in it. Force it hidden here so it only ever appears when the
|
||||
// texture-displacement gizmo asks for it.
|
||||
if (wxAuiPaneInfo &uv_pane = m_aui_mgr.GetPane("uv_editor"); uv_pane.IsOk())
|
||||
uv_pane.Hide();
|
||||
|
||||
sidebar_layout.is_collapsed = !sidebar.IsShown();
|
||||
}
|
||||
|
||||
@@ -13936,7 +13965,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<ConfigOptionFloats>("nozzle_diameter");
|
||||
std::vector<int> extruder_types = printer_config->option<ConfigOptionEnumsGeneric>("extruder_type")->values;
|
||||
@@ -18060,6 +18089,33 @@ GLCanvas3D* Plater::get_assmeble_canvas3D()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UVEditorCanvas* Plater::get_uv_editor_canvas()
|
||||
{
|
||||
return p->uv_editor_canvas;
|
||||
}
|
||||
|
||||
void Plater::show_uv_editor(bool show)
|
||||
{
|
||||
if (p->uv_editor_panel == nullptr)
|
||||
return;
|
||||
const wxAuiPaneInfo &pane = p->m_aui_mgr.GetPane(p->uv_editor_panel);
|
||||
if (!pane.IsOk() || pane.IsShown() == show)
|
||||
return;
|
||||
|
||||
// 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
|
||||
// the 3D canvas is drawing into. Doing the layout once the frame is over avoids that entirely.
|
||||
CallAfter([this, show]() {
|
||||
wxAuiPaneInfo &deferred_pane = p->m_aui_mgr.GetPane(p->uv_editor_panel);
|
||||
if (!deferred_pane.IsOk() || deferred_pane.IsShown() == show)
|
||||
return;
|
||||
deferred_pane.Show(show);
|
||||
p->m_aui_mgr.Update();
|
||||
});
|
||||
}
|
||||
|
||||
GLCanvas3D* Plater::get_current_canvas3D(bool exclude_preview)
|
||||
{
|
||||
return p->get_current_canvas3D(exclude_preview);
|
||||
|
||||
@@ -624,6 +624,13 @@ public:
|
||||
GLCanvas3D* get_assmeble_canvas3D();
|
||||
wxWindow* get_select_machine_dialog();
|
||||
|
||||
// Docked UV-editor pane used by GLGizmoTextureDisplacement's LSCM projection preview (see
|
||||
// UVEditorCanvas.hpp). Returns nullptr only before the main window is fully constructed.
|
||||
class UVEditorCanvas* get_uv_editor_canvas();
|
||||
// Shows or hides the UV-editor AUI pane, updating its docked layout accordingly. Safe to call
|
||||
// repeatedly (e.g. every time the gizmo's active layer/projection method changes).
|
||||
void show_uv_editor(bool show);
|
||||
|
||||
void arrange();
|
||||
void orient();
|
||||
void find_new_position(const ModelInstancePtrs &instances);
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||
#include "slic3r/plugin/PluginConfig.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "format.hpp"
|
||||
@@ -1795,9 +1796,19 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
||||
|
||||
// Keep this preset's "plugins" manifest in sync when a plugin picker changes, so full_config() and
|
||||
// save_to_json() always find resolved "name;uuid;capability" references and rebuild it nowhere else.
|
||||
// Also drop any plugin_config_overrides entries for a capability the change just stopped
|
||||
// referencing (e.g. a plugin removed from slicing_pipeline_plugin), so a saved preset never
|
||||
// carries configuration for a capability it no longer names. The Configure button is a separate
|
||||
// field holding its own cached copy of that value, so it needs to be told explicitly, or it
|
||||
// keeps showing the stale count until something else happens to refresh it.
|
||||
if (const ConfigOptionDef* opt_def = m_config->def()->get(opt_key);
|
||||
opt_def && opt_def->is_plugin_backed())
|
||||
opt_def && opt_def->is_plugin_backed()) {
|
||||
m_config->update_plugin_manifest();
|
||||
if (prune_stale_plugin_overrides(*m_config)) {
|
||||
if (Field* overrides_field = get_field(PLUGIN_OVERRIDES_OPTION_KEY))
|
||||
overrides_field->set_value(boost::any(m_config->opt_string(PLUGIN_OVERRIDES_OPTION_KEY)), false);
|
||||
}
|
||||
}
|
||||
|
||||
if (opt_key == "gcode_flavor" && m_type == Preset::TYPE_PRINTER) {
|
||||
if (auto printer_tab = dynamic_cast<TabPrinter*>(this))
|
||||
|
||||
208
src/slic3r/GUI/TextureLibrary.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
#include "TextureLibrary.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <wx/image.h>
|
||||
|
||||
#include "libslic3r/PNGReadWrite.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
namespace {
|
||||
|
||||
// Extensions the picker will list. The shipped folder only ever contains .png; the rest are here
|
||||
// so a user who drops a .jpg straight into their own folder still sees it (load_texture_image_data()
|
||||
// converts anything it can open).
|
||||
bool is_image_file(const boost::filesystem::path &path)
|
||||
{
|
||||
std::string ext = path.extension().string();
|
||||
boost::algorithm::to_lower(ext);
|
||||
return ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp";
|
||||
}
|
||||
|
||||
void scan_dir(const boost::filesystem::path &dir, bool is_user, std::vector<TextureLibraryEntry> &out)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
if (!boost::filesystem::is_directory(dir, ec))
|
||||
return;
|
||||
|
||||
const size_t first = out.size();
|
||||
for (boost::filesystem::directory_iterator it(dir, ec), end; it != end && !ec; it.increment(ec)) {
|
||||
if (!boost::filesystem::is_regular_file(it->path(), ec) || !is_image_file(it->path()))
|
||||
continue;
|
||||
out.push_back({ it->path().stem().string(), it->path().string(), is_user });
|
||||
}
|
||||
std::sort(out.begin() + first, out.end(),
|
||||
[](const TextureLibraryEntry &a, const TextureLibraryEntry &b) { return a.name < b.name; });
|
||||
}
|
||||
|
||||
// Slic3r::png only writes PNGs to a file, so the encode round-trips through a temp file rather than
|
||||
// staying in memory. It happens once per import / per texture pick, not per frame, so the I/O is
|
||||
// not worth avoiding with a second PNG encoder.
|
||||
bool encode_gray_png_bytes(const wxImage &image, std::vector<unsigned char> &out, std::string &error)
|
||||
{
|
||||
const wxImage gray = image.ConvertToGreyscale();
|
||||
const int w = gray.GetWidth();
|
||||
const int h = gray.GetHeight();
|
||||
if (w <= 0 || h <= 0) {
|
||||
error = _u8L("The selected image is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// wxImage always stores 3 bytes per pixel; after ConvertToGreyscale the three are equal.
|
||||
std::vector<uint8_t> pixels(size_t(w) * size_t(h));
|
||||
const unsigned char *rgb = gray.GetData();
|
||||
for (size_t i = 0; i < pixels.size(); ++i)
|
||||
pixels[i] = rgb[i * 3];
|
||||
|
||||
const boost::filesystem::path tmp = boost::filesystem::temp_directory_path()
|
||||
/ boost::filesystem::unique_path("orca_texdisp_%%%%%%%%.png");
|
||||
if (!Slic3r::png::write_gray_to_file(tmp.string(), size_t(w), size_t(h), pixels)) {
|
||||
error = _u8L("Failed to prepare the texture for use.");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
std::ifstream ifs(tmp.string(), std::ios::binary);
|
||||
out.assign(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
|
||||
}
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove(tmp, ec);
|
||||
|
||||
if (out.empty()) {
|
||||
error = _u8L("Failed to prepare the texture for use.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> read_file_bytes(const std::string &path)
|
||||
{
|
||||
std::ifstream ifs(path, std::ios::binary);
|
||||
return std::vector<unsigned char>(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
|
||||
}
|
||||
|
||||
// True if these bytes are already the 8-bit grayscale PNG libslic3r can decode, i.e. can be stored
|
||||
// on a layer as-is. Mirrors exactly what decode_height_texture() accepts.
|
||||
bool is_supported_height_map(const std::vector<unsigned char> &bytes)
|
||||
{
|
||||
if (bytes.empty())
|
||||
return false;
|
||||
const png::ReadBuf rbuf{ bytes.data(), bytes.size() };
|
||||
if (!png::is_png(rbuf))
|
||||
return false;
|
||||
png::ImageGreyscale img;
|
||||
return png::decode_png(rbuf, img) && img.cols > 0 && img.rows > 0;
|
||||
}
|
||||
|
||||
std::vector<TextureLibraryEntry> g_library;
|
||||
bool g_library_scanned = false;
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string user_texture_dir()
|
||||
{
|
||||
const boost::filesystem::path dir = boost::filesystem::path(Slic3r::data_dir()) / "textures" / "displacement";
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::create_directories(dir, ec);
|
||||
if (ec) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Could not create the user texture directory " << dir.string() << ": " << ec.message();
|
||||
return {};
|
||||
}
|
||||
return dir.string();
|
||||
}
|
||||
|
||||
const std::vector<TextureLibraryEntry> &texture_library(bool force_rescan)
|
||||
{
|
||||
if (g_library_scanned && !force_rescan)
|
||||
return g_library;
|
||||
|
||||
g_library.clear();
|
||||
scan_dir(boost::filesystem::path(Slic3r::resources_dir()) / "textures" / "displacement", false, g_library);
|
||||
const std::string user_dir = user_texture_dir();
|
||||
if (!user_dir.empty())
|
||||
scan_dir(boost::filesystem::path(user_dir), true, g_library);
|
||||
|
||||
g_library_scanned = true;
|
||||
return g_library;
|
||||
}
|
||||
|
||||
std::optional<TextureLibraryEntry> import_texture_to_library(const std::string &source_path, std::string &error)
|
||||
{
|
||||
const std::string user_dir = user_texture_dir();
|
||||
if (user_dir.empty()) {
|
||||
error = _u8L("Could not create the folder for imported textures.");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
wxImage image;
|
||||
if (!image.LoadFile(from_u8(source_path)) || !image.IsOk()) {
|
||||
error = _u8L("Could not load the selected image.");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> bytes;
|
||||
if (!encode_gray_png_bytes(image, bytes, error))
|
||||
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.
|
||||
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)
|
||||
dest = boost::filesystem::path(user_dir) / (stem + " (" + std::to_string(i) + ").png");
|
||||
|
||||
{
|
||||
boost::nowide::ofstream ofs(dest.string(), std::ios::binary);
|
||||
ofs.write(reinterpret_cast<const char *>(bytes.data()), std::streamsize(bytes.size()));
|
||||
if (!ofs.good()) {
|
||||
error = _u8L("Failed to save the imported texture.");
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
texture_library(true); // pick the new file up
|
||||
const std::string dest_str = dest.string();
|
||||
for (const TextureLibraryEntry &e : g_library)
|
||||
if (e.path == dest_str)
|
||||
return e;
|
||||
|
||||
error = _u8L("Failed to save the imported texture.");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::shared_ptr<std::vector<unsigned char>> load_texture_image_data(const std::string &path, std::string &error)
|
||||
{
|
||||
std::vector<unsigned char> bytes = read_file_bytes(path);
|
||||
if (bytes.empty()) {
|
||||
error = _u8L("Could not read the texture file.");
|
||||
return nullptr;
|
||||
}
|
||||
if (is_supported_height_map(bytes))
|
||||
return std::make_shared<std::vector<unsigned char>>(std::move(bytes));
|
||||
|
||||
// Not an 8-bit grayscale PNG (a colour image somebody copied into the folder by hand, say):
|
||||
// convert it the same way an import would, but leave the file on disk alone.
|
||||
wxImage image;
|
||||
if (!image.LoadFile(from_u8(path)) || !image.IsOk()) {
|
||||
error = _u8L("Could not load the selected image.");
|
||||
return nullptr;
|
||||
}
|
||||
std::vector<unsigned char> converted;
|
||||
if (!encode_gray_png_bytes(image, converted, error))
|
||||
return nullptr;
|
||||
return std::make_shared<std::vector<unsigned char>>(std::move(converted));
|
||||
}
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
50
src/slic3r/GUI/TextureLibrary.hpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#ifndef slic3r_TextureLibrary_hpp_
|
||||
#define slic3r_TextureLibrary_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
// One selectable height-map texture in the texture-displacement gizmo's texture picker.
|
||||
struct TextureLibraryEntry
|
||||
{
|
||||
std::string name; // display name (the file's stem, e.g. "Wood Grain")
|
||||
std::string path; // absolute path on disk
|
||||
bool is_user; // imported by the user, as opposed to shipped with OrcaSlicer
|
||||
};
|
||||
|
||||
// Every available height-map texture: the ones shipped in resources/textures/displacement first,
|
||||
// then the user's own from <data_dir>/textures/displacement, each group sorted by name.
|
||||
//
|
||||
// The two live in separate directories deliberately: an app update replaces the resources tree
|
||||
// wholesale, so anything the user imported has to sit somewhere that update can never overwrite or
|
||||
// delete. `is_user` is what the picker uses to show them under separate headings.
|
||||
//
|
||||
// Scanned once and cached. Pass force_rescan after an import, or to pick up a file the user dropped
|
||||
// into either folder by hand while the app was running.
|
||||
const std::vector<TextureLibraryEntry> &texture_library(bool force_rescan = false);
|
||||
|
||||
// <data_dir>/textures/displacement, created if it does not exist yet. Empty string on failure.
|
||||
std::string user_texture_dir();
|
||||
|
||||
// Reads any image format wxWidgets can open, converts it to the 8-bit grayscale PNG that
|
||||
// libslic3r's decode_height_texture() understands, and saves it into user_texture_dir() (uniquified
|
||||
// if that name is taken). The conversion has to happen here rather than in libslic3r, which has no
|
||||
// image toolkit and so only ever handles the one already-normalized format.
|
||||
//
|
||||
// Returns the newly imported entry, or nullopt with `error` set. `source_path` is only read.
|
||||
std::optional<TextureLibraryEntry> import_texture_to_library(const std::string &source_path, std::string &error);
|
||||
|
||||
// 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.
|
||||
// Returns nullptr with `error` set if the file cannot be read or decoded at all.
|
||||
std::shared_ptr<std::vector<unsigned char>> load_texture_image_data(const std::string &path, std::string &error);
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_TextureLibrary_hpp_
|
||||
97
src/slic3r/GUI/TextureProjectorFrame.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include "TextureProjectorFrame.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/image.h>
|
||||
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
TextureProjectorFrame::TextureProjectorFrame(wxWindow *parent)
|
||||
: wxFrame(parent, wxID_ANY, _L("Projection frame - drag over the model, then Apply"), wxDefaultPosition,
|
||||
wxSize(360, 360),
|
||||
// Caption and resize border so moving and sizing are the native gestures the user
|
||||
// already knows - "align it by moving the window" only works if the window moves the
|
||||
// ordinary way. FLOAT_ON_PARENT keeps it above the 3D view without the antisocial
|
||||
// always-on-top-of-everything behaviour of wxSTAY_ON_TOP.
|
||||
wxCAPTION | wxRESIZE_BORDER | wxCLOSE_BOX | wxFRAME_NO_TASKBAR | wxFRAME_FLOAT_ON_PARENT)
|
||||
{
|
||||
SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
Bind(wxEVT_PAINT, &TextureProjectorFrame::on_paint, this);
|
||||
// A resize changes the gate, so the texture has to be re-stretched under it.
|
||||
Bind(wxEVT_SIZE, [this](wxSizeEvent &evt) { Refresh(); evt.Skip(); });
|
||||
SetTransparent(wxByte(m_alpha));
|
||||
|
||||
// Hide rather than destroy: the gizmo owns this window's lifetime, and reopening should keep the
|
||||
// frame exactly where it was left - its position is the placement.
|
||||
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt) {
|
||||
if (evt.CanVeto()) {
|
||||
evt.Veto();
|
||||
Hide();
|
||||
} else
|
||||
evt.Skip();
|
||||
});
|
||||
}
|
||||
|
||||
void TextureProjectorFrame::set_texture(const std::vector<unsigned char> &gray, int width, int height)
|
||||
{
|
||||
if (width <= 0 || height <= 0 || gray.size() < size_t(width) * size_t(height)) {
|
||||
m_bitmap = wxBitmap();
|
||||
Refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
wxImage img(width, height);
|
||||
unsigned char *dst = img.GetData();
|
||||
for (size_t i = 0, n = size_t(width) * size_t(height); i < n; ++i) {
|
||||
const unsigned char v = gray[i];
|
||||
dst[i * 3 + 0] = v;
|
||||
dst[i * 3 + 1] = v;
|
||||
dst[i * 3 + 2] = v;
|
||||
}
|
||||
m_bitmap = wxBitmap(img);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void TextureProjectorFrame::set_opacity(int alpha)
|
||||
{
|
||||
m_alpha = std::clamp(alpha, 20, 255);
|
||||
SetTransparent(wxByte(m_alpha));
|
||||
Refresh();
|
||||
}
|
||||
|
||||
wxRect TextureProjectorFrame::client_rect_on_screen() const
|
||||
{
|
||||
const wxSize sz = GetClientSize();
|
||||
return wxRect(ClientToScreen(wxPoint(0, 0)), sz);
|
||||
}
|
||||
|
||||
void TextureProjectorFrame::on_paint(wxPaintEvent &)
|
||||
{
|
||||
wxPaintDC dc(this);
|
||||
const wxSize sz = GetClientSize();
|
||||
if (sz.x <= 0 || sz.y <= 0)
|
||||
return;
|
||||
|
||||
dc.SetBackground(wxBrush(wxColour(20, 20, 20)));
|
||||
dc.Clear();
|
||||
|
||||
if (m_bitmap.IsOk()) {
|
||||
// Stretched to fill the client area rather than kept at its own aspect: the gate maps to the
|
||||
// uv unit square whatever its shape, so a non-square window genuinely does project a
|
||||
// stretched texture. Showing it any other way would misrepresent the bake.
|
||||
wxImage scaled = m_bitmap.ConvertToImage().Scale(sz.x, sz.y, wxIMAGE_QUALITY_NORMAL);
|
||||
dc.DrawBitmap(wxBitmap(scaled), 0, 0, false);
|
||||
}
|
||||
|
||||
// The border is the projection's hard edge, so it is drawn explicitly - with the window
|
||||
// translucent, the native frame alone reads poorly against a busy 3D scene.
|
||||
dc.SetPen(wxPen(wxColour(0, 200, 180), 2));
|
||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||
dc.DrawRectangle(0, 0, sz.x, sz.y);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
56
src/slic3r/GUI/TextureProjectorFrame.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#ifndef slic3r_TextureProjectorFrame_hpp_
|
||||
#define slic3r_TextureProjectorFrame_hpp_
|
||||
|
||||
// The projection-frame overlay for TextureProjectionMethod::ViewProjected.
|
||||
//
|
||||
// A semi-transparent, resizable window that the user drags over the 3D view like a slide projector's
|
||||
// gate: whatever the model shows through this window is what the texture is projected onto, and the
|
||||
// window's border is the hard edge of the projection. "Apply" then reads the window's client
|
||||
// rectangle, converts it into the 3D canvas's own pixel space, and builds an exact projective map
|
||||
// from it (see GLGizmoTextureDisplacement::apply_projection_frame()).
|
||||
//
|
||||
// The window itself is deliberately dumb - it owns no placement state and reports nothing
|
||||
// continuously. Its position and size *are* the placement, and they are read on demand at Apply,
|
||||
// which is also when the (expensive) visible-facet selection runs. Moving the window is therefore
|
||||
// free, and nothing recomputes until the user asks for it.
|
||||
//
|
||||
// Plain 2D (wxGraphicsContext), not a wxGLCanvas: a second GL canvas would have to share the app's
|
||||
// one real wxGLContext, the cause of bugs #10 and #14 in TEXTURE_DISPLACEMENT.md. A paint-DC window
|
||||
// has no such failure mode, and this one only ever draws a bitmap and a border.
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/frame.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class TextureProjectorFrame : public wxFrame
|
||||
{
|
||||
public:
|
||||
explicit TextureProjectorFrame(wxWindow *parent);
|
||||
|
||||
// The same 8-bit grayscale pixels build_texture_displacement() samples, so what is aligned here
|
||||
// is what gets baked. Pass width/height <= 0 to clear it.
|
||||
void set_texture(const std::vector<unsigned char> &grayscale_pixels, int width, int height);
|
||||
|
||||
// Whole-window opacity, 0..255. Low enough to see the model through it, high enough to judge
|
||||
// where the texture lands - the useful range is roughly 60..200.
|
||||
void set_opacity(int alpha);
|
||||
int opacity() const { return m_alpha; }
|
||||
|
||||
// The client area (the gate itself, excluding caption and borders) in screen coordinates. This
|
||||
// is what the projection is built from, so it deliberately excludes the window decorations -
|
||||
// the user aligns what they see, which is the client area.
|
||||
wxRect client_rect_on_screen() const;
|
||||
|
||||
private:
|
||||
wxBitmap m_bitmap;
|
||||
int m_alpha = 140;
|
||||
|
||||
void on_paint(wxPaintEvent &);
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_TextureProjectorFrame_hpp_
|
||||
1458
src/slic3r/GUI/UVEditorCanvas.cpp
Normal file
340
src/slic3r/GUI/UVEditorCanvas.hpp
Normal file
@@ -0,0 +1,340 @@
|
||||
#ifndef slic3r_UVEditorCanvas_hpp_
|
||||
#define slic3r_UVEditorCanvas_hpp_
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// Must come before wx/glcanvas.h in any translation unit that includes this header: glcanvas.h
|
||||
// pulls in the platform's real GL/gl.h, and glad/gl.h errors out if that happens first (it wants
|
||||
// to be the one to define the standard include guards GL/gl.h itself defines).
|
||||
#include <glad/gl.h>
|
||||
#include <wx/glcanvas.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/tglbtn.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/sizer.h>
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "GLModel.hpp"
|
||||
#include "GLTexture.hpp"
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// shader", which only means anything for canvases sharing the app's one real GL context.
|
||||
class UVEditorCanvas : public wxGLCanvas
|
||||
{
|
||||
public:
|
||||
explicit UVEditorCanvas(wxWindow *parent);
|
||||
|
||||
// Maps one island's raw unwrap coordinate to a texture UV: the island's own hand placement and
|
||||
// then the layer's tiling/rotation/offset, composed into a single affine (columns: x basis,
|
||||
// y basis, translation).
|
||||
using IslandTransform = Eigen::Matrix<float, 2, 3>;
|
||||
|
||||
// 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.
|
||||
struct Islands
|
||||
{
|
||||
std::vector<Vec2f> uvs;
|
||||
std::vector<Vec3i32> indices;
|
||||
std::vector<int> vertex_island; // per uv
|
||||
std::vector<std::pair<int, int>> boundary_edges; // island outlines, indices into uvs
|
||||
int island_count = 0;
|
||||
};
|
||||
void set_islands(Islands islands);
|
||||
|
||||
// The cheap path: one transform per island. Safe to call on every mouse-move of a drag.
|
||||
void set_island_transforms(std::vector<IslandTransform> transforms);
|
||||
|
||||
// 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<ColorRGBA> 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
|
||||
// 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);
|
||||
|
||||
// Same 8-bit grayscale pixels build_texture_displacement()'s height sampling uses, shown
|
||||
// beneath the wireframe (expanded to RGBA on upload) so the unwrap can be checked against the
|
||||
// texture it will actually sample. Pass width/height <= 0 to clear it.
|
||||
void set_background_texture(const std::vector<unsigned char> &grayscale_pixels, int width, int height);
|
||||
|
||||
// Snap a dragged island's boundary to a neighbouring island's when they come close (#2). Off is
|
||||
// the honest default for overlap-friendly layouts; the pane toolbar toggles it.
|
||||
void set_snap_enabled(bool enabled) { m_snap_enabled = enabled; }
|
||||
bool snap_enabled() const { return m_snap_enabled; }
|
||||
|
||||
// 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.
|
||||
enum class Command { FrameAll, ToggleSnap, AverageScale, CutSelectedIsland, ProjectFromView, JoinSelected, UnjoinSelected };
|
||||
void run_command(Command cmd);
|
||||
using CommandFn = std::function<void(Command)>;
|
||||
void set_command_callback(CommandFn fn) { m_on_command = std::move(fn); }
|
||||
|
||||
// Called whenever the one-line status/hint text changes (current gesture + the shortcuts that
|
||||
// apply right now), so the pane can show it Blender-style along the bottom.
|
||||
using StatusFn = std::function<void(const wxString &)>;
|
||||
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
|
||||
// *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
|
||||
// gesture, so the owner can rebuild the 3D preview once rather than on every motion event.
|
||||
using IslandEditFn =
|
||||
std::function<void(int island, const Vec2f &offset_delta, float rotation_delta, float scale_factor, bool finished)>;
|
||||
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
|
||||
// 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 };
|
||||
void set_select_mode(SelectMode mode);
|
||||
SelectMode select_mode() const { return m_select_mode; }
|
||||
|
||||
// Reports a committed vertex/edge edit: the list of (unwrapped-vertex index, its new raw-unwrap
|
||||
// coordinate in mm). Fired once, on mouse release, since it re-solves the displacement preview; the
|
||||
// pane shows the edit live from its own geometry in the meantime. The owner maps the unwrapped index
|
||||
// to a mesh vertex (via the unwrap's source_vertex) and stores the override.
|
||||
using UVVertexEditFn = std::function<void(const std::vector<std::pair<int, Vec2f>> &edits)>;
|
||||
void set_vertex_edit_callback(UVVertexEditFn fn) { m_on_vertex_edit = std::move(fn); }
|
||||
|
||||
// The primary (last-clicked) island, still the pivot for rotate/scale and the target of the
|
||||
// single-island toolbar commands (Cut/Join/Unjoin). -1 if nothing is selected.
|
||||
int selected_island() const { return m_selected_island; }
|
||||
// The full multi-selection (Shift adds, Ctrl toggles). Always contains m_selected_island when it is
|
||||
// >= 0. The gizmo reads this to decide which islands a drag moves together, unioned with each
|
||||
// selected island's join group.
|
||||
const std::vector<int> &selected_islands() const { return m_selection; }
|
||||
void reset_view();
|
||||
|
||||
private:
|
||||
void on_paint(wxPaintEvent &evt);
|
||||
void on_size(wxSizeEvent &evt);
|
||||
void on_mouse(wxMouseEvent &evt);
|
||||
void on_key(wxKeyEvent &evt);
|
||||
void on_leave(wxMouseEvent &evt); // drops the +/- cursor hint when the pointer leaves the canvas
|
||||
void on_erase_background(wxEraseEvent &evt) {} // required to avoid flicker on MSW, deliberately a no-op
|
||||
|
||||
void render();
|
||||
void rebuild_island_models();
|
||||
void rebuild_background_texture();
|
||||
void rebuild_background_quad();
|
||||
void rebuild_grid();
|
||||
// The UV region worth looking at: every island, plus always at least the texture's first tile, so
|
||||
// there is something sensibly framed even before anything is painted.
|
||||
void content_bounds(Vec2f &min_uv, Vec2f &max_uv) const;
|
||||
// Frames content_bounds(). Bound to Home, and run once each time an unwrap first appears.
|
||||
void fit_view_to_content();
|
||||
|
||||
// Half-extents of the visible UV region. Split out because both rendering and every mouse
|
||||
// gesture need them, and they have to agree exactly or picking lands in the wrong place.
|
||||
void view_half_extents(float &half_w, float &half_h) const;
|
||||
Vec2f screen_to_uv(const wxPoint &px) const;
|
||||
// Raw unwrap coordinate -> texture UV, through the island's own transform.
|
||||
Vec2f island_uv(size_t vertex) const;
|
||||
// The island under `uv`, or -1. Prefers the current selection when islands overlap, so that
|
||||
// dragging one that sits under another doesn't hand the drag to its neighbour halfway through.
|
||||
int island_at(const Vec2f &uv) const;
|
||||
// Nearest unwrapped vertex to `uv` within a screen-space threshold, or -1 (Vertex mode picking).
|
||||
int vertex_at(const Vec2f &uv) const;
|
||||
// Nearest island-boundary edge to `uv` within a screen-space threshold, as its two unwrapped-vertex
|
||||
// indices, or {-1,-1} (Edge mode picking).
|
||||
std::pair<int, int> edge_at(const Vec2f &uv) const;
|
||||
// Moves one unwrapped vertex by a texture-UV delta, converting it back into the vertex's own raw
|
||||
// unwrap space through its island's inverse transform, and marks the mesh dirty so it redraws.
|
||||
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.
|
||||
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).
|
||||
Vec2f snap_correction(int island) const;
|
||||
void end_gesture();
|
||||
// Rebuilds the status line from the current gesture/selection and pushes it to m_on_status.
|
||||
void update_status();
|
||||
|
||||
wxGLContext *m_context = nullptr; // owned by OpenGLManager/GUI_App, not by this canvas
|
||||
|
||||
Islands m_islands;
|
||||
std::vector<IslandTransform> m_transforms;
|
||||
// Per-island fill colour override (distortion heatmap); empty means use the default wash (#7).
|
||||
std::vector<ColorRGBA> m_island_fill_colors;
|
||||
// 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<std::vector<int>> m_island_boundary_verts;
|
||||
|
||||
bool m_mesh_dirty = true;
|
||||
// One set of models per island, so an island can be drawn through its own transform. Built once
|
||||
// per unwrap, never on a drag.
|
||||
std::vector<GLModel> m_island_wireframe; // interior edges
|
||||
std::vector<GLModel> m_island_boundary; // outline
|
||||
std::vector<GLModel> 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_grid_glmodel;
|
||||
float m_grid_step = 0.f; // the UV step m_grid_glmodel was built for; 0 = not built
|
||||
|
||||
float m_tiling_scale = 1.f;
|
||||
float m_rotation_deg = 0.f;
|
||||
bool m_tile_enabled = true;
|
||||
bool m_tile_mirrored = false;
|
||||
bool m_snap_enabled = false;
|
||||
|
||||
std::vector<unsigned char> m_background_pixels; // RGBA, expanded from the grayscale input
|
||||
int m_background_width = 0, m_background_height = 0;
|
||||
bool m_background_dirty = false; // the pixels need (re)uploading
|
||||
bool m_background_quad_dirty = true; // only the quad's extent changed
|
||||
GLTexture m_background_texture;
|
||||
// Covers content_bounds(), not just [0,1]: the unwrap is packed in mm and then divided by the
|
||||
// layer's tile size, so it routinely spans many tiles, and a single-unit-square backdrop would
|
||||
// leave most of the islands sitting over bare background. Texcoord == position, so the GL wrap
|
||||
// mode repeats it exactly the way DecodedHeightTexture::sample() does.
|
||||
GLModel m_background_glmodel;
|
||||
|
||||
// 2D pan/zoom. m_pan is the UV-space point at the center of the view, m_zoom half the UV-space
|
||||
// extent visible across the shorter screen edge. v runs *down* the screen, matching both the
|
||||
// texture's own row order and every other UV editor's convention.
|
||||
Vec2f m_pan = Vec2f(0.5f, 0.5f);
|
||||
float m_zoom = 0.75f;
|
||||
bool m_needs_fit = true; // fit the view to the next unwrap that arrives
|
||||
|
||||
enum class Gesture
|
||||
{
|
||||
None,
|
||||
Pan,
|
||||
MoveIsland,
|
||||
RotateIsland, // right-drag: rotation tracks the mouse, ends when the button is released
|
||||
RotateIslandModal, // 'R': rotation tracks the mouse until a click confirms or Esc cancels
|
||||
ScaleIslandModal, // 'S': likewise, distance from the centre drives the scale
|
||||
MoveVertex, // Vertex mode: drag one unwrapped vertex
|
||||
MoveEdge, // Edge mode: drag both endpoints of one boundary edge
|
||||
};
|
||||
Gesture m_gesture = Gesture::None;
|
||||
|
||||
SelectMode m_select_mode = SelectMode::Island;
|
||||
// The sub-element being edited in Vertex/Edge mode (unwrapped-vertex indices), or -1/{-1,-1}. This
|
||||
// is the *primary* (last-picked) element of the multi-selection below.
|
||||
int m_active_vertex = -1;
|
||||
std::pair<int, int> m_active_edge{ -1, -1 };
|
||||
// Multi-selection for Vertex/Edge modes, mirroring the island selection: plain click replaces, Shift
|
||||
// adds, Ctrl toggles, and a drag moves the whole set together. m_active_vertex/m_active_edge stay the
|
||||
// primary. Kept as small vectors (tiny, and order doesn't matter here).
|
||||
std::vector<int> m_sel_vertices;
|
||||
std::vector<std::pair<int, int>> m_sel_edges;
|
||||
bool is_vertex_selected(int v) const
|
||||
{
|
||||
return std::find(m_sel_vertices.begin(), m_sel_vertices.end(), v) != m_sel_vertices.end();
|
||||
}
|
||||
bool is_edge_selected(const std::pair<int, int> &e) const
|
||||
{
|
||||
return std::find(m_sel_edges.begin(), m_sel_edges.end(), e) != m_sel_edges.end();
|
||||
}
|
||||
// Unique unwrapped-vertex endpoints of every selected edge (an endpoint shared by two selected edges
|
||||
// is returned once, so a drag doesn't move it twice).
|
||||
std::vector<int> selected_edge_endpoints() const;
|
||||
// Last known mouse position over the canvas, and whether the pointer is currently inside it. Used to
|
||||
// draw the +/- add/remove sign next to the cursor in Vertex/Edge mode.
|
||||
wxPoint m_cursor_px{ 0, 0 };
|
||||
bool m_cursor_inside = false;
|
||||
// Set once a Vertex/Edge drag actually moves, so a bare click (select without drag) doesn't commit a
|
||||
// no-op edit and take an undo snapshot for nothing.
|
||||
bool m_vertex_edit_moved = false;
|
||||
// Lazily-built small filled square, drawn at an edited/hovered vertex as a handle.
|
||||
GLModel m_vertex_marker_glmodel;
|
||||
// Rebuilt each frame at the cursor while a +/- add-remove hint is shown (Vertex/Edge mode).
|
||||
GLModel m_cursor_sign_glmodel;
|
||||
int m_selected_island = -1;
|
||||
// The full multi-selection; m_selected_island is its primary (last-clicked) member. Kept as a small
|
||||
// vector rather than a set because it is tiny and iteration order (primary last) is convenient.
|
||||
std::vector<int> m_selection;
|
||||
bool is_selected(int island) const
|
||||
{
|
||||
return std::find(m_selection.begin(), m_selection.end(), island) != m_selection.end();
|
||||
}
|
||||
wxPoint m_drag_last_px;
|
||||
Vec2f m_gesture_last_uv = Vec2f::Zero();
|
||||
float m_gesture_last_angle = 0.f;
|
||||
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
|
||||
// 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.
|
||||
float m_rot_raw_deg = 0.f;
|
||||
float m_rot_applied_deg = 0.f;
|
||||
// The island's absolute on-screen rotation when the gesture began (decoded from its transform), so
|
||||
// Shift can snap to *global* 15-degree marks (0/15/30...) rather than 15 degrees relative to
|
||||
// wherever the island happened to start (#10). Also drives the angle read-out and the dial.
|
||||
float m_rot_base_deg = 0.f;
|
||||
float m_rot_display_deg = 0.f; // current absolute angle, for the status line and dial needle
|
||||
float m_modal_scale_accum = 1.f; // so Esc can undo exactly what the modal scale applied, as a factor
|
||||
|
||||
// A protractor drawn around the island while it rotates: a ring, a tick every 15 degrees, and a
|
||||
// needle at the current angle, so the rotation is legible (#11). Rebuilt each frame during a
|
||||
// rotation gesture (cheap: a few hundred short lines) and left empty otherwise.
|
||||
GLModel m_dial_glmodel;
|
||||
void rebuild_rotation_dial();
|
||||
// The island's current absolute rotation in degrees, decoded from its transform's first column.
|
||||
float island_rotation_deg(int island) const;
|
||||
|
||||
IslandEditFn m_on_island_edit;
|
||||
UVVertexEditFn m_on_vertex_edit;
|
||||
CommandFn m_on_command;
|
||||
StatusFn m_on_status;
|
||||
};
|
||||
|
||||
// Hosts a UVEditorCanvas together with a small icon toolbar (frame, snap, average scale, cut,
|
||||
// project-from-view) and a Blender-style status line along the bottom that names the current gesture
|
||||
// and the shortcuts in play (#18). This is what actually goes into Plater's "uv_editor" AUI pane;
|
||||
// the gizmo still talks to the inner canvas, reached via canvas().
|
||||
class UVEditorPanel : public wxPanel
|
||||
{
|
||||
public:
|
||||
explicit UVEditorPanel(wxWindow *parent);
|
||||
UVEditorCanvas *canvas() { return m_canvas; }
|
||||
|
||||
private:
|
||||
void on_tool(wxCommandEvent &evt);
|
||||
|
||||
UVEditorCanvas *m_canvas = nullptr;
|
||||
wxToggleButton *m_snap_button = nullptr;
|
||||
wxStaticText *m_status = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Slic3r::GUI
|
||||
|
||||
#endif // slic3r_UVEditorCanvas_hpp_
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
|
||||
#include <libslic3r/Config.hpp>
|
||||
#include <libslic3r/PresetBundle.hpp>
|
||||
#include <libslic3r/PrintConfig.hpp>
|
||||
#include <slic3r/GUI/GUI.hpp>
|
||||
@@ -164,6 +165,20 @@ bool CapabilityConfigDocument::erase(const PluginCapabilityId& id)
|
||||
return erased;
|
||||
}
|
||||
|
||||
bool CapabilityConfigDocument::prune_unreferenced(const std::set<std::pair<PluginCapabilityType, std::string>>& referenced)
|
||||
{
|
||||
bool changed = false;
|
||||
for (auto it = m_entries.begin(); it != m_entries.end();) {
|
||||
if (referenced.count({it->first.type, it->first.name}) != 0) {
|
||||
++it;
|
||||
} else {
|
||||
it = m_entries.erase(it);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool CapabilityConfigDocument::empty() const
|
||||
{
|
||||
return m_entries.empty() && m_opaque_entries.empty();
|
||||
@@ -342,6 +357,50 @@ std::string serialize_plugin_overrides(const CapabilityConfigDocument& document)
|
||||
return document.empty() ? std::string() : document.serialize_entries().dump();
|
||||
}
|
||||
|
||||
bool prune_stale_plugin_overrides(DynamicConfig& config)
|
||||
{
|
||||
const auto* overrides_opt = dynamic_cast<const ConfigOptionString*>(config.option(PLUGIN_OVERRIDES_OPTION_KEY));
|
||||
if (overrides_opt == nullptr || overrides_opt->value.empty())
|
||||
return false;
|
||||
|
||||
CapabilityConfigDocument overrides;
|
||||
std::string error;
|
||||
if (!parse_plugin_overrides(overrides_opt->value, overrides, error)) {
|
||||
// Malformed text is not ours to fix up here: leave it untouched rather than risk
|
||||
// discarding data the user might still be able to recover.
|
||||
BOOST_LOG_TRIVIAL(error) << "prune_stale_plugin_overrides: " << error;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Capability names currently referenced by a plugin-backed option's value(s) — e.g.
|
||||
// slicing_pipeline_plugin's ConfigOptionStrings entries name SlicingPipeline capabilities
|
||||
// directly, the same raw values save_plugin_collection() resolves into the "plugins" manifest.
|
||||
std::set<std::pair<PluginCapabilityType, std::string>> referenced;
|
||||
const ConfigDef* def = config.def();
|
||||
for (const std::string& opt_key : config.keys()) {
|
||||
const ConfigOptionDef* opt_def = def != nullptr ? def->get(opt_key) : nullptr;
|
||||
if (opt_def == nullptr || !opt_def->is_plugin_backed())
|
||||
continue;
|
||||
|
||||
const ConfigOption* opt = config.option(opt_key);
|
||||
const PluginCapabilityType type = plugin_capability_type_from_string(opt_def->plugin_type);
|
||||
if (const auto* string_opt = dynamic_cast<const ConfigOptionString*>(opt)) {
|
||||
if (!string_opt->value.empty())
|
||||
referenced.emplace(type, string_opt->value);
|
||||
} else if (const auto* vector_opt = dynamic_cast<const ConfigOptionVectorBase*>(opt)) {
|
||||
for (const std::string& value : vector_opt->vserialize())
|
||||
if (!value.empty())
|
||||
referenced.emplace(type, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!overrides.prune_unreferenced(referenced))
|
||||
return false;
|
||||
|
||||
config.set_key_value(PLUGIN_OVERRIDES_OPTION_KEY, new ConfigOptionString(serialize_plugin_overrides(overrides)));
|
||||
return true;
|
||||
}
|
||||
|
||||
EffectiveCapabilityConfig PresetPluginConfigService::get_effective_config(const CapabilityConfigDocument& overrides,
|
||||
const PluginCapabilityId& id) const
|
||||
{
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#define PLUGIN_CONFIG_DIR "config.json"
|
||||
@@ -16,6 +18,7 @@
|
||||
namespace Slic3r {
|
||||
|
||||
class Preset;
|
||||
class DynamicConfig;
|
||||
struct CapabilityConfigEntry
|
||||
{
|
||||
PluginCapabilityId id;
|
||||
@@ -35,6 +38,9 @@ public:
|
||||
bool contains(const PluginCapabilityId& id) const;
|
||||
bool upsert(CapabilityConfigEntry entry);
|
||||
bool erase(const PluginCapabilityId& id);
|
||||
// Drops every entry whose (type, name) is not in `referenced`, e.g. capabilities a preset's
|
||||
// plugin-backed options no longer name. Returns true if anything was removed.
|
||||
bool prune_unreferenced(const std::set<std::pair<PluginCapabilityType, std::string>>& referenced);
|
||||
bool empty() const;
|
||||
nlohmann::json serialize_entries() const;
|
||||
nlohmann::json root_json() const;
|
||||
@@ -50,6 +56,14 @@ std::string plugin_overrides_of(const Preset& preset);
|
||||
bool parse_plugin_overrides(const std::string& raw, CapabilityConfigDocument& document, std::string& error);
|
||||
std::string serialize_plugin_overrides(const CapabilityConfigDocument& document);
|
||||
|
||||
// Drops plugin_config_overrides entries for capabilities no longer named by any plugin-backed
|
||||
// option's current value in `config` (e.g. slicing_pipeline_plugin cleared or switched to a
|
||||
// different capability), and writes the result back if anything changed. Called wherever a
|
||||
// plugin-backed option's value changes, so a saved preset never carries configuration for a
|
||||
// capability it no longer references. Returns true if `config` was modified, so a caller holding a
|
||||
// GUI field over PLUGIN_OVERRIDES_OPTION_KEY knows it must refresh that field's displayed value.
|
||||
bool prune_stale_plugin_overrides(DynamicConfig& config);
|
||||
|
||||
struct EffectiveCapabilityConfig
|
||||
{
|
||||
PluginCapabilityId id;
|
||||
|
||||
@@ -33,6 +33,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_optimizers.cpp
|
||||
# test_png_io.cpp
|
||||
test_indexed_triangle_set.cpp
|
||||
test_texture_displacement.cpp
|
||||
../libnest2d/printer_parts.cpp
|
||||
)
|
||||
|
||||
|
||||
235
tests/libslic3r/test_texture_displacement.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
#define NOMINMAX
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <fstream>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "libslic3r/TextureDisplacement.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/TriangleSelector.hpp"
|
||||
#include "libslic3r/PNGReadWrite.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using Catch::Matchers::WithinAbs;
|
||||
|
||||
// Encodes a flat (uniform-value) grayscale image through Slic3r's own PNG writer/reader round
|
||||
// trip, so decode_height_texture() (which only accepts true 8-bit grayscale PNG) is guaranteed a
|
||||
// compatible file, exactly like the GUI's "Add texture" import path does.
|
||||
static std::shared_ptr<std::vector<unsigned char>> make_flat_gray_png(uint8_t value, size_t w = 4, size_t h = 4)
|
||||
{
|
||||
std::vector<uint8_t> pixels(w * h, value);
|
||||
const boost::filesystem::path tmp_path = boost::filesystem::temp_directory_path()
|
||||
/ boost::filesystem::unique_path("texdisp_test_%%%%%%%%.png");
|
||||
REQUIRE(Slic3r::png::write_gray_to_file(tmp_path.string(), w, h, pixels));
|
||||
|
||||
std::vector<unsigned char> bytes;
|
||||
{
|
||||
std::ifstream ifs(tmp_path.string(), std::ios::binary);
|
||||
bytes.assign(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
|
||||
}
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::remove(tmp_path, ec);
|
||||
|
||||
REQUIRE_FALSE(bytes.empty());
|
||||
return std::make_shared<std::vector<unsigned char>>(std::move(bytes));
|
||||
}
|
||||
|
||||
TEST_CASE("TextureDisplacement: decode_height_texture round-trips an 8-bit grayscale PNG", "[TextureDisplacement]")
|
||||
{
|
||||
TextureDisplacementLayer layer;
|
||||
layer.image_data = make_flat_gray_png(128, 4, 4);
|
||||
|
||||
DecodedHeightTexture tex = decode_height_texture(layer);
|
||||
REQUIRE_FALSE(tex.empty());
|
||||
CHECK(tex.width == 4);
|
||||
CHECK(tex.height == 4);
|
||||
REQUIRE_THAT(tex.sample(Vec2f(0.5f, 0.5f)), WithinAbs(128.0 / 255.0, 1.0 / 255.0));
|
||||
}
|
||||
|
||||
TEST_CASE("TextureDisplacement: an empty layer list leaves the mesh unchanged", "[TextureDisplacement]")
|
||||
{
|
||||
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
|
||||
const std::vector<TextureDisplacementLayer> layers; // none
|
||||
TextureDisplacementFacetsData facets{}; // all empty
|
||||
|
||||
const indexed_triangle_set result = build_texture_displacement(cube, layers, facets);
|
||||
|
||||
REQUIRE(result.vertices.size() == cube.vertices.size());
|
||||
REQUIRE(result.indices.size() == cube.indices.size());
|
||||
for (size_t i = 0; i < cube.vertices.size(); ++i)
|
||||
for (int c = 0; c < 3; ++c)
|
||||
CHECK(result.vertices[i](c) == cube.vertices[i](c));
|
||||
}
|
||||
|
||||
TEST_CASE("TextureDisplacement: fully painting a mesh displaces every vertex along its own normal", "[TextureDisplacement]")
|
||||
{
|
||||
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
|
||||
const TriangleMesh cube_mesh(cube);
|
||||
|
||||
TriangleSelector selector(cube_mesh);
|
||||
for (int f = 0; f < int(cube.indices.size()); ++f)
|
||||
selector.set_facet(f, EnforcerBlockerType::ENFORCER);
|
||||
|
||||
TextureDisplacementFacetsData facets{};
|
||||
facets[0] = selector.serialize();
|
||||
|
||||
TextureDisplacementLayer layer;
|
||||
layer.slot = 0;
|
||||
layer.depth_mm = 2.0f;
|
||||
layer.tiling_scale = 5.0f;
|
||||
layer.image_data = make_flat_gray_png(255); // sample() == 1.0 everywhere -> full depth_mm displacement
|
||||
|
||||
const indexed_triangle_set result = build_texture_displacement(cube, {layer}, facets);
|
||||
|
||||
REQUIRE(result.vertices.size() == cube.vertices.size());
|
||||
for (size_t i = 0; i < cube.vertices.size(); ++i) {
|
||||
const float moved = (result.vertices[i] - cube.vertices[i]).norm();
|
||||
CHECK_THAT(moved, WithinAbs(layer.depth_mm, 1e-3f));
|
||||
}
|
||||
}
|
||||
|
||||
// Paints every facet of `mesh` into a serialized mask, the way "Select whole model" does.
|
||||
static TriangleSelector::TriangleSplittingData paint_whole_mesh(const indexed_triangle_set &mesh)
|
||||
{
|
||||
const TriangleMesh tm(mesh);
|
||||
TriangleSelector selector(tm);
|
||||
for (int f = 0; f < int(mesh.indices.size()); ++f)
|
||||
selector.set_facet(f, EnforcerBlockerType::ENFORCER);
|
||||
return selector.serialize();
|
||||
}
|
||||
|
||||
// Regression test for the bug this feature shipped with: with two layers painted over the same
|
||||
// area, the second one was silently dropped (its paint mask was remapped onto the mesh the first
|
||||
// layer had already displaced, which routinely produced an empty bitstream). Every layer is now
|
||||
// evaluated against the original mesh instead, so both must show up in the total.
|
||||
TEST_CASE("TextureDisplacement: a second layer over the same area is applied too", "[TextureDisplacement]")
|
||||
{
|
||||
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
|
||||
|
||||
TextureDisplacementFacetsData facets{};
|
||||
facets[0] = paint_whole_mesh(cube);
|
||||
facets[1] = facets[0]; // both layers cover the whole cube
|
||||
|
||||
TextureDisplacementLayer base;
|
||||
base.slot = 0;
|
||||
base.depth_mm = 1.0f;
|
||||
base.tiling_scale = 5.0f;
|
||||
base.image_data = make_flat_gray_png(255); // height 1.0 everywhere
|
||||
|
||||
TextureDisplacementLayer second = base;
|
||||
second.slot = 1;
|
||||
second.depth_mm = 0.5f;
|
||||
second.blend_mode = TextureBlendMode::Add;
|
||||
|
||||
const indexed_triangle_set result = build_texture_displacement(cube, {base, second}, facets);
|
||||
|
||||
// Topology is preserved exactly, so vertices can be compared 1:1 with the input.
|
||||
REQUIRE(result.vertices.size() == cube.vertices.size());
|
||||
REQUIRE(result.indices.size() == cube.indices.size());
|
||||
for (size_t i = 0; i < cube.vertices.size(); ++i)
|
||||
CHECK_THAT((result.vertices[i] - cube.vertices[i]).norm(), WithinAbs(1.5f, 1e-3f)); // 1.0 + 0.5, not just 1.0
|
||||
}
|
||||
|
||||
TEST_CASE("TextureDisplacement: blend modes combine a layer with the ones below it", "[TextureDisplacement]")
|
||||
{
|
||||
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
|
||||
|
||||
TextureDisplacementFacetsData facets{};
|
||||
facets[0] = paint_whole_mesh(cube);
|
||||
facets[1] = facets[0];
|
||||
|
||||
TextureDisplacementLayer base;
|
||||
base.slot = 0;
|
||||
base.depth_mm = 2.0f;
|
||||
base.tiling_scale = 5.0f;
|
||||
base.image_data = make_flat_gray_png(255); // -> contributes exactly +2.0 mm
|
||||
|
||||
TextureDisplacementLayer second = base;
|
||||
second.slot = 1;
|
||||
second.depth_mm = 0.5f; // -> its own value is 0.5 mm
|
||||
|
||||
// Expected total displacement for each mode, given base = 2.0 mm and second = 0.5 mm. Multiply
|
||||
// and Divide treat the layer's value as a factor relative to 1 mm (see TextureBlendMode).
|
||||
const auto expected = GENERATE(table<TextureBlendMode, float>({
|
||||
{ TextureBlendMode::Add, 2.5f }, // 2.0 + 0.5
|
||||
{ TextureBlendMode::Subtract, 1.5f }, // 2.0 - 0.5
|
||||
{ TextureBlendMode::Multiply, 1.0f }, // 2.0 * 0.5
|
||||
{ TextureBlendMode::Divide, 4.0f }, // 2.0 / 0.5
|
||||
}));
|
||||
second.blend_mode = std::get<0>(expected);
|
||||
|
||||
const indexed_triangle_set result = build_texture_displacement(cube, {base, second}, facets);
|
||||
|
||||
REQUIRE(result.vertices.size() == cube.vertices.size());
|
||||
for (size_t i = 0; i < cube.vertices.size(); ++i)
|
||||
CHECK_THAT((result.vertices[i] - cube.vertices[i]).norm(), WithinAbs(std::get<1>(expected), 1e-3f));
|
||||
}
|
||||
|
||||
TEST_CASE("TextureDisplacement: the lowest layer ignores its blend mode", "[TextureDisplacement]")
|
||||
{
|
||||
// Multiply against the implicit zero base would annihilate the only layer present; the first
|
||||
// layer to reach a vertex always starts the total off additively instead.
|
||||
const indexed_triangle_set cube = its_make_cube(10., 10., 10.);
|
||||
|
||||
TextureDisplacementFacetsData facets{};
|
||||
facets[0] = paint_whole_mesh(cube);
|
||||
|
||||
TextureDisplacementLayer layer;
|
||||
layer.slot = 0;
|
||||
layer.depth_mm = 2.0f;
|
||||
layer.tiling_scale = 5.0f;
|
||||
layer.blend_mode = TextureBlendMode::Multiply;
|
||||
layer.image_data = make_flat_gray_png(255);
|
||||
|
||||
const indexed_triangle_set result = build_texture_displacement(cube, {layer}, facets);
|
||||
|
||||
for (size_t i = 0; i < cube.vertices.size(); ++i)
|
||||
CHECK_THAT((result.vertices[i] - cube.vertices[i]).norm(), WithinAbs(2.0f, 1e-3f));
|
||||
}
|
||||
|
||||
TEST_CASE("TextureDisplacement: boundary vertices shared with unpainted triangles are pinned", "[TextureDisplacement]")
|
||||
{
|
||||
// A small triangle fan around a central vertex O, with 4 outer points A/B/C/D forming 4
|
||||
// triangles T0..T3 in the XY plane. Only T0, T1, T2 are painted, T3 is left unpainted:
|
||||
// O: touches all 4 triangles (incl. unpainted T3) -> boundary, must NOT move
|
||||
// A: touches T0 (painted) and T3 (unpainted) -> boundary, must NOT move
|
||||
// D: touches T2 (painted) and T3 (unpainted) -> boundary, must NOT move
|
||||
// B: touches only T0 and T1 (both painted) -> interior, SHOULD move
|
||||
// C: touches only T1 and T2 (both painted) -> interior, SHOULD move
|
||||
indexed_triangle_set fan;
|
||||
fan.vertices = { {0.f, 0.f, 0.f}, {1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {-1.f, 0.f, 0.f}, {0.f, -1.f, 0.f} };
|
||||
fan.indices = { {0, 1, 2}, {0, 2, 3}, {0, 3, 4}, {0, 4, 1} };
|
||||
|
||||
const TriangleMesh fan_mesh(fan);
|
||||
TriangleSelector selector(fan_mesh);
|
||||
selector.set_facet(0, EnforcerBlockerType::ENFORCER);
|
||||
selector.set_facet(1, EnforcerBlockerType::ENFORCER);
|
||||
selector.set_facet(2, EnforcerBlockerType::ENFORCER);
|
||||
// facet 3 (T3) is left at its default EnforcerBlockerType::NONE.
|
||||
|
||||
TextureDisplacementFacetsData facets{};
|
||||
facets[0] = selector.serialize();
|
||||
|
||||
TextureDisplacementLayer layer;
|
||||
layer.slot = 0;
|
||||
layer.depth_mm = 1.0f;
|
||||
layer.tiling_scale = 5.0f;
|
||||
layer.image_data = make_flat_gray_png(255);
|
||||
|
||||
const indexed_triangle_set result = build_texture_displacement(fan, {layer}, facets);
|
||||
|
||||
// Find each named vertex's post-bake position by matching the original (pinned vertices keep
|
||||
// their exact original position; moved ones won't match any original position anymore).
|
||||
auto still_at_original_position = [&](const Vec3f &original) {
|
||||
for (const Vec3f &v : result.vertices)
|
||||
if ((v - original).norm() < 1e-6f)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
CHECK(still_at_original_position(fan.vertices[0])); // O: boundary
|
||||
CHECK(still_at_original_position(fan.vertices[1])); // A: boundary
|
||||
CHECK(still_at_original_position(fan.vertices[4])); // D: boundary
|
||||
CHECK_FALSE(still_at_original_position(fan.vertices[2])); // B: interior, must have moved
|
||||
CHECK_FALSE(still_at_original_position(fan.vertices[3])); // C: interior, must have moved
|
||||
}
|
||||