Compare commits
21 Commits
feat/print
...
feature/te
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2db770b672 | ||
|
|
2a46197322 | ||
|
|
15fd3fc97c | ||
|
|
00f778e863 | ||
|
|
dc1e8b6dc3 | ||
|
|
753054974b | ||
|
|
eacc236ccb | ||
|
|
345369e005 | ||
|
|
18c5d31fb2 | ||
|
|
61d2d4355a | ||
|
|
ab023f3f6d | ||
|
|
b073aa4d23 | ||
|
|
8247514ae2 | ||
|
|
00f55639d3 | ||
|
|
68d754d946 | ||
|
|
dc5a48bdfd | ||
|
|
05083bb6ab | ||
|
|
a393b21642 | ||
|
|
a7c8dcc58d | ||
|
|
3514249197 | ||
|
|
7f2598d0d6 |
35
AGENTS.md
@@ -17,7 +17,7 @@ cmake --build . --config %build_type% --target ALL_BUILD -- -m
|
||||
|
||||
## Testing
|
||||
|
||||
Catch2 framework. Tests in `tests/`; see [tests/AGENTS.md](tests/AGENTS.md) for where a new test belongs and the conventions to follow.
|
||||
Catch2 framework. Tests in `tests/` directory.
|
||||
|
||||
```bash
|
||||
cd build && ctest --output-on-failure # all tests
|
||||
@@ -30,7 +30,6 @@ ctest --test-dir ./tests/fff_print
|
||||
- C++17, selective C++20. PascalCase classes, snake_case functions/variables
|
||||
- `#pragma once` for headers. Smart pointers and RAII preferred
|
||||
- Parallelization via TBB — be mindful of shared state
|
||||
- Always use `SetSizerAndFit(sizer)` instead of `SetSizer(sizer)` on top level window. Unless `SetSizer` must be called before the full layout is built, call `sizer->SetSizeHints(window)` afterwards in this case.
|
||||
|
||||
## Key Entry Points
|
||||
|
||||
@@ -60,31 +59,7 @@ ctest --test-dir ./tests/fff_print
|
||||
|
||||
## Localization & translations
|
||||
|
||||
Catalogs live in `localization/i18n/<lang>/OrcaSlicer_<lang>.po`; the template is `OrcaSlicer.pot`.
|
||||
See the [Localization guide](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_guide.md) for the human-facing version of these principles.
|
||||
|
||||
### Terminology
|
||||
|
||||
- Use the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_glossary.md) as the source of truth for recurring terms, so the same English term is always rendered the same way within a language, and terms that must stay in English (brand/product names, acronyms, materials, file formats, G-code tokens, macros/variables/identifiers) are not translated.
|
||||
- If a term's established translation changes, update both the affected `.po` files and the glossary (`localization_glossary.tsv`, then regenerate) so they stay in sync.
|
||||
- Translate the *meaning*, not the words. Check what the string actually controls before translating it — English reuses one word for different things. `Flow ratio` (multiplier), `Flow Rate` (throughput) and `Flow Dynamics` (pressure compensation) are three different terms; `extruder` may mean the toolhead, the feeder motor, or the nozzle depending on the string.
|
||||
- Reuse one template per recurring message shape (`Failed to connect to …`, `Are you sure you want to …?`), even where the English wording varies.
|
||||
|
||||
### Editing rules
|
||||
|
||||
- Only edit `msgstr` — **never** change `msgid`, and never "fix" wrong English in the translation alone. Report the source string instead.
|
||||
- Preserve exactly: placeholders (`%s`, `%d`, `%1%`, `%zu`, `%%`), every `\n` (count *and* position, including leading/trailing), leading/trailing spaces, HTML tags, `℃`, and the file's encoding and line endings.
|
||||
- **Never reorder positional arguments** in a `c-format` string. If the msgid is `%d` then `%s`, that order must hold — swapping them breaks at runtime.
|
||||
- `msgctxt` separates homonyms — always read it. `Back`/`Camera View` is the rear view of the 3D navigator, while `Back`/`Navigation` is the go-back button; `Top` exists in the *Alignment*, *Layers* and *Camera View* senses.
|
||||
- When a string needs disambiguating, add context in the source (`_L_CONTEXT`/`_u8L_CONTEXT`), don't work around it in the translation.
|
||||
- A literal `%` inside a string xgettext flagged `possible-c-format` will fail `msgfmt`. Fix it with a `// xgettext:no-c-format, no-boost-format` comment above the string in the source — do not mangle the translation or use `%%` in text that is never passed through printf.
|
||||
- Plural entries: read `nplurals` from the catalog's `Plural-Forms` header (it is **not** always 2 — ja/ko/zh/th/vi use 1, ru/cs/pl/lt use 3, uk uses 4). Each form must be genuinely inflected for its quantity; repeating one sentence across all forms is a bug in Slavic/Baltic languages, though it is correct for Turkish and Hungarian.
|
||||
- An entry whose `msgstr` equals its `msgid` is untranslated even though it is not empty; a plural entry with any empty form is likewise incomplete.
|
||||
- Mark machine-produced translations with an `# AI Translated` translator comment. Don't add it to a human translation you didn't actually rewrite.
|
||||
- Don't reflow or re-wrap unrelated entries — keep the diff limited to the strings you changed.
|
||||
|
||||
### Verifying
|
||||
|
||||
- `scripts/run_gettext.bat --full` (Windows) regenerates the template, merges every catalog and compiles the `.mo` files. It must exit 0.
|
||||
- Or check a single catalog with `msgfmt --check-format -o <out>.mo localization/i18n/<lang>/OrcaSlicer_<lang>.po`.
|
||||
- Fuzzy entries are not shown to users. If you correct one, clear its `fuzzy` flag, otherwise the fix never ships.
|
||||
- Translation catalogs live in `localization/i18n/<lang>/OrcaSlicer_<lang>.po`.
|
||||
- When creating or reviewing translations, use the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_glossary.md) as the source of truth for recurring terms, so the same English term is always rendered the same way within a language and terms that must stay in English (brand/product names, acronyms, file formats, G-code, macros/variables) are not translated.
|
||||
- If a term's established translation changes, update both the affected `.po` files and the glossary so they stay in sync.
|
||||
- Only edit `msgstr` (never `msgid`); keep placeholders (`%s`, `%1%`, `\n`), context (`msgctxt`), and file encoding/line endings intact.
|
||||
|
||||
@@ -80,12 +80,8 @@ endif()
|
||||
|
||||
if (DEFINED BBL_RELEASE_TO_PUBLIC)
|
||||
add_compile_definitions("BBL_RELEASE_TO_PUBLIC=${BBL_RELEASE_TO_PUBLIC}")
|
||||
if (BBL_RELEASE_TO_PUBLIC)
|
||||
add_compile_definitions(WXINSPECTOR_DISABLE)
|
||||
endif ()
|
||||
else ()
|
||||
add_compile_definitions("BBL_RELEASE_TO_PUBLIC=$<CONFIG:Release>")
|
||||
add_compile_definitions("$<$<CONFIG:Release>:WXINSPECTOR_DISABLE>")
|
||||
endif ()
|
||||
|
||||
find_package(Git)
|
||||
@@ -839,7 +835,7 @@ endif ()
|
||||
set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/i18n")
|
||||
set(BBL_L18N_DIR "${CMAKE_CURRENT_SOURCE_DIR}/localization/i18n")
|
||||
add_custom_target(gettext_make_pot
|
||||
COMMAND xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_CONTEXT:1,2c --keyword=_u8L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost
|
||||
COMMAND xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_CTX:1,2c --keyword=_CTX_utf8:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --no-location --debug --boost
|
||||
-f "${BBL_L18N_DIR}/list.txt"
|
||||
-o "${BBL_L18N_DIR}/OrcaSlicer.pot"
|
||||
COMMAND hintsToPot ${SLIC3R_RESOURCES_DIR} ${BBL_L18N_DIR}
|
||||
@@ -857,6 +853,7 @@ foreach(po_file ${BBL_L10N_PO_FILES})
|
||||
add_custom_command(
|
||||
TARGET gettext_merge_po_with_pot PRE_BUILD
|
||||
COMMAND msgmerge -N -o ${po_file} ${po_file} "${BBL_L18N_DIR}/OrcaSlicer.pot"
|
||||
DEPENDS ${po_file}
|
||||
)
|
||||
endforeach()
|
||||
add_custom_target(gettext_po_to_mo
|
||||
@@ -872,6 +869,7 @@ foreach(po_file ${BBL_L10N_PO_FILES})
|
||||
TARGET gettext_po_to_mo PRE_BUILD
|
||||
COMMAND msgfmt ARGS --check-format -o ${mo_file} ${po_file}
|
||||
#COMMAND msgfmt ARGS --check-compatibility -o ${mo_file} ${po_file}
|
||||
DEPENDS ${po_file}
|
||||
)
|
||||
endforeach()
|
||||
|
||||
|
||||
36
README.md
@@ -10,6 +10,7 @@
|
||||
|
||||
OrcaSlicer: an open source Next-Gen Slicing Software for Precision 3D Prints.
|
||||
Optimize your prints with ultra-fast slicing, intelligent support generation, and seamless printer compatibility—engineered for perfection.
|
||||
<h3>
|
||||
|
||||
# Official links and community
|
||||
|
||||
@@ -89,22 +90,11 @@ Visit our GitHub Releases page for the latest stable version of OrcaSlicer, reco
|
||||
🌙 **[Download the Latest Nightly Build](https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds)**
|
||||
Explore the latest developments in OrcaSlicer with our nightly builds. Feedback on these versions is highly appreciated.
|
||||
|
||||
### Belt Printer Builds
|
||||
|
||||
The [nightly release](https://github.com/OrcaSlicer/OrcaSlicer/releases/tag/nightly-builds) ships **two parallel builds**: the standard build and a belt-printer build. Both are attached to the same release — tell them apart by the filename suffix:
|
||||
|
||||
- **Standard** — no suffix (e.g. `OrcaSlicer_Windows_Installer_x64_nightly.exe`)
|
||||
- **Belt** — `_belt` suffix (e.g. `OrcaSlicer_Windows_Installer_x64_nightly_belt.exe`)
|
||||
|
||||
The `_belt` builds add **experimental support for belt / conveyor (infinite-Z) printers**, where the model is sliced against a tilted belt surface instead of a flat horizontal bed. They include ready-to-use belt printer profiles, the full belt slicing pipeline (mesh rotation and G-code transforms), belt-aware support generation, and a tilted-bed preview.
|
||||
|
||||
> ⚠️ Belt printer support is under active development and is **not yet merged into `main`** — it currently ships only in these parallel `_belt` builds, produced from the [`belt-printer`](https://github.com/OrcaSlicer/OrcaSlicer/tree/belt-printer) branch. See tracking PR [#14394](https://github.com/OrcaSlicer/OrcaSlicer/pull/14394) and the original documentation in [#12998](https://github.com/OrcaSlicer/OrcaSlicer/pull/12998).
|
||||
|
||||
# How to install
|
||||
|
||||
## Windows
|
||||
|
||||
Download the **Windows Installer exe** for your preferred version from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases). Both `x64` and `arm64` installers are published — pick the one matching your CPU.
|
||||
Download the **Windows Installer exe** for your preferred version from the [releases page](https://github.com/OrcaSlicer/OrcaSlicer/releases).
|
||||
|
||||
- *For convenience there is also a portable build available.*
|
||||
<details>
|
||||
@@ -119,11 +109,7 @@ Download the **Windows Installer exe** for your preferred version from the [rele
|
||||
- This file may already be available on your computer if you've installed visual studio. Check the following location: `%VCINSTALLDIR%Redist\MSVC\v142`
|
||||
</details>
|
||||
|
||||
### Microsoft Store
|
||||
|
||||
Install from the [Microsoft Store](https://apps.microsoft.com/detail/9mv6gl23xm59) when you prefer a Store-signed package (helps on Windows 11 Smart App Control).
|
||||
|
||||
### Windows Package Manager
|
||||
Windows Package Manager
|
||||
|
||||
```shell
|
||||
winget install --id=SoftFever.OrcaSlicer -e
|
||||
@@ -131,7 +117,7 @@ winget install --id=SoftFever.OrcaSlicer -e
|
||||
|
||||
## Mac
|
||||
|
||||
1. Download the universal DMG, which runs on both Apple Silicon and Intel Macs.
|
||||
1. Download the DMG for your computer: `arm64` version for Apple Silicon and `x86_64` for Intel CPU.
|
||||
2. Drag OrcaSlicer.app to Application folder.
|
||||
3. *If you want to run a build from a PR, you also need to follow the instructions below:*
|
||||
|
||||
@@ -156,14 +142,6 @@ winget install --id=SoftFever.OrcaSlicer -e
|
||||

|
||||
</details>
|
||||
|
||||
### Homebrew Cask
|
||||
|
||||
```shell
|
||||
brew install --cask orcaslicer
|
||||
```
|
||||
|
||||
The [Homebrew cask](https://formulae.brew.sh/cask/orcaslicer) installs the official macOS DMG from [GitHub Releases](https://github.com/OrcaSlicer/OrcaSlicer/releases).
|
||||
|
||||
## Linux
|
||||
|
||||
### Flathub (Recommended)
|
||||
@@ -212,8 +190,8 @@ resolution: 0.1
|
||||
|
||||
# Supports
|
||||
|
||||
**OrcaSlicer** is an open-source project, and we're deeply grateful to all our sponsors and backers.
|
||||
Their generous support helps fund filaments and other essential 3D printing materials for the project.
|
||||
**OrcaSlicer** is an open-source project and I'm deeply grateful to all my sponsors and backers.
|
||||
Their generous support enables me to purchase filaments and other essential 3D printing materials for the project.
|
||||
Thank you! :)
|
||||
|
||||
## Sponsors
|
||||
@@ -237,7 +215,7 @@ Thank you! :)
|
||||
|
||||
**Ko-fi supporters** ☕: [Backers list](https://github.com/user-attachments/files/16147016/Supporters_638561417699952499.csv)
|
||||
|
||||
## Support the project
|
||||
## Support me
|
||||
|
||||
<a href="https://github.com/sponsors/SoftFever"><img src="https://img.shields.io/badge/GitHub%20Sponsors-30363D?style=flat&logo=GitHub-Sponsors&logoColor=EA4AAA" height="50"></a>
|
||||
<a href="https://ko-fi.com/G2G5IP3CP"><img src="https://img.shields.io/badge/Support_me_on_Ko--fi-FF5E5B?style=flat&logo=ko-fi&logoColor=white" height="50"></a>
|
||||
|
||||
551
TEXTURE_DISPLACEMENT.md
Normal file
@@ -0,0 +1,551 @@
|
||||
# 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, options)` 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. Where the paint does *not* cover every triangle around a vertex, the normal
|
||||
is recomputed from the painted triangles alone (the union over all layers, so it stays one direction
|
||||
per vertex). On the rim of a fully painted top face the whole-mesh normal is the 45 degrees bisector
|
||||
it shares with the side wall, and displacing along that flares the rim outwards instead of raising
|
||||
it. Interior vertices are unaffected - all their triangles are painted, so the two coincide. Paint
|
||||
coverage per original triangle comes straight off `TriangleSplittingData::triangles_to_split`.
|
||||
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 border vertex. Whether it moves is
|
||||
`TextureDisplacementOptions::displace_border`, and it **does by default**. The original design
|
||||
pinned it, justified as stopping the patch tearing away from the surrounding surface - which stopped
|
||||
being true the moment the bake became topology-preserving. There is no seam to tear: a border vertex
|
||||
is *one* vertex shared by both regions, and moving it simply tilts the unpainted triangles that use
|
||||
it. What pinning actually does is clamp the outermost ring of relief to zero, so on a fully painted
|
||||
face the pattern collapses into a ring of steep ramps right at the edge - the reported "it doesn't
|
||||
extrude at the border" artifact. Pinning is kept as an option for the case where the relief must not
|
||||
spill past the paint at all. Either way the border still drives the `edge_smoothing` falloff.
|
||||
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.
|
||||
7. Optionally (`TextureDisplacementOptions::smooth_*`) relax the result - see Post-process smoothing.
|
||||
|
||||
### Post-process smoothing
|
||||
|
||||
`smooth_mesh_vertices(mesh, movable, strength, iterations)` - plain Laplacian relaxation, run after all
|
||||
layers have been folded in, restricted to the vertices flagged in `movable`. Each pass moves a movable
|
||||
vertex a `strength` fraction of the way to the average of its one-ring, read from a **snapshot** of the
|
||||
previous pass so the result does not depend on vertex order (a Gauss-Seidel sweep would smooth several
|
||||
times as hard at the end of the array as at the start). Neighbours come from a CSR-style adjacency built
|
||||
once per call.
|
||||
|
||||
Its job is to round off the hard steps a bitmap height map leaves behind, which is a different knob from
|
||||
`TextureDisplacementLayer::smoothing` - that blurs the *height map* before it is ever sampled, this
|
||||
relaxes the *geometry* afterwards. Topology-preserving, like the bake.
|
||||
|
||||
Two ways in, sharing one set of settings on the volume:
|
||||
- The **"Smooth result"** checkbox + "Smoothing (%)" / "Passes" ride along with Preview and Bake.
|
||||
`movable` is exactly the set of vertices the displacement moved, so the untouched part of the model
|
||||
keeps its exact geometry and the ring just outside the displaced set anchors the relaxation (the
|
||||
relief cannot creep outward).
|
||||
- **"Smooth baked mesh now"** (`GLGizmoTextureDisplacement::smooth_model()`) applies the same settings to
|
||||
the volume's *committed* geometry, for relief that is already baked in. `movable` there is the painted
|
||||
triangles' vertices. Because smoothing never touches the triangle list, this is the one geometry
|
||||
operation in the gizmo that keeps **every** paint channel verbatim - it saves and restores the eight
|
||||
texture-displacement masks around `set_mesh()` rather than remapping or dropping them.
|
||||
|
||||
**"Ignore outer ring"** (`smooth_skip_border`, **on by default**) drops the patch's own outermost ring of
|
||||
vertices from `movable`. That ring's neighbours *outside* the paint never move, so relaxing it drags the
|
||||
rim of the relief back down toward the flat surface and the pattern comes out half-melted exactly where
|
||||
it meets the edge - crisp everywhere else, which is what makes it look like a bug rather than a setting.
|
||||
Held out, the border keeps the full depth the texture asked for and only the interior relaxes. Turning it
|
||||
off softens the outer edge deliberately (a blunter version of the per-layer edge-smoothing falloff).
|
||||
Note this is the *smoothing* rim, a separate question from whether that rim is displaced at all
|
||||
(`displace_border`, step 4 above) - the two are independent and both default to "keep the border sharp".
|
||||
|
||||
### 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 — two modes
|
||||
|
||||
**Uniform (`subdivide_mesh_uniform()`)** — whole-mesh, 1-to-4 split. Recursive edge-midpoint split 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). Whole-mesh so it never leaves a
|
||||
T-junction, at the cost of densifying everywhere. Wired as a "Subdivide steps" slider (**0–5**, 0 =
|
||||
no subdivision), Apply snaps back to 0. Drops texture-displacement paint (no remap) via the standard
|
||||
`save_painting()`/`set_mesh()`/`restore_painting()` dance; the other four channels are remapped.
|
||||
|
||||
**Adaptive (`subdivide_mesh_adaptive()`)** — refine **only the painted area**, by **Rivara longest-edge
|
||||
bisection**. This is the algorithm that was "scoped out" originally for fear of the T-junction/crack
|
||||
problem; it is safe because it is *conformal by construction*. Only **terminal** edges are ever bisected
|
||||
- an edge that is the longest edge of *every* triangle sharing it - which splits both those triangles
|
||||
along one shared midpoint at once, so a hanging node is never created. The edge to split for a triangle
|
||||
that wants refining is found by **longest-edge propagation (LEPP)**: walk to the longest edge of
|
||||
ever-longer-edged neighbours until a terminal one is reached, and bisect that. Edge length strictly
|
||||
increases along the path (ties broken by mesh-vertex key, which both sides of an edge compute
|
||||
identically), so the walk cannot cycle, and Rivara's result is that repeating it refines the original
|
||||
triangle in a bounded number of bisections. The transition triangles it pulls in just outside the
|
||||
painted patch are the graded band that makes the size change conformal.
|
||||
|
||||
The win: a small decal on a big model no longer quadruples the *whole* model's triangle count.
|
||||
|
||||
**Run to completion, worst-first, against a triangle budget.** The refinement loop is not a fixed number
|
||||
of sweeps: it holds every triangle that is over its criteria in a max-heap keyed by *how many times over*
|
||||
it is, pops the worst, walks its LEPP, bisects, and re-scores. Edge adjacency (`nb[e]`, the triangle
|
||||
across each edge) is built **once** and maintained incrementally through each bisection, so the cost
|
||||
scales with the refined region rather than with the whole model. `max_triangles` is the only bound;
|
||||
stopping on it leaves a perfectly valid, still-conformal mesh that spent its budget on the largest errors.
|
||||
|
||||
This shape replaced a first version that ran a fixed 12 sweeps, each rebuilding a whole-mesh edge map and
|
||||
bisecting one terminal edge per active triangle. Two failure modes came out of that, and they are worth
|
||||
remembering because they look like separate bugs and are not: the sweeps were consumed grading the
|
||||
*coarse surroundings* (whose edges are the longest, so they win every terminal-edge contest), which both
|
||||
**stopped refinement of the painted patch far short** of the requested detail and left the band outside
|
||||
it looking wildly over-refined relative to the patch itself.
|
||||
|
||||
**It carries the paint forward**, which is what makes it usable (uniform/remesh both drop paint). Because
|
||||
the refinement is *driven by* the paint, the remap is trivial: `subdivide_mesh_adaptive()` fills an
|
||||
`out_source[new_tri] = input_tri` map (children inherit their parent), and the gizmo rebuilds each
|
||||
layer's mask on the new mesh - a new triangle is painted iff its source was fully painted in that
|
||||
layer. `collect_paint_region()` derives both:
|
||||
- the union refine-region: **exactly** the original triangles the brush touched, read straight off
|
||||
`TriangleSplittingData::triangles_to_split` (`serialize()` records an entry per original triangle that
|
||||
is either split - i.e. partially painted, the patch boundary - or carries a non-default state). No
|
||||
dilation. An earlier version marked every triangle sharing a *vertex* with the patch, which drags in a
|
||||
whole fan of huge unpainted neighbours and then refines *those* down to the resolution floor, since the
|
||||
height field the detail test samples is not restricted to the painted area. The conformal closure
|
||||
already grades the size change outward on its own; it does not need help.
|
||||
- the per-layer fully-painted-triangle sets (a `get_facets_strict(ENFORCER)` sub-triangle with all three
|
||||
*original* vertex indices == a whole, fully-painted original triangle; a partial stroke's sub-triangles
|
||||
always carry a split vertex).
|
||||
|
||||
The other four channels still ride the normal `restore_painting()` remap. Covered by a conformality unit
|
||||
test (`every_edge_used_twice` on a partially-refined cube - an exact crack detector for a closed mesh),
|
||||
plus tests that the target edge length is actually *reached* and that the budget caps the result without
|
||||
opening a crack.
|
||||
|
||||
Both share the gizmo's Preview/Apply/Done flow; the **"Only painted area (adaptive)"** checkbox picks
|
||||
the mode, and the adaptive preview follows the paint live (`rebuild_preview()` refreshes the wireframe
|
||||
while the subdivide preview is open in adaptive mode). The panel shows the previewed triangle count.
|
||||
|
||||
**Feature-adaptive (follow texture detail).** A sub-mode of adaptive (the **"Follow texture detail"**
|
||||
checkbox) that puts triangles where the *displaced surface actually bends*, not evenly. The insight:
|
||||
a flat region or a linear **ramp** needs no extra vertices (linear interpolation is exact for a ramp);
|
||||
what needs them is **curvature** - the *second* derivative, not the gradient. So the extra predicate is a
|
||||
**chord-error** test: sample the combined displacement at the triangle's three edge midpoints *and its
|
||||
centroid* (sampling the interior is what catches a bump sitting inside a triangle, the blind spot of an
|
||||
edge-only test) and take the largest departure from the flat triangle's barycentric interpolation. Refine
|
||||
while that exceeds `chord_tolerance_mm` ("Detail (mm)"). Zero chord error on a ramp ⇒ untouched; high on
|
||||
a bump/ridge/noise ⇒ refined until captured. Same conformal machinery, so still crack-free. The
|
||||
per-triangle error is cached and recomputed only for the children of a split.
|
||||
|
||||
Four knobs bracket it, and all four matter:
|
||||
- **"Max edge (mm)"** (`target_edge_length_mm`) is a **baseline that applies in feature mode too**.
|
||||
Without it the chord test aliases: a big triangle over a fine pattern can sample four points that all
|
||||
land at similar heights, report no error, and stall before refinement ever starts. The baseline
|
||||
guarantees a sampling density fine enough for the curvature test to see the texture at all.
|
||||
- **"Detail (mm)"** is the chord tolerance above.
|
||||
- **"Min edge (mm)"** is a hard floor under both, and is what guarantees termination across a sharp
|
||||
texture *step*, where the error never falls however fine the mesh gets.
|
||||
- **"Added triangles (k)"** is the budget, passed as `max_triangles` (the model's own triangle count plus
|
||||
the slider, so the control still means something on an already-dense model).
|
||||
|
||||
The height field is `make_combined_displacement_sampler()` - it mirrors `build_texture_displacement()`'s
|
||||
per-layer setup (decode, patch centroid, cylinder axis, blend order, "lowest layer folds additively")
|
||||
but evaluated per point. Two deliberate simplifications, both erring toward *more* detail (safe -
|
||||
over-refinement is never a crack): every sampleable layer is sampled at every point (no per-point paint
|
||||
test), and edge-smoothing falloff is ignored. Note the first one is *why* the refine region must not be
|
||||
dilated - outside the paint the sampler still reports full relief. **LSCM layers are skipped** (no
|
||||
per-point UV); a purely LSCM stack yields a null sampler and the code falls back to the length baseline
|
||||
alone. Per-vertex heights are sampled lazily, so a small patch on a huge model never pays for the rest of
|
||||
it. Covered by unit tests: a Gaussian bump refines densely at its center and leaves flat corners coarse,
|
||||
a linear ramp produces *zero* extra triangles (the case a gradient criterion would over-refine), and a
|
||||
flat field still honours the max-edge baseline.
|
||||
|
||||
### 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, uniform subdivide, and remesh all drop it via `reset_extra_facets()`). The
|
||||
other four paint channels (supported/seam/mmu/fuzzy) do get remapped in these cases. The lone
|
||||
exception is **adaptive subdivide**, which carries texture-displacement paint forward itself via its
|
||||
source map (see the Subdivision section) - a targeted remap that only works because the operation is
|
||||
driven by the paint.
|
||||
- **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** — and is now the **default** view when the gizmo
|
||||
opens (`m_use_bump_preview = true`): it is the instant, no-CPU-meshing preview, so it is the better
|
||||
first impression while painting. The exact true-displacement view is one click away in the View row.
|
||||
- **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 subdivision controls are for
|
||||
(uniform, adaptive, or feature-adaptive; see the Subdivision section). 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 (uniform + adaptive longest-edge bisection), post-process smoothing
|
||||
(`smooth_mesh_vertices()`), and `TextureDisplacementOptions` (the whole-stack settings). 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`, `texture_displacement_options`, 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.
|
||||
@@ -128,10 +128,6 @@ cmake_minimum_required(VERSION 3.13)
|
||||
if(POLICY CMP0074)
|
||||
cmake_policy(SET CMP0074 NEW)
|
||||
endif()
|
||||
# Re-set after cmake_minimum_required above cleared it; use BoostConfig, not the removed FindBoost.
|
||||
if(POLICY CMP0167)
|
||||
cmake_policy(SET CMP0167 NEW)
|
||||
endif()
|
||||
|
||||
if(OpenVDB_FIND_QUIETLY)
|
||||
set (_quiet "QUIET")
|
||||
|
||||
2
deps/CMakeLists.txt
vendored
@@ -423,7 +423,6 @@ endif ()
|
||||
include(OCCT/OCCT.cmake)
|
||||
include(OpenCV/OpenCV.cmake)
|
||||
include(python3/python3.cmake)
|
||||
include(wxInspector/wxInspector.cmake)
|
||||
|
||||
set(_dep_list
|
||||
dep_Boost
|
||||
@@ -447,7 +446,6 @@ set(_dep_list
|
||||
${EXPAT_PKG}
|
||||
dep_libnoise
|
||||
dep_python3
|
||||
dep_wxInspector
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
|
||||
8
deps/OpenSSL/OpenSSL.cmake
vendored
@@ -52,14 +52,6 @@ ExternalProject_Add(dep_OpenSSL
|
||||
CONFIGURE_COMMAND ${_conf_cmd} ${_cross_arch}
|
||||
"--openssldir=${DESTDIR}"
|
||||
"--prefix=${DESTDIR}"
|
||||
# OpenSSL's linux-x86_64 target sets multilib=64, so it installs to
|
||||
# <prefix>/lib64 while every other dep uses <prefix>/lib. CPython's
|
||||
# --with-openssl only ever emits -L<dir>/lib, so it misses the bundled
|
||||
# static libs and silently links the system OpenSSL instead -- which,
|
||||
# against 1.1.1w headers, leaves _ssl.so with an undefined
|
||||
# SSL_get_peer_certificate (removed in OpenSSL 3.x). Pin libdir so the
|
||||
# prefix stays single-layout.
|
||||
"--libdir=lib"
|
||||
${_cross_comp_prefix_line}
|
||||
no-shared
|
||||
no-asm
|
||||
|
||||
13
deps/wxInspector/wxInspector.cmake
vendored
@@ -1,13 +0,0 @@
|
||||
orcaslicer_add_cmake_project(
|
||||
wxInspector
|
||||
URL https://github.com/Noisyfox/wxInspector/archive/refs/tags/v1.0.0.zip
|
||||
URL_HASH SHA256=0ba163956f2d468b19a91b96c5aba66ee9610843ea41dda628ea44cdafde7db7
|
||||
DEPENDS ${WXWIDGETS_PKG}
|
||||
CMAKE_ARGS
|
||||
-DCMAKE_CXX_FLAGS="-DwxDEBUG_LEVEL=0"
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
add_debug_dep(dep_wxInspector)
|
||||
endif ()
|
||||
1
deps/wxWidgets/wxWidgets.cmake
vendored
@@ -26,7 +26,6 @@ orcaslicer_add_cmake_project(
|
||||
GIT_REPOSITORY "https://github.com/SoftFever/Orca-deps-wxWidgets"
|
||||
GIT_TAG v3.3.2
|
||||
GIT_SHALLOW ON
|
||||
GIT_SUBMODULES 3rdparty/catch 3rdparty/pcre 3rdparty/libwebp
|
||||
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} ${JPEG_PKG}
|
||||
CMAKE_ARGS
|
||||
-DwxBUILD_PRECOMP=ON
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
# Printer agents
|
||||
|
||||
Printer agents let OrcaSlicer communicate with printers through a
|
||||
standardized protocol. They translate between a printer's
|
||||
native API and the application interfaces that the app already
|
||||
uses.
|
||||
|
||||
This documentation explains the compatibility boundary, runtime ownership,
|
||||
connection and status flow, command and feature behavior, built-in and plugin
|
||||
agent implementations, and the testing evidence required for compatibility
|
||||
claims.
|
||||
|
||||
## What printer agents do
|
||||
|
||||
A printer agent has two jobs:
|
||||
|
||||
1. Accept the app's existing commands and translate the ones its
|
||||
printer supports.
|
||||
2. Convert native printer status into correctly-shaped state that
|
||||
`MachineObject` understands.
|
||||
|
||||
Currently, agents work at a compatibility boundary, i.e., making other vendors compatible with Bambu-shaped code, not a vendor-neutral one.
|
||||
Some Bambu concepts remain part of the payload and command vocabulary.
|
||||
End goal is to make the whole command and payload interfaces vendor-neutral.
|
||||
|
||||
## Vocabulary
|
||||
|
||||
Every chapter reuses these terms. The "Is not" column is the part that
|
||||
causes confusion when it is left implicit.
|
||||
|
||||
| Term | Is | Selected by | Is not |
|
||||
| --- | --- | --- | --- |
|
||||
| Agent ID | Which printer agent implementation to use | `printer_agent` on the printer preset; empty is the legacy `bbl`-or-`orca` sentinel | Which printer |
|
||||
| Printer agent | The live `IPrinterAgent` instance for that ID, created and cached once per ID by `NetworkAgentFactory` | Factory lookup on the agent ID | A connection, and not one object per printer |
|
||||
| Device ID | One printer inside that implementation | Bind with Access Code for the Moonraker family, where the entered address becomes the ID; Bambu uses its own discovery identity | Which protocol |
|
||||
| `MachineObject` | The Device tab's view of one selected printer | `DeviceManager::selected_machine`, which stores only an ID | Proof that a printer is reachable |
|
||||
| Freshness | `is_connected()`, a test over the last-update time | Any reset of the update time, including one no status has followed | Proof that status arrived |
|
||||
| Status-confirmed readiness | A push-status message has actually been parsed | The first real status message | The same thing as a successful `connect_printer()` |
|
||||
|
||||
Earlier drafts used "transport" for the printer agent instance. That term
|
||||
is retired: the code selects an implementation, not a wire protocol.
|
||||
|
||||
## How to use this guide
|
||||
|
||||
- [Architecture](architecture.md) describes objects, ownership, lifetimes,
|
||||
error handling, the feature gate, and compatibility contracts for printer
|
||||
agents.
|
||||
- [Connection and status](connection-and-status.md) describes how presets,
|
||||
machines, access codes, status messages, and commands fit together at
|
||||
runtime. Unlike Architecture, it follows the sequence of selecting an
|
||||
agent, connecting, receiving status, and sending commands.
|
||||
- [Printing](printing.md), [filament synchronization](filament.md), and
|
||||
[camera support](camera.md) are separate chapters because they contain
|
||||
per-feature detail rather than because they are universally special:
|
||||
Printing has its send, preflight, recovery, and start contracts; Filament
|
||||
covers acquisition, mapping selection, and print-time delivery; Camera
|
||||
covers the distinct Bambu, Moonraker, and Snapmaker ownership models.
|
||||
- [Built-in agents](agents.md) describes the Moonraker family and the Qidi,
|
||||
Snapmaker variants.
|
||||
- [Python plugin agents](plugin-agents.md) describes the plugin bridge and
|
||||
lifecycle.
|
||||
- [Testing and troubleshooting](testing.md) explains automated checks, manual
|
||||
hardware work, known defects, and the evidence required for compatibility
|
||||
claims.
|
||||
- The [capability matrix](reference/capability-matrix.md) is the compact
|
||||
feature reference. The [manual checklist](reference/manual-checklist.html)
|
||||
is for a live-printer verification pass.
|
||||
|
||||
Treat source code as authoritative when it differs from this guide. In
|
||||
particular, preserve the compatibility rules called out in each chapter:
|
||||
they protect stored presets, existing profiles, and the Device tab's
|
||||
assumptions.
|
||||
@@ -1,205 +0,0 @@
|
||||
# Built-in printer agents
|
||||
|
||||
*Owns the per-vendor behavior of the built-in agents: what each subclass
|
||||
changes and what it inherits unchanged. Defers the interface every agent
|
||||
implements to [Architecture](architecture.md) and
|
||||
[Python plugin agents](plugin-agents.md).*
|
||||
|
||||
This chapter covers the built-in Moonraker family: the general
|
||||
`MoonrakerPrinterAgent` and the Qidi and Snapmaker variants. Creality
|
||||
(`CrealityPrintAgent`) is also a member of this family and inherits the base
|
||||
behavior, but has no section here; see the capability matrix for its
|
||||
per-feature coverage. They share the same connection and status machinery.
|
||||
Change the base class only when the behavior is valid for all of them.
|
||||
|
||||
Each subclass is thin. `MoonrakerPrinterAgent` holds the HTTP connection,
|
||||
the WebSocket status subscription, the REST command worker, thumbnail
|
||||
lookup, the chamber-light heuristic, and the upload-and-start path.
|
||||
`QidiPrinterAgent` overrides filament discovery and adds multi-color box
|
||||
mapping; `SnapmakerPrinterAgent` overrides filament discovery and camera
|
||||
setup; `CrealityPrintAgent` overrides filament refresh. Each derives from
|
||||
`MoonrakerPrinterAgent` and is `final`, which is why the guard rule below
|
||||
must be type-based.
|
||||
|
||||
## Moonraker family
|
||||
|
||||
### Connection and commands
|
||||
|
||||
Moonraker-family agents use plain HTTP for the LAN connection. The connection
|
||||
path deliberately ignores a TLS request because the supported printer stacks
|
||||
serve Moonraker or a reverse proxy over HTTP. Restoring the caller's TLS
|
||||
default can send a connection to an unavailable HTTPS endpoint.
|
||||
|
||||
Status is a Moonraker WebSocket subscription. Commands use REST. Command
|
||||
translation happens immediately, but the resulting HTTP work runs through one
|
||||
agent-owned FIFO worker. Each queued operation captures the current base URL
|
||||
and API key before it is queued, so a later printer switch does not redirect
|
||||
an earlier command. Keep this separation: network work on the UI path makes
|
||||
controls feel stalled, and allowing a queued command to reread connection
|
||||
state can send it to the wrong printer.
|
||||
|
||||
Pause, resume, and cancel use the dedicated Moonraker print endpoints. Do not
|
||||
replace them with queued `PAUSE`, `RESUME`, or `CANCEL_PRINT` G-code. The
|
||||
endpoints interrupt the print directly; a G-code command can wait behind the
|
||||
active print or macro.
|
||||
|
||||
The request router accepts the Bambu-shaped JSON used by the native device
|
||||
tab. Supply object-shaped namespaces such as `print` and `system`. A malformed
|
||||
but parseable payload with a scalar where the router expects an object can
|
||||
still fail before the unsupported-command fallback. The supported generic fan
|
||||
status is the standard `fan` object, which represents the part fan only.
|
||||
Ordinary part-fan control also works through the legacy `gcode_line` path,
|
||||
which sends `M106` while `is_enable_np` is false. Auxiliary and chamber fans
|
||||
are neither reported nor controlled.
|
||||
|
||||
Do not add `cfg`, `fun`, `aux`, and `stat` to the Moonraker status payload just
|
||||
to make it look more complete. Together those fields set `is_enable_np` and
|
||||
make the UI choose its structured fan and extruder commands instead. The
|
||||
Moonraker agent does not translate those commands, so working controls become
|
||||
unsupported no-ops. This is a UI-routing constraint, not a reason to expose
|
||||
structured fan support.
|
||||
|
||||
### Status shown by the native device tab
|
||||
|
||||
The agent translates Moonraker status into the Bambu-shaped status payload the
|
||||
existing Device tab understands. Some fields are necessarily synthetic:
|
||||
|
||||
- The virtual SD-card readiness bit and a basic software-version row make the
|
||||
native UI consider the printer ready. Each pull payload also ensures
|
||||
`m_push_count` and `m_full_msg_count` are at least one and refreshes
|
||||
`last_push_time`. Together with the normal-storage state and a placeholder
|
||||
module version, this satisfies the native `is_info_ready()` and printing
|
||||
gates. These are compatibility scaffolding, not reports of physical storage
|
||||
or OTA support.
|
||||
- Current and total layers are emitted only when `print_stats.info` contains
|
||||
numeric values. Moonraker may send `null`, and many profiles do not emit the
|
||||
`SET_PRINT_STATS_INFO` data needed to populate them. Do not turn that gap
|
||||
into a JSON conversion exception.
|
||||
- Remaining time is estimated from elapsed print time and virtual-SD progress.
|
||||
It is omitted below two percent progress because the early estimate is too
|
||||
unstable. Do not derive an ETA by subtracting Moonraker duration counters:
|
||||
both are elapsed counters, so their difference is overhead, not remaining
|
||||
time.
|
||||
- Temperature readings are available, but nozzle diameter and nozzle type are
|
||||
not supplied in the status payload. The UI can therefore show an unknown
|
||||
nozzle. Do not make print submission depend on those missing fields.
|
||||
|
||||
### Camera thumbnails and lights
|
||||
|
||||
For a running job, the agent asks Moonraker for thumbnails and chooses the
|
||||
widest usable entry, rather than assuming the first entry is useful. It accepts
|
||||
both thumbnail path spellings used by Moonraker versions, encodes each path
|
||||
segment, and caches the result by filename. A failed transient lookup is tried
|
||||
again only a bounded number of times; a clean response without a thumbnail is
|
||||
cached as a negative result. The response shape handling is source-derived,
|
||||
not hardware-verified.
|
||||
|
||||
> **Do not perform this HTTP lookup while holding `payload_mutex`.** The
|
||||
> WebSocket thread builds the status payload under that mutex and the UI
|
||||
> path also needs it, so a thumbnail timeout taken under the lock would
|
||||
> stall status delivery or the UI. The lookup still blocks the WebSocket
|
||||
> thread briefly, so move it to a worker if that becomes measurable.
|
||||
|
||||
Chamber-light control searches Moonraker objects for names that look like a
|
||||
light or a standalone LED, then writes the first matching pin, LED, or macro.
|
||||
The filter exists to avoid treating unrelated objects, such as a beeper, as a
|
||||
lamp. It remains a heuristic. The incoming `led_node` is validated, but only
|
||||
`chamber_light` is acted on; `chamber_light2` is deliberately ignored. A
|
||||
printer with more than one lamp therefore has no reliable node-to-object map.
|
||||
|
||||
### Common maintenance limits
|
||||
|
||||
The same cache is reused for a selected agent ID, not per physical printer.
|
||||
Qidi and Snapmaker inherit this behavior. A stateful feature added
|
||||
to the base class must be reset carefully when a preset switches hosts.
|
||||
|
||||
> **Keep guards for this family type-based** - check whether an agent
|
||||
> derives from `MoonrakerPrinterAgent` rather than comparing its ID to
|
||||
> `moonraker`. An ID-based guard silently excludes Qidi, Snapmaker, and
|
||||
> Creality, even though they share the base behavior.
|
||||
|
||||
The family has no generic implementation for firmware-specific AMS write
|
||||
commands. Keep unsupported commands unsupported until the printer-side macro
|
||||
or API is known. Reporting success for an untranslated command makes the
|
||||
native UI claim that an action happened when it did not.
|
||||
|
||||
## Qidi
|
||||
|
||||
Qidi inherits the Moonraker connection, status, camera, and local-print path.
|
||||
Its differences are Qidi filament discovery and the pre-print multi-color-box
|
||||
mapping.
|
||||
|
||||
### Filament discovery
|
||||
|
||||
Discovery first reads the printer's device information to infer a Qidi series
|
||||
identifier, then falls back to the configured Orca model if needed. Series
|
||||
inference intentionally recognizes only a narrow set of known names. An
|
||||
unknown model still produces usable generic filament data, but not a
|
||||
series-specific preset identifier.
|
||||
|
||||
The agent reads a Qidi filament dictionary and the `save_variables` plus
|
||||
slot-runout data. Failing to fetch the dictionary is non-fatal: slot discovery
|
||||
continues with fallback material and colour values. Failing to fetch or parse
|
||||
slot data is fatal to the refresh. A missing runout value means the agent
|
||||
cannot prove filament is loaded, so it reports that slot as empty. This is an
|
||||
ambiguity in the firmware data, not proof that the box is empty.
|
||||
|
||||
`save_variables.variables` must be an object. Qidi firmware can return `null`
|
||||
there, and generic JSON value access can throw on a present null. The parser
|
||||
rejects that shape without throwing. Preserve the null-slot tests whenever the
|
||||
response parser changes.
|
||||
|
||||
### Multi-color mapping before a print
|
||||
|
||||
Before every Qidi print-start wrapper, the agent writes `enable_box` and, for
|
||||
mapped tools, persistent `value_t<tool>` variables. These writes survive the
|
||||
job. Invalid mapping JSON is checked only after `enable_box` has been written.
|
||||
When the mapping is enabled, that failure can therefore leave `enable_box=1`.
|
||||
There is no rollback for this or for a later per-tool write failure, so a
|
||||
partial mapping can remain on the printer. An empty mapping is accepted when
|
||||
the box is enabled. Single-colour jobs disable the box but leave old per-tool
|
||||
assignments in place.
|
||||
|
||||
`enable_box` currently follows `task_use_ams`. That meaning has not been
|
||||
verified against all Qidi firmware: if firmware treats it as "a box exists"
|
||||
rather than "use the box for this job", this gate is wrong and needs hardware
|
||||
evidence before it changes.
|
||||
|
||||
Only `start_local_print` reaches Moonraker's real upload-and-start path. The
|
||||
other Qidi mapping wrappers currently return success stubs after applying the
|
||||
mapping. Do not describe those wrappers as confirmed print paths.
|
||||
|
||||
Because the agent cache is keyed by agent type, a Qidi mapping can also become
|
||||
stale when switching between Qidi printers. This is a generic Moonraker-family
|
||||
state risk, made more consequential by Qidi's persistent firmware variables.
|
||||
The configured `printer_type` can also be stale, so treat it as a fallback
|
||||
hint rather than device truth.
|
||||
|
||||
## Snapmaker
|
||||
|
||||
Snapmaker uses the Moonraker base and overrides filament discovery and camera
|
||||
setup. Neither path is hardware-verified in the current documentation set.
|
||||
|
||||
Filament information comes from parallel arrays in `print_task_config`.
|
||||
`filament_exist` defines the number of slots; shorter type, subtype, colour,
|
||||
vendor, or NFC arrays use safe fallback values. The agent first tries a visible
|
||||
vendor, type, and colour preset, then a visible type match, and finally a
|
||||
generic identifier when no preset bundle is available. An empty reported type
|
||||
is changed to `PLA`, so an unknown occupied spool can look like confirmed PLA.
|
||||
An unrecognized type can also reach the visible-preset fallback and be paired
|
||||
with an unrelated visible preset. Treat the resulting preset as a suggestion,
|
||||
not printer-ground truth.
|
||||
|
||||
Snapmaker U1 camera support starts the printer's monitor RPC, then serves the
|
||||
still JPEG through a small local HTML page that reloads it after each load or
|
||||
error. The wrapper is required because a direct still-image URL looks frozen.
|
||||
The RPC is sent from a detached thread so the UI timer does not block on socket
|
||||
I/O. That thread captures `this` directly, so agent destruction can race with
|
||||
the camera command. Do not widen this pattern. Route future asynchronous work
|
||||
through owned lifetime-managed work where possible.
|
||||
|
||||
## Source locations
|
||||
|
||||
- `src/slic3r/Utils/MoonrakerPrinterAgent.cpp`
|
||||
- `src/slic3r/Utils/QidiPrinterAgent.cpp`
|
||||
- `src/slic3r/Utils/SnapmakerPrinterAgent.cpp`
|
||||
@@ -1,158 +0,0 @@
|
||||
# Architecture
|
||||
|
||||
*Owns the structural rules: what the objects are, who owns them, what an
|
||||
agent must implement, and which behaviors are compatibility contracts.
|
||||
Defers the runtime sequence - selecting, connecting, receiving status,
|
||||
sending commands - to [Connection and status](connection-and-status.md).*
|
||||
|
||||
## The compatibility boundary
|
||||
|
||||
The Device tab was built around Bambu-style commands and status. A printer
|
||||
agent is the translation boundary between that existing contract and a
|
||||
vendor's native protocol:
|
||||
|
||||
```text
|
||||
Device tab <-> MachineObject <-> NetworkAgent <-> IPrinterAgent
|
||||
<-> vendor protocol
|
||||
```
|
||||
|
||||
Note: end goal is to move beyond this and achieve a truly vendor-neutral translation layer.
|
||||
|
||||
The GUI builds commands and reads `MachineObject` state. An agent owns the
|
||||
vendor request, response, connection, and status translation. Keep vendor
|
||||
details on the agent side of this boundary.
|
||||
|
||||
Status translation is deliberately Bambu-shaped. Agents deliver payloads
|
||||
through the callbacks used by the existing Bambu path, and
|
||||
`MachineObject::parse_json()` interprets them. This preserves the Device
|
||||
tab's established behavior, but it is not a vendor-neutral protocol.
|
||||
|
||||
Important (again): end goal is to move beyond this and achieve a truly vendor-neutral translation layer.
|
||||
|
||||
## Runtime objects and ownership
|
||||
|
||||
`NetworkAgent` is the facade used by the application. It holds one live
|
||||
`IPrinterAgent` pointer, which is initially null and may return to null
|
||||
when a selected ID is unavailable. Callers must handle the null case. An
|
||||
absent agent is an inert state, not permission to fall back to another
|
||||
printer agent. A fallback would connect to a different implementation than
|
||||
the one selected by the preset, and could therefore send commands or status
|
||||
work to the wrong printer.
|
||||
|
||||
`NetworkAgentFactory` registers built-in and plugin implementations by
|
||||
agent ID. It creates and caches one implementation for each ID. The ID
|
||||
selects a printer agent implementation, while a `MachineObject` selects one
|
||||
printer by device ID. The resulting cardinality is one active agent to many
|
||||
machines.
|
||||
|
||||
For example, suppose two Moonraker printers are on the LAN at
|
||||
`192.168.1.20` and `192.168.1.21`. In the Device tab machine-select popup, the
|
||||
user chooses **Bind with Access Code**; `PinCodePanel::on_mouse_left_up` opens
|
||||
`InputIpAddressDialog`, and each entered address is bound as a separate
|
||||
printer. Both presets store the same agent ID, `moonraker`, so
|
||||
`NetworkAgentFactory::create_printer_agent_by_id` returns the same cached
|
||||
`IPrinterAgent` pointer for both presets. Each printer nevertheless has its
|
||||
own `MachineObject` and device ID. For the Moonraker family,
|
||||
`MoonrakerPrinterAgent::bind_detect` calls `init_device_info` with the entered
|
||||
address as both the device ID and address, so the two device IDs are the two
|
||||
addresses.
|
||||
|
||||
That is what one active agent to many machines means. Per-printer state must
|
||||
be keyed by device ID rather than held only on the agent instance, because one
|
||||
agent object is shared by both printers. State stored only on that object
|
||||
would be shared between two different machines and could route status or
|
||||
commands to the wrong one. The same sharing explains why
|
||||
`GUI_App::switch_printer_agent` compares device IDs even when the agent pointer
|
||||
is unchanged: otherwise its unchanged-agent early return would skip
|
||||
reselection when the user switches between these presets, leaving status and
|
||||
filament work aimed at the previous printer.
|
||||
|
||||
> **Do not make an agent instance per printer just to hold device state.**
|
||||
> Keep per-printer state keyed by device ID, because one agent object is
|
||||
> shared by every printer of that type - state held on the instance would
|
||||
> route status or commands to the wrong `MachineObject`.
|
||||
|
||||
> **Do not fall back to another printer agent when the live one is null.**
|
||||
> An absent agent is an inert state. A fallback would connect to a
|
||||
> different implementation than the preset selected.
|
||||
|
||||
## Commands and unsupported work
|
||||
|
||||
An agent must either translate a Device-tab command or return an explicit
|
||||
error. `ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED` means no translation exists.
|
||||
`ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE` means a translation exists but this
|
||||
printer cannot use it. `MachineObject::publish_json()` turns either result
|
||||
into the user-visible unsupported-command response.
|
||||
|
||||
Every Device-tab command must leave by one of these four exits. The fifth
|
||||
path is the one to watch for in review:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
CMD["Device-tab command JSON"] --> PUSH{"pushing namespace?"}
|
||||
PUSH -- yes --> OK1["Accept - the status stream already satisfies it"]
|
||||
PUSH -- no --> TRANS{"Translation exists for this agent?"}
|
||||
TRANS -- no --> E1["Return ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED"]
|
||||
TRANS -- yes --> CAP{"This printer can use it?"}
|
||||
CAP -- no --> E2["Return ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE"]
|
||||
CAP -- yes --> OK2["Translate and send to the printer"]
|
||||
TRANS -. FORBIDDEN .-> BAD["Return success without translating"]
|
||||
E1 --> PUB["MachineObject::publish_json turns both errors<br/>into the unsupported-command response"]
|
||||
E2 --> PUB
|
||||
BAD --> LIE["UI reports an action that never happened"]
|
||||
```
|
||||
|
||||
> **Do not return success for an unhandled command.** That makes an
|
||||
> unsupported button look as though it worked and hides missing coverage
|
||||
> from both users and maintainers.
|
||||
|
||||
The `pushing` command namespace is the exception. Its request means
|
||||
"send status"; an active status stream already satisfies it. The Device
|
||||
Manager sends these requests repeatedly as a keepalive, so rejecting them
|
||||
would surface a warning repeatedly even though no action is missing.
|
||||
|
||||
## Feature gate
|
||||
|
||||
`use_printer_agents` enables printer-agent routing. With the gate off,
|
||||
agent code must have no observable effect. Released profiles can already
|
||||
contain `printer_agent` values, so activating an agent while the gate is
|
||||
off would change existing user behavior merely by loading a profile.
|
||||
|
||||
Keep the gate at the routing call sites. Do not fold it into general Bambu
|
||||
vendor checks: slicing and hardware decisions such as AMS, lidar, bed
|
||||
types, and G-code flavor still describe printer capabilities, not the
|
||||
selected printer agent.
|
||||
|
||||
## Backward compatibility
|
||||
|
||||
`printer_agent` remains a `coString`, even when the ID is currently
|
||||
unregistered. A preset may refer to an optional plugin that is not
|
||||
installed. The unknown string must load, remain unchanged, and round-trip
|
||||
without making the preset dirty. The UI may show it as missing, but must
|
||||
not rewrite it to a fallback ID.
|
||||
|
||||
Keep the feature gate's off-path behavior unchanged, preserve stored agent
|
||||
IDs, and treat Bambu-shaped payloads as a compatibility contract.
|
||||
|
||||
The reason these three are grouped is that each looks like a local code
|
||||
change and is not. Switching which printer agent handles a preset edits no
|
||||
profile and no project file, so it reads in review as contained to the
|
||||
agent layer. But a user's stored presets and `.3mf` projects already carry
|
||||
`printer_agent` values and were saved against the Bambu-shaped payload. So
|
||||
a change that is local in the code is not local in effect: it reaches
|
||||
every previously saved file. That is why the gate must be inert when off,
|
||||
an unknown ID must survive untouched, and the payload shape is treated as
|
||||
a contract rather than an implementation detail.
|
||||
|
||||
## Threading rule
|
||||
|
||||
Agents may perform network work on their own threads, but all mutations of
|
||||
Device Manager maps and `MachineObject` UI state must run on the UI thread.
|
||||
Queue incoming status before it reaches `parse_json()` or any operation
|
||||
that adds, removes, selects, or changes a device. This prevents races
|
||||
between background network callbacks and UI reads. For example, when a status
|
||||
callback arrives on an agent's network thread, queue it to the UI thread
|
||||
before it reaches `MachineObject::parse_json()` or changes a device map or
|
||||
selection. The Device tab reads those same structures on the UI thread, so
|
||||
parsing or adding, removing, or selecting a device from the network thread
|
||||
could race with that read.
|
||||
@@ -1,96 +0,0 @@
|
||||
# Camera support
|
||||
|
||||
*Owns the three camera ownership models and what each one renders through.
|
||||
Defers the Snapmaker filament path to [Built-in agents](agents.md), even
|
||||
though the same subclass owns both.*
|
||||
|
||||
Camera support has three ownership models. They share the Device panel,
|
||||
but not a common frame or stream interface.
|
||||
|
||||
Two render surfaces, never one:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
BU["Bambu URL provider<br/>LAN or cloud, not from IPrinterAgent"]
|
||||
MU["Moonraker webcam discovery<br/>/server/webcams/list stream URL"]
|
||||
SU["Snapmaker camera page<br/>local HTML that polls monitor.jpg"]
|
||||
|
||||
BV["wxMediaCtrl2<br/>native Bambu media pipeline"]
|
||||
WV["Device-panel wxWebView<br/>stream URL or local polling page"]
|
||||
|
||||
BU --> BV
|
||||
MU --> WV
|
||||
SU -- overrides normal discovery --> WV
|
||||
```
|
||||
|
||||
## Bambu
|
||||
|
||||
Bambu playback uses `wxMediaCtrl2` and the native Bambu media pipeline.
|
||||
The URL comes from the Bambu LAN or cloud path, not from
|
||||
`IPrinterAgent`. A printer agent should not attempt to force a Bambu URL
|
||||
through the Moonraker or WebView path.
|
||||
|
||||
## Moonraker live view
|
||||
|
||||
On connection, the Moonraker agent obtains the first enabled webcam URL
|
||||
from `/server/webcams/list`. An absolute HTTP URL is used directly. A
|
||||
relative URL is resolved against the printer's host web root, with the
|
||||
Moonraker API port removed. This is necessary because a relative webcam
|
||||
path may exist on the printer's web server but not on the API port.
|
||||
|
||||
The connection generation guards the result. A late request must not
|
||||
replace the URL after the user has selected a different printer. Failed
|
||||
discovery clears the URL, which prevents a prior camera from remaining
|
||||
visible on a printer with no camera.
|
||||
|
||||
The agent places the discovered URL in its status payload. The Device
|
||||
panel renders it in `wxWebView`. It reloads only when the URL changes and
|
||||
resets the camera-start timestamp at that point. Reloading every update
|
||||
would loop indefinitely for endpoints that redirect, so an unchanged URL
|
||||
is shown again without calling `LoadURL()`.
|
||||
|
||||
The trade-off is intentional: a WebView that loses an unchanged stream
|
||||
does not automatically reload. Camera controls beyond live viewing remain
|
||||
out of scope for Moonraker. Recording, timelapse, settings, and virtual
|
||||
camera are Bambu-oriented features and must not be presented as supported
|
||||
merely because live view works.
|
||||
|
||||
## Snapmaker polling view
|
||||
|
||||
Snapmaker overrides normal webcam discovery. It writes a per-printer local
|
||||
HTML page that polls the printer's `monitor.jpg` with a cache-busting URL.
|
||||
Each next request starts after the prior image loads or fails, preventing
|
||||
requests from piling up on a slow printer. A raw snapshot URL is not used,
|
||||
because it would display one frozen frame instead of a live-looking view.
|
||||
|
||||
The printer must be asked to start its camera capture task. While the
|
||||
camera view is visible, the Device panel requests this at first display
|
||||
and then attempts another request every 300 seconds. Other agents reject
|
||||
the command quietly, so the common timer does not create an error for
|
||||
Bambu or ordinary Moonraker.
|
||||
|
||||
The Snapmaker command is sent from a detached thread because the request
|
||||
can block on socket I/O and the printer responds over a different channel.
|
||||
This avoids blocking the UI but leaves a raw-`this` lifetime risk: the
|
||||
agent can be destroyed while the detached operation still refers to it.
|
||||
Do not extend this path without addressing that ownership boundary.
|
||||
|
||||
The wrapper is written below the application cache with a name derived from
|
||||
the printer IP. The source contains no cleanup path for those files, so they
|
||||
can accumulate as different printer IPs are used. This is source-derived and
|
||||
was not reproduced during this rewrite.
|
||||
|
||||
Source code proves 300-second renewal attempts only. The long-running
|
||||
behavior of the shipped polling and renewal cycle has not yet been tested.
|
||||
Do not claim that the attempt renews an active capture task or that it
|
||||
prevents camera expiry until hardware verification establishes both.
|
||||
|
||||
## Maintenance checklist
|
||||
|
||||
- Keep the three ownership models separate.
|
||||
- Preserve host-root resolution for relative Moonraker URLs.
|
||||
- Keep generation guards and stale-URL clearing on every discovery path.
|
||||
- Reload WebView content only after a URL change.
|
||||
- Reset the camera-start timestamp when the camera URL changes.
|
||||
- Treat Moonraker as live-view-only and Snapmaker lifecycle behavior as
|
||||
not yet verified beyond the observed renewal attempts.
|
||||
@@ -1,248 +0,0 @@
|
||||
# Connection and status
|
||||
|
||||
*Owns the runtime sequence in order: selecting an agent and machine,
|
||||
starting a connection, receiving status, sending commands. Defers the
|
||||
structural rules those steps must obey - ownership, no-fallback, unknown
|
||||
IDs, threading - to [Architecture](architecture.md), and cites them at the
|
||||
point where they bite.*
|
||||
|
||||
## The four runtime concepts
|
||||
|
||||
Keep these concepts separate when tracing a connection problem:
|
||||
|
||||
- A preset stores an agent ID and printer address.
|
||||
- `NetworkAgent` holds the active printer agent for that agent ID.
|
||||
- `MachineObject` represents the selected printer at that address.
|
||||
- Freshness and status-confirmed readiness are separate states.
|
||||
|
||||
An agent ID selects a printer agent implementation. A device ID selects one
|
||||
printer within that implementation.
|
||||
|
||||
A non-Bambu printer reaches the machine list through **Bind with Access
|
||||
Code**, the tile in the Device tab's machine-select popup. The user enters
|
||||
an address and an access code, `bind_detect()` probes the address before
|
||||
any connect, and `DeviceManager::insert_local_device()` creates the
|
||||
`MachineObject`. For the Moonraker family the address itself becomes the
|
||||
device ID: `MoonrakerPrinterAgent::bind_detect()` seeds `dev_name` and
|
||||
`dev_id` from the entered address, so an unreachable or unnamed printer
|
||||
still shows up as its IP rather than blank.
|
||||
|
||||
Binding is the only route for that family. `MoonrakerPrinterAgent::start_discovery()`
|
||||
deliberately announces nothing, because a partial discovery implementation
|
||||
would populate the machine list with stale hosts. Bambu is the exception:
|
||||
it has its own discovery identity and does not use the address as an ID.
|
||||
|
||||
`DeviceManager::selected_machine` is only a selected ID. It can name no
|
||||
resolvable object. `get_selected_machine()` answers whether an object is
|
||||
actually available. `set_selected_machine()` accepting an ID therefore
|
||||
does not prove the printer is connected. The selected ID can remain when its
|
||||
object is unavailable, so connection state must come from the object itself.
|
||||
|
||||
## Selecting the agent and machine
|
||||
|
||||
`GUI_App::switch_printer_agent()` reads the edited printer preset and
|
||||
resolves its stored agent ID through `NetworkAgentFactory`.
|
||||
|
||||
1. An empty stored ID is a legacy sentinel. It resolves to `bbl` for a
|
||||
Bambu vendor preset and to `orca` otherwise.
|
||||
2. If that effective ID is registered, the factory provides the matching
|
||||
printer agent implementation.
|
||||
3. Clear the live printer agent only when a nonempty stored ID is unregistered
|
||||
or the factory cannot construct the matching registered agent.
|
||||
4. When the active printer agent changes, clear the current selection, user
|
||||
selection, stale device discoveries, sidebar state, and AMS state before
|
||||
installing the replacement.
|
||||
5. Select the preset's address-derived machine for non-Bambu agents.
|
||||
|
||||
The lifetimes are easier to see than to read. Note that the agent pointer
|
||||
can be unchanged while the machine still must be re-selected - that is the
|
||||
trap in the same-agent path below:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as User
|
||||
participant P as Printer preset
|
||||
participant G as GUI_App::switch_printer_agent
|
||||
participant F as NetworkAgentFactory
|
||||
participant N as NetworkAgent
|
||||
participant M as MachineObject
|
||||
|
||||
U->>P: Edit or switch the printer preset
|
||||
P->>G: printer_agent value, possibly empty
|
||||
G->>G: resolve_printer_agent_id - empty becomes bbl or orca
|
||||
G->>F: get_printer_agent_info(effective ID)
|
||||
|
||||
alt ID not registered, and stored value was nonempty
|
||||
F-->>G: no info
|
||||
G->>N: clear the live printer agent
|
||||
Note over N: null is inert - never fall back to another agent
|
||||
else ID registered
|
||||
F->>F: create once per ID, then serve from cache
|
||||
F-->>G: the cached IPrinterAgent for this ID
|
||||
alt Agent pointer changed
|
||||
G->>N: clear selection, user selection, discoveries, sidebar, AMS
|
||||
G->>N: install the replacement agent
|
||||
else Same cached pointer returned
|
||||
Note over G,F: Two presets can share one agent ID
|
||||
end
|
||||
G->>M: compare device ID, re-select if the address differs
|
||||
end
|
||||
```
|
||||
|
||||
> **Do not use the first available machine as a fallback** (rule owned by
|
||||
> [Architecture](architecture.md), Runtime objects and ownership). It
|
||||
> connects to a printer the user did not choose, including one owned by a
|
||||
> different printer agent.
|
||||
|
||||
The same-agent path is important too. Two presets can use one agent type
|
||||
but point at different addresses, and the factory caches one agent per ID,
|
||||
so switching between them returns the same pointer and would otherwise
|
||||
skip reselection entirely. Re-select the machine whenever the preset's
|
||||
address changes, even when the factory returned the same active agent.
|
||||
Otherwise status and filament work can continue against the previous
|
||||
printer.
|
||||
|
||||
Note: this is a legacy coupling, not the primary workflow. It reads an
|
||||
address stored on the printer preset itself (`print_host` and
|
||||
`printhost_port`, named here only so the keys can be found in the code)
|
||||
and derives a device ID from it with `dev_id_from_address()`. Those keys
|
||||
predate printer agents and are edited through `PhysicalPrinterDialog`,
|
||||
which despite its name writes the printer preset rather than a
|
||||
`PhysicalPrinter` object - that object is no longer constructed. Printers
|
||||
normally arrive through Bind with Access Code instead, which does not
|
||||
touch the preset. Both routes end at `insert_local_device()`, so they must
|
||||
agree on the device ID: `dev_id_from_address()` strips the URL scheme and
|
||||
drops an empty port, while the bind path stores the address as the user
|
||||
typed it.
|
||||
|
||||
The unknown-`coString` compatibility rule belongs to `architecture.md` under
|
||||
Backward compatibility. Keep a nonempty unknown `printer_agent` ID unchanged
|
||||
and display a missing state if needed; do not rewrite it during plugin unload
|
||||
or choose an arbitrary replacement, so the preset can round-trip while its
|
||||
plugin is temporarily unavailable.
|
||||
|
||||
## Starting a connection
|
||||
|
||||
Machine selection causes `MachineObject::connect()` to invoke the active
|
||||
agent's `connect_printer()` with the selected address and effective access
|
||||
code. A success return means that the connection attempt started. It does
|
||||
not mean that the printer is ready or that a status stream is alive.
|
||||
|
||||
Moonraker-family agents must force HTTP. Moonraker and print-host
|
||||
installations commonly serve plain HTTP, while the generic machine path
|
||||
can request TLS by default. Passing that default through turns a valid
|
||||
connection into an HTTPS request the printer will refuse. The agent therefore
|
||||
must keep the connection on HTTP unless its protocol support changes
|
||||
deliberately and is verified.
|
||||
|
||||
## Access codes: four coordinated slots
|
||||
|
||||
One effective access code can live in four places:
|
||||
|
||||
| Slot | Location | Purpose |
|
||||
| --- | --- | --- |
|
||||
| Device runtime | `MachineObject::access_code` | Code learned from the device. |
|
||||
| User runtime | `MachineObject::user_access_code` | Code entered by the user. |
|
||||
| Device config | `access_code[dev_id]` | Persisted device value. |
|
||||
| User config | `user_access_code[dev_id]` | Persisted user value. |
|
||||
|
||||
The effective code prefers the user value when present, then the device
|
||||
value. Keep user input in the user path and device replies in the device
|
||||
path. Crossing those paths obscures which value should win.
|
||||
|
||||
`set_access_code()` deliberately does not save configuration immediately.
|
||||
Device replies and polls can update it often; forcing a full config write
|
||||
for each message adds unnecessary work. The normal deferred config save
|
||||
persists dirty state later. Do not add an eager save just to make this one
|
||||
path symmetric: device replies and polls update it often, so a config
|
||||
write per message is wasted work.
|
||||
|
||||
> **Do not erase the user access code when a printer connects.** On the
|
||||
> LAN reselection path that code can be the only credential that lets the
|
||||
> machine pass the access check and receive the status or access-code
|
||||
> reply that would refresh it, so erasing it at connection time can leave
|
||||
> the machine permanently unable to receive updates. A failed connection
|
||||
> is the place to handle a proven bad credential.
|
||||
|
||||
## Receiving status
|
||||
|
||||
An agent receives native status, translates it to the existing payload
|
||||
shape, and dispatches it to the matching `MachineObject`. The object
|
||||
parses the payload and records when it last received an update.
|
||||
|
||||
Readiness is four states, and three of them look connected:
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> SelectedIdOnly
|
||||
SelectedIdOnly: Selected ID only
|
||||
SelectedIdOnly: selected_machine names no resolvable object
|
||||
SelectedIdOnly --> FreshWindow: connect_printer returns success
|
||||
|
||||
FreshWindow: Fresh window
|
||||
FreshWindow: reset seeded the update time - no status yet
|
||||
FreshWindow --> Connecting: still fresh, still no push-status
|
||||
|
||||
Connecting: Connecting
|
||||
Connecting: is_connecting true - the honest state
|
||||
Connecting --> Ready: first push-status message parsed
|
||||
|
||||
Ready: Status-confirmed ready
|
||||
Ready: the only state that proves a usable printer
|
||||
Ready --> Stale: update time ages out
|
||||
|
||||
Stale: Stale
|
||||
Stale: is_connected false
|
||||
Stale --> Ready: a later status message arrives
|
||||
|
||||
note right of FreshWindow
|
||||
is_connected() is true from here on.
|
||||
It is a freshness test over the update
|
||||
time - not proof that any status arrived.
|
||||
end note
|
||||
```
|
||||
|
||||
`is_connected()` is a freshness test over the update time. It does not
|
||||
describe whether `connect_printer()` returned success or whether any status
|
||||
message arrived: reset initializes the update time, creating an initial
|
||||
freshness window. `is_connecting()` distinguishes that window from
|
||||
status-confirmed readiness: while the object is fresh and no push-status
|
||||
message has arrived, it remains connecting.
|
||||
|
||||
> **Do not treat freshness or a successful connect as proof of readiness.**
|
||||
> Code that needs a usable printer must wait for status-confirmed
|
||||
> readiness, because the fresh window exists before any status has been
|
||||
> parsed.
|
||||
|
||||
The UI-thread mutation rule belongs to `architecture.md` under Threading rule.
|
||||
Dispatch the status callback to the UI thread before changing device maps,
|
||||
selection, or `MachineObject` state, because network callbacks may run in a
|
||||
worker thread and mutating these structures there races with the Device tab.
|
||||
|
||||
## Sending commands
|
||||
|
||||
`MachineObject` builds the established command JSON and sends it through
|
||||
the active `NetworkAgent`. The agent translates it or returns an explicit
|
||||
unsupported result. It must not report success when no translation exists.
|
||||
|
||||
The `pushing` command exception belongs to `architecture.md` under Commands
|
||||
and unsupported work. It asks for status, and a working status stream already
|
||||
supplies it, so accepting it avoids false unsupported warnings from the Device
|
||||
Manager's repeated keepalive.
|
||||
|
||||
## Maintainer constraints
|
||||
|
||||
- Preserve same-agent reselection by address, because an agent type can
|
||||
serve more than one printer.
|
||||
- Preserve the null-agent, no-fallback, and unknown-`coString` rules in
|
||||
`architecture.md`; selection must remain an explicit user or preset choice,
|
||||
and stale state must not belong to a replacement printer agent.
|
||||
- An empty value is the legacy Bambu-or-Orca sentinel, not a missing printer
|
||||
agent.
|
||||
- Preserve deferred access-code saves and the no-on-connect-erase rule;
|
||||
they prevent excessive config writes and credential-driven status loss.
|
||||
- Keep Moonraker connections HTTP-only unless the agent's protocol support
|
||||
changes deliberately and is verified.
|
||||
- Do not treat freshness as proof that status arrived; wait for
|
||||
status-confirmed readiness. The UI-thread mutation rule is in
|
||||
`architecture.md` under Threading rule.
|
||||
@@ -1,110 +0,0 @@
|
||||
# Filament synchronization and mapping
|
||||
|
||||
*Owns the three filament stages end to end: acquiring printer state,
|
||||
selecting a mapping, and delivering it at print time. Defers the upload
|
||||
and start mechanics to [Printing](printing.md), and per-vendor discovery
|
||||
detail to [Built-in agents](agents.md).*
|
||||
|
||||
Filament support has three separate stages. A successful first stage does
|
||||
not mean that a selected mapping will be delivered to the printer.
|
||||
|
||||
Stage 3 is where the two paths diverge, and only one of them reaches the
|
||||
printer:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
SRC["Filament source<br/>Moonraker lane_data, or the classic MMU object"]
|
||||
ACQ["1 - Acquire state<br/>Moonraker pull, not subscription"]
|
||||
ST["DevFilaSystem<br/>Bambu-shaped view, synthetic 4-slot groups"]
|
||||
SEL["2 - Select mapping<br/>Send dialog matches project filament to slots"]
|
||||
PAR["PrintParams<br/>ams_mapping fields"]
|
||||
QIDI["Qidi agent writes enable_box<br/>and value_t per tool, then starts the print"]
|
||||
BASE["Base Moonraker, Happy Hare, AFC<br/>upload and start ignore the mapping fields"]
|
||||
OK["Mapping reaches the printer"]
|
||||
DEAD["Mapping never reaches the printer<br/>the job runs on the printer's own loaded-lane behavior"]
|
||||
|
||||
SRC --> ACQ --> ST --> SEL --> PAR
|
||||
PAR -- Qidi --> QIDI --> OK
|
||||
PAR -- every other target --> BASE --> DEAD
|
||||
```
|
||||
|
||||
## 1. Acquire printer state
|
||||
|
||||
`FilamentSyncMode` declares how the UI obtains filament state:
|
||||
|
||||
| Mode | Meaning |
|
||||
| --- | --- |
|
||||
| `subscription` | A status stream keeps the state current. |
|
||||
| `pull` | The UI must request state before it can use it. |
|
||||
| `none` | The agent has no usable filament state. |
|
||||
|
||||
Moonraker uses `pull`. Its ordinary status stream does not supply the
|
||||
filament data used by this UI. In particular, `lane_data` is a Moonraker
|
||||
database namespace, not a printer object that the existing subscription
|
||||
can follow. Changing Moonraker to `subscription` would suppress the pull
|
||||
that actually populates the UI.
|
||||
|
||||
The agent first reads `lane_data`, which can describe AFC and newer Happy
|
||||
Hare installations. If that is unavailable, it reads the classic Happy
|
||||
Hare `mmu` object. Those response shapes are source-supported but not yet
|
||||
verified against current Happy Hare and AFC installations.
|
||||
|
||||
The current parser expects lane values as strings and silently skips
|
||||
numeric values. Whether current AFC or Happy Hare installations emit
|
||||
numeric lane values is unverified.
|
||||
|
||||
The received lanes are converted into a Bambu-shaped model so existing
|
||||
AMS UI can render them. The model groups numeric lane indexes into
|
||||
synthetic groups of four slots and passes the result through
|
||||
`ParseV1_0`. This is a UI compatibility adapter, not evidence that the
|
||||
printer has a Bambu AMS.
|
||||
|
||||
Pull state can be stale. The Send dialog can build a mapping from the
|
||||
current `DevFilaSystem` without refreshing it first, and a failed pull can
|
||||
leave older state visible. Do not represent a displayed lane list as a
|
||||
fresh printer read unless the call site just performed the pull.
|
||||
|
||||
## 2. Select a mapping
|
||||
|
||||
The Send dialog matches each project filament to a compatible reported
|
||||
slot. It rejects a mismatched material type and then prefers compatible
|
||||
slots according to the existing mapping rules. The result is carried in
|
||||
the legacy linear mapping, the explicit AMS-and-slot mapping, and mapping
|
||||
metadata for the job.
|
||||
|
||||
Treat lane numbers as printer contracts. A numeric lane index is used as a
|
||||
slot index in the synthetic four-slot view, so an incorrect numbering
|
||||
assumption can select the wrong physical lane.
|
||||
|
||||
The material identity code also retains a defect: ABS and ASA can be
|
||||
shown as PLA when profile identifiers collide. This is not fixed here,
|
||||
and multi-color mapping has not received hardware verification.
|
||||
|
||||
## 3. Deliver the mapping at print time
|
||||
|
||||
`PrintJob` copies the selected mappings into `PrintParams`, but base
|
||||
Moonraker does not read those fields when it uploads and starts a print.
|
||||
For plain Moonraker, Happy Hare, and AFC targets using that base path, a
|
||||
correct-looking mapping in the UI is therefore not delivered to the
|
||||
printer. The print runs using the printer's own loaded-lane behavior.
|
||||
|
||||
Qidi is the implemented exception. Its agent writes its own box mapping
|
||||
before starting the print. That is a Qidi-specific delivery contract, not
|
||||
a generic Moonraker solution.
|
||||
|
||||
There are deliberately no guessed Happy Hare or AFC write macros. Their
|
||||
macro and variable names are defined by printer-side configuration, so a
|
||||
guessed command could silently do nothing or control the wrong setup. Add
|
||||
a delivery path only after verifying the exact contract against upstream
|
||||
documentation or a real printer.
|
||||
|
||||
## Maintenance checklist
|
||||
|
||||
- Keep Moonraker in `pull` mode while `lane_data` remains pull-only.
|
||||
- Refresh or clearly surface stale state before relying on Send-dialog
|
||||
mappings.
|
||||
- Do not claim base Moonraker honors mappings until it consumes them at
|
||||
print time.
|
||||
- Preserve Qidi as a distinct delivery implementation.
|
||||
- Verify lane numbering, material identity, and multi-color behavior on
|
||||
hardware before expanding the mapping contract.
|
||||
@@ -1,161 +0,0 @@
|
||||
# Python printer-agent plugins
|
||||
|
||||
*Owns the plugin bridge: the implementation contract, registration and
|
||||
lifetime, and the audit scope. Defers what the agent must do once live to
|
||||
[Architecture](architecture.md) and
|
||||
[Connection and status](connection-and-status.md).*
|
||||
|
||||
Python printer agents use the current capability bridge. They do not use a
|
||||
separate adapter or a Moonraker-specific plugin path.
|
||||
|
||||
## What an agent must implement
|
||||
|
||||
Not overriding a member of `IPrinterAgent` has four different
|
||||
consequences depending on which tier it is in. This is the whole plugin
|
||||
contract:
|
||||
|
||||
| Tier | Members | Consequence of not overriding |
|
||||
| --- | --- | --- |
|
||||
| Pure virtual | `connect_printer`, `disconnect_printer`, `send_message_to_printer`, the `start_*` print operations, `start_discovery`, `bind`, `bind_detect`, `unbind`, the callback setters, `set_cloud_agent`, `get_agent_info`, and the rest of the pure surface | Compile error |
|
||||
| Concrete, succeeds | `start_subscribe`, `stop_subscribe`, `add_subscribe`, `del_subscribe` | Silently returns `BAMBU_NETWORK_SUCCESS` |
|
||||
| Concrete, declines | `command_ams_refresh_rfid`, `command_ams_calibrate`, `command_ams_select_tray`, `command_start_camera` | Silently returns `ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED` |
|
||||
| Concrete, inert | `get_filament_sync_mode`, `fetch_filament_info` | Reports `FilamentSyncMode::none` and `false` - no filament capability at all |
|
||||
|
||||
The refusal tier is deliberate: those commands carry Bambu-dialect G-code
|
||||
in their bodies, so the honest default is a refusal that
|
||||
`MachineObject::publish_json()` turns into a dialog. The success tier is
|
||||
equally deliberate - a printer whose status already streams needs no
|
||||
subscription call.
|
||||
|
||||
> **Do not assume a missing override quietly inherits useful behavior, and
|
||||
> do not assume it fails loudly either.** Only the first tier fails at
|
||||
> compile time. The second silently reports success, the third silently
|
||||
> declines, and the fourth silently reports no filament capability.
|
||||
|
||||
## The plugin contract
|
||||
|
||||
A plugin subclasses `printer_agent.PrinterAgentBase`, the Python binding for
|
||||
`PrinterAgentPluginCapability`. The capability itself is the live native
|
||||
`IPrinterAgent`; there is no intermediate protocol adapter, because
|
||||
`PrinterAgentPluginCapability` inherits both `PluginCapabilityInterface`
|
||||
and `IPrinterAgent` directly.
|
||||
|
||||
`get_type()` stays a `PluginCapabilityInterface` method and
|
||||
`set_cloud_agent()` remains the native host injection point. A plugin must
|
||||
implement the pure connection, communication, discovery, binding, print,
|
||||
callback-registration, and filament-refresh operations. The certificate,
|
||||
bind-ticket, HMS-snapshot, and user-selected-machine members are pure too;
|
||||
the table above abridges the list.
|
||||
|
||||
The only tracked Python printer-agent implementation is the BBL plugin. There
|
||||
is no Python Moonraker printer agent in the current source tree. Moonraker is
|
||||
implemented by the built-in C++ class.
|
||||
|
||||
## Registration and lifetime
|
||||
|
||||
When an enabled plugin advertises a printer-connection capability, the factory
|
||||
gets its `AgentInfo` and registers a factory under `AgentInfo.id`. This is the
|
||||
same registry used for built-in agents.
|
||||
|
||||
Two similarly named structs are involved, and they are not the same thing.
|
||||
`AgentInfo` is what the agent says about itself; `PrinterAgentInfo` is the
|
||||
registry's entry about it:
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class AgentInfo {
|
||||
<<returned by the agent via get_agent_info>>
|
||||
string id
|
||||
string name
|
||||
string version
|
||||
string description
|
||||
}
|
||||
class PrinterAgentInfo {
|
||||
<<the registry entry>>
|
||||
string id
|
||||
string display_name
|
||||
string plugin_identifier
|
||||
PrinterAgentFactory factory
|
||||
}
|
||||
class PrinterAgentFactory {
|
||||
<<std::function>>
|
||||
takes cloud_agent and log_dir
|
||||
returns shared_ptr~IPrinterAgent~
|
||||
}
|
||||
class NetworkAgentFactory {
|
||||
<<all static>>
|
||||
register_printer_agent(id, display_name, factory)
|
||||
create_printer_agent_by_id(id, cloud_agent, log_dir)
|
||||
clear_printer_agent_cache()
|
||||
register_python_printer_agent(plugin_key, capability_name)
|
||||
deregister_python_printer_agent(plugin_key, capability_name)
|
||||
}
|
||||
|
||||
AgentInfo ..> PrinterAgentInfo : id becomes the registry key
|
||||
PrinterAgentInfo *-- PrinterAgentFactory
|
||||
NetworkAgentFactory o-- PrinterAgentInfo : one entry per ID
|
||||
PrinterAgentFactory ..> PrinterAgentPluginCapability : weak reference
|
||||
```
|
||||
|
||||
`plugin_identifier` is empty for built-ins and
|
||||
`<plugin_key>;<uuid>;<capability_name>` for plugins - that is how the
|
||||
registry tells the two apart at deregistration time. Built-in IDs are the
|
||||
constants `ORCA_PRINTER_AGENT_ID` and `BBL_PRINTER_AGENT_ID`.
|
||||
|
||||
Agent IDs are global. A plugin cannot replace a built-in agent or another
|
||||
plugin with the same ID. Registry rejection is unconditional. The conflicting
|
||||
capability is disabled and the user is shown the conflict only when `wxTheApp`
|
||||
exists and the app is not closing. Re-registering the same plugin capability
|
||||
is allowed so a reload can replace its factory with the current capability
|
||||
instance.
|
||||
|
||||
The registered factory holds a weak reference to the capability. If the plugin
|
||||
has already gone away, creation returns null instead of reviving a destroyed
|
||||
Python object. Callers must treat that as no active printer agent.
|
||||
|
||||
On deregistration, the factory removes the registry entry and cached agent,
|
||||
disconnects a cached agent, and clears the live agent if it has the same ID.
|
||||
This order prevents `NetworkAgent` from retaining a Python implementation
|
||||
whose module is about to unload. The current path is UI-thread oriented. Raw
|
||||
pointer hazards become relevant only if deregistration moves to another thread
|
||||
without adding synchronization around the GUI-held active-agent handle.
|
||||
|
||||
## Device-tab integration
|
||||
|
||||
Plugins share the native Device tab with built-in agents. There is no
|
||||
printer-agent API for adding custom Device-tab panels and no plugin-owned
|
||||
`MachineObject` to populate directly.
|
||||
|
||||
Instead, the plugin supplies the same callbacks as any `IPrinterAgent`. Its
|
||||
status messages must use the Bambu-shaped payload that `MachineObject` already
|
||||
parses. If a required field is absent, the shared native UI shows its default
|
||||
or incomplete state. A custom protocol is acceptable inside the plugin, but
|
||||
its boundary with the app must perform this translation.
|
||||
|
||||
## Python calls, errors, and audit scope
|
||||
|
||||
The C++ trampoline acquires the Python GIL, invokes each pure virtual override,
|
||||
logs a Python exception, and rethrows it. A missing override is a separate
|
||||
C++ pure-virtual failure, not a logged Python traceback. Python construction
|
||||
also bypasses the virtual trampoline, so the bridge logs a constructor failure
|
||||
at the construction boundary.
|
||||
|
||||
Plugin-created threads need their own exception handling. An exception raised
|
||||
there does not cross the C++ trampoline; it reaches Python's thread exception
|
||||
handling and is recorded through redirected Python standard error.
|
||||
|
||||
The audit hook is defense in depth, not a sandbox. Current printer-agent
|
||||
trampoline calls use loading audit mode. In that mode, normal reads are
|
||||
allowed, only some file writes are checked against allowed roots, and many
|
||||
operations are outside the policy, including network access and process
|
||||
creation. Work that runs outside an active trampoline scope, including a
|
||||
plugin-created thread, has no attributed plugin context and is allowed by
|
||||
default. Do not treat this mechanism as permission to run untrusted code.
|
||||
|
||||
## Source locations
|
||||
|
||||
- `src/slic3r/plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp`
|
||||
- `src/slic3r/plugin/pluginTypes/printerAgent/`
|
||||
`PrinterAgentPluginCapabilityTrampoline.hpp`
|
||||
- `src/slic3r/Utils/NetworkAgentFactory.cpp`
|
||||
- `resources/orca_plugins/BBLPrinterAgentPlugin.py`
|
||||
@@ -1,138 +0,0 @@
|
||||
# Printing through printer agents
|
||||
|
||||
*Owns the send path: connection choices, preflight, upload, start, and the
|
||||
two recovery flows. Defers filament mapping delivery to
|
||||
[Filament synchronization](filament.md), which is a separate contract even
|
||||
though it is applied at print time.*
|
||||
|
||||
This chapter describes the printer-agent send path. It is separate from
|
||||
the older print-host implementation, even when both target Moonraker.
|
||||
Keep the paths separate unless their contracts and failure handling can
|
||||
be deliberately reconciled.
|
||||
|
||||
## Connection choices
|
||||
|
||||
Three connection paths are in use:
|
||||
|
||||
| Target | Connection path | Use |
|
||||
| --- | --- | --- |
|
||||
| Native Bambu | Custom TLS tunnel on port 6000 | Send and optional eMMC preflight |
|
||||
| Bambu Python agent | Implicit FTPS on port 990 | Upload and Bambu preflight fallback |
|
||||
| Moonraker family | HTTP | Upload and start print |
|
||||
|
||||
The Bambu connection paths are independent. Selecting one does not prove that
|
||||
the other is available. The Moonraker agent uploads with a multipart
|
||||
request to its `gcodes` storage and then starts the uploaded filename;
|
||||
it does not reuse the legacy `Moonraker` print-host class.
|
||||
|
||||
## Bambu native tunnel
|
||||
|
||||
The native tunnel depends on the versioned networking DLL and its
|
||||
file-transfer module. `InitFTModule()` is a single-owner initialization:
|
||||
it rejects a second call. Any future shared initialization must therefore
|
||||
be idempotent, while `BBLNetworkPlugin` remains the single teardown owner.
|
||||
It must call `UnloadFTModule()` before freeing the DLL, otherwise the
|
||||
module's function pointers can point into unloaded code.
|
||||
|
||||
There is currently an initialization gap: selecting a printer agent does
|
||||
not initialize this module. It is initialized only when the
|
||||
`installed_networking` option causes the native BBL network plugin to
|
||||
initialize. Calls to the tunnel must continue to fail safely until that
|
||||
path has initialized the module. The Send UI catches this failure and
|
||||
reports an initialization error instead of letting an exception leave a
|
||||
wx event handler.
|
||||
|
||||
## Bambu FTPS upload
|
||||
|
||||
The Python Bambu agent uses implicit FTPS on port 990. Its live upload
|
||||
path closes the data connection, then waits at most two seconds for the
|
||||
control response with `voidresp()`. A `TimeoutError` is accepted as a
|
||||
completed transfer. An `error_reply` is also accepted when its reply
|
||||
begins with `200`. This is the behavior to preserve.
|
||||
|
||||
Do not describe the path as using TLS `unwrap()`: the live construction
|
||||
does not enable it. Enabling it without a bounded wait could hang while
|
||||
waiting for the peer's TLS close notification. The current timeout-based
|
||||
handling has not been verified on hardware against every printer and FTP
|
||||
server combination.
|
||||
|
||||
## Print preflight and recovery
|
||||
|
||||
For normal LAN prints, `PrintJob` performs a preflight before the real
|
||||
send. When eMMC is eligible it tries the native tunnel, then it sends a
|
||||
small `verify_job` upload through the selected agent. The latter is a real
|
||||
upload, not a special protocol command. Non-Bambu agents therefore upload
|
||||
the probe too.
|
||||
|
||||
> **Do not re-enable eMMC by default** without hardware coverage for the
|
||||
> affected devices. It is opt-in because the tunnel can hang during upload
|
||||
> on some printers.
|
||||
|
||||
The whole send, including the thread hop and the recovery fork:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant UI as Send UI (UI thread)
|
||||
participant J as PrintJob (worker)
|
||||
participant A as Selected IPrinterAgent
|
||||
participant P as Printer
|
||||
|
||||
UI->>J: Start send
|
||||
opt eMMC eligible - off by default
|
||||
J->>P: Native Bambu tunnel attempt
|
||||
Note over J,P: Can hang on some printers
|
||||
end
|
||||
J->>A: verify_job preflight
|
||||
Note over J,A: A real upload, not a protocol probe -<br/>non-Bambu agents upload it too
|
||||
A->>P: Upload probe
|
||||
|
||||
alt Preflight and upload succeed
|
||||
J->>A: Upload the real job
|
||||
A->>P: Upload, then request print start
|
||||
Note over A,P: The start response may stay open while<br/>the printer prepares - a timeout is not<br/>proof of failure, so check reported print state
|
||||
A-->>UI: Result from the reported print state
|
||||
else Upload fails
|
||||
J-->>UI: Failure callback, marshalled to the UI thread
|
||||
Note over UI: Re-resolve the machine here.<br/>Never reuse a machine pointer captured<br/>on the worker - agent or machine may have changed
|
||||
alt Printer still connected
|
||||
UI->>UI: Explain that storage upload failed
|
||||
else Printer disconnected
|
||||
UI->>UI: Open the IP or access-code flow
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
An upload failure and a disconnected printer need different recovery:
|
||||
|
||||
| Condition | UI response |
|
||||
| --- | --- |
|
||||
| Printer is still connected | Explain that storage upload failed. |
|
||||
| Printer is disconnected | Open the IP or access-code flow. |
|
||||
|
||||
> **Do not retain a machine pointer from a worker callback.** The callback
|
||||
> that chooses between these two outcomes runs on the UI thread and
|
||||
> re-resolves the machine there, because the selected agent or machine can
|
||||
> change first. The connection check is adequate for choosing the message,
|
||||
> but is not a strong enough signal to authorize a reconnect.
|
||||
|
||||
## Moonraker upload and start
|
||||
|
||||
`MoonrakerPrinterAgent` uploads through Moonraker HTTP, then requests the
|
||||
print start separately. The start endpoint may keep its response open
|
||||
while the printer prepares the job. A timeout after that request is not
|
||||
automatically proof that the start failed: the agent checks the reported
|
||||
print state before deciding the result.
|
||||
|
||||
The legacy print-host Moonraker path implements its own upload and start
|
||||
logic. It is not the agent path and should not be changed as an implicit
|
||||
side effect of agent work.
|
||||
|
||||
## Maintenance checklist
|
||||
|
||||
- Test the selected connection path, not just another path on the same
|
||||
printer.
|
||||
- Preserve cancellation and progress callbacks across upload and start.
|
||||
- Treat `verify_job` as an actual upload when estimating storage effects.
|
||||
- Keep eMMC opt-in until its hanging behavior is resolved and verified.
|
||||
- Keep the connected-upload-failure dialog distinct from the disconnected
|
||||
recovery flow.
|
||||
@@ -1,137 +0,0 @@
|
||||
# Printer-agent capability matrix
|
||||
|
||||
This is a compact lookup for the built-in Moonraker family. It combines
|
||||
implementation state with recorded evidence. It is not a promise that every
|
||||
firmware configuration behaves the same way. Python plug-in behavior depends
|
||||
on the plug-in, not on this matrix.
|
||||
|
||||
Use [testing and troubleshooting](../testing.md) before calling a live-printer
|
||||
result complete.
|
||||
|
||||
For a quicker tour of the controls users actually see, open the
|
||||
[annotated Device-tab view](device-tab-annotations.html). The annotations
|
||||
explain the important routing constraints; this matrix remains the compact
|
||||
cross-agent reference.
|
||||
|
||||
## Status definitions
|
||||
|
||||
- Supported - implemented, with a relevant live-printer result recorded.
|
||||
- Partial - an important condition, limitation, or defect applies.
|
||||
- Unsupported - no applicable implementation, or deliberate refusal.
|
||||
- Not verified - implemented or source-inspected, but without a relevant live
|
||||
result.
|
||||
|
||||
`Base` means `MoonrakerPrinterAgent`. Qidi, Creality, and Snapmaker inherit
|
||||
from it unless a row identifies an override.
|
||||
|
||||
## Connection and status
|
||||
|
||||
| Capability | Base | Qidi | Creality | Snapmaker |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Direct LAN connection with API key | Supported | Supported | Not verified | Not verified |
|
||||
| WebSocket status updates | Supported | Supported | Not verified | Not verified |
|
||||
| Reconnect and fresh status | Partial | Partial | Partial | Partial |
|
||||
| Discovery and cloud binding | Unsupported | Unsupported | Unsupported | Unsupported |
|
||||
| Device identity with a configured port | Partial | Partial | Partial | Partial |
|
||||
|
||||
- Reconnect completion can leave the Device tab with stale status.
|
||||
- A bare IP and `host:port` can become separate device identities.
|
||||
- The Base and Qidi Supported grades come from prior hardware sessions. They
|
||||
were carried into this rewrite and not rerun.
|
||||
|
||||
## Controls
|
||||
|
||||
| Capability | Base | Qidi | Creality | Snapmaker |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Home and arbitrary G-code | Not verified | Not verified | Not verified | Not verified |
|
||||
| Bed and nozzle temperature | Not verified | Not verified | Not verified | Not verified |
|
||||
| Pause, resume, and cancel | Not verified | Not verified | Not verified | Not verified |
|
||||
| Configured chamber light | Partial | Partial | Partial | Partial |
|
||||
| Jog and manual extrusion | Partial | Partial | Not verified | Not verified |
|
||||
| Legacy part-fan speed control | Partial | Partial | Not verified | Not verified |
|
||||
| Structured fan, chamber, and AI controls | Unsupported | Unsupported | Unsupported | Unsupported |
|
||||
| AMS RFID, calibration, and tray control | Unsupported | Unsupported | Unsupported | Unsupported |
|
||||
|
||||
- Chamber light needs a recognised light object.
|
||||
- Base and Qidi jog works, but can leave relative positioning active. Do not
|
||||
use it as a general safe-control test until its G-code state is restored.
|
||||
- Base and Qidi part-fan control works through legacy `gcode_line` while
|
||||
`is_enable_np` is false. Adding `cfg`, `fun`, `aux`, and `stat` flips that
|
||||
flag and routes fan and extruder controls to unsupported structured commands.
|
||||
- Creality and Snapmaker inherit the source path but have no separate live
|
||||
evidence for jog or fan control.
|
||||
|
||||
## Printing
|
||||
|
||||
| Capability | Base | Qidi | Creality | Snapmaker |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Upload G-code without starting | Not verified | Not verified | Not verified | Not verified |
|
||||
| Upload and start a local print | Not verified | Not verified | Not verified | Not verified |
|
||||
| Mapped multi-material print | Unsupported | Not verified | Unsupported | Unsupported |
|
||||
| Cloud or SD-card start variants | Unsupported | Partial | Unsupported | Unsupported |
|
||||
| Cancel during upload | Not verified | Not verified | Not verified | Not verified |
|
||||
| Send with no nozzle identity | Not applicable | Not verified | Not applicable | Not applicable |
|
||||
|
||||
- Qidi applies mapping before it routes the real local print path.
|
||||
- Some Qidi print variants can reach base success stubs after mapping.
|
||||
- Qidi tolerates missing nozzle data in source, but that Send preflight is not
|
||||
hardware-verified.
|
||||
|
||||
## Filament
|
||||
|
||||
| Capability | Base | Qidi | Creality | Snapmaker |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Sync mode | Not verified | Not verified | Not verified | Not verified |
|
||||
| Read installed material and slots | Partial | Not verified | Not verified | Not verified |
|
||||
| Slot, material, and colour refresh | Unsupported | Not verified | Not verified | Not verified |
|
||||
| Cleanup after removed material | Not verified | Not verified | Not verified | Not verified |
|
||||
| Load, unload, or write a slot | Unsupported | Partial | Unsupported | Unsupported |
|
||||
| Auto Refill | Unsupported | Unsupported | Unsupported | Unsupported |
|
||||
|
||||
- All built-in agents use pull-mode sync.
|
||||
- Base reads Happy Hare or AFC data when present. Qidi has print-time mapping;
|
||||
Creality has CFS logic; Snapmaker reads printer arrays and NFC data.
|
||||
- The Base Device-tab slot refresh uses a proprietary AMS command and has no
|
||||
generic Moonraker translation.
|
||||
- Generic write-side macros stay unsupported until their printer contract is
|
||||
known and verified.
|
||||
|
||||
## Camera
|
||||
|
||||
| Capability | Base | Qidi | Creality | Snapmaker |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| Discover a Moonraker webcam | Not verified | Not verified | Not verified | Unsupported |
|
||||
| Provide a camera source | Not verified | Not verified | Not verified | Not verified |
|
||||
| Live camera view | Not verified | Not verified | Not verified | Not verified |
|
||||
| Snapshot-only camera start | Unsupported | Unsupported | Unsupported | Not verified |
|
||||
| Camera start renewal and teardown | Unsupported | Unsupported | Unsupported | Partial |
|
||||
| Print thumbnail | Not verified | Not verified | Not verified | Not verified |
|
||||
|
||||
- Snapmaker bypasses webcam discovery with a local snapshot-polling page.
|
||||
- Its renewal and teardown path has lifetime risks without live evidence.
|
||||
- Moonraker thumbnail endpoint responses and filename-cache behavior need live
|
||||
coverage.
|
||||
|
||||
## Python plug-ins
|
||||
|
||||
| Capability | Python plug-in agent |
|
||||
| --- | --- |
|
||||
| Registration and re-registration | Not verified - lifecycle tests cover replacement |
|
||||
| Duplicate agent ID | Not verified - conflict is rejected and reported |
|
||||
| Disable or unload | Not verified - deregistration is tested; session teardown needs coverage |
|
||||
| Capability surface | Defined by the plug-in and exposed Python API |
|
||||
|
||||
## Reading the matrix safely
|
||||
|
||||
- `Partial` is not a softer form of `Supported`. It names a condition that must
|
||||
be checked before use. `Not verified` means the code was found, but no
|
||||
relevant live result is recorded.
|
||||
- Pair each claim with the evidence grades in the testing guide. This matters
|
||||
especially for CFS, Snapmaker camera, MMU macros, Qidi Send preflight, and
|
||||
thumbnail endpoints.
|
||||
|
||||
## Background - deliberate exclusions
|
||||
|
||||
The matrix excludes Bambu-specific cloud binding, RFID, calibration, and
|
||||
camera-control features from the Moonraker family. They use different protocol
|
||||
contracts and are deliberately refused when no safe Klipper equivalent exists.
|
||||
@@ -1,280 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Printer-agent manual checklist</title>
|
||||
<style>
|
||||
:root { color-scheme: light dark; --bg:#111827; --panel:#1f2937; --line:#4b5563;
|
||||
--text:#f9fafb; --muted:#cbd5e1; --pass:#34d399; --fail:#f87171; --block:#fbbf24; }
|
||||
* { box-sizing:border-box; }
|
||||
body { max-width:960px; margin:0 auto; padding:24px; font:15px/1.5 system-ui,sans-serif;
|
||||
color:var(--text); background:var(--bg); }
|
||||
h1 { margin:0 0 4px; } h2 { margin-top:32px; } p, li { max-width:78ch; }
|
||||
.muted { color:var(--muted); } .meta { display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
|
||||
gap:12px; margin:20px 0; } label { display:grid; gap:4px; }
|
||||
input, textarea, button { font:inherit; } input, textarea { width:100%; padding:8px;
|
||||
border:1px solid var(--line); border-radius:6px; color:inherit; background:var(--panel); }
|
||||
textarea { min-height:58px; margin-top:8px; } .toolbar { display:flex; flex-wrap:wrap;
|
||||
gap:8px; align-items:center; margin:16px 0; } button { border:1px solid var(--line);
|
||||
border-radius:6px; padding:6px 10px; cursor:pointer; color:inherit; background:var(--panel); }
|
||||
button[data-state="pass"].active { color:#062d1d; background:var(--pass); }
|
||||
button[data-state="fail"].active { color:#3b0808; background:var(--fail); }
|
||||
button[data-state="blocked"].active { color:#3b2600; background:var(--block); }
|
||||
.progress { flex:1 1 240px; height:10px; overflow:hidden; border-radius:999px; background:var(--line); }
|
||||
.progress > div { height:100%; width:0; background:var(--pass); transition:width .15s; }
|
||||
.case { margin:10px 0; padding:12px; border:1px solid var(--line); border-left:4px solid var(--line);
|
||||
border-radius:6px; background:var(--panel); } .case.pass { border-left-color:var(--pass); }
|
||||
.case.fail { border-left-color:var(--fail); } .case.blocked { border-left-color:var(--block); }
|
||||
.case-head { display:flex; justify-content:space-between; gap:12px; align-items:start; }
|
||||
.case h3 { margin:0; font-size:1rem; } .actions { display:flex; gap:5px; white-space:nowrap; }
|
||||
.restore-warning { padding:8px 10px; border:1px solid var(--block); border-radius:6px;
|
||||
color:var(--block); background:var(--panel); }
|
||||
code { overflow-wrap:anywhere; } @media (max-width:600px) { body { padding:16px; }
|
||||
.meta { grid-template-columns:1fr; } .case-head { display:block; } .actions { margin-top:8px; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Printer-agent manual checklist</h1>
|
||||
<p class="muted">Record what the printer did, not just what the UI displayed. Mark a case
|
||||
blocked when the required printer, configuration, or safe test condition is unavailable.</p>
|
||||
|
||||
<section class="meta" aria-label="Test context">
|
||||
<label>Agent and printer<input id="target" placeholder="for example: Moonraker - printer model"></label>
|
||||
<label>Firmware and configuration<input id="environment" placeholder="firmware, MMU, camera, API key setup"></label>
|
||||
<label>Build or package<input id="build" placeholder="version or build identifier"></label>
|
||||
<label>Tester and date<input id="tester" placeholder="name and date"></label>
|
||||
</section>
|
||||
|
||||
<div class="toolbar">
|
||||
<strong id="summary">0 of 0 cases marked</strong>
|
||||
<div class="progress" aria-label="Checklist progress"><div id="bar"></div></div>
|
||||
<button id="export" type="button">Export Markdown</button>
|
||||
<button id="reset" type="button">Reset checklist</button>
|
||||
</div>
|
||||
|
||||
<p id="restore-warning" class="restore-warning" hidden>Saved checklist data could not be
|
||||
restored. You can export the current blank checklist or use Reset to remove the saved data.</p>
|
||||
|
||||
<p class="muted">Use Pass only after observing the expected result. Fail needs enough evidence
|
||||
to reproduce it. Include response text, log markers, or firmware behavior in the note.</p>
|
||||
|
||||
<section>
|
||||
<h2>Connect and observe status</h2>
|
||||
<div class="case" data-id="connect">
|
||||
<div class="case-head"><div><h3>Connect to the selected printer</h3>
|
||||
<p>Expected: the Device tab receives a fresh status update after connection. Do not use a
|
||||
successful connection return alone as the result.</p></div><div class="actions"></div></div><textarea placeholder="Evidence, result, or blocker"></textarea></div>
|
||||
<div class="case" data-id="status">
|
||||
<div class="case-head"><div><h3>Observe live status changes</h3>
|
||||
<p>Expected: temperature and target changes, fan state, print state, filename, progress,
|
||||
elapsed time, and homing state reach the UI while the printer changes state.</p></div><div class="actions"></div></div><textarea placeholder="Evidence, result, or blocker"></textarea></div>
|
||||
<div class="case" data-id="reconnect">
|
||||
<div class="case-head"><div><h3>Disconnect and reconnect</h3>
|
||||
<p>Expected: a second connection produces new status messages and does not create a duplicate
|
||||
device. Record the post-reconnect status evidence.</p></div><div class="actions"></div></div><textarea placeholder="Evidence, result, or blocker"></textarea></div>
|
||||
<div class="case" data-id="network-errors">
|
||||
<div class="case-head"><div><h3>Handle network and response failures</h3>
|
||||
<p>Expected: for discovery, status, G-code, upload, and print start, exercise controlled
|
||||
HTTP 401, 404, and 500 responses, invalid JSON, and refused sockets. Each operation must
|
||||
fail clearly or offer a retry, without a crash or a false success. Record the operation,
|
||||
injected failure, UI result, and any retry.</p></div><div class="actions"></div></div><textarea placeholder="Operation, injected failure, UI result, retry, and evidence"></textarea></div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Controls and printing</h2>
|
||||
<div class="case" data-id="controls">
|
||||
<div class="case-head"><div><h3>Exercise safe controls</h3>
|
||||
<p>Expected: home, bed and nozzle temperature, and a harmless G-code command take effect on
|
||||
the printer. Do not use Moonraker jog as a safe control test while it can leave relative
|
||||
positioning active.</p></div><div class="actions"></div></div><textarea placeholder="Commands sent and printer-side result"></textarea></div>
|
||||
<div class="case" data-id="fifo-order">
|
||||
<div class="case-head"><div><h3>Verify queued command order under latency</h3>
|
||||
<p>Expected: queue three harmless, uniquely marked commands while a proxy, network shaper,
|
||||
or request log introduces or records latency. The printer-side log must show the markers in
|
||||
the same order they were queued. Record the latency method and observed order.</p></div><div class="actions"></div></div><textarea placeholder="Queued markers, latency method, printer-side order, and result"></textarea></div>
|
||||
<div class="case" data-id="send-only">
|
||||
<div class="case-head"><div><h3>Send a file without starting it</h3>
|
||||
<p>Expected: the file appears on the printer and no print starts.</p></div><div class="actions"></div></div><textarea placeholder="Filename and observed result"></textarea></div>
|
||||
<div class="case" data-id="print">
|
||||
<div class="case-head"><div><h3>Start a small print</h3>
|
||||
<p>Expected: upload completes, the printer starts the selected file, and status transitions
|
||||
to printing.</p></div><div class="actions"></div></div><textarea placeholder="Filename, response, and observed result"></textarea></div>
|
||||
<div class="case" data-id="active-print-controls">
|
||||
<div class="case-head"><div><h3>Pause, resume, and cancel an active print</h3>
|
||||
<p>Expected: after the small print is actively printing, pause it and observe a paused state
|
||||
on both printer and UI; resume it and observe printing again; then cancel it and observe the
|
||||
printer stop and the UI leave the active or paused state.</p></div><div class="actions"></div></div><textarea placeholder="State transitions and printer-side result"></textarea></div>
|
||||
<div class="case" data-id="print-failure">
|
||||
<div class="case-head"><div><h3>Check upload failure handling</h3>
|
||||
<p>Expected: cancellation during upload and a missing input fail clearly and do not begin a
|
||||
partial or unintended print.</p></div><div class="actions"></div></div><textarea placeholder="Failure path and observed result"></textarea></div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Filament, camera, and agent-specific checks</h2>
|
||||
<div class="case" data-id="filament-read">
|
||||
<div class="case-head"><div><h3>Refresh material-system data</h3>
|
||||
<p>Expected: populated slots, empty slots, material, colour, and a live change are represented
|
||||
correctly. Moonraker-family agents pull this data; do not expect a subscription callback.</p></div><div class="actions"></div></div><textarea placeholder="MMU or box configuration and observed result"></textarea></div>
|
||||
<div class="case" data-id="filament-cleanup">
|
||||
<div class="case-head"><div><h3>Clear material data when filament or the system is absent</h3>
|
||||
<p>Expected: remove filament or disable the material system, refresh, and confirm the UI no
|
||||
longer shows obsolete slots, material names, or colours.</p></div><div class="actions"></div></div><textarea placeholder="Change made, refresh evidence, and remaining or cleared data"></textarea></div>
|
||||
<div class="case" data-id="filament-write">
|
||||
<div class="case-head"><div><h3>Verify print-time mapping where supported</h3>
|
||||
<p>Expected: only agents with a documented mapping implementation change printer-side mapping.
|
||||
Do not attempt load, unload, or slot-setting macros unless their printer-specific contract is
|
||||
known and safe.</p></div><div class="actions"></div></div><textarea placeholder="Mapping path, printer configuration, and result"></textarea></div>
|
||||
<div class="case" data-id="qidi-nozzle-preflight">
|
||||
<div class="case-head"><div><h3>Check Qidi Send preflight with missing nozzle identity</h3>
|
||||
<p>Expected: on a Qidi agent and compatible single-nozzle slice, Send proceeds when the
|
||||
Device tab has no reported nozzle diameter or type. It must not stop with
|
||||
<code>PrintStatusNozzleDataInvalid</code>. Record any reported identity and any mismatch
|
||||
result separately; this does not approve a known mismatch.</p></div><div class="actions"></div></div><textarea placeholder="Slice, reported nozzle data, preflight result, and printer-side result"></textarea></div>
|
||||
<div class="case" data-id="camera">
|
||||
<div class="case-head"><div><h3>Verify a camera feed</h3>
|
||||
<p>Expected: frames advance and switching printers does not display a stale feed. For Snapmaker,
|
||||
observe immediately before and after 300 seconds in one open view. The renewal result is
|
||||
unknown until hardware evidence exists. Then swap agents and shut down the app to exercise
|
||||
teardown around the detached callback's raw-<code>this</code> lifetime risk.</p></div><div class="actions"></div></div><textarea placeholder="Camera type, timestamps, agent swap or shutdown result, and evidence"></textarea></div>
|
||||
<div class="case" data-id="thumbnail">
|
||||
<div class="case-head"><div><h3>Verify the print thumbnail</h3>
|
||||
<p>Expected: test a reused filename after its thumbnail changes, response payloads with both
|
||||
<code>thumbnail_path</code> and <code>relative_path</code>, and a path below the G-code
|
||||
root. The displayed image must match the current file in each case.</p></div><div class="actions"></div></div><textarea placeholder="Filename, endpoint key and path, displayed image, and observed result"></textarea></div>
|
||||
<div class="case" data-id="plugin">
|
||||
<div class="case-head"><div><h3>Reload a Python printer-agent plug-in</h3>
|
||||
<p>Expected: the capability registers once, duplicate agent IDs are rejected visibly, and
|
||||
disable or unload removes the agent cleanly.</p></div><div class="actions"></div></div><textarea placeholder="Plug-in identifier, actions, and observed result"></textarea></div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const storageKey = 'orca-printer-agent-manual-checklist-v1';
|
||||
const cases = [...document.querySelectorAll('.case')];
|
||||
const inputs = [...document.querySelectorAll('input')];
|
||||
const restoreWarning = document.querySelector('#restore-warning');
|
||||
|
||||
function emptyState() {
|
||||
return { meta: {}, cases: {} };
|
||||
}
|
||||
function loadState() {
|
||||
const saved = localStorage.getItem(storageKey);
|
||||
if (!saved) return emptyState();
|
||||
try {
|
||||
const parsed = JSON.parse(saved);
|
||||
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object' ||
|
||||
(parsed.meta !== undefined && (Array.isArray(parsed.meta) || typeof parsed.meta !== 'object')) ||
|
||||
(parsed.cases !== undefined && (Array.isArray(parsed.cases) || typeof parsed.cases !== 'object'))) {
|
||||
throw new Error('incompatible saved checklist state');
|
||||
}
|
||||
return { meta: parsed.meta || {}, cases: parsed.cases || {} };
|
||||
} catch (error) {
|
||||
restoreWarning.hidden = false;
|
||||
return emptyState();
|
||||
}
|
||||
}
|
||||
const state = loadState();
|
||||
|
||||
function caseText(caseElement) {
|
||||
return caseElement.querySelector('h3').textContent.trim();
|
||||
}
|
||||
function caseCriteria(caseElement) {
|
||||
return [...caseElement.querySelectorAll('.case-head p')]
|
||||
.map(paragraph => markdownParagraph(paragraph.textContent))
|
||||
.filter(Boolean)
|
||||
.join('\n\n');
|
||||
}
|
||||
function save() {
|
||||
const data = { meta: {}, cases: {} };
|
||||
inputs.forEach(input => { data.meta[input.id] = input.value; });
|
||||
cases.forEach(item => {
|
||||
data.cases[item.dataset.id] = { status: item.dataset.status || '', note: item.querySelector('textarea').value };
|
||||
});
|
||||
localStorage.setItem(storageKey, JSON.stringify(data));
|
||||
updateProgress();
|
||||
}
|
||||
function renderStatus(item, status) {
|
||||
item.dataset.status = status;
|
||||
item.classList.toggle('pass', status === 'pass');
|
||||
item.classList.toggle('fail', status === 'fail');
|
||||
item.classList.toggle('blocked', status === 'blocked');
|
||||
item.querySelectorAll('button[data-state]').forEach(button => {
|
||||
button.classList.toggle('active', button.dataset.state === status);
|
||||
});
|
||||
}
|
||||
function applyStatus(item, status) {
|
||||
renderStatus(item, status);
|
||||
save();
|
||||
}
|
||||
function updateProgress() {
|
||||
const marked = cases.filter(item => item.dataset.status).length;
|
||||
document.querySelector('#summary').textContent = `${marked} of ${cases.length} cases marked`;
|
||||
document.querySelector('#bar').style.width = `${cases.length ? marked * 100 / cases.length : 0}%`;
|
||||
}
|
||||
function markdown() {
|
||||
const meta = stateFromInputs();
|
||||
const lines = ['# Printer-agent manual verification', '',
|
||||
`Target: ${markdownInline(meta.target) || 'not recorded'}`,
|
||||
`Firmware and configuration: ${markdownInline(meta.environment) || 'not recorded'}`,
|
||||
`Build or package: ${markdownInline(meta.build) || 'not recorded'}`,
|
||||
`Tester and date: ${markdownInline(meta.tester) || 'not recorded'}`, ''];
|
||||
cases.forEach(item => {
|
||||
const status = item.dataset.status || 'unmarked';
|
||||
const note = markdownInline(item.querySelector('textarea').value);
|
||||
lines.push(`## ${markdownInline(caseText(item))}`, '',
|
||||
`Status: ${status}`, '',
|
||||
'### Test criteria and expected result', '',
|
||||
caseCriteria(item) || 'Not recorded', '',
|
||||
'### Notes', '', note || 'None', '');
|
||||
});
|
||||
return lines.join('\n');
|
||||
}
|
||||
function markdownParagraph(value) {
|
||||
return markdownInline(value);
|
||||
}
|
||||
function markdownInline(value) {
|
||||
return String(value || '').replace(/\r\n?|\n/g, ' ').replace(/[\\`*_{}[\]<>#+!|]/g, '\\$&')
|
||||
.replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
function stateFromInputs() {
|
||||
return Object.fromEntries(inputs.map(input => [input.id, input.value]));
|
||||
}
|
||||
function downloadMarkdown() {
|
||||
const blob = new Blob([markdown()], { type: 'text/markdown;charset=utf-8' });
|
||||
const link = document.createElement('a');
|
||||
link.href = URL.createObjectURL(blob);
|
||||
link.download = 'printer-agent-manual-verification.md';
|
||||
link.click();
|
||||
URL.revokeObjectURL(link.href);
|
||||
}
|
||||
|
||||
cases.forEach(item => {
|
||||
const actions = item.querySelector('.actions');
|
||||
['pass', 'fail', 'blocked'].forEach(status => {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.dataset.state = status;
|
||||
button.textContent = status[0].toUpperCase() + status.slice(1);
|
||||
button.addEventListener('click', () => applyStatus(item, status));
|
||||
actions.appendChild(button);
|
||||
});
|
||||
const saved = state.cases[item.dataset.id] || {};
|
||||
item.querySelector('textarea').value = typeof saved.note === 'string' ? saved.note : '';
|
||||
if (['pass', 'fail', 'blocked'].includes(saved.status)) renderStatus(item, saved.status);
|
||||
item.querySelector('textarea').addEventListener('input', save);
|
||||
});
|
||||
inputs.forEach(input => {
|
||||
input.value = typeof state.meta[input.id] === 'string' ? state.meta[input.id] : '';
|
||||
input.addEventListener('input', save);
|
||||
});
|
||||
document.querySelector('#export').addEventListener('click', downloadMarkdown);
|
||||
document.querySelector('#reset').addEventListener('click', () => {
|
||||
if (!confirm('Clear all saved checklist data for this browser?')) return;
|
||||
localStorage.removeItem(storageKey);
|
||||
location.reload();
|
||||
});
|
||||
updateProgress();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,249 +0,0 @@
|
||||
# Testing and troubleshooting printer agents
|
||||
|
||||
*Owns evidence grades, the automated and manual verification passes, and
|
||||
the open-defect register. Every "not hardware-verified" note elsewhere in
|
||||
this guide resolves to a grade defined here.*
|
||||
|
||||
This page describes how to assess a printer-agent change without treating
|
||||
source inspection as a hardware result. Use the
|
||||
[manual checklist](reference/manual-checklist.html) for a repeatable live-printer
|
||||
pass, and use the [capability matrix](reference/capability-matrix.md) to decide
|
||||
which cases apply to the agent being changed.
|
||||
|
||||
## Evidence grades
|
||||
|
||||
Keep these grades separate in reviews and release notes.
|
||||
|
||||
- Source-inspected - the current implementation was read. It establishes
|
||||
intended behavior, not printer compatibility.
|
||||
- Automated - a targeted test ran. It covers its inputs and assertions, not a
|
||||
printer, firmware version, or network failure that it does not model.
|
||||
- Hardware-verified - the stated behavior was observed on a named class of
|
||||
live printer. Record the model, firmware, configuration, and result with the
|
||||
test evidence.
|
||||
|
||||
Do not call a capability supported by hardware solely because the code compiles or
|
||||
a unit test passes. Conversely, a hardware observation should not be generalized
|
||||
to every Moonraker-family printer without checking its configuration.
|
||||
|
||||
## Carried hardware evidence
|
||||
|
||||
Prior hardware sessions verified direct Moonraker-family connection, live
|
||||
WebSocket status, and jog on both a Qidi/Moonraker printer and a generic
|
||||
Moonraker box. The jog moved the printer but could leave relative positioning
|
||||
active. This evidence establishes the Moonraker-base and Qidi grades in the
|
||||
capability matrix. It was carried into this rewrite and was not rerun here.
|
||||
|
||||
It does not establish those behaviors for Creality or Snapmaker, and it does
|
||||
not cover Qidi-specific filament discovery, box mapping, or print wrappers.
|
||||
|
||||
## Build and automated tests
|
||||
|
||||
Run the smallest relevant test target first, then broaden the run if the change
|
||||
crosses shared agent, plug-in, or Device-tab code. Set
|
||||
`<configured-build-dir>` to the CMake build tree that was already configured for
|
||||
the compiler, generator, and build type you intend to use. Do not replace it
|
||||
with the source directory or assume a `build` subdirectory exists.
|
||||
|
||||
```powershell
|
||||
cmake --build <configured-build-dir> --config RelWithDebInfo --target slic3rutils_tests
|
||||
cmake --build <configured-build-dir> --config RelWithDebInfo --target printer_agent_plugin_tests
|
||||
ctest --test-dir <configured-build-dir>/tests/libslic3r --output-on-failure
|
||||
```
|
||||
|
||||
`--config RelWithDebInfo` is needed for multi-config generators such as Visual
|
||||
Studio. Omit it only when the configured generator is single-config and its
|
||||
build type was selected at configure time. Parallel-build options belong to the
|
||||
generator: for example, pass `--parallel 6` to CMake when the generator
|
||||
supports it, or use the generator's own trailing arguments only when that
|
||||
generator documents them. Do not combine a changed working directory, a
|
||||
generator-specific flag, and an assumed build-tree layout in one command.
|
||||
|
||||
On Windows, start from an MSVC developer environment. A shell without the MSVC
|
||||
include paths can fail in dependencies before it compiles Orca code, with errors
|
||||
such as `C1083: Cannot open include file: 'stddef.h'`, `'time.h'`, or `'cstdint'`.
|
||||
Those signatures are environment failures, not evidence against the agent change.
|
||||
|
||||
If a machine exhausts MSVC precompiled-header memory, use the documented lower
|
||||
parallelism command:
|
||||
|
||||
```powershell
|
||||
cmake --build <configured-build-dir> --config RelWithDebInfo --target slic3rutils_tests --parallel 6
|
||||
```
|
||||
|
||||
Errors such as `C3859: Failed to create virtual memory for PCH` and `C1076:
|
||||
internal heap limit reached` are machine-specific resource failures. If a build
|
||||
appears hung and file operations are blocked, inspect for idle `cl.exe` processes
|
||||
holding locks before changing source.
|
||||
|
||||
Relevant automated coverage includes:
|
||||
|
||||
- `tests/slic3rutils/test_qidi_printer_agent.cpp` validates malformed and null
|
||||
Qidi slot responses without throwing.
|
||||
- `tests/slic3rutils/test_printer_agent.cpp` checks the public printer-agent
|
||||
surface, including filament-sync mode exposure.
|
||||
- `tests/slic3rutils/test_printer_agent_plugin.cpp` exercises plug-in
|
||||
registration, replacement, conflict handling, and deregistration.
|
||||
|
||||
Do not present a historic test count, failure count, or skipped-test count as the
|
||||
current state. Run the command above and attach its own output when a current
|
||||
result is needed.
|
||||
|
||||
## Manual hardware verification
|
||||
|
||||
Use a small, disposable model and a printer that can safely accept the actions.
|
||||
The checklist groups the work in the order below.
|
||||
|
||||
1. Confirm the printer accepts its configured URL and API key, then select it in
|
||||
the Device tab. Verify a fresh status update, not merely a successful connect
|
||||
return code.
|
||||
2. Observe temperatures, targets, fan state, print state, filename, progress,
|
||||
elapsed time, and axis homing while the printer changes state.
|
||||
3. Exercise safe idle controls first: home, bed and nozzle temperature, and a
|
||||
harmless G-code command. Verify the printer's action as well as the UI
|
||||
response.
|
||||
4. Send a small file without starting it, then start a small print. Once the
|
||||
print is active, pause it, confirm the printer and UI both enter a paused
|
||||
state, resume it, and confirm both return to printing. Cancel only after
|
||||
observing an active or paused print, then confirm that the printer stops and
|
||||
the UI leaves that state. Cancel an upload and retry a missing input so that
|
||||
failure handling is observed too.
|
||||
5. For Moonraker command workers, send three harmless, uniquely marked commands
|
||||
while a proxy, network shaper, or request log introduces or records latency.
|
||||
Pass only if the printer-side log records the markers in the same order they
|
||||
were queued. Record the latency method and the observed order.
|
||||
6. For a material system, verify populated slots, empty slots, material, colour,
|
||||
refresh after a change, and cleanup when the system is absent or filament is
|
||||
removed. The latter must remove obsolete slot or material data from the UI.
|
||||
Do not infer write support from read support.
|
||||
7. For Qidi, use a compatible single-nozzle slice and Send it while the Device
|
||||
tab has no reported nozzle diameter or type. Pass only if Send proceeds past
|
||||
preflight without `PrintStatusNozzleDataInvalid`; record any reported
|
||||
diameter/type and any mismatch message separately. This checks the intended
|
||||
tolerance for missing identity data, not that a mismatched known nozzle is
|
||||
safe.
|
||||
8. Verify the camera only on hardware that advertises or implements it. Check
|
||||
that frames advance, switching printers starts the newly selected camera, and
|
||||
closing or changing the view does not leave misleading stale output. For
|
||||
Snapmaker, also test immediately before and after 300 seconds in an
|
||||
uninterrupted view. The expected renewal result is unknown until hardware
|
||||
evidence exists. Swap agents and shut down the app after the camera cases to
|
||||
exercise teardown around the detached callback's raw-`this` lifetime risk.
|
||||
9. For Moonraker thumbnails, test a reused filename after its thumbnail changes,
|
||||
responses that use `thumbnail_path` and `relative_path`, and a thumbnail in a
|
||||
subdirectory below the G-code root. Record the endpoint payload and displayed
|
||||
image for each case.
|
||||
10. Disconnect and reconnect the printer, then confirm that new status messages
|
||||
still reach the UI. A reconnect completion alone is insufficient evidence.
|
||||
11. Test network and response failures for discovery, status, G-code, upload,
|
||||
and print start. For each operation, exercise HTTP 401, 404, and 500,
|
||||
invalid JSON, and a refused socket with a controlled proxy or test server.
|
||||
Each case must fail clearly or offer a retry, without a crash or a false
|
||||
success. Record the operation, injected failure, UI result, and any retry.
|
||||
|
||||
For Moonraker, record whether the thumbnail endpoint returns the response shape
|
||||
the agent expects. That response has not yet been verified across a live
|
||||
Moonraker deployment.
|
||||
|
||||
## Troubleshooting by symptom
|
||||
|
||||
### Connection appears successful but the Device tab stays stale
|
||||
|
||||
Treat status freshness as the connection result. Enable `ORCA_NETWORK_DEBUG` and
|
||||
look for a new `parse_json: dev_id=` entry after the connection or reconnection.
|
||||
The unresolved reconnect-delivery problem can complete the second connection
|
||||
without delivering any new parsed messages. Capture an instrumented second
|
||||
connection before changing dispatch or message-delay logic, because both remain
|
||||
plausible causes.
|
||||
|
||||
Check identity too. One path can use a bare IP address while another uses
|
||||
`host:port`; configuring a port can therefore create two machine objects. Do not
|
||||
diagnose a duplicate as a printer-agent failure until the identities are
|
||||
compared.
|
||||
|
||||
### A control reports success but the printer did not change
|
||||
|
||||
First establish that the command has a documented translation in the capability
|
||||
matrix. Unsupported commands are deliberately rejected rather than silently
|
||||
accepted. For Moonraker, queued controls are asynchronous, so wait for the
|
||||
printer-side result and capture the request or log before concluding it was lost.
|
||||
|
||||
Moonraker jog is a special case. The current path can leave the printer in
|
||||
relative positioning mode after a jog. Do not use it as a general verification
|
||||
control until it is changed to save state, issue `G91` and the move, then restore
|
||||
state with `SAVE_GCODE_STATE` and `RESTORE_GCODE_STATE`. Extruder-relative moves
|
||||
use a separate `M83` path.
|
||||
|
||||
### A thumbnail is missing or belongs to an earlier print
|
||||
|
||||
The thumbnail lookup accepts both `thumbnail_path` and `relative_path`, but the
|
||||
live endpoint response is not yet verified. The cache is keyed by filename, so
|
||||
reusing a common name can retain the previous image. Test a distinct filename
|
||||
before changing the lookup. Paths below the G-code root also need live coverage
|
||||
for the `relative_path` fallback.
|
||||
|
||||
### Filament looks stale, blank, or does not follow an edit
|
||||
|
||||
Moonraker-family agents use pull-mode filament sync. Verify the pull request and
|
||||
the resulting Device-tab update rather than expecting a subscription callback.
|
||||
Read-side discovery does not establish load, unload, slot-setting, or Auto Refill
|
||||
support. Happy Hare and AFC macro names are printer-side configuration; do not
|
||||
guess them. A guessed macro can silently do nothing or issue the wrong action.
|
||||
|
||||
### A Python agent disappears or cannot be enabled
|
||||
|
||||
Check its agent ID first. A duplicate ID is rejected and the conflicting
|
||||
capability is disabled rather than auto-promoted later, because automatic
|
||||
promotion could change the active printer implementation without user intent.
|
||||
Reload and teardown also need a live check: registration tests cover lifecycle
|
||||
logic, but a plug-in can still be exposed to API drift or a teardown race in a
|
||||
real session.
|
||||
|
||||
## Known defects and safeguards
|
||||
|
||||
- Reconnect delivery remains unresolved. Instrument the second connection before
|
||||
attempting a fix; the observed failure is stale data after a completed reconnect.
|
||||
- Moonraker jog can leave relative mode active. Keep the future state-save and
|
||||
restore sequence together so the jog cannot affect later G-code positioning.
|
||||
- Qidi's nozzle-data Send-preflight tolerance for unreported diameter and type
|
||||
has not received a hardware verification.
|
||||
- A configured `host:port` can coexist with a bare-IP machine identity. This can
|
||||
duplicate devices and confuse selection.
|
||||
- Moonraker thumbnail caching can show an old image when a filename is reused.
|
||||
- Moonraker filament data can be stale, and its pull/read path does not provide
|
||||
safe generic write-side MMU operations.
|
||||
- Duplicate plug-in agent IDs are rejected. There is no automatic fallback to a
|
||||
losing capability after the winner unloads.
|
||||
- Plug-in implementations can drift from the Python printer-agent API. Treat an
|
||||
import or interface error as a plug-in compatibility issue until proved otherwise.
|
||||
- Snapmaker camera callbacks can outlive their view during agent replacement or
|
||||
shutdown because the detached path retains a raw `this` pointer. Treat a crash
|
||||
or stale callback during those transitions as a source-derived use-after-free
|
||||
risk until the lifetime is made explicit.
|
||||
- Snapmaker writes an IP-specific local camera HTML file below the application
|
||||
cache. The source contains no cleanup path, so residual files can accumulate
|
||||
for each unique printer IP. This is source-derived and was not reproduced.
|
||||
|
||||
## Not yet hardware-verified
|
||||
|
||||
- Moonraker command-worker FIFO behavior under recorded or injected network
|
||||
latency.
|
||||
- Moonraker thumbnail responses: reused filenames, `thumbnail_path`,
|
||||
`relative_path`, and subdirectory paths.
|
||||
- Snapmaker camera behavior on a live U1: frames, renewal across a long-open
|
||||
view including the 300-second boundary, switching between printers, agent
|
||||
replacement, and shutdown teardown.
|
||||
- Whether Snapmaker's renewal cadence prevents a stale-frame interval. Do not
|
||||
shorten it as a workaround without resolving the printer-side behavior first.
|
||||
- Creality CFS detection and preset scoring on a real printer.
|
||||
- Qidi Send preflight when firmware omits nozzle diameter and type.
|
||||
- Moonraker-family write-side MMU commands. They remain blocked on verified,
|
||||
printer-specific macro contracts.
|
||||
|
||||
## Background - source locations for maintainers
|
||||
|
||||
The Moonraker command worker, status stream, print path, and thumbnail
|
||||
lookup live in `MoonrakerPrinterAgent`. Qidi maps its material box before routing
|
||||
to the Moonraker base. Snapmaker adds the camera start request and its snapshot
|
||||
page. Python agent registration and conflict handling live in
|
||||
`NetworkAgentFactory`.
|
||||
@@ -1,111 +0,0 @@
|
||||
# Move `wxInspectable` into `DPIAware` — Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Move `wxInspector::wxInspectable` from individual leaf classes into the common `DPIAware<P>` template so every DPIAware widget is automatically inspectable and gets the inspector keyboard shortcut.
|
||||
|
||||
**Architecture:** `DPIAware<P>` gains `wxInspector::wxInspectable` as a second base class and calls `SetupInspectorAccelerator(this)` in its constructor. `DPIDialog` and `MainFrame` drop their now-redundant `wxInspectable` inheritance and `SetupInspectorAccelerator` calls.
|
||||
|
||||
**Tech Stack:** C++17, wxWidgets, wxInspector
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Build with `D:\VisualStudio\2026\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe`
|
||||
- Use `--config RelWithDebInfo` for all builds
|
||||
- Cross-platform: must compile on Windows, macOS, and Linux
|
||||
- Match existing code style: PascalCase classes, `#pragma once`
|
||||
- Do NOT commit files under `.superpowers/`
|
||||
- Do NOT commit `task.md`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Move `wxInspectable` and `SetupInspectorAccelerator` into `DPIAware<P>`
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/slic3r/GUI/GUI_Utils.hpp:92` (DPIAware template — add wxInspectable base + SetupInspectorAccelerator call)
|
||||
- Modify: `src/slic3r/GUI/GUI_Utils.hpp:276` (DPIDialog — drop wxInspectable + SetupInspectorAccelerator)
|
||||
- Modify: `src/slic3r/GUI/MainFrame.hpp:96` (MainFrame — drop wxInspectable)
|
||||
- Modify: `src/slic3r/GUI/MainFrame.cpp:304` (MainFrame constructor — drop SetupInspectorAccelerator)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Nothing (standalone refactor)
|
||||
- Produces: All DPIAware widgets automatically inherit `wxInspector::wxInspectable` and get Ctrl+Shift+I accelerator
|
||||
|
||||
- [ ] **Step 1: Add `wxInspectable` to `DPIAware<P>` and call `SetupInspectorAccelerator`**
|
||||
|
||||
In `src/slic3r/GUI/GUI_Utils.hpp`, line 92, change the base class:
|
||||
|
||||
```cpp
|
||||
// Before:
|
||||
template<class P> class DPIAware : public P
|
||||
// After:
|
||||
template<class P> class DPIAware : public P, public wxInspector::wxInspectable
|
||||
```
|
||||
|
||||
In the constructor body of `DPIAware<P>`, after `this->CenterOnParent();` (currently line 110), add:
|
||||
|
||||
```cpp
|
||||
SetupInspectorAccelerator(this);
|
||||
```
|
||||
|
||||
(`<wx/inspector/inspector.h>` is already included at line 23.)
|
||||
|
||||
- [ ] **Step 2: Remove redundant `wxInspectable` and `SetupInspectorAccelerator` from `DPIDialog`**
|
||||
|
||||
In `src/slic3r/GUI/GUI_Utils.hpp`, line 276, change:
|
||||
|
||||
```cpp
|
||||
// Before:
|
||||
class DPIDialog : public DPIAware<wxDialog>, public wxInspector::wxInspectable
|
||||
// After:
|
||||
class DPIDialog : public DPIAware<wxDialog>
|
||||
```
|
||||
|
||||
In the `DPIDialog` constructor body, remove the `SetupInspectorAccelerator(this);` line (currently line 286). The rest of the constructor stays.
|
||||
|
||||
- [ ] **Step 3: Remove redundant `wxInspectable` from `MainFrame`**
|
||||
|
||||
In `src/slic3r/GUI/MainFrame.hpp`, line 96, change:
|
||||
|
||||
```cpp
|
||||
// Before:
|
||||
class MainFrame : public DPIFrame, public wxInspector::wxInspectable
|
||||
// After:
|
||||
class MainFrame : public DPIFrame
|
||||
```
|
||||
|
||||
`MainFrame` now gets `wxInspectable` through `DPIFrame` → `DPIAware<wxFrame>`.
|
||||
|
||||
- [ ] **Step 4: Remove redundant `SetupInspectorAccelerator` from `MainFrame` constructor**
|
||||
|
||||
In `src/slic3r/GUI/MainFrame.cpp`, line 304, remove the line:
|
||||
|
||||
```cpp
|
||||
SetupInspectorAccelerator(this);
|
||||
```
|
||||
|
||||
It is now called automatically by the `DPIAware<wxFrame>` constructor.
|
||||
|
||||
- [ ] **Step 5: Build to verify compilation**
|
||||
|
||||
```powershell
|
||||
$cmakePath = "D:\VisualStudio\2026\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe"
|
||||
& $cmakePath --build . --config RelWithDebInfo --target ALL_BUILD -- -m
|
||||
```
|
||||
|
||||
Expected: Build succeeds with zero new errors or warnings.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add src/slic3r/GUI/GUI_Utils.hpp src/slic3r/GUI/MainFrame.hpp src/slic3r/GUI/MainFrame.cpp
|
||||
git commit -m "refactor: move wxInspectable and SetupInspectorAccelerator into DPIAware
|
||||
|
||||
DPIAware<P> now inherits wxInspector::wxInspectable and calls
|
||||
SetupInspectorAccelerator in its constructor, making all DPIAware
|
||||
widgets automatically appear in the inspector tree with the
|
||||
Ctrl+Shift+I shortcut. Remove redundant wxInspectable inheritance
|
||||
and SetupInspectorAccelerator calls from DPIDialog and MainFrame.
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||
```
|
||||
@@ -1,753 +0,0 @@
|
||||
# wxInspector Plugins for OrcaSlicer — Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Build two wxInspector plugins (DPIAware + CustomWidgets) that expose OrcaSlicer custom control properties in the inspector's property grid.
|
||||
|
||||
**Architecture:** Two plugins in a shared folder under `src/slic3r/Utils/wxInspectorPlugins/`. DPIAwarePlugin uses `dynamic_cast<DPIFrame*>/<DPIDialog*>` for detection; CustomWidgetsPlugin uses per-type `dynamic_cast`. Both registered as static singletons via a single inline function in `Registration.hpp`, called from `MainFrame` constructor.
|
||||
|
||||
**Tech Stack:** C++17, wxWidgets, wxInspector plugin API (`wx/inspector/plugin.h`, `wx/inspector/inspector.h`), OrcaSlicer custom widget headers
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Plugins placed under `src/slic3r/Utils/wxInspectorPlugins/`
|
||||
- Build with `D:\VisualStudio\2026\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe`
|
||||
- Minimal source changes: only trivial (one-line) getters/setters added to existing classes
|
||||
- Cross-platform: must compile on Windows, macOS, and Linux
|
||||
- Match existing code style: PascalCase classes, snake_case functions, `#pragma once`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add getters/setters to existing Orca widget headers
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/slic3r/GUI/GUI_Utils.hpp` (DPIAware template — add 4 methods)
|
||||
- Modify: `src/slic3r/GUI/Widgets/Button.hpp` (add 3 getters)
|
||||
- Modify: `src/slic3r/GUI/Widgets/CheckBox.hpp` (add 1 getter)
|
||||
- Modify: `src/slic3r/GUI/Widgets/TextInput.hpp` (add 1 getter)
|
||||
- Modify: `src/slic3r/GUI/Widgets/LabeledStaticBox.hpp` (add 4 getter declarations)
|
||||
- Modify: `src/slic3r/GUI/Widgets/LabeledStaticBox.cpp` (add 4 getter implementations)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Nothing (prerequisite for all other tasks)
|
||||
- Produces:
|
||||
- `DPIAware<P>::set_scale_factor(float)`, `DPIAware<P>::set_prev_scale_factor(float)`, `DPIAware<P>::set_em_unit(int)`, `DPIAware<P>::force_rescale() const`
|
||||
- `Button::GetStyle()`, `Button::GetType()`, `Button::IsSelected()`
|
||||
- `CheckBox::IsHalfChecked()`
|
||||
- `TextInput::GetCornerRadius()`
|
||||
- `LabeledStaticBox::GetCornerRadius()`, `LabeledStaticBox::GetBorderWidth()`, `LabeledStaticBox::GetBorderColor()`, `LabeledStaticBox::GetScale()`
|
||||
|
||||
- [ ] **Step 1: Add DPIAware setters/getter in GUI_Utils.hpp**
|
||||
|
||||
After line 184 (`float prev_scale_factor() const { return m_prev_scale_factor; }`), add:
|
||||
|
||||
```cpp
|
||||
void set_scale_factor(float v) { m_scale_factor = v; }
|
||||
void set_prev_scale_factor(float v) { m_prev_scale_factor = v; }
|
||||
void set_em_unit(int v) { m_em_unit = v; }
|
||||
bool force_rescale() const { return m_force_rescale; }
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add Button getters in Button.hpp**
|
||||
|
||||
After line 79 (`void SetSelected(bool selected = true) { m_selected = selected; }`), add:
|
||||
|
||||
```cpp
|
||||
ButtonStyle GetStyle() const { return m_style; }
|
||||
ButtonType GetType() const { return m_type; }
|
||||
bool IsSelected() const { return m_selected; }
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add CheckBox getter in CheckBox.hpp**
|
||||
|
||||
After line 16 (`void SetHalfChecked(bool value = true);`), add:
|
||||
|
||||
```cpp
|
||||
bool IsHalfChecked() const { return m_half_checked; }
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add TextInput getter in TextInput.hpp**
|
||||
|
||||
After line 44 (`void SetCornerRadius(double radius);`), add:
|
||||
|
||||
```cpp
|
||||
int GetCornerRadius() const { return static_cast<int>(radius); }
|
||||
```
|
||||
|
||||
(Note: `radius` is inherited from `StaticBox` which has it as a protected `double` member.)
|
||||
|
||||
- [ ] **Step 5: Add LabeledStaticBox getter declarations in LabeledStaticBox.hpp**
|
||||
|
||||
After line 46 (`bool Enable(bool enable) override;`), add:
|
||||
|
||||
```cpp
|
||||
int GetCornerRadius() const { return m_radius; }
|
||||
int GetBorderWidth() const { return m_border_width; }
|
||||
StateColor GetBorderColor() const { return border_color; }
|
||||
float GetScale() const { return m_scale; }
|
||||
```
|
||||
|
||||
(Note: all of `m_radius`, `m_border_width`, `border_color`, `m_scale` are protected members, accessible to inline methods.)
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add src/slic3r/GUI/GUI_Utils.hpp src/slic3r/GUI/Widgets/Button.hpp src/slic3r/GUI/Widgets/CheckBox.hpp src/slic3r/GUI/Widgets/TextInput.hpp src/slic3r/GUI/Widgets/LabeledStaticBox.hpp
|
||||
git commit -m "feat: add getters/setters for wxInspector plugin access
|
||||
|
||||
Add minimal public accessors to DPIAware (set_scale_factor,
|
||||
set_prev_scale_factor, set_em_unit, force_rescale), Button
|
||||
(GetStyle, GetType, IsSelected), CheckBox (IsHalfChecked),
|
||||
TextInput (GetCornerRadius), and LabeledStaticBox
|
||||
(GetCornerRadius, GetBorderWidth, GetBorderColor, GetScale)."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Create Registration helper header
|
||||
|
||||
**Files:**
|
||||
- Create: `src/slic3r/Utils/wxInspectorPlugins/Registration.hpp`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Nothing (forward-declares plugin classes)
|
||||
- Produces: `RegisterOrcaInspectorPlugins()`
|
||||
|
||||
- [ ] **Step 1: Create directory**
|
||||
|
||||
```bash
|
||||
mkdir -p src/slic3r/Utils/wxInspectorPlugins
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write Registration.hpp**
|
||||
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
namespace wxInspector {
|
||||
class wxInspectorPlugin;
|
||||
void RegisterPlugin(wxInspectorPlugin* plugin);
|
||||
}
|
||||
|
||||
// Forward declare our plugins
|
||||
class DPIAwarePlugin;
|
||||
class CustomWidgetsPlugin;
|
||||
|
||||
inline void RegisterOrcaInspectorPlugins()
|
||||
{
|
||||
static DPIAwarePlugin dpiaware;
|
||||
static CustomWidgetsPlugin customWidgets;
|
||||
wxInspector::RegisterPlugin(&dpiaware);
|
||||
wxInspector::RegisterPlugin(&customWidgets);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/slic3r/Utils/wxInspectorPlugins/Registration.hpp
|
||||
git commit -m "feat: add wxInspector plugin registration helper
|
||||
|
||||
Add RegisterOrcaInspectorPlugins() inline function that creates
|
||||
and registers the DPIAwarePlugin and CustomWidgetsPlugin as
|
||||
static instances (matching wxInspector's built-in pattern)."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Create DPIAwarePlugin
|
||||
|
||||
**Files:**
|
||||
- Create: `src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.hpp`
|
||||
- Create: `src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.cpp`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Task 1 (DPIAware getters/setters), Task 2 (registration pattern)
|
||||
- Produces: `class DPIAwarePlugin : public wxInspector::wxInspectorPlugin`
|
||||
|
||||
- [ ] **Step 1: Write DPIAwarePlugin.hpp**
|
||||
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
#include <wx/inspector/plugin.h>
|
||||
|
||||
class DPIAwarePlugin : public wxInspector::wxInspectorPlugin
|
||||
{
|
||||
public:
|
||||
wxString GetName() const override;
|
||||
|
||||
bool CanProvideProperties(wxClassInfo* info) override;
|
||||
|
||||
wxVector<wxInspector::PropertyDef> GetProperties(
|
||||
wxInspector::InspectableObject& obj) override;
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write DPIAwarePlugin.cpp**
|
||||
|
||||
```cpp
|
||||
#include "DPIAwarePlugin.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI_Utils.hpp" // DPIFrame, DPIDialog, DPIAware<P>
|
||||
|
||||
#include <wx/window.h>
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename T>
|
||||
void addDPIProps(T* dpi, wxVector<wxInspector::PropertyDef>& props)
|
||||
{
|
||||
using namespace wxInspector;
|
||||
|
||||
props.push_back({"Scale Factor", "DPI Scaling", PropertyType::String,
|
||||
wxString::Format("%.2f", dpi->scale_factor()), false, {},
|
||||
[dpi]() { return wxString::Format("%.2f", dpi->scale_factor()); },
|
||||
[dpi](const wxString& v) {
|
||||
double val;
|
||||
if (wxSscanf(v, "%lf", &val) != 1) return false;
|
||||
dpi->set_scale_factor((float) val);
|
||||
return true;
|
||||
}});
|
||||
|
||||
props.push_back({"Prev Scale Factor", "DPI Scaling", PropertyType::String,
|
||||
wxString::Format("%.2f", dpi->prev_scale_factor()), false, {},
|
||||
[dpi]() { return wxString::Format("%.2f", dpi->prev_scale_factor()); },
|
||||
[dpi](const wxString& v) {
|
||||
double val;
|
||||
if (wxSscanf(v, "%lf", &val) != 1) return false;
|
||||
dpi->set_prev_scale_factor((float) val);
|
||||
return true;
|
||||
}});
|
||||
|
||||
props.push_back({"EM Unit", "DPI Scaling", PropertyType::Integer,
|
||||
wxString::Format("%d", dpi->em_unit()), false, {},
|
||||
[dpi]() { return wxString::Format("%d", dpi->em_unit()); },
|
||||
[dpi](const wxString& v) {
|
||||
long val;
|
||||
if (!v.ToLong(&val)) return false;
|
||||
dpi->set_em_unit((int) val);
|
||||
return true;
|
||||
}});
|
||||
|
||||
props.push_back({"Normal Font", "DPI Scaling", PropertyType::ReadOnly,
|
||||
dpi->normal_font().GetNativeFontInfoDesc(), true, {},
|
||||
[dpi]() { return dpi->normal_font().GetNativeFontInfoDesc(); },
|
||||
nullptr});
|
||||
|
||||
props.push_back({"Force Rescale", "DPI Scaling", PropertyType::Boolean,
|
||||
dpi->force_rescale() ? "true" : "false", true, {},
|
||||
[dpi]() { return dpi->force_rescale() ? "true" : "false"; },
|
||||
nullptr});
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
wxString DPIAwarePlugin::GetName() const
|
||||
{
|
||||
return "OrcaDPIAware";
|
||||
}
|
||||
|
||||
bool DPIAwarePlugin::CanProvideProperties(wxClassInfo* info)
|
||||
{
|
||||
return info->IsKindOf(CLASSINFO(wxWindow));
|
||||
}
|
||||
|
||||
wxVector<wxInspector::PropertyDef> DPIAwarePlugin::GetProperties(
|
||||
wxInspector::InspectableObject& obj)
|
||||
{
|
||||
wxVector<wxInspector::PropertyDef> props;
|
||||
wxWindow* win = obj.AsWindow();
|
||||
if (!win) return props;
|
||||
|
||||
if (auto* frame = dynamic_cast<DPIFrame*>(win)) {
|
||||
addDPIProps(frame, props);
|
||||
} else if (auto* dlg = dynamic_cast<DPIDialog*>(win)) {
|
||||
addDPIProps(dlg, props);
|
||||
}
|
||||
|
||||
return props;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.hpp src/slic3r/Utils/wxInspectorPlugins/DPIAwarePlugin.cpp
|
||||
git commit -m "feat: add DPIAware wxInspector plugin
|
||||
|
||||
Exposes DPI scaling properties (scale_factor, prev_scale_factor,
|
||||
em_unit, normal_font, force_rescale) on DPIFrame and DPIDialog
|
||||
widgets. Uses dynamic_cast for detection and a template helper
|
||||
to capture the correct static type for lambda accessors."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Create CustomWidgetsPlugin
|
||||
|
||||
**Files:**
|
||||
- Create: `src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp`
|
||||
- Create: `src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Task 1 (all widget getters), Task 2 (registration pattern)
|
||||
- Produces: `class CustomWidgetsPlugin : public wxInspector::wxInspectorPlugin`
|
||||
|
||||
- [ ] **Step 1: Write CustomWidgetsPlugin.hpp**
|
||||
|
||||
```cpp
|
||||
#pragma once
|
||||
|
||||
#include <wx/inspector/plugin.h>
|
||||
|
||||
class CustomWidgetsPlugin : public wxInspector::wxInspectorPlugin
|
||||
{
|
||||
public:
|
||||
wxString GetName() const override;
|
||||
|
||||
bool CanProvideProperties(wxClassInfo* info) override;
|
||||
|
||||
wxVector<wxInspector::PropertyDef> GetProperties(
|
||||
wxInspector::InspectableObject& obj) override;
|
||||
|
||||
private:
|
||||
void addButtonProps(class Button* btn,
|
||||
wxVector<wxInspector::PropertyDef>& props);
|
||||
void addCheckBoxProps(class CheckBox* cb,
|
||||
wxVector<wxInspector::PropertyDef>& props);
|
||||
void addTextInputProps(class TextInput* ti,
|
||||
wxVector<wxInspector::PropertyDef>& props);
|
||||
void addSwitchButtonProps(class SwitchButton* sb,
|
||||
wxVector<wxInspector::PropertyDef>& props);
|
||||
void addProgressBarProps(class ProgressBar* pb,
|
||||
wxVector<wxInspector::PropertyDef>& props);
|
||||
void addLabelProps(class Label* lbl,
|
||||
wxVector<wxInspector::PropertyDef>& props);
|
||||
void addLabeledStaticBoxProps(class LabeledStaticBox* lsb,
|
||||
wxVector<wxInspector::PropertyDef>& props);
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write CustomWidgetsPlugin.cpp — includes and GetName/CanProvideProperties**
|
||||
|
||||
```cpp
|
||||
#include "CustomWidgetsPlugin.hpp"
|
||||
|
||||
#include "slic3r/GUI/Widgets/Button.hpp"
|
||||
#include "slic3r/GUI/Widgets/CheckBox.hpp"
|
||||
#include "slic3r/GUI/Widgets/TextInput.hpp"
|
||||
#include "slic3r/GUI/Widgets/SwitchButton.hpp"
|
||||
#include "slic3r/GUI/Widgets/ProgressBar.hpp"
|
||||
#include "slic3r/GUI/Widgets/Label.hpp"
|
||||
#include "slic3r/GUI/Widgets/LabeledStaticBox.hpp"
|
||||
|
||||
#include <wx/window.h>
|
||||
#include <wx/tglbtn.h>
|
||||
|
||||
wxString CustomWidgetsPlugin::GetName() const
|
||||
{
|
||||
return "OrcaCustomWidgets";
|
||||
}
|
||||
|
||||
bool CustomWidgetsPlugin::CanProvideProperties(wxClassInfo* info)
|
||||
{
|
||||
return info->IsKindOf(CLASSINFO(wxWindow));
|
||||
}
|
||||
|
||||
wxVector<wxInspector::PropertyDef> CustomWidgetsPlugin::GetProperties(
|
||||
wxInspector::InspectableObject& obj)
|
||||
{
|
||||
wxVector<wxInspector::PropertyDef> props;
|
||||
wxWindow* win = obj.AsWindow();
|
||||
if (!win) return props;
|
||||
|
||||
if (auto* btn = dynamic_cast<Button*>(win))
|
||||
addButtonProps(btn, props);
|
||||
if (auto* cb = dynamic_cast<CheckBox*>(win))
|
||||
addCheckBoxProps(cb, props);
|
||||
if (auto* ti = dynamic_cast<TextInput*>(win))
|
||||
addTextInputProps(ti, props);
|
||||
if (auto* sb = dynamic_cast<SwitchButton*>(win))
|
||||
addSwitchButtonProps(sb, props);
|
||||
if (auto* pb = dynamic_cast<ProgressBar*>(win))
|
||||
addProgressBarProps(pb, props);
|
||||
if (auto* lbl = dynamic_cast<Label*>(win))
|
||||
addLabelProps(lbl, props);
|
||||
if (auto* lsb = dynamic_cast<LabeledStaticBox*>(win))
|
||||
addLabeledStaticBoxProps(lsb, props);
|
||||
|
||||
return props;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Write CustomWidgetsPlugin.cpp — addButtonProps**
|
||||
|
||||
```cpp
|
||||
void CustomWidgetsPlugin::addButtonProps(Button* btn,
|
||||
wxVector<wxInspector::PropertyDef>& props)
|
||||
{
|
||||
using namespace wxInspector;
|
||||
|
||||
wxVector<wxString> styleChoices;
|
||||
styleChoices.push_back("Regular");
|
||||
styleChoices.push_back("Confirm");
|
||||
styleChoices.push_back("Alert");
|
||||
styleChoices.push_back("Disabled");
|
||||
|
||||
auto styleToStr = [](ButtonStyle s) -> wxString {
|
||||
switch (s) {
|
||||
case ButtonStyle::Regular: return "Regular";
|
||||
case ButtonStyle::Confirm: return "Confirm";
|
||||
case ButtonStyle::Alert: return "Alert";
|
||||
case ButtonStyle::Disabled: return "Disabled";
|
||||
}
|
||||
return "Regular";
|
||||
};
|
||||
|
||||
props.push_back({"Button Style", "Orca Button", PropertyType::Choice,
|
||||
styleToStr(btn->GetStyle()), false, styleChoices,
|
||||
[btn, styleToStr]() { return styleToStr(btn->GetStyle()); },
|
||||
[btn](const wxString& v) {
|
||||
ButtonStyle s = ButtonStyle::Regular;
|
||||
if (v == "Confirm") s = ButtonStyle::Confirm;
|
||||
else if (v == "Alert") s = ButtonStyle::Alert;
|
||||
else if (v == "Disabled") s = ButtonStyle::Disabled;
|
||||
btn->SetStyle(s, btn->GetType());
|
||||
return true;
|
||||
}});
|
||||
|
||||
wxVector<wxString> typeChoices;
|
||||
typeChoices.push_back("Compact");
|
||||
typeChoices.push_back("Window");
|
||||
typeChoices.push_back("Choice");
|
||||
typeChoices.push_back("Parameter");
|
||||
typeChoices.push_back("Icon");
|
||||
typeChoices.push_back("Expanded");
|
||||
|
||||
auto typeToStr = [](ButtonType t) -> wxString {
|
||||
switch (t) {
|
||||
case ButtonType::Compact: return "Compact";
|
||||
case ButtonType::Window: return "Window";
|
||||
case ButtonType::Choice: return "Choice";
|
||||
case ButtonType::Parameter: return "Parameter";
|
||||
case ButtonType::Icon: return "Icon";
|
||||
case ButtonType::Expanded: return "Expanded";
|
||||
}
|
||||
return "Compact";
|
||||
};
|
||||
|
||||
props.push_back({"Button Type", "Orca Button", PropertyType::Choice,
|
||||
typeToStr(btn->GetType()), false, typeChoices,
|
||||
[btn, typeToStr]() { return typeToStr(btn->GetType()); },
|
||||
[btn](const wxString& v) {
|
||||
ButtonType t = ButtonType::Compact;
|
||||
if (v == "Window") t = ButtonType::Window;
|
||||
else if (v == "Choice") t = ButtonType::Choice;
|
||||
else if (v == "Parameter") t = ButtonType::Parameter;
|
||||
else if (v == "Icon") t = ButtonType::Icon;
|
||||
else if (v == "Expanded") t = ButtonType::Expanded;
|
||||
btn->SetStyle(btn->GetStyle(), t);
|
||||
return true;
|
||||
}});
|
||||
|
||||
props.push_back({"Selected", "Orca Button", PropertyType::Boolean,
|
||||
btn->IsSelected() ? "true" : "false", false, {},
|
||||
[btn]() { return btn->IsSelected() ? "true" : "false"; },
|
||||
[btn](const wxString& v) {
|
||||
btn->SetSelected(v == "true");
|
||||
btn->Refresh();
|
||||
return true;
|
||||
}});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Write CustomWidgetsPlugin.cpp — addCheckBoxProps**
|
||||
|
||||
```cpp
|
||||
void CustomWidgetsPlugin::addCheckBoxProps(CheckBox* cb,
|
||||
wxVector<wxInspector::PropertyDef>& props)
|
||||
{
|
||||
using namespace wxInspector;
|
||||
|
||||
props.push_back({"Half Checked", "Orca CheckBox", PropertyType::Boolean,
|
||||
cb->IsHalfChecked() ? "true" : "false", false, {},
|
||||
[cb]() { return cb->IsHalfChecked() ? "true" : "false"; },
|
||||
[cb](const wxString& v) {
|
||||
cb->SetHalfChecked(v == "true");
|
||||
return true;
|
||||
}});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Write CustomWidgetsPlugin.cpp — addTextInputProps**
|
||||
|
||||
```cpp
|
||||
void CustomWidgetsPlugin::addTextInputProps(TextInput* ti,
|
||||
wxVector<wxInspector::PropertyDef>& props)
|
||||
{
|
||||
using namespace wxInspector;
|
||||
|
||||
props.push_back({"Label", "Orca TextInput", PropertyType::String,
|
||||
ti->GetLabel(), false, {},
|
||||
[ti]() { return ti->GetLabel(); },
|
||||
[ti](const wxString& v) { ti->SetLabel(v); return true; }});
|
||||
|
||||
props.push_back({"Text Value", "Orca TextInput", PropertyType::String,
|
||||
ti->GetTextCtrl()->GetValue(), false, {},
|
||||
[ti]() { return ti->GetTextCtrl()->GetValue(); },
|
||||
[ti](const wxString& v) { ti->GetTextCtrl()->SetValue(v); return true; }});
|
||||
|
||||
props.push_back({"Corner Radius", "Orca TextInput", PropertyType::Integer,
|
||||
wxString::Format("%d", ti->GetCornerRadius()), false, {},
|
||||
[ti]() { return wxString::Format("%d", ti->GetCornerRadius()); },
|
||||
[ti](const wxString& v) {
|
||||
long val;
|
||||
if (!v.ToLong(&val)) return false;
|
||||
ti->SetCornerRadius((double) val);
|
||||
ti->Refresh();
|
||||
return true;
|
||||
}});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Write CustomWidgetsPlugin.cpp — addSwitchButtonProps**
|
||||
|
||||
```cpp
|
||||
void CustomWidgetsPlugin::addSwitchButtonProps(SwitchButton* sb,
|
||||
wxVector<wxInspector::PropertyDef>& props)
|
||||
{
|
||||
using namespace wxInspector;
|
||||
|
||||
props.push_back({"Value", "Orca SwitchButton", PropertyType::Boolean,
|
||||
sb->GetValue() ? "true" : "false", false, {},
|
||||
[sb]() { return sb->GetValue() ? "true" : "false"; },
|
||||
[sb](const wxString& v) {
|
||||
sb->SetValue(v == "true");
|
||||
return true;
|
||||
}});
|
||||
}
|
||||
```
|
||||
|
||||
(Note: `GetValue()` and `SetValue()` are inherited from `wxBitmapToggleButton` → `wxToggleButton`.)
|
||||
|
||||
- [ ] **Step 7: Write CustomWidgetsPlugin.cpp — addProgressBarProps**
|
||||
|
||||
```cpp
|
||||
void CustomWidgetsPlugin::addProgressBarProps(ProgressBar* pb,
|
||||
wxVector<wxInspector::PropertyDef>& props)
|
||||
{
|
||||
using namespace wxInspector;
|
||||
|
||||
props.push_back({"Proportion", "Orca ProgressBar", PropertyType::String,
|
||||
wxString::Format("%.2f", pb->m_proportion), false, {},
|
||||
[pb]() { return wxString::Format("%.2f", pb->m_proportion); },
|
||||
[pb](const wxString& v) {
|
||||
double val;
|
||||
if (wxSscanf(v, "%lf", &val) != 1) return false;
|
||||
pb->m_proportion = val;
|
||||
pb->Refresh();
|
||||
return true;
|
||||
}});
|
||||
|
||||
props.push_back({"Show Number", "Orca ProgressBar", PropertyType::Boolean,
|
||||
pb->m_shownumber ? "true" : "false", false, {},
|
||||
[pb]() { return pb->m_shownumber ? "true" : "false"; },
|
||||
[pb](const wxString& v) {
|
||||
pb->m_shownumber = (v == "true");
|
||||
pb->Refresh();
|
||||
return true;
|
||||
}});
|
||||
}
|
||||
```
|
||||
|
||||
(Note: `m_proportion` and `m_shownumber` are public members on `ProgressBar`.)
|
||||
|
||||
- [ ] **Step 8: Write CustomWidgetsPlugin.cpp — addLabelProps**
|
||||
|
||||
```cpp
|
||||
void CustomWidgetsPlugin::addLabelProps(Label* lbl,
|
||||
wxVector<wxInspector::PropertyDef>& props)
|
||||
{
|
||||
using namespace wxInspector;
|
||||
|
||||
bool isHyperlink = (lbl->GetWindowStyleFlag() & 0x0020) != 0; // LB_HYPERLINK
|
||||
|
||||
props.push_back({"Is Hyperlink", "Orca Label", PropertyType::Boolean,
|
||||
isHyperlink ? "true" : "false", true, {},
|
||||
[lbl]() {
|
||||
return (lbl->GetWindowStyleFlag() & 0x0020) ? "true" : "false";
|
||||
},
|
||||
nullptr});
|
||||
|
||||
props.push_back({"Font Point Size", "Orca Label", PropertyType::ReadOnly,
|
||||
wxString::Format("%d", lbl->GetFont().GetPointSize()), true, {},
|
||||
[lbl]() {
|
||||
return wxString::Format("%d", lbl->GetFont().GetPointSize());
|
||||
},
|
||||
nullptr});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 9: Write CustomWidgetsPlugin.cpp — addLabeledStaticBoxProps**
|
||||
|
||||
```cpp
|
||||
void CustomWidgetsPlugin::addLabeledStaticBoxProps(LabeledStaticBox* lsb,
|
||||
wxVector<wxInspector::PropertyDef>& props)
|
||||
{
|
||||
using namespace wxInspector;
|
||||
|
||||
props.push_back({"Corner Radius", "LabeledStaticBox", PropertyType::Integer,
|
||||
wxString::Format("%d", lsb->GetCornerRadius()), false, {},
|
||||
[lsb]() { return wxString::Format("%d", lsb->GetCornerRadius()); },
|
||||
[lsb](const wxString& v) {
|
||||
long val;
|
||||
if (!v.ToLong(&val)) return false;
|
||||
lsb->SetCornerRadius((int) val);
|
||||
return true;
|
||||
}});
|
||||
|
||||
props.push_back({"Border Width", "LabeledStaticBox", PropertyType::Integer,
|
||||
wxString::Format("%d", lsb->GetBorderWidth()), false, {},
|
||||
[lsb]() { return wxString::Format("%d", lsb->GetBorderWidth()); },
|
||||
[lsb](const wxString& v) {
|
||||
long val;
|
||||
if (!v.ToLong(&val)) return false;
|
||||
lsb->SetBorderWidth((int) val);
|
||||
return true;
|
||||
}});
|
||||
|
||||
// Border Color: display as hex string
|
||||
wxColour bc = lsb->GetBorderColor().colorForStates(0);
|
||||
props.push_back({"Border Color", "LabeledStaticBox", PropertyType::String,
|
||||
bc.GetAsString(wxC2S_HTML_SYNTAX), false, {},
|
||||
[lsb]() {
|
||||
return lsb->GetBorderColor()
|
||||
.colorForStates(0)
|
||||
.GetAsString(wxC2S_HTML_SYNTAX);
|
||||
},
|
||||
[lsb](const wxString& v) {
|
||||
wxColour c(v);
|
||||
if (!c.IsOk()) return false;
|
||||
lsb->SetBorderColor(StateColor(c));
|
||||
return true;
|
||||
}});
|
||||
|
||||
props.push_back({"Scale", "LabeledStaticBox", PropertyType::ReadOnly,
|
||||
wxString::Format("%.2f", lsb->GetScale()), true, {},
|
||||
[lsb]() { return wxString::Format("%.2f", lsb->GetScale()); },
|
||||
nullptr});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 10: Commit**
|
||||
|
||||
```bash
|
||||
git add src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp src/slic3r/Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp
|
||||
git commit -m "feat: add OrcaCustomWidgets wxInspector plugin
|
||||
|
||||
Exposes Orca-specific properties on 7 widget types:
|
||||
- Button: Style, Type, Selected
|
||||
- CheckBox: Half Checked
|
||||
- TextInput: Label, Text Value, Corner Radius
|
||||
- SwitchButton: Value
|
||||
- ProgressBar: Proportion, Show Number
|
||||
- Label: Is Hyperlink, Font Point Size
|
||||
- LabeledStaticBox: Corner Radius, Border Width, Border Color, Scale
|
||||
|
||||
Each widget type uses dynamic_cast for safe detection."
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Wire plugins into MainFrame and CMakeLists
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/slic3r/GUI/MainFrame.cpp` (add include + registration call)
|
||||
- Modify: `src/slic3r/CMakeLists.txt` (add 4 source files)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: Tasks 1-4 (all plugins and registration helper)
|
||||
- Produces: Registered plugins available at runtime, buildable project
|
||||
|
||||
- [ ] **Step 1: Add include in MainFrame.cpp**
|
||||
|
||||
After the existing includes (around line 30, near the other Utils includes), add:
|
||||
|
||||
```cpp
|
||||
#include "slic3r/Utils/wxInspectorPlugins/Registration.hpp"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add registration call in MainFrame constructor**
|
||||
|
||||
After `SetupInspectorAccelerator(this);` (currently line ~303), add:
|
||||
|
||||
```cpp
|
||||
RegisterOrcaInspectorPlugins();
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add source files to CMakeLists.txt**
|
||||
|
||||
Find the `SLIC3R_GUI_SOURCES` list in `src/slic3r/CMakeLists.txt`. After the existing `Utils/*.cpp` entries (around line 650-754), add:
|
||||
|
||||
```cmake
|
||||
Utils/wxInspectorPlugins/DPIAwarePlugin.hpp
|
||||
Utils/wxInspectorPlugins/DPIAwarePlugin.cpp
|
||||
Utils/wxInspectorPlugins/CustomWidgetsPlugin.hpp
|
||||
Utils/wxInspectorPlugins/CustomWidgetsPlugin.cpp
|
||||
Utils/wxInspectorPlugins/Registration.hpp
|
||||
```
|
||||
|
||||
(Note: Add all 5 files — 2 .hpp + 2 .cpp + 1 Registration.hpp. wxWidgets cmake needs headers listed too for the resource system.)
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add src/slic3r/GUI/MainFrame.cpp src/slic3r/CMakeLists.txt
|
||||
git commit -m "feat: wire wxInspector plugins into MainFrame and build
|
||||
|
||||
- Call RegisterOrcaInspectorPlugins() after SetupInspectorAccelerator
|
||||
- Add all plugin source files to SLIC3R_GUI_SOURCES"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Build and verify
|
||||
|
||||
**Files:**
|
||||
- None modified (verification only)
|
||||
|
||||
- [ ] **Step 1: Configure the build**
|
||||
|
||||
```powershell
|
||||
$cmakePath = "D:\VisualStudio\2026\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe"
|
||||
& $cmakePath --build . --config Debug --target ALL_BUILD -- -m
|
||||
```
|
||||
|
||||
Expected: Build succeeds with zero errors and zero warnings from our new files.
|
||||
|
||||
- [ ] **Step 2: Fix any compilation errors**
|
||||
|
||||
If the build fails:
|
||||
- Check that `#include` paths resolve (the `slic3r/GUI/…` relative paths use `src/` as the include root — verify this is set up in CMake via `include_directories`)
|
||||
- Check that `ButtonStyle` and `ButtonType` enums are visible (they're defined in `Button.hpp`)
|
||||
- Check that `StateColor` constructor from `wxColour` is valid (it has `StateColor(wxColour const&)`)
|
||||
- Check that `LabeledStaticBox::GetBorderColor()` returns by value (StateColor copy is fine)
|
||||
- On macOS: static box margin removal call needs `#ifdef __WXOSX__` guard
|
||||
|
||||
- [ ] **Step 3: Launch OrcaSlicer and verify inspector**
|
||||
|
||||
Launch the built OrcaSlicer, press Ctrl+Shift+I to open the inspector:
|
||||
1. Select the MainFrame in the tree — verify "DPI Scaling" category appears with Scale Factor, Prev Scale Factor, EM Unit, Normal Font, Force Rescale
|
||||
2. Select an Orca Button — verify "Orca Button" category appears
|
||||
3. Select an Orca CheckBox — verify "Orca CheckBox" category appears
|
||||
4. Edit a property value (e.g., Scale Factor) — verify the setter applies correctly
|
||||
5. Select a LabeledStaticBox — verify corner radius, border width, border color, scale appear
|
||||
|
||||
- [ ] **Step 5: Commit (if fixes were needed) or mark complete**
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
If clean: verification complete. If changes were made: `git add` and commit with fix message.
|
||||
@@ -1,102 +0,0 @@
|
||||
# Move `wxInspectable` into `DPIAware` — Design Spec
|
||||
|
||||
Date: 2026-07-23
|
||||
Branch: `dev/layout-inspector`
|
||||
|
||||
## Overview
|
||||
|
||||
Move the `wxInspector::wxInspectable` base class from individual leaf classes (`DPIDialog`, `MainFrame`) into the common `DPIAware<P>` template. This makes every DPIAware widget automatically visible in the inspector tree without requiring each subclass to opt in.
|
||||
|
||||
## Motivation
|
||||
|
||||
Currently, only `DPIDialog` and `MainFrame` explicitly inherit `wxInspectable`. `DPIFrame` (which `MainFrame` inherits from) does not — `MainFrame` adds it manually. This means:
|
||||
|
||||
- Any `DPIAware<T>` widget that isn't `DPIDialog` or `MainFrame` is invisible in the inspector tree
|
||||
- `DPIFrame` subclasses (`BaseTransparentDPIFrame`, `ImageDPIFrame`, `ModelMallDialog`, `MediaFileFrame`, `SecondaryCheckDialog`, `PrintErrorDialog`, etc.) don't appear
|
||||
- Adding a new DPIAware widget type requires remembering to also inherit `wxInspectable`
|
||||
|
||||
Moving `wxInspectable` to `DPIAware` fixes this for all current and future DPIAware widgets at once.
|
||||
|
||||
## Design
|
||||
|
||||
### Change 1: `GUI_Utils.hpp` — `DPIAware<P>`
|
||||
|
||||
Add `wxInspector::wxInspectable` as a second base class, and call `SetupInspectorAccelerator(this)` in the constructor (after `this->CenterOnParent()`):
|
||||
|
||||
```cpp
|
||||
// Before:
|
||||
template<class P> class DPIAware : public P
|
||||
|
||||
// After:
|
||||
template<class P> class DPIAware : public P, public wxInspector::wxInspectable
|
||||
```
|
||||
|
||||
Add in the constructor body (after `this->CenterOnParent()` at line 110):
|
||||
```cpp
|
||||
SetupInspectorAccelerator(this);
|
||||
```
|
||||
|
||||
This gives every `DPIAware<T>` widget both inspectability and the Ctrl+Shift+I keyboard shortcut automatically. `#include <wx/inspector/inspector.h>` is already present in the file.
|
||||
|
||||
### Change 2: `GUI_Utils.hpp` — `DPIDialog`
|
||||
|
||||
Remove the now-redundant `wxInspector::wxInspectable` and the `SetupInspectorAccelerator(this)` call:
|
||||
|
||||
```cpp
|
||||
// Before:
|
||||
class DPIDialog : public DPIAware<wxDialog>, public wxInspector::wxInspectable
|
||||
// ...
|
||||
SetupInspectorAccelerator(this);
|
||||
|
||||
// After:
|
||||
class DPIDialog : public DPIAware<wxDialog>
|
||||
// (SetupInspectorAccelerator call removed — now done in DPIAware constructor)
|
||||
```
|
||||
|
||||
`DPIDialog` gets `wxInspectable` and the accelerator through `DPIAware<wxDialog>` now.
|
||||
|
||||
### Change 3: `MainFrame.hpp` — `MainFrame`
|
||||
|
||||
Remove the now-redundant `wxInspector::wxInspectable`:
|
||||
|
||||
```cpp
|
||||
// Before:
|
||||
class MainFrame : public DPIFrame, public wxInspector::wxInspectable
|
||||
|
||||
// After:
|
||||
class MainFrame : public DPIFrame
|
||||
```
|
||||
|
||||
`MainFrame` gets `wxInspectable` through `DPIFrame` → `DPIAware<wxFrame>`.
|
||||
|
||||
### Change 4: `MainFrame.cpp` — `MainFrame` constructor
|
||||
|
||||
Remove the now-redundant `SetupInspectorAccelerator(this)` call (line 304). It will be called automatically by the `DPIAware` constructor.
|
||||
|
||||
## Impact
|
||||
|
||||
| Widget | Before | After |
|
||||
|--------|--------|-------|
|
||||
| `DPIDialog` subclasses (~80) | ✓ inspectable | ✓ inspectable (transitive) |
|
||||
| `MainFrame` | ✓ inspectable | ✓ inspectable (transitive) |
|
||||
| `DPIFrame` subclasses (8 others) | ✗ invisible | ✓ inspectable |
|
||||
| Future `DPIAware<T>` | ✗ invisible | ✓ inspectable |
|
||||
|
||||
## Files Modified
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `src/slic3r/GUI/GUI_Utils.hpp` | `DPIAware<P>` gains `wxInspector::wxInspectable` + `SetupInspectorAccelerator(this)` call; `DPIDialog` drops redundant `wxInspector::wxInspectable` and `SetupInspectorAccelerator(this)` |
|
||||
| `src/slic3r/GUI/MainFrame.hpp` | `MainFrame` drops redundant `wxInspector::wxInspectable` |
|
||||
| `src/slic3r/GUI/MainFrame.cpp` | Remove redundant `SetupInspectorAccelerator(this)` from MainFrame constructor |
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- The `DPIAwarePlugin` detection logic (`dynamic_cast<DPIFrame*>` / `dynamic_cast<DPIDialog*>`) is unchanged
|
||||
- No new DPI properties — this is purely about tree visibility and accelerator setup
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
- **Multiple inheritance**: `DPIAware<P>` already has a vtable (virtual destructor). Adding `wxInspectable` adds a second base but no additional data members. The `wxInspector::wxInspectable` class is expected to be a lightweight marker interface.
|
||||
- **Build**: No new includes needed; `<wx/inspector/inspector.h>` is already included in `GUI_Utils.hpp`.
|
||||
- **Cross-platform**: The change is standard C++ multiple inheritance — no platform-specific concerns.
|
||||
@@ -1,244 +0,0 @@
|
||||
# wxInspector Plugins for OrcaSlicer Custom Controls — Design Spec
|
||||
|
||||
Date: 2026-07-23
|
||||
Branch: `dev/layout-inspector`
|
||||
|
||||
## Overview
|
||||
|
||||
Create wxInspector plugins that expose OrcaSlicer's custom widget properties in the inspector's property grid. Without these plugins, the inspector shows only generic wxWidgets properties — missing all DPI-awareness data, custom styling, and Orca-specific control state.
|
||||
|
||||
## Goals
|
||||
|
||||
1. **DPIAware properties** — Inspect and update `scale_factor`, `prev_scale_factor`, `em_unit`, and `normal_font` on any DPIAware-derived widget
|
||||
2. **Custom widget properties** — Surface Orca-specific properties on `Button`, `CheckBox`, `TextInput`, `SwitchButton`, `ProgressBar`, `Label`, and `LabeledStaticBox`
|
||||
3. **Minimal source changes** — Only add trivial (one-line) getters/setters to existing classes; no architectural refactoring of Orca's widget hierarchy
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Custom inspector panels or AUI tabs (use the existing property grid and method invoker)
|
||||
- Python-plugin integration (this is C++ wxInspector, not Orca's Python plugin system)
|
||||
- Event logging customization (the built-in event logger already works)
|
||||
|
||||
## Architecture
|
||||
|
||||
### Two Plugins
|
||||
|
||||
| Plugin | Class | Files |
|
||||
|--------|-------|-------|
|
||||
| DPIAware plugin | `DPIAwarePlugin` | `DPIAwarePlugin.hpp`, `DPIAwarePlugin.cpp` |
|
||||
| Custom widgets plugin | `CustomWidgetsPlugin` | `CustomWidgetsPlugin.hpp`, `CustomWidgetsPlugin.cpp` |
|
||||
| Registration helper | inline function | `Registration.hpp` |
|
||||
|
||||
All files live under `src/slic3r/Utils/wxInspectorPlugins/`.
|
||||
|
||||
### Plugin Detection Strategy
|
||||
|
||||
**DPIAware plugin**: Uses `dynamic_cast<DPIFrame*>` and `dynamic_cast<DPIDialog*>` as detection gates. `DPIFrame` = `DPIAware<wxFrame>`, `DPIDialog` = `DPIAware<wxDialog>`. Since these are concrete typedefs, `dynamic_cast` works at runtime. This covers `MainFrame`, `SettingsDialog`, and all 8 calibration dialogs (which inherit `DPIDialog`).
|
||||
|
||||
**Custom widgets plugin**: Gates broadly on `CLASSINFO(wxWindow)`, then uses per-type `dynamic_cast` inside `GetProperties` to check each Orca-specific type. Only matching types append properties.
|
||||
|
||||
### Registration
|
||||
|
||||
A single `RegisterOrcaInspectorPlugins()` inline function in `Registration.hpp` creates both plugins as function-local statics (matching the wxInspector built-in provider pattern) and registers them via `wxInspector::RegisterPlugin()`.
|
||||
|
||||
Called once from `MainFrame::MainFrame()` after `SetupInspectorAccelerator(this)`.
|
||||
|
||||
### Why Separate Plugins?
|
||||
|
||||
- DPIAware is a C++ template concept (not a wxClassInfo-isKindOf check), so it needs its own detection logic
|
||||
- Custom widgets use standard wxClassInfo-based detection, matching the built-in provider pattern
|
||||
- Two focused files are easier to review and maintain than one monolithic plugin
|
||||
- Compile-time failure isolation: if a widget header changes, only one plugin breaks
|
||||
|
||||
## DPIAware Plugin — Property Specification
|
||||
|
||||
### Source Changes (GUI_Utils.hpp)
|
||||
|
||||
Four one-liner methods added to the `DPIAware<P>` template class (public section):
|
||||
|
||||
```cpp
|
||||
float scale_factor() const { return m_scale_factor; } // already exists
|
||||
float prev_scale_factor() const { return m_prev_scale_factor; } // already exists
|
||||
int em_unit() const { return m_em_unit; } // already exists
|
||||
void set_scale_factor(float v) { m_scale_factor = v; } // NEW
|
||||
void set_prev_scale_factor(float v) { m_prev_scale_factor = v; } // NEW
|
||||
void set_em_unit(int v) { m_em_unit = v; } // NEW
|
||||
bool force_rescale() const { return m_force_rescale; } // NEW
|
||||
// m_normal_font getter already exists: normal_font()
|
||||
```
|
||||
|
||||
### Detection
|
||||
|
||||
```cpp
|
||||
bool CanProvideProperties(wxClassInfo* info) override {
|
||||
// Gated in GetProperties via dynamic_cast on the window itself
|
||||
return info->IsKindOf(CLASSINFO(wxWindow));
|
||||
}
|
||||
```
|
||||
|
||||
In `GetProperties`:
|
||||
```cpp
|
||||
auto* win = obj.AsWindow();
|
||||
bool isDPI = dynamic_cast<DPIFrame*>(win) || dynamic_cast<DPIDialog*>(win);
|
||||
if (!isDPI) return props;
|
||||
```
|
||||
|
||||
### Property Table (category: "DPI Scaling")
|
||||
|
||||
| Name | Type | Editable | Getter | Setter |
|
||||
|------|------|----------|--------|--------|
|
||||
| Scale Factor | String (float) | Yes | `dpi->scale_factor()` | `dpi->set_scale_factor(v)` |
|
||||
| Prev Scale Factor | String (float) | Yes | `dpi->prev_scale_factor()` | `dpi->set_prev_scale_factor(v)` |
|
||||
| EM Unit | Integer | Yes | `dpi->em_unit()` | `dpi->set_em_unit(v)` |
|
||||
| Normal Font | ReadOnly | No | `dpi->normal_font().GetNativeFontInfoDesc()` | — |
|
||||
| Force Rescale | Boolean (ReadOnly) | No | `dpi->force_rescale()` | — |
|
||||
|
||||
**Note on setters**: The setters simply store values. They do NOT trigger a widget rescale/layout. To see the effect of a changed scale factor, use the inspector's Methods panel to call `Layout()` or resize the window — which triggers the DPI_CHANGED event path naturally.
|
||||
|
||||
## Custom Widgets Plugin — Property Specification
|
||||
|
||||
All properties are appended to the built-in wxWindow properties. Each widget type is independently detected via `dynamic_cast`.
|
||||
|
||||
### Detection gates (in `GetProperties`)
|
||||
|
||||
```cpp
|
||||
auto* win = obj.AsWindow();
|
||||
if (auto* btn = dynamic_cast<Button*>(win)) { addButtonProperties(btn, props); }
|
||||
if (auto* cb = dynamic_cast<CheckBox*>(win)) { addCheckBoxProperties(cb, props); }
|
||||
if (auto* ti = dynamic_cast<TextInput*>(win)) { addTextInputProperties(ti, props); }
|
||||
if (auto* sb = dynamic_cast<SwitchButton*>(win)) { addSwitchButtonProperties(sb, props); }
|
||||
if (auto* pb = dynamic_cast<ProgressBar*>(win)) { addProgressBarProperties(pb, props); }
|
||||
if (auto* lbl = dynamic_cast<Label*>(win)) { addLabelProperties(lbl, props); }
|
||||
if (auto* lsb = dynamic_cast<LabeledStaticBox*>(win)) { addLabeledStaticBoxProperties(lsb, props); }
|
||||
```
|
||||
|
||||
### Orca Button (`Button`) — category: "Orca Button"
|
||||
|
||||
| Name | Type | Editable | Getter | Setter |
|
||||
|------|------|----------|--------|--------|
|
||||
| Button Style | Choice | Yes | enum→string | string→enum |
|
||||
| Button Type | Choice | Yes | enum→string | string→enum |
|
||||
| Selected | Boolean | Yes | `m_selected` (needs getter) | `SetSelected(v)` |
|
||||
| Active Icon | ReadOnly | No | icon name string | — |
|
||||
| Inactive Icon | ReadOnly | No | icon name string | — |
|
||||
|
||||
Choices for Button Style: `Regular`, `Confirm`, `Alert`, `Disabled`
|
||||
Choices for Button Type: `Compact`, `Window`, `Choice`, `Parameter`, `Icon`, `Expanded`
|
||||
|
||||
**Source changes needed**: Button's `m_selected` is private. Add one-liner getter:
|
||||
```cpp
|
||||
bool IsSelected() const { return m_selected; }
|
||||
```
|
||||
|
||||
### Orca CheckBox (`CheckBox`) — category: "Orca CheckBox"
|
||||
|
||||
| Name | Type | Editable | Getter | Setter |
|
||||
|------|------|----------|--------|--------|
|
||||
| Half Checked | Boolean | Yes | `m_half_checked` (needs getter) | `SetHalfChecked(v)` |
|
||||
|
||||
**Source changes needed**: `m_half_checked` is private. Add one-liner getter:
|
||||
```cpp
|
||||
bool IsHalfChecked() const { return m_half_checked; }
|
||||
```
|
||||
|
||||
### Orca TextInput (`TextInput`) — category: "Orca TextInput"
|
||||
|
||||
| Name | Type | Editable | Getter | Setter |
|
||||
|------|------|----------|--------|--------|
|
||||
| Label | String | Yes | `GetLabel()` (inherited from wxWindow) | `SetLabel(v)` (exists) |
|
||||
| Text Value | String | Yes | `GetTextCtrl()->GetValue()` (GetTextCtrl is public) | `GetTextCtrl()->SetValue(v)` |
|
||||
| Corner Radius | Integer | Yes | `GetCornerRadius()` (NEW) | `SetCornerRadius(v)` (exists) |
|
||||
|
||||
**Source changes needed**: Add one getter to `TextInput`:
|
||||
```cpp
|
||||
int GetCornerRadius() const { return static_cast<int>(radius); }
|
||||
```
|
||||
(`radius` is inherited from StaticBox. `SetCornerRadius(double)` already exists. `GetTextCtrl()` is already public.)
|
||||
|
||||
### Orca SwitchButton (`SwitchButton`) — category: "Orca SwitchButton"
|
||||
|
||||
| Name | Type | Editable | Getter | Setter |
|
||||
|------|------|----------|--------|--------|
|
||||
| Value | Boolean | Yes | existing getter | existing setter |
|
||||
|
||||
### Orca ProgressBar (`ProgressBar`) — category: "Orca ProgressBar"
|
||||
|
||||
| Name | Type | Editable | Getter | Setter |
|
||||
|------|------|----------|--------|--------|
|
||||
| Proportion | Float (0-1) | Yes | `pb->m_proportion` (public member) | `pb->m_proportion = v` |
|
||||
| Show Number | Boolean | Yes | `pb->m_shownumber` (public member) | `pb->m_shownumber = v` |
|
||||
|
||||
**No source changes needed**: `m_proportion` and `m_shownumber` are already public members. `SetValue(int)` and `SetProgress(int)` already exist as public methods.
|
||||
|
||||
### Orca Label (`Label`) — category: "Orca Label"
|
||||
|
||||
| Name | Type | Editable | Getter | Setter |
|
||||
|------|------|----------|--------|--------|
|
||||
| Is Hyperlink | Boolean | No | existing flag check | — |
|
||||
| Font Size | ReadOnly | No | `GetFont().GetPointSize()` | — |
|
||||
|
||||
### LabeledStaticBox — category: "LabeledStaticBox"
|
||||
|
||||
| Name | Type | Editable | Getter | Setter |
|
||||
|------|------|----------|--------|--------|
|
||||
| Corner Radius | Integer | Yes | `GetCornerRadius()` (NEW) | `SetCornerRadius(v)` (exists) |
|
||||
| Border Width | Integer | Yes | `GetBorderWidth()` (NEW) | `SetBorderWidth(v)` (exists) |
|
||||
| Border Color | String (hex) | Yes | `GetBorderColor()` (NEW) | `SetBorderColor(v)` (exists) |
|
||||
| Scale | Float (ReadOnly) | No | `m_scale` (protected, needs getter) | — |
|
||||
|
||||
**Source changes needed**: Four one-liner getters added to `LabeledStaticBox`:
|
||||
```cpp
|
||||
int GetCornerRadius() const { return m_radius; }
|
||||
int GetBorderWidth() const { return m_border_width; }
|
||||
StateColor GetBorderColor() const { return border_color; }
|
||||
float GetScale() const { return m_scale; }
|
||||
```
|
||||
|
||||
## Files Modified (Existing Code)
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `src/slic3r/GUI/GUI_Utils.hpp` | +4 methods in `DPIAware<P>`: `set_scale_factor()`, `set_prev_scale_factor()`, `set_em_unit()`, `force_rescale()` |
|
||||
| `src/slic3r/GUI/Widgets/LabeledStaticBox.hpp` | +4 getter declarations: `GetCornerRadius()`, `GetBorderWidth()`, `GetBorderColor()`, `GetScale()` |
|
||||
| `src/slic3r/GUI/Widgets/LabeledStaticBox.cpp` | +4 getter implementations |
|
||||
| `src/slic3r/GUI/Widgets/Button.hpp` | +1 getter: `IsSelected()` |
|
||||
| `src/slic3r/GUI/Widgets/CheckBox.hpp` | +1 getter: `IsHalfChecked()` |
|
||||
| `src/slic3r/GUI/Widgets/TextInput.hpp` | +1 getter: `GetCornerRadius()` |
|
||||
| `src/slic3r/GUI/Widgets/ProgressBar.hpp` | None (public members are used directly) |
|
||||
| `src/slic3r/GUI/MainFrame.cpp` | +1 `#include`, +1 call to `RegisterOrcaInspectorPlugins()` |
|
||||
| `src/slic3r/CMakeLists.txt` | +4 entries in `SLIC3R_GUI_SOURCES` (the .cpp plugin files) |
|
||||
|
||||
## Files Created
|
||||
|
||||
```
|
||||
src/slic3r/Utils/wxInspectorPlugins/
|
||||
├── DPIAwarePlugin.hpp
|
||||
├── DPIAwarePlugin.cpp
|
||||
├── CustomWidgetsPlugin.hpp
|
||||
├── CustomWidgetsPlugin.cpp
|
||||
└── Registration.hpp
|
||||
```
|
||||
|
||||
## Build & Linking
|
||||
|
||||
The `wxInspector` dependency is already wired:
|
||||
- `deps/wxInspector/wxInspector.cmake` fetches and builds wxInspector
|
||||
- `src/CMakeLists.txt` lines 92-93 link `wxInspector::wxInspector` into `wxWidgets_LIBRARIES`
|
||||
- The plugin files only need `#include <wx/inspector/plugin.h>` and `#include <wx/inspector/inspector.h>` — both available from the installed dependency
|
||||
|
||||
No new CMake dependencies needed. Only the new source files need listing in `SLIC3R_GUI_SOURCES`.
|
||||
|
||||
## Error Handling & Edge Cases
|
||||
|
||||
- **Stale pointers**: Plugin lambdas capture raw pointers, regenerated on every `GetProperties` call (matching wxInspector's built-in provider pattern). Pointers live only until the next tree selection.
|
||||
- **Widget destruction**: If a widget is destroyed while the inspector is showing its properties, `InspectableObject::IsValid()` returns false and properties are not displayed. The inspector won't show stale data.
|
||||
- **Invalid property values**: Setters use `sscanf` / `ToLong` with validation (matching built-in patterns). Bogus input is rejected — setter returns `false`, property grid shows error state.
|
||||
- **DPI drift**: Setting `scale_factor` without triggering rescale means displayed sizes don't match the new factor. This is acceptable — the inspector is a developer tool; operators know to call `Layout()` after making changes.
|
||||
- **Missing widget type**: If a `dynamic_cast` fails for all types, only built-in wxWindow properties are shown. No crash, no error — just reduced info.
|
||||
|
||||
## Future Work (Out of Scope)
|
||||
|
||||
- **StateColor visualization**: `StateColor` is a multi-value type (maps bitmask states to colors). A full solution would need a custom property editor (e.g., a table showing each state→color pair). Keep it simple for now.
|
||||
- **ScalableBitmap display**: Could show the bitmap as an inline thumbnail. Complex property editor work — deferred.
|
||||
- **More widget types**: `SwitchBoard`, `MultiSwitchButton`, `StepCtrl`, `FanControl`, `DropDown`, `ComboBox`, `AMS*` widgets could all benefit. Add as needed.
|
||||
- **Property refresh on tree selection**: Currently properties are static snapshots. A "refresh" button or auto-poll could keep values current for rapidly-changing widgets (progress bars, etc.). The built-in wxInspector already provides a tree-refresh button.
|
||||
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-29 17:40-0300\n"
|
||||
"POT-Creation-Date: 2026-07-20 10:39-0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -552,7 +552,6 @@ msgstr ""
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Enter"
|
||||
msgstr ""
|
||||
|
||||
@@ -878,7 +877,6 @@ msgstr ""
|
||||
msgid "Select all connectors"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Cut tool"
|
||||
msgid "Cut"
|
||||
msgstr ""
|
||||
|
||||
@@ -982,7 +980,7 @@ msgstr ""
|
||||
#, possible-boost-format
|
||||
msgid ""
|
||||
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
||||
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
||||
"Thank you."
|
||||
msgstr ""
|
||||
|
||||
@@ -1734,14 +1732,12 @@ msgstr ""
|
||||
msgid "Select point"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart selection"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Esc"
|
||||
msgstr ""
|
||||
|
||||
@@ -1842,7 +1838,6 @@ msgstr ""
|
||||
msgid "Leaving Measure gizmo"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Assembly tool"
|
||||
msgid "Assemble"
|
||||
msgstr ""
|
||||
|
||||
@@ -2353,9 +2348,6 @@ msgstr ""
|
||||
msgid "Import File"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose files"
|
||||
msgstr ""
|
||||
|
||||
@@ -2474,14 +2466,12 @@ msgstr ""
|
||||
msgid "Show"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Del"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete the selected object"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Backspace"
|
||||
msgstr ""
|
||||
|
||||
@@ -2669,9 +2659,6 @@ msgstr ""
|
||||
msgid "Restore to Meter"
|
||||
msgstr ""
|
||||
|
||||
msgid "Assemble"
|
||||
msgstr ""
|
||||
|
||||
msgid "Assemble the selected objects into an object with multiple parts"
|
||||
msgstr ""
|
||||
|
||||
@@ -3385,6 +3372,7 @@ msgstr ""
|
||||
msgid "Innerloop"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgid "Top"
|
||||
msgstr ""
|
||||
|
||||
@@ -3415,7 +3403,6 @@ msgctxt "air_duct"
|
||||
msgid "Left(Aux)"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Hotend Heat Breaker Fan"
|
||||
msgid "Hotend"
|
||||
msgstr ""
|
||||
|
||||
@@ -3608,9 +3595,6 @@ msgstr ""
|
||||
msgid "Arranging"
|
||||
msgstr ""
|
||||
|
||||
msgid "Arranging "
|
||||
msgstr ""
|
||||
|
||||
msgid "Arranging canceled."
|
||||
msgstr ""
|
||||
|
||||
@@ -4136,14 +4120,6 @@ msgstr ""
|
||||
msgid "Print with filament in AMS"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Nozzle position"
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Nozzle position"
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
msgid "When the current material run out, the printer will continue to print in the following order."
|
||||
msgstr ""
|
||||
|
||||
@@ -5371,7 +5347,6 @@ msgstr ""
|
||||
msgid "Quality / Speed"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Mesh action"
|
||||
msgid "Smooth"
|
||||
msgstr ""
|
||||
|
||||
@@ -5472,24 +5447,10 @@ msgstr ""
|
||||
msgid "Align to Y axis"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Front"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgctxt "Camera View"
|
||||
msgid "Top"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgctxt "Camera View"
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
@@ -5870,13 +5831,19 @@ msgstr ""
|
||||
msgid "Top View"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgid "Bottom View"
|
||||
msgstr ""
|
||||
|
||||
msgid "Front"
|
||||
msgstr ""
|
||||
|
||||
msgid "Front View"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Rear"
|
||||
msgstr ""
|
||||
|
||||
@@ -5988,9 +5955,6 @@ msgstr ""
|
||||
msgid "Redo"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cut"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cut selection to clipboard"
|
||||
msgstr ""
|
||||
|
||||
@@ -6090,10 +6054,6 @@ msgstr ""
|
||||
msgid "Preferences"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Menu"
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
|
||||
@@ -8655,26 +8615,6 @@ msgstr ""
|
||||
msgid "Displays current viewport FPS in the top-right corner."
|
||||
msgstr ""
|
||||
|
||||
msgid "G-code Preview"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dim lower layers"
|
||||
msgstr ""
|
||||
|
||||
msgid "When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."
|
||||
msgstr ""
|
||||
|
||||
msgid "Dimmed layer brightness"
|
||||
msgstr ""
|
||||
|
||||
msgid "%"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"How brightly the dimmed layers are rendered when \"Dim lower layers\" is enabled.\n"
|
||||
"99% is barely darkened, 0% renders them black. Capped at 99% because 100% would be the same as disabling the option."
|
||||
msgstr ""
|
||||
|
||||
msgid "Login region"
|
||||
msgstr ""
|
||||
|
||||
@@ -8957,7 +8897,6 @@ msgstr ""
|
||||
msgid "The selected preset is null!"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Layer range"
|
||||
msgid "End"
|
||||
msgstr ""
|
||||
|
||||
@@ -9241,9 +9180,6 @@ msgstr ""
|
||||
msgid "The current printer does not support timelapse in Traditional Mode when printing By-Object."
|
||||
msgstr ""
|
||||
|
||||
msgid "I have checked the installed nozzle and want to print anyway."
|
||||
msgstr ""
|
||||
|
||||
msgid "Errors"
|
||||
msgstr ""
|
||||
|
||||
@@ -10868,28 +10804,24 @@ msgstr ""
|
||||
msgid "Select objects by rectangle"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Up"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move selection 10mm in positive Y direction"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Down"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move selection 10mm in negative Y direction"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Left"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move selection 10mm in negative X direction"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Right"
|
||||
msgstr ""
|
||||
|
||||
@@ -10974,17 +10906,9 @@ msgstr ""
|
||||
msgid "Toggle printable for object/part"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Tab"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch between Prepare/Preview"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Space"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open actions speed dial"
|
||||
msgstr ""
|
||||
|
||||
@@ -11042,17 +10966,9 @@ msgstr ""
|
||||
msgid "Move slider 5x faster"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
msgid "Horizontal slider - Move to start position"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "End"
|
||||
msgstr ""
|
||||
|
||||
msgid "Horizontal slider - Move to last position"
|
||||
msgstr ""
|
||||
|
||||
@@ -12371,26 +12287,12 @@ msgstr ""
|
||||
msgid "Intra-layer order"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Order in which object instances are visited within a single layer, which controls how much travel is spent moving between them.\n"
|
||||
"\n"
|
||||
"Default: nearest-neighbor chaining, refined with 2-opt and crossing removal. A good general choice.\n"
|
||||
"As object list: instances are printed in the same order as the object list, without any path optimization. Use it when you need a predictable, manually controlled order.\n"
|
||||
"Best of all (shortest path): every strategy is evaluated and the shortest one is used. The object instance order is decided once for the whole print, while the ordering of individual islands is decided per layer, so different layers may end up using different strategies. Slightly slower to slice.\n"
|
||||
"Snake: serpentine row-by-row traversal, refined with 2-opt. Well suited to regular grids of many small parts.\n"
|
||||
"\n"
|
||||
"With multiple filaments or tools in the same layer, minimizing tool changes takes priority: objects are grouped by filament first and this setting only orders the instances within each filament group, so the overall sequence may not look like the shortest path across the plate."
|
||||
msgid "Print order within a single layer."
|
||||
msgstr ""
|
||||
|
||||
msgid "As object list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Best of all (shortest path)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Snake"
|
||||
msgstr ""
|
||||
|
||||
msgid "Slow printing down for better layer cooling"
|
||||
msgstr ""
|
||||
|
||||
@@ -14764,7 +14666,7 @@ msgstr ""
|
||||
msgid "Retract amount after wipe"
|
||||
msgstr ""
|
||||
|
||||
#, no-c-format, no-boost-format
|
||||
#, possible-c-format
|
||||
msgid ""
|
||||
"The length of fast retraction after wipe, relative to retraction length.\n"
|
||||
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
||||
@@ -15196,9 +15098,6 @@ msgstr ""
|
||||
msgid "Traditional"
|
||||
msgstr ""
|
||||
|
||||
msgid "Smooth"
|
||||
msgstr ""
|
||||
|
||||
msgid "Farthest point timelapse"
|
||||
msgstr ""
|
||||
|
||||
@@ -16102,12 +16001,6 @@ msgstr ""
|
||||
msgid "Whether this printer supports fast purge mode with optimized temperature and multiplier."
|
||||
msgstr ""
|
||||
|
||||
msgid "Filament change"
|
||||
msgstr ""
|
||||
|
||||
msgid "The volume of material required to prime the extruder on the tower, excluding a hotend change."
|
||||
msgstr ""
|
||||
|
||||
msgid "The volume of material required to prime the extruder for a hotend change on the tower."
|
||||
msgstr ""
|
||||
|
||||
@@ -17246,15 +17139,6 @@ msgid ""
|
||||
"Please select one that should be used."
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto-scale for nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"This model is designed around a 0.4 mm nozzle with a 0.2 mm layer height. \n"
|
||||
"When the scaling option is enabled (recommended), it dynamically resizes to match your current nozzle diameter and an appropriate layer height, making the test both accurate and easy to read.\n"
|
||||
"Turn scaling off only if you wish to print the reference model exactly as-is."
|
||||
msgstr ""
|
||||
|
||||
msgid "PA Calibration"
|
||||
msgstr ""
|
||||
|
||||
@@ -17377,12 +17261,6 @@ msgstr ""
|
||||
msgid "End speed: "
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto-adjust to max volumetric speed"
|
||||
msgstr ""
|
||||
|
||||
msgid "If the end speed would exceed the filament's maximum volumetric speed, automatically lower the layer height (keeping standard values and staying within the machine's limits) to reach it. If even the minimum layer height is not enough, lower the end speed instead."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Please input valid values:\n"
|
||||
"start > 10\n"
|
||||
@@ -17390,39 +17268,6 @@ msgid ""
|
||||
"end > start + step"
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid ""
|
||||
"The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s), which limits the outer wall to about %.0f mm/s at this line width and layer height.\n"
|
||||
" Speeds above this will be clamped, so the upper blocks of the tower will not print at the requested speed.\n"
|
||||
"\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid ""
|
||||
"The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s) at the default layer height (%.2f mm).\n"
|
||||
"\n"
|
||||
"The layer height has been reduced to %.2f mm (a value used by this printer's profiles) so the tower can reach the requested speed."
|
||||
msgstr ""
|
||||
|
||||
#, possible-c-format, possible-boost-format
|
||||
msgid ""
|
||||
"Even at the smallest layer height used by this printer's profiles (%.2f mm) the end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s).\n"
|
||||
"\n"
|
||||
"The layer height will be set to %.2f mm and the end speed lowered to %.0f mm/s.\n"
|
||||
"\n"
|
||||
"Continue?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Continue anyway?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable \"Auto-adjust\" to fix this automatically, or continue anyway?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable \"Auto-scale for nozzle\" and \"Auto-adjust\" to fix this automatically, or continue anyway?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start retraction length: "
|
||||
msgstr ""
|
||||
|
||||
@@ -19204,15 +19049,15 @@ msgstr ""
|
||||
msgid "Show details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Version to install:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Download and Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Skip for Now"
|
||||
msgstr ""
|
||||
|
||||
msgid "A new version of the Bambu Network Plug-in is available."
|
||||
msgstr ""
|
||||
|
||||
@@ -19223,10 +19068,16 @@ msgstr ""
|
||||
msgid "Update to version:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Don't Ask Again"
|
||||
msgid "Update Now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update Now"
|
||||
msgid "Remind Later"
|
||||
msgstr ""
|
||||
|
||||
msgid "Skip Version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Don't Ask Again"
|
||||
msgstr ""
|
||||
|
||||
msgid "(Latest)"
|
||||
@@ -19244,6 +19095,9 @@ msgstr ""
|
||||
msgid "Restart Now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart Later"
|
||||
msgstr ""
|
||||
|
||||
msgid "NO RAMMING AT ALL"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Orca Slicer\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-29 17:40-0300\n"
|
||||
"POT-Creation-Date: 2026-07-20 10:39-0300\n"
|
||||
"PO-Revision-Date: 2026-06-17 15:44-0300\n"
|
||||
"Last-Translator: Alexandre Folle de Menezes\n"
|
||||
"Language-Team: \n"
|
||||
@@ -548,7 +548,6 @@ msgstr ""
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Enter"
|
||||
msgstr ""
|
||||
|
||||
@@ -874,7 +873,6 @@ msgstr ""
|
||||
msgid "Select all connectors"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Cut tool"
|
||||
msgid "Cut"
|
||||
msgstr ""
|
||||
|
||||
@@ -978,7 +976,7 @@ msgstr ""
|
||||
#, boost-format
|
||||
msgid ""
|
||||
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
|
||||
"Please report to the OrcaSlicer team in which scenario this issue happened.\n"
|
||||
"Please report to PrusaSlicer team in which scenario this issue happened.\n"
|
||||
"Thank you."
|
||||
msgstr ""
|
||||
|
||||
@@ -1730,14 +1728,12 @@ msgstr ""
|
||||
msgid "Select point"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart selection"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Esc"
|
||||
msgstr ""
|
||||
|
||||
@@ -1838,7 +1834,6 @@ msgstr ""
|
||||
msgid "Leaving Measure gizmo"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Assembly tool"
|
||||
msgid "Assemble"
|
||||
msgstr ""
|
||||
|
||||
@@ -2349,9 +2344,6 @@ msgstr ""
|
||||
msgid "Import File"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
msgid "Choose files"
|
||||
msgstr ""
|
||||
|
||||
@@ -2470,14 +2462,12 @@ msgstr ""
|
||||
msgid "Show"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Del"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete the selected object"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Backspace"
|
||||
msgstr ""
|
||||
|
||||
@@ -2665,9 +2655,6 @@ msgstr ""
|
||||
msgid "Restore to Meter"
|
||||
msgstr ""
|
||||
|
||||
msgid "Assemble"
|
||||
msgstr ""
|
||||
|
||||
msgid "Assemble the selected objects into an object with multiple parts"
|
||||
msgstr ""
|
||||
|
||||
@@ -3381,6 +3368,7 @@ msgstr ""
|
||||
msgid "Innerloop"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgid "Top"
|
||||
msgstr ""
|
||||
|
||||
@@ -3411,7 +3399,6 @@ msgctxt "air_duct"
|
||||
msgid "Left(Aux)"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Hotend Heat Breaker Fan"
|
||||
msgid "Hotend"
|
||||
msgstr ""
|
||||
|
||||
@@ -3604,9 +3591,6 @@ msgstr ""
|
||||
msgid "Arranging"
|
||||
msgstr ""
|
||||
|
||||
msgid "Arranging "
|
||||
msgstr ""
|
||||
|
||||
msgid "Arranging canceled."
|
||||
msgstr ""
|
||||
|
||||
@@ -4132,14 +4116,6 @@ msgstr ""
|
||||
msgid "Print with filament in AMS"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Nozzle position"
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Nozzle position"
|
||||
msgid "Right"
|
||||
msgstr ""
|
||||
|
||||
msgid "When the current material run out, the printer will continue to print in the following order."
|
||||
msgstr ""
|
||||
|
||||
@@ -5367,7 +5343,6 @@ msgstr ""
|
||||
msgid "Quality / Speed"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Mesh action"
|
||||
msgid "Smooth"
|
||||
msgstr ""
|
||||
|
||||
@@ -5468,24 +5443,10 @@ msgstr ""
|
||||
msgid "Align to Y axis"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Front"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Top
|
||||
msgctxt "Camera View"
|
||||
msgid "Top"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgctxt "Camera View"
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Left"
|
||||
msgstr ""
|
||||
@@ -5866,13 +5827,19 @@ msgstr ""
|
||||
msgid "Top View"
|
||||
msgstr ""
|
||||
|
||||
#. TRN To be shown in the main menu View->Bottom
|
||||
msgid "Bottom"
|
||||
msgstr ""
|
||||
|
||||
msgid "Bottom View"
|
||||
msgstr ""
|
||||
|
||||
msgid "Front"
|
||||
msgstr ""
|
||||
|
||||
msgid "Front View"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Camera View"
|
||||
msgid "Rear"
|
||||
msgstr ""
|
||||
|
||||
@@ -5984,9 +5951,6 @@ msgstr ""
|
||||
msgid "Redo"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cut"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cut selection to clipboard"
|
||||
msgstr ""
|
||||
|
||||
@@ -6086,10 +6050,6 @@ msgstr ""
|
||||
msgid "Preferences"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Menu"
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
|
||||
@@ -8651,26 +8611,6 @@ msgstr ""
|
||||
msgid "Displays current viewport FPS in the top-right corner."
|
||||
msgstr ""
|
||||
|
||||
msgid "G-code Preview"
|
||||
msgstr ""
|
||||
|
||||
msgid "Dim lower layers"
|
||||
msgstr ""
|
||||
|
||||
msgid "When scrubbing the layer slider in the sliced preview, render the layers below the current one darkened so that only the layer being viewed is shown at full brightness."
|
||||
msgstr ""
|
||||
|
||||
msgid "Dimmed layer brightness"
|
||||
msgstr ""
|
||||
|
||||
msgid "%"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"How brightly the dimmed layers are rendered when \"Dim lower layers\" is enabled.\n"
|
||||
"99% is barely darkened, 0% renders them black. Capped at 99% because 100% would be the same as disabling the option."
|
||||
msgstr ""
|
||||
|
||||
msgid "Login region"
|
||||
msgstr ""
|
||||
|
||||
@@ -8953,7 +8893,6 @@ msgstr ""
|
||||
msgid "The selected preset is null!"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Layer range"
|
||||
msgid "End"
|
||||
msgstr ""
|
||||
|
||||
@@ -9237,9 +9176,6 @@ msgstr ""
|
||||
msgid "The current printer does not support timelapse in Traditional Mode when printing By-Object."
|
||||
msgstr ""
|
||||
|
||||
msgid "I have checked the installed nozzle and want to print anyway."
|
||||
msgstr ""
|
||||
|
||||
msgid "Errors"
|
||||
msgstr ""
|
||||
|
||||
@@ -10864,28 +10800,24 @@ msgstr ""
|
||||
msgid "Select objects by rectangle"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Up"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move selection 10mm in positive Y direction"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Down"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move selection 10mm in negative Y direction"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Left"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move selection 10mm in negative X direction"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Arrow Right"
|
||||
msgstr ""
|
||||
|
||||
@@ -10970,17 +10902,9 @@ msgstr ""
|
||||
msgid "Toggle printable for object/part"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Tab"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch between Prepare/Preview"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Space"
|
||||
msgstr ""
|
||||
|
||||
msgid "Open actions speed dial"
|
||||
msgstr ""
|
||||
|
||||
@@ -11038,17 +10962,9 @@ msgstr ""
|
||||
msgid "Move slider 5x faster"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
|
||||
msgid "Horizontal slider - Move to start position"
|
||||
msgstr ""
|
||||
|
||||
msgctxt "Keyboard Shortcut"
|
||||
msgid "End"
|
||||
msgstr ""
|
||||
|
||||
msgid "Horizontal slider - Move to last position"
|
||||
msgstr ""
|
||||
|
||||
@@ -12367,26 +12283,12 @@ msgstr ""
|
||||
msgid "Intra-layer order"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Order in which object instances are visited within a single layer, which controls how much travel is spent moving between them.\n"
|
||||
"\n"
|
||||
"Default: nearest-neighbor chaining, refined with 2-opt and crossing removal. A good general choice.\n"
|
||||
"As object list: instances are printed in the same order as the object list, without any path optimization. Use it when you need a predictable, manually controlled order.\n"
|
||||
"Best of all (shortest path): every strategy is evaluated and the shortest one is used. The object instance order is decided once for the whole print, while the ordering of individual islands is decided per layer, so different layers may end up using different strategies. Slightly slower to slice.\n"
|
||||
"Snake: serpentine row-by-row traversal, refined with 2-opt. Well suited to regular grids of many small parts.\n"
|
||||
"\n"
|
||||
"With multiple filaments or tools in the same layer, minimizing tool changes takes priority: objects are grouped by filament first and this setting only orders the instances within each filament group, so the overall sequence may not look like the shortest path across the plate."
|
||||
msgid "Print order within a single layer."
|
||||
msgstr ""
|
||||
|
||||
msgid "As object list"
|
||||
msgstr ""
|
||||
|
||||
msgid "Best of all (shortest path)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Snake"
|
||||
msgstr ""
|
||||
|
||||
msgid "Slow printing down for better layer cooling"
|
||||
msgstr ""
|
||||
|
||||
@@ -14760,7 +14662,7 @@ msgstr ""
|
||||
msgid "Retract amount after wipe"
|
||||
msgstr ""
|
||||
|
||||
#, no-c-format, no-boost-format
|
||||
#, c-format
|
||||
msgid ""
|
||||
"The length of fast retraction after wipe, relative to retraction length.\n"
|
||||
"The value will be clamped by 100% minus the retract amount before the wipe value."
|
||||
@@ -15192,9 +15094,6 @@ msgstr ""
|
||||
msgid "Traditional"
|
||||
msgstr ""
|
||||
|
||||
msgid "Smooth"
|
||||
msgstr ""
|
||||
|
||||
msgid "Farthest point timelapse"
|
||||
msgstr ""
|
||||
|
||||
@@ -16098,12 +15997,6 @@ msgstr ""
|
||||
msgid "Whether this printer supports fast purge mode with optimized temperature and multiplier."
|
||||
msgstr ""
|
||||
|
||||
msgid "Filament change"
|
||||
msgstr ""
|
||||
|
||||
msgid "The volume of material required to prime the extruder on the tower, excluding a hotend change."
|
||||
msgstr ""
|
||||
|
||||
msgid "The volume of material required to prime the extruder for a hotend change on the tower."
|
||||
msgstr ""
|
||||
|
||||
@@ -17242,15 +17135,6 @@ msgid ""
|
||||
"Please select one that should be used."
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto-scale for nozzle"
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"This model is designed around a 0.4 mm nozzle with a 0.2 mm layer height. \n"
|
||||
"When the scaling option is enabled (recommended), it dynamically resizes to match your current nozzle diameter and an appropriate layer height, making the test both accurate and easy to read.\n"
|
||||
"Turn scaling off only if you wish to print the reference model exactly as-is."
|
||||
msgstr ""
|
||||
|
||||
msgid "PA Calibration"
|
||||
msgstr ""
|
||||
|
||||
@@ -17373,12 +17257,6 @@ msgstr ""
|
||||
msgid "End speed: "
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto-adjust to max volumetric speed"
|
||||
msgstr ""
|
||||
|
||||
msgid "If the end speed would exceed the filament's maximum volumetric speed, automatically lower the layer height (keeping standard values and staying within the machine's limits) to reach it. If even the minimum layer height is not enough, lower the end speed instead."
|
||||
msgstr ""
|
||||
|
||||
msgid ""
|
||||
"Please input valid values:\n"
|
||||
"start > 10\n"
|
||||
@@ -17386,39 +17264,6 @@ msgid ""
|
||||
"end > start + step"
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid ""
|
||||
"The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s), which limits the outer wall to about %.0f mm/s at this line width and layer height.\n"
|
||||
" Speeds above this will be clamped, so the upper blocks of the tower will not print at the requested speed.\n"
|
||||
"\n"
|
||||
"%s"
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid ""
|
||||
"The end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s) at the default layer height (%.2f mm).\n"
|
||||
"\n"
|
||||
"The layer height has been reduced to %.2f mm (a value used by this printer's profiles) so the tower can reach the requested speed."
|
||||
msgstr ""
|
||||
|
||||
#, c-format, boost-format
|
||||
msgid ""
|
||||
"Even at the smallest layer height used by this printer's profiles (%.2f mm) the end speed (%.0f mm/s) exceeds the filament's maximum volumetric speed (%.1f mm³/s).\n"
|
||||
"\n"
|
||||
"The layer height will be set to %.2f mm and the end speed lowered to %.0f mm/s.\n"
|
||||
"\n"
|
||||
"Continue?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Continue anyway?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable \"Auto-adjust\" to fix this automatically, or continue anyway?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable \"Auto-scale for nozzle\" and \"Auto-adjust\" to fix this automatically, or continue anyway?"
|
||||
msgstr ""
|
||||
|
||||
msgid "Start retraction length: "
|
||||
msgstr ""
|
||||
|
||||
@@ -19200,15 +19045,15 @@ msgstr ""
|
||||
msgid "Show details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Hide details"
|
||||
msgstr ""
|
||||
|
||||
msgid "Version to install:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Download and Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Skip for Now"
|
||||
msgstr ""
|
||||
|
||||
msgid "A new version of the Bambu Network Plug-in is available."
|
||||
msgstr ""
|
||||
|
||||
@@ -19219,10 +19064,16 @@ msgstr ""
|
||||
msgid "Update to version:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Don't Ask Again"
|
||||
msgid "Update Now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Update Now"
|
||||
msgid "Remind Later"
|
||||
msgstr ""
|
||||
|
||||
msgid "Skip Version"
|
||||
msgstr ""
|
||||
|
||||
msgid "Don't Ask Again"
|
||||
msgstr ""
|
||||
|
||||
msgid "(Latest)"
|
||||
@@ -19240,6 +19091,9 @@ msgstr ""
|
||||
msgid "Restart Now"
|
||||
msgstr ""
|
||||
|
||||
msgid "Restart Later"
|
||||
msgstr ""
|
||||
|
||||
msgid "NO RAMMING AT ALL"
|
||||
msgstr ""
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
#version 140
|
||||
// Multisample depth texture for the anti-aliased outline (see 3DScene.cpp render_with_outline).
|
||||
// Optional on the GLSL 140 path: if unavailable, fallback to a non-multisample depth texture.
|
||||
#extension GL_ARB_texture_multisample : enable
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
//BBS: add grey and orange
|
||||
@@ -39,14 +36,7 @@ uniform SlopeDetection slope;
|
||||
|
||||
//BBS: add outline_color
|
||||
uniform bool is_outline;
|
||||
// The outline is a per-fragment discard mask, which the framebuffer MSAA cannot smooth, so the
|
||||
// silhouette is resolved per sample from a multisample copy of the outlined model's depth buffer.
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
uniform sampler2DMS depth_tex;
|
||||
uniform int msaa_samples; // samples in depth_tex, 1 when MSAA is off
|
||||
#else
|
||||
uniform sampler2D depth_tex;
|
||||
#endif
|
||||
uniform vec2 screen_size;
|
||||
|
||||
#ifdef ENABLE_ENVIRONMENT_MAP
|
||||
@@ -109,85 +99,43 @@ float GetTolerance(float d, float k)
|
||||
return -k*(d+A)*(d+A)/B;
|
||||
}
|
||||
|
||||
// Depth of sample s at integer pixel coord.
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
float FetchDepth(ivec2 coord, int s)
|
||||
{
|
||||
// texelFetch has no wrap mode, so clamp to the edge texel (sampler2D used CLAMP_TO_EDGE).
|
||||
ivec2 sz = textureSize(depth_tex);
|
||||
return abs(texelFetch(depth_tex, clamp(coord, ivec2(0), sz - 1), s).r);
|
||||
}
|
||||
#else
|
||||
float FetchDepth(ivec2 coord, int s)
|
||||
{
|
||||
return abs(texture(depth_tex, (vec2(coord) + 0.5) / screen_size).r);
|
||||
}
|
||||
#endif
|
||||
|
||||
float DetectSilho(ivec2 coord, ivec2 dir, int s)
|
||||
float DetectSilho(vec2 fragCoord, vec2 dir)
|
||||
{
|
||||
// -------------------------------------------
|
||||
// x0 ___ x1----o
|
||||
// :\ :
|
||||
// x0 ___ x1----o
|
||||
// :\ :
|
||||
// r0 : \ : r1
|
||||
// : \ :
|
||||
// : \ :
|
||||
// o---x2 ___ x3
|
||||
//
|
||||
// r0 and r1 are the differences between actual
|
||||
// and expected (as if x0..3 where on the same
|
||||
// plane) depth values.
|
||||
// -------------------------------------------
|
||||
float x0 = FetchDepth(coord + dir*-2, s);
|
||||
float x1 = FetchDepth(coord + dir*-1, s);
|
||||
float x2 = FetchDepth(coord, s);
|
||||
float x3 = FetchDepth(coord + dir* 1, s);
|
||||
|
||||
|
||||
float x0 = abs(texture(depth_tex, (fragCoord + dir*-2.0) / screen_size).r);
|
||||
float x1 = abs(texture(depth_tex, (fragCoord + dir*-1.0) / screen_size).r);
|
||||
float x2 = abs(texture(depth_tex, (fragCoord + dir* 0.0) / screen_size).r);
|
||||
float x3 = abs(texture(depth_tex, (fragCoord + dir* 1.0) / screen_size).r);
|
||||
|
||||
float d0 = (x1-x0);
|
||||
float d1 = (x2-x3);
|
||||
|
||||
|
||||
float r0 = x1 + d0 - x2;
|
||||
float r1 = x2 + d1 - x1;
|
||||
|
||||
|
||||
float tol = GetTolerance(x2, 0.04);
|
||||
|
||||
|
||||
return smoothstep(0.0, tol*tol, max( - r0*r1, 0.0));
|
||||
|
||||
}
|
||||
|
||||
float DetectSilho(ivec2 coord, int s)
|
||||
{
|
||||
return max(
|
||||
DetectSilho(coord, ivec2(1,0), s), // Horizontal
|
||||
DetectSilho(coord, ivec2(0,1), s) // Vertical
|
||||
);
|
||||
}
|
||||
|
||||
// Full response of one sample. Reduce the max() per sample and average only afterwards:
|
||||
// max(mean) <= mean(max), and averaging first hollows out diagonal and curved lines.
|
||||
float DetectSilhoSample(ivec2 coord, int s)
|
||||
{
|
||||
float v = DetectSilho(coord, s);
|
||||
// Makes silhouettes thicker.
|
||||
for (int i = 1; i <= INFLATE; ++i)
|
||||
{
|
||||
v = max(v, DetectSilho(coord + ivec2(i, 0), s));
|
||||
v = max(v, DetectSilho(coord + ivec2(0, i), s));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// Average the per-sample coverage into the sub-pixel anti-aliasing of the line.
|
||||
float DetectSilho(vec2 fragCoord)
|
||||
{
|
||||
ivec2 coord = ivec2(fragCoord);
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
int n = max(msaa_samples, 1);
|
||||
#else
|
||||
const int n = 1;
|
||||
#endif
|
||||
float acc = 0.0;
|
||||
for (int s = 0; s < n; ++s)
|
||||
acc += DetectSilhoSample(coord, s);
|
||||
return acc / float(n);
|
||||
return max(
|
||||
DetectSilho(fragCoord, vec2(1,0)), // Horizontal
|
||||
DetectSilho(fragCoord, vec2(0,1)) // Vertical
|
||||
);
|
||||
}
|
||||
|
||||
// Returns a lighting multiplier in [1 - shadow_intensity, 1]: < 1 where the fragment is
|
||||
@@ -276,7 +224,14 @@ void main()
|
||||
//BBS: add outline_color
|
||||
if (is_outline) {
|
||||
color = vec4((vec3(intensity.y) + color.rgb * intensity.x) * shade, color.a);
|
||||
float s = DetectSilho(gl_FragCoord.xy);
|
||||
vec2 fragCoord = gl_FragCoord.xy;
|
||||
float s = DetectSilho(fragCoord);
|
||||
// Makes silhouettes thicker.
|
||||
for(int i=1;i<=INFLATE; i++)
|
||||
{
|
||||
s = max(s, DetectSilho(fragCoord.xy + vec2(i, 0)));
|
||||
s = max(s, DetectSilho(fragCoord.xy + vec2(0, i)));
|
||||
}
|
||||
if (s < 0.01)
|
||||
discard;
|
||||
out_color = vec4(mix(color.rgb, getBackfaceColor(color.rgb), s), color.a);
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
#version 140
|
||||
// Multisample depth texture for the anti-aliased outline (see 3DScene.cpp render_with_outline).
|
||||
// Optional on the GLSL 140 path: if unavailable, fallback to a non-multisample depth texture.
|
||||
#extension GL_ARB_texture_multisample : enable
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
const vec3 LightRed = vec3(0.78, 0.0, 0.0);
|
||||
@@ -54,14 +51,7 @@ uniform SlopeDetection slope;
|
||||
|
||||
//BBS: add outline_color
|
||||
uniform bool is_outline;
|
||||
// The outline is a per-fragment discard mask, which the framebuffer MSAA cannot smooth, so the
|
||||
// silhouette is resolved per sample from a multisample copy of the outlined model's depth buffer.
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
uniform sampler2DMS depth_tex;
|
||||
uniform int msaa_samples; // samples in depth_tex, 1 when MSAA is off
|
||||
#else
|
||||
uniform sampler2D depth_tex;
|
||||
#endif
|
||||
uniform vec2 screen_size;
|
||||
|
||||
#ifdef ENABLE_ENVIRONMENT_MAP
|
||||
@@ -110,27 +100,12 @@ float GetTolerance(float d, float k)
|
||||
return -k*(d+A)*(d+A)/B;
|
||||
}
|
||||
|
||||
// Depth of sample s at integer pixel coord.
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
float FetchDepth(ivec2 coord, int s)
|
||||
float DetectSilho(vec2 fragCoord, vec2 dir)
|
||||
{
|
||||
// texelFetch has no wrap mode, so clamp to the edge texel (sampler2D used CLAMP_TO_EDGE).
|
||||
ivec2 sz = textureSize(depth_tex);
|
||||
return abs(texelFetch(depth_tex, clamp(coord, ivec2(0), sz - 1), s).r);
|
||||
}
|
||||
#else
|
||||
float FetchDepth(ivec2 coord, int s)
|
||||
{
|
||||
return abs(texture(depth_tex, (vec2(coord) + 0.5) / screen_size).r);
|
||||
}
|
||||
#endif
|
||||
|
||||
float DetectSilho(ivec2 coord, ivec2 dir, int s)
|
||||
{
|
||||
float x0 = FetchDepth(coord + dir*-2, s);
|
||||
float x1 = FetchDepth(coord + dir*-1, s);
|
||||
float x2 = FetchDepth(coord, s);
|
||||
float x3 = FetchDepth(coord + dir* 1, s);
|
||||
float x0 = abs(texture(depth_tex, (fragCoord + dir*-2.0) / screen_size).r);
|
||||
float x1 = abs(texture(depth_tex, (fragCoord + dir*-1.0) / screen_size).r);
|
||||
float x2 = abs(texture(depth_tex, (fragCoord + dir* 0.0) / screen_size).r);
|
||||
float x3 = abs(texture(depth_tex, (fragCoord + dir* 1.0) / screen_size).r);
|
||||
|
||||
float d0 = (x1-x0);
|
||||
float d1 = (x2-x3);
|
||||
@@ -141,43 +116,15 @@ float DetectSilho(ivec2 coord, ivec2 dir, int s)
|
||||
float tol = GetTolerance(x2, 0.04);
|
||||
|
||||
return smoothstep(0.0, tol*tol, max( - r0*r1, 0.0));
|
||||
|
||||
}
|
||||
|
||||
float DetectSilho(ivec2 coord, int s)
|
||||
{
|
||||
return max(
|
||||
DetectSilho(coord, ivec2(1,0), s),
|
||||
DetectSilho(coord, ivec2(0,1), s)
|
||||
);
|
||||
}
|
||||
|
||||
// Full response of one sample. Reduce the max() per sample and average only afterwards:
|
||||
// max(mean) <= mean(max), and averaging first hollows out diagonal and curved lines.
|
||||
float DetectSilhoSample(ivec2 coord, int s)
|
||||
{
|
||||
float v = DetectSilho(coord, s);
|
||||
// Makes silhouettes thicker.
|
||||
for (int i = 1; i <= INFLATE; ++i)
|
||||
{
|
||||
v = max(v, DetectSilho(coord + ivec2(i, 0), s));
|
||||
v = max(v, DetectSilho(coord + ivec2(0, i), s));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
// Average the per-sample coverage into the sub-pixel anti-aliasing of the line.
|
||||
float DetectSilho(vec2 fragCoord)
|
||||
{
|
||||
ivec2 coord = ivec2(fragCoord);
|
||||
#ifdef GL_ARB_texture_multisample
|
||||
int n = max(msaa_samples, 1);
|
||||
#else
|
||||
const int n = 1;
|
||||
#endif
|
||||
float acc = 0.0;
|
||||
for (int s = 0; s < n; ++s)
|
||||
acc += DetectSilhoSample(coord, s);
|
||||
return acc / float(n);
|
||||
return max(
|
||||
DetectSilho(fragCoord, vec2(1,0)),
|
||||
DetectSilho(fragCoord, vec2(0,1))
|
||||
);
|
||||
}
|
||||
|
||||
float compute_ssao_factor(vec3 normal, vec3 view_dir, vec3 eye_pos)
|
||||
@@ -323,7 +270,13 @@ void main()
|
||||
if (is_outline) {
|
||||
vec3 shaded_rgb = (vec3(specular) + window_reflection + color.rgb * diffuse) * PHONG_BRIGHTNESS * shade;
|
||||
vec4 shaded_color = vec4(clamp(shaded_rgb, vec3(0.0), vec3(1.0)), color.a);
|
||||
float s = DetectSilho(gl_FragCoord.xy);
|
||||
vec2 fragCoord = gl_FragCoord.xy;
|
||||
float s = DetectSilho(fragCoord);
|
||||
for(int i=1;i<=INFLATE; i++)
|
||||
{
|
||||
s = max(s, DetectSilho(fragCoord.xy + vec2(i, 0)));
|
||||
s = max(s, DetectSilho(fragCoord.xy + vec2(0, i)));
|
||||
}
|
||||
if (s < 0.01)
|
||||
discard;
|
||||
out_color = vec4(mix(shaded_color.rgb, getBackfaceColor(shaded_color.rgb), s), shaded_color.a);
|
||||
|
||||
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 |
@@ -48,7 +48,6 @@
|
||||
<span id="statusText" class="status-text"></span>
|
||||
</footer>
|
||||
</main>
|
||||
<script src="../js/plugin-config-ui.js"></script>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -9,12 +9,6 @@ let selectedCapabilityType = "";
|
||||
let selectedHasPresetOverride = false;
|
||||
let selectedReadOnly = false;
|
||||
|
||||
// Whether the frame already holds the selected capability's custom UI (payloads are gated by
|
||||
// IsCurrentCapability and every selection change clears the view). Saving re-sends the whole
|
||||
// capability_config payload, and rebuilding the frame from it would reload the plugin's page under
|
||||
// the user's cursor, so a loaded frame gets the new values posted in instead.
|
||||
let customFrameLoaded = false;
|
||||
|
||||
function SafeJsonParse(text) {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
@@ -199,7 +193,6 @@ function ClearCapabilityConfigView() {
|
||||
if (custom) {
|
||||
custom.hidden = true;
|
||||
custom.removeAttribute("srcdoc");
|
||||
customFrameLoaded = false;
|
||||
}
|
||||
if (text)
|
||||
text.value = "";
|
||||
@@ -255,14 +248,8 @@ function ApplyCapabilityConfig(payload) {
|
||||
|
||||
if (html) {
|
||||
if (custom) {
|
||||
const context = OrcaConfigContext(payload, "preset");
|
||||
custom.hidden = false;
|
||||
if (customFrameLoaded && custom.contentWindow) {
|
||||
custom.contentWindow.postMessage({ __orca: "config", config: config, context: context }, "*");
|
||||
} else {
|
||||
customFrameLoaded = true;
|
||||
custom.srcdoc = BuildCustomConfigDocument(html, config, context);
|
||||
}
|
||||
custom.srcdoc = BuildCustomConfigDocument(html, config);
|
||||
}
|
||||
if (editor)
|
||||
editor.hidden = true;
|
||||
@@ -273,7 +260,6 @@ function ApplyCapabilityConfig(payload) {
|
||||
if (custom) {
|
||||
custom.hidden = true;
|
||||
custom.removeAttribute("srcdoc");
|
||||
customFrameLoaded = false;
|
||||
}
|
||||
if (editor)
|
||||
editor.hidden = false;
|
||||
@@ -368,6 +354,39 @@ function ApplyCapabilityConfigSaved(payload) {
|
||||
SetConfigValidation("");
|
||||
}
|
||||
|
||||
// The whole host surface a custom config UI gets: read the config, save one, drop the preset's
|
||||
// override, and be told when either lands. The frame is sandboxed into an opaque origin, so this
|
||||
// bridge is its only channel.
|
||||
function BuildCustomConfigDocument(html, config) {
|
||||
// Inlined into a <script>: a stored "</script>" would close the tag early, so escape "<" — the
|
||||
// literal stays valid JSON.
|
||||
const seed = JSON.stringify(config).replace(/</g, "\\u003c");
|
||||
const bridge = `<script>
|
||||
(function () {
|
||||
var handlers = [];
|
||||
var current = ${seed};
|
||||
window.orca = {
|
||||
getConfig: function () { return current; },
|
||||
saveConfig: function (cfg) { parent.postMessage({ __orca: "save", config: cfg }, "*"); },
|
||||
restoreDefaults: function () { parent.postMessage({ __orca: "restore" }, "*"); },
|
||||
onConfig: function (cb) {
|
||||
if (typeof cb !== "function") return;
|
||||
handlers.push(cb);
|
||||
try { cb(current); } catch (e) {}
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", function (event) {
|
||||
if (!event.data || event.data.__orca !== "config") return;
|
||||
current = event.data.config || {};
|
||||
handlers.forEach(function (handler) {
|
||||
try { handler(current); } catch (e) {}
|
||||
});
|
||||
});
|
||||
})();
|
||||
<\/script>`;
|
||||
return bridge + html;
|
||||
}
|
||||
|
||||
function OnCustomConfigMessage(event) {
|
||||
const custom = document.getElementById("configCustom");
|
||||
// Only the frame we created, and only while it is actually showing.
|
||||
@@ -413,7 +432,6 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
// OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when
|
||||
// sandboxed), and ignores anything else.
|
||||
window.addEventListener("message", OnCustomConfigMessage);
|
||||
OrcaWatchThemeForFrame("configCustom");
|
||||
|
||||
SendMessage("request_capabilities");
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<script type="text/javascript" src="../../data/text.js"></script>
|
||||
<script type="text/javascript" src="../js/globalapi.js"></script>
|
||||
<script type="text/javascript" src="../js/common.js"></script>
|
||||
<script src="../js/plugin-config-ui.js"></script>
|
||||
<script src="./index.js"></script>
|
||||
<script src="./plugin-sort.js"></script>
|
||||
<script src="../js/fuzzy-search.js"></script>
|
||||
|
||||
@@ -90,7 +90,6 @@ function OnInit() {
|
||||
// OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when
|
||||
// sandboxed), and ignores anything else.
|
||||
window.addEventListener("message", OnCustomConfigMessage);
|
||||
OrcaWatchThemeForFrame("configCustom");
|
||||
|
||||
document.addEventListener("click", (event) => {
|
||||
if (!event.target.closest(".ctx"))
|
||||
@@ -575,7 +574,7 @@ function CapabilityCanRun(plugin, capability) {
|
||||
}
|
||||
|
||||
function IsPluginChecked(plugin) {
|
||||
return plugin.is_loaded;
|
||||
return GetStatus(plugin) === "Activated";
|
||||
}
|
||||
|
||||
function HasMixedCapabilityState(plugin) {
|
||||
@@ -602,17 +601,15 @@ function SourceLabel(source) {
|
||||
return "Mine";
|
||||
case "subscribed":
|
||||
return "Subscribed";
|
||||
case "orphaned":
|
||||
return "Orphaned";
|
||||
default:
|
||||
return "Local";
|
||||
}
|
||||
}
|
||||
|
||||
// Shared source pill, used both after the row name and in the info panel.
|
||||
// Shared Local/Subscribed/Mine pill, used both after the row name and in the info panel.
|
||||
function SourceBadge(source) {
|
||||
const normalized = String(source || "").toLowerCase();
|
||||
const variant = (normalized === "mine" || normalized === "subscribed" || normalized === "orphaned") ? normalized : "local";
|
||||
const variant = (normalized === "mine" || normalized === "subscribed") ? normalized : "local";
|
||||
const badge = document.createElement("span");
|
||||
badge.className = `plugin-source-badge source-${variant}`;
|
||||
badge.textContent = SourceLabel(source);
|
||||
@@ -656,7 +653,7 @@ function LabelCell(plugin, isExpanded = false, capabilityCount = 0, nameRanges =
|
||||
const labelCell = document.createElement("span");
|
||||
labelCell.className = "label-cell";
|
||||
|
||||
const hasCloudLink = plugin.source === "mine" || plugin.source === "subscribed" || plugin.source === "orphaned";
|
||||
const hasCloudLink = plugin.source === "mine" || plugin.source === "subscribed";
|
||||
const pluginLabelText = plugin.label || plugin.name || plugin.plugin_id || "";
|
||||
const canExpand = capabilityCount > 0;
|
||||
|
||||
@@ -707,7 +704,7 @@ function LabelCell(plugin, isExpanded = false, capabilityCount = 0, nameRanges =
|
||||
function SourceCell(plugin) {
|
||||
const cell = document.createElement("span");
|
||||
const normalized = String(plugin.source || "").toLowerCase();
|
||||
const variant = (normalized === "mine" || normalized === "subscribed" || normalized === "orphaned") ? normalized : "local";
|
||||
const variant = (normalized === "mine" || normalized === "subscribed") ? normalized : "local";
|
||||
cell.className = `source-cell source-${variant}`;
|
||||
|
||||
const sourceLabel = document.createElement("span");
|
||||
@@ -1080,7 +1077,7 @@ function ApplyCapabilityConfig(payload) {
|
||||
if (html) {
|
||||
if (custom) {
|
||||
custom.hidden = false;
|
||||
custom.srcdoc = BuildCustomConfigDocument(html, config, OrcaConfigContext(payload, "global"));
|
||||
custom.srcdoc = BuildCustomConfigDocument(html, config);
|
||||
}
|
||||
if (editor)
|
||||
editor.hidden = true;
|
||||
@@ -1181,6 +1178,39 @@ function ApplyCapabilityConfigSaved(payload) {
|
||||
SetConfigValidation("");
|
||||
}
|
||||
|
||||
// The whole host surface a custom config UI gets: read the config, save one, restore the plugin's
|
||||
// defaults, and be told when either lands. The frame is sandboxed into an opaque origin, so this
|
||||
// bridge is its only channel.
|
||||
function BuildCustomConfigDocument(html, config) {
|
||||
// Inlined into a <script>: a stored "</script>" would close the tag early, so escape "<" — the
|
||||
// literal stays valid JSON.
|
||||
const seed = JSON.stringify(config).replace(/</g, "\\u003c");
|
||||
const bridge = `<script>
|
||||
(function () {
|
||||
var handlers = [];
|
||||
var current = ${seed};
|
||||
window.orca = {
|
||||
getConfig: function () { return current; },
|
||||
saveConfig: function (cfg) { parent.postMessage({ __orca: "save", config: cfg }, "*"); },
|
||||
restoreDefaults: function () { parent.postMessage({ __orca: "restore" }, "*"); },
|
||||
onConfig: function (cb) {
|
||||
if (typeof cb !== "function") return;
|
||||
handlers.push(cb);
|
||||
try { cb(current); } catch (e) {}
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", function (event) {
|
||||
if (!event.data || event.data.__orca !== "config") return;
|
||||
current = event.data.config || {};
|
||||
handlers.forEach(function (handler) {
|
||||
try { handler(current); } catch (e) {}
|
||||
});
|
||||
});
|
||||
})();
|
||||
<\/script>`;
|
||||
return bridge + html;
|
||||
}
|
||||
|
||||
function OnCustomConfigMessage(event) {
|
||||
const custom = document.getElementById("configCustom");
|
||||
// Only the frame we created, and only while it is actually showing.
|
||||
@@ -1235,7 +1265,7 @@ function RenderDescription(plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isCloud = plugin && (plugin.source === "mine" || plugin.source === "subscribed" || plugin.source === "orphaned");
|
||||
const isCloud = plugin && (plugin.source === "mine" || plugin.source === "subscribed");
|
||||
if (isCloud && String(plugin?.sharing_token || "")) {
|
||||
node.appendChild(document.createTextNode("View on OrcaCloud "));
|
||||
const link = document.createElement("a");
|
||||
@@ -1347,8 +1377,6 @@ function StatusDescription(plugin) {
|
||||
return "This plugin is still loading.";
|
||||
case "Error":
|
||||
return "This plugin is blocked until its error is fixed.";
|
||||
case "RuntimeError":
|
||||
return "This plugin is loaded but a capability reported an error.";
|
||||
case "Inactive":
|
||||
default:
|
||||
return "This plugin is inactive. Activate it to install or load it.";
|
||||
@@ -1374,13 +1402,6 @@ function RenderDetailSummary(container, plugin) {
|
||||
message.textContent = errorText || StatusDescription(plugin);
|
||||
container.appendChild(message);
|
||||
|
||||
if (plugin.orphaned === true) {
|
||||
const warning = document.createElement("div");
|
||||
warning.className = "detail-description detail-warning-text";
|
||||
warning.textContent = "Orphaned: This plugin is no longer subscribed or available in OrcaCloud. The local copy remains installed and can still be used.";
|
||||
container.appendChild(warning);
|
||||
}
|
||||
|
||||
const updateStatus = GetUpdateStatus(plugin);
|
||||
if (updateStatus === "update_available") {
|
||||
const note = document.createElement("div");
|
||||
|
||||
@@ -251,11 +251,6 @@ body.pane-resizing {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.source-cell.source-orphaned {
|
||||
color: var(--plugin-status-warn);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.source-cell.source-local {
|
||||
color: var(--plugin-source-neutral-text);
|
||||
}
|
||||
@@ -424,11 +419,6 @@ body.pane-resizing {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-cell.status-runtimeerror {
|
||||
color: var(--plugin-status-warn);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-cell.status-loading {
|
||||
color: var(--plugin-status-warn);
|
||||
font-weight: 600;
|
||||
@@ -658,10 +648,6 @@ body.pane-resizing {
|
||||
color: var(--plugin-status-danger);
|
||||
}
|
||||
|
||||
.detail-warning-text {
|
||||
color: var(--plugin-status-warn);
|
||||
}
|
||||
|
||||
.detail-status-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -685,11 +671,6 @@ body.pane-resizing {
|
||||
color: var(--plugin-status-danger);
|
||||
}
|
||||
|
||||
.detail-status-chip.status-runtimeerror {
|
||||
background: var(--plugin-status-warn-bg);
|
||||
color: var(--plugin-status-warn);
|
||||
}
|
||||
|
||||
.detail-status-chip.status-loading {
|
||||
background: var(--plugin-status-warn-bg);
|
||||
color: var(--plugin-status-warn);
|
||||
@@ -934,11 +915,6 @@ body.pane-resizing {
|
||||
color: var(--plugin-source-subscribed-text);
|
||||
}
|
||||
|
||||
.plugin-source-badge.source-orphaned {
|
||||
background: var(--plugin-status-warn-bg);
|
||||
color: var(--plugin-status-warn);
|
||||
}
|
||||
|
||||
.plugin-cloud-link {
|
||||
color: var(--plugin-link-text);
|
||||
cursor: pointer;
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
// The host surface a plugin capability's custom configuration UI gets, shared by the Plugins dialog
|
||||
// and by a preset's plugin configuration dialog. Both sandbox the page into an opaque origin, so this
|
||||
// bridge is its only channel — and both must offer plugin authors the same one.
|
||||
|
||||
// The host theme (WebViewHostDialog::host_theme_vars_css) is injected as a ":root{--orca-*}" rule in
|
||||
// <style id="orca-host-theme-vars">, but only on the top-level page, so a sandboxed config UI never
|
||||
// sees it unless we hand it over. Relay that CSS verbatim instead of re-deriving it: C++ stays the
|
||||
// only place the contract is spelled out, and the host re-themes the style in place, so reading it
|
||||
// always yields the live theme.
|
||||
function OrcaThemeSnapshot() {
|
||||
const style = document.getElementById("orca-host-theme-vars");
|
||||
return {
|
||||
theme: document.documentElement.getAttribute("data-orca-theme") === "dark" ? "dark" : "light",
|
||||
css: style ? style.textContent : ""
|
||||
};
|
||||
}
|
||||
|
||||
// Inlined into a <script> or a JSON payload: a stored "</script>" would close the tag early, so
|
||||
// escape "<" — the literal stays valid JSON.
|
||||
function OrcaInlineJson(value) {
|
||||
return JSON.stringify(value).replace(/</g, "\\u003c");
|
||||
}
|
||||
|
||||
// What a custom UI can learn about the surface it is edited on, kept to what changes the page's own
|
||||
// behavior: "Restore defaults" writes the plugin's get_default_config() in the Plugins dialog but
|
||||
// drops the preset's override in a preset dialog, so a page needs the scope to label its own button.
|
||||
function OrcaConfigContext(payload, scope) {
|
||||
return {
|
||||
scope: scope,
|
||||
readOnly: payload ? payload.read_only === true : false,
|
||||
hasPresetOverride: payload ? payload.has_preset_override === true : false
|
||||
};
|
||||
}
|
||||
|
||||
// The whole host surface: read the config, save one, restore defaults, follow the theme, and be told
|
||||
// when any of it lands.
|
||||
function BuildCustomConfigDocument(html, config, context) {
|
||||
const theme = OrcaThemeSnapshot();
|
||||
const bridge = `<style id="orca-host-theme-vars"></style>
|
||||
<script>
|
||||
(function () {
|
||||
var handlers = [];
|
||||
var themeHandlers = [];
|
||||
var current = ${OrcaInlineJson(config === undefined ? {} : config)};
|
||||
var context = ${OrcaInlineJson(context || {})};
|
||||
var theme = ${OrcaInlineJson(theme)};
|
||||
|
||||
function applyTheme(next) {
|
||||
if (next && typeof next.css === "string") theme = next;
|
||||
var style = document.getElementById("orca-host-theme-vars");
|
||||
if (style) style.textContent = theme.css;
|
||||
if (document.documentElement) document.documentElement.setAttribute("data-orca-theme", theme.theme);
|
||||
themeHandlers.forEach(function (handler) {
|
||||
try { handler(theme.theme); } catch (e) {}
|
||||
});
|
||||
}
|
||||
|
||||
window.orca = {
|
||||
getConfig: function () { return current; },
|
||||
saveConfig: function (cfg) { parent.postMessage({ __orca: "save", config: cfg }, "*"); },
|
||||
restoreDefaults: function () { parent.postMessage({ __orca: "restore" }, "*"); },
|
||||
getContext: function () { return context; },
|
||||
onConfig: function (cb) {
|
||||
if (typeof cb !== "function") return;
|
||||
handlers.push(cb);
|
||||
try { cb(current); } catch (e) {}
|
||||
},
|
||||
onTheme: function (cb) {
|
||||
if (typeof cb !== "function") return;
|
||||
themeHandlers.push(cb);
|
||||
try { cb(theme.theme); } catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", function (event) {
|
||||
if (!event.data) return;
|
||||
if (event.data.__orca === "theme") { applyTheme(event.data.theme); return; }
|
||||
if (event.data.__orca !== "config") return;
|
||||
current = event.data.config || {};
|
||||
if (event.data.context) context = event.data.context;
|
||||
handlers.forEach(function (handler) {
|
||||
try { handler(current); } catch (e) {}
|
||||
});
|
||||
});
|
||||
|
||||
applyTheme();
|
||||
})();
|
||||
<\/script>`;
|
||||
return bridge + html;
|
||||
}
|
||||
|
||||
// The host re-themes an open dialog in place (WebViewHostDialog::host_theme_apply_js), which a
|
||||
// sandboxed child frame never sees. Relay it so a custom UI follows a light/dark switch live.
|
||||
function OrcaWatchThemeForFrame(frameId) {
|
||||
const relay = () => {
|
||||
const frame = document.getElementById(frameId);
|
||||
if (!frame || frame.hidden || !frame.contentWindow)
|
||||
return;
|
||||
frame.contentWindow.postMessage({ __orca: "theme", theme: OrcaThemeSnapshot() }, "*");
|
||||
};
|
||||
new MutationObserver(relay).observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["data-orca-theme"]
|
||||
});
|
||||
}
|
||||
@@ -3,18 +3,18 @@
|
||||
# dependencies = ["numpy"]
|
||||
#
|
||||
# [tool.orcaslicer.plugin]
|
||||
# name = "Orca Inspector"
|
||||
# name = "Orca Inspector Example"
|
||||
# description = "An interactive panel that browses the whole orca.host read-only API and demos every orca.host.ui facility."
|
||||
# author = "SoftFever"
|
||||
# version = "0.0.3"
|
||||
# author = "OrcaSlicer"
|
||||
# version = "0.0.1"
|
||||
# ///
|
||||
"""Orca Inspector — a guided tour of `orca.host` and `orca.host.ui`.
|
||||
"""Orca Inspector — the worked example for `orca.host` and `orca.host.ui`.
|
||||
|
||||
Run it from the Plugins dialog. It opens a NON-MODAL window (OrcaSlicer stays
|
||||
usable) with a sidebar of sections, each exercising one part of the API:
|
||||
|
||||
Overview plater state, scene statistics, current printer/process/filaments
|
||||
Presets every PresetCollection, filament slots, config viewer windows
|
||||
Presets every PresetCollection, preset flags, any preset's full config
|
||||
Config the complete merged full_config as a searchable table
|
||||
Model Model -> Object -> Volume/Instance tree with lazy mesh geometry
|
||||
Assembly objects grouped by module_name, per-instance assemble transforms
|
||||
@@ -32,10 +32,9 @@ and heavy mesh geometry (zero-copy numpy arrays, world-space math, the little
|
||||
point-cloud previews) only when you ask for it. "Refresh" drops the cache and
|
||||
refetches the visible section.
|
||||
|
||||
Style rules the pages follow (see the plugin docs):
|
||||
- no hardcoded theme: BASE_CSS aliases the host-injected --orca-* variables
|
||||
(with fallbacks so the raw HTML previews in a plain browser) and styles the
|
||||
body and native controls from them — the host injects variables only;
|
||||
Style rules the page follows (see the plugin docs):
|
||||
- no hardcoded colors — only the host-injected --orca-* theme variables, so
|
||||
the panel matches light and dark mode automatically;
|
||||
- self-contained HTML (inline CSS/JS, no external resources);
|
||||
- on_message runs on the UI thread, so long work (the progress demos) is
|
||||
offloaded to a thread and results are pushed back with win.post().
|
||||
@@ -46,7 +45,6 @@ transforms need bindings added in July 2026 — on older builds the panel shows
|
||||
what is missing instead of failing.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
@@ -127,19 +125,6 @@ def split_config_list(value):
|
||||
# --------------------------------------------------------------------------- #
|
||||
# section builders
|
||||
# --------------------------------------------------------------------------- #
|
||||
def filament_slots(bundle):
|
||||
"""One entry per filament slot: the mounted preset (current_filament_presets)
|
||||
plus its color and material from the merged config."""
|
||||
cfg = bundle.full_config_value
|
||||
colors = split_config_list(cfg("filament_colour"))
|
||||
types = split_config_list(cfg("filament_type"))
|
||||
return [{"slot": i + 1,
|
||||
"color": colors[i] if i < len(colors) else "",
|
||||
"material": types[i] if i < len(types) else "",
|
||||
"preset": preset_dict(preset) if preset else None}
|
||||
for i, preset in enumerate(bundle.current_filament_presets())]
|
||||
|
||||
|
||||
def build_overview():
|
||||
plater = orca.host.plater()
|
||||
model = orca.host.model()
|
||||
@@ -147,7 +132,17 @@ def build_overview():
|
||||
objects = model.objects()
|
||||
|
||||
cfg = bundle.full_config_value
|
||||
filaments = filament_slots(bundle)
|
||||
colors = split_config_list(cfg("filament_colour"))
|
||||
types = split_config_list(cfg("filament_type"))
|
||||
filaments = []
|
||||
for i, preset in enumerate(bundle.current_filament_presets()):
|
||||
filaments.append({
|
||||
"name": preset.name if preset else "<missing preset>",
|
||||
"color": colors[i] if i < len(colors) else "",
|
||||
"type": types[i] if i < len(types) else "",
|
||||
"is_system": bool(preset.is_system) if preset else False,
|
||||
"is_dirty": bool(preset.is_dirty) if preset else False,
|
||||
})
|
||||
|
||||
printer = bundle.current_printer_preset()
|
||||
process = bundle.current_process_preset()
|
||||
@@ -194,12 +189,13 @@ def build_overview():
|
||||
}
|
||||
|
||||
|
||||
# PresetCollection attribute -> label, in the order the Presets tab stacks its groups.
|
||||
# The bundle also exposes sla_prints/sla_materials, omitted as OrcaSlicer has no SLA.
|
||||
# label -> how to reach the PresetCollection on the bundle
|
||||
COLLECTIONS = [
|
||||
("prints", "Process"),
|
||||
("printers", "Printer"),
|
||||
("filaments", "Filament"),
|
||||
("prints", "Process"),
|
||||
("sla_prints", "SLA print"),
|
||||
("sla_materials", "SLA material"),
|
||||
]
|
||||
|
||||
|
||||
@@ -210,25 +206,21 @@ def build_presets():
|
||||
coll = getattr(bundle, attr)
|
||||
names = coll.preset_names()
|
||||
selected = coll.selected_preset()
|
||||
selected_name = coll.selected_preset_name()
|
||||
shown = names[:MAX_PRESET_NAMES]
|
||||
if selected_name and selected_name not in shown:
|
||||
shown.insert(0, selected_name) # the active preset stays pickable past the cap
|
||||
collections.append({
|
||||
"key": attr,
|
||||
"label": label,
|
||||
"size": coll.size(),
|
||||
"selected_name": selected_name,
|
||||
"selected_name": coll.selected_preset_name(),
|
||||
# selected = as saved on disk; edited = selected + unsaved modifications
|
||||
"selected": {"name": selected.name, "is_dirty": selected.is_dirty,
|
||||
"config_key_count": len(selected.config_keys())},
|
||||
"edited": preset_dict(coll.edited_preset()),
|
||||
"names": shown,
|
||||
"names": names[:MAX_PRESET_NAMES],
|
||||
"truncated": max(0, len(names) - MAX_PRESET_NAMES),
|
||||
})
|
||||
return {
|
||||
"collections": collections,
|
||||
"filament_slots": filament_slots(bundle),
|
||||
"filament_names": bundle.current_filament_preset_names(),
|
||||
}
|
||||
|
||||
|
||||
@@ -239,7 +231,8 @@ def build_preset_config(collection_key, name):
|
||||
preset = getattr(bundle, collection_key).find_preset(name)
|
||||
if preset is None:
|
||||
raise ValueError(f"preset {name!r} not found")
|
||||
return config_rows(preset.config_keys(), preset.config_value)
|
||||
return {"preset": preset_dict(preset),
|
||||
"rows": config_rows(preset.config_keys(), preset.config_value)}
|
||||
|
||||
|
||||
def build_config():
|
||||
@@ -474,149 +467,102 @@ SECTION_BUILDERS = {
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# the pages — themed via BASE_CSS, which aliases the injected --orca-* vars
|
||||
# the page — sidebar app, themed exclusively via the injected --orca-* vars
|
||||
# --------------------------------------------------------------------------- #
|
||||
# The host injects theme *variables* only (never element styles), so every page owns
|
||||
# its base look. The fallbacks keep the raw HTML previewable in a plain browser.
|
||||
BASE_CSS = r"""
|
||||
:root {
|
||||
--bg: var(--orca-bg, #ffffff);
|
||||
--fg: var(--orca-fg, #1f2429);
|
||||
--muted: var(--orca-muted, #6b7580);
|
||||
--border: var(--orca-border, #d9dee3);
|
||||
--accent: var(--orca-accent, #009688);
|
||||
--accent-fg: var(--orca-accent-fg, #ffffff);
|
||||
--ui: var(--orca-font, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif);
|
||||
--mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--bg: var(--orca-bg, #2b2d30);
|
||||
--fg: var(--orca-fg, #e4e6e8);
|
||||
--muted: var(--orca-muted, #9aa0a6);
|
||||
--border: var(--orca-border, #3d4043);
|
||||
}
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin:0; background:var(--bg); color:var(--fg); font:13px/1.45 var(--ui); }
|
||||
button { font:inherit; padding:4px 12px; cursor:pointer; border-radius:6px;
|
||||
background:var(--accent); color:var(--accent-fg); border:1px solid var(--accent); }
|
||||
button.secondary { background:transparent; color:var(--fg); border-color:var(--border); }
|
||||
button:disabled { opacity:.55; cursor:default; }
|
||||
input, select { font:inherit; color:var(--fg); background:var(--bg);
|
||||
border:1px solid var(--border); border-radius:6px; padding:4px 8px; }
|
||||
:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
|
||||
"""
|
||||
|
||||
# Identity hues for the preset collections, shared by the main page's group styling and
|
||||
# the config viewer window. Deliberately the same in light and dark mode: they say what a
|
||||
# card is, not what theme is on.
|
||||
HUES = {"printers": "#9a6ee8", "filaments": "#ee8f41", "prints": "#4e8df6"}
|
||||
HUE_CSS = "".join(f" .hue-{key} {{ --hue:{color}; }}\n" for key, color in HUES.items())
|
||||
|
||||
# The filterable key/value config table, shared by the Config tab and the viewer window.
|
||||
CFG_TABLE_CSS = r"""
|
||||
table.cfg { width:100%; border-collapse:collapse; font-size:12px; }
|
||||
table.cfg th { text-align:left; font-size:11px; letter-spacing:.07em;
|
||||
text-transform:uppercase; color:var(--muted); padding-bottom:4px; }
|
||||
table.cfg td:first-child { font-family:var(--mono); white-space:nowrap;
|
||||
vertical-align:top; }
|
||||
table.cfg td.val { font-family:var(--mono); white-space:pre-wrap; word-break:break-word; }
|
||||
td.val .more { color:var(--accent); cursor:pointer; }
|
||||
"""
|
||||
|
||||
PAGE = r"""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>""" + BASE_CSS + r"""
|
||||
body { height:100vh; display:flex; flex-direction:column; overflow:hidden; }
|
||||
<style>
|
||||
:root { --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
||||
* { box-sizing: border-box; }
|
||||
body { margin:0; height:100vh; display:flex; flex-direction:column; overflow:hidden; }
|
||||
|
||||
header { flex:none; display:flex; align-items:center; gap:10px;
|
||||
padding:10px 16px; border-bottom:1px solid var(--border); }
|
||||
padding:10px 16px; border-bottom:1px solid var(--orca-border); }
|
||||
header h1 { font-size:15px; margin:0; }
|
||||
#stamp { flex:1; color:var(--muted); font-size:12px; }
|
||||
#stamp { flex:1; color:var(--orca-muted); font-size:12px; }
|
||||
button.secondary { background:transparent; color:var(--orca-fg);
|
||||
border-color:var(--orca-border); }
|
||||
button.small { padding:2px 10px; font-size:12px; }
|
||||
|
||||
main { flex:1; display:flex; min-height:0; }
|
||||
nav { flex:none; width:150px; padding:8px 0; overflow-y:auto;
|
||||
border-right:1px solid var(--border); }
|
||||
border-right:1px solid var(--orca-border); }
|
||||
nav .item { padding:8px 14px; cursor:pointer; user-select:none;
|
||||
color:var(--muted); border-left:3px solid transparent; }
|
||||
nav .item:hover { color:var(--fg); }
|
||||
nav .item.active { color:var(--fg); font-weight:600;
|
||||
border-left-color:var(--accent); }
|
||||
color:var(--orca-muted); border-left:3px solid transparent; }
|
||||
nav .item:hover { color:var(--orca-fg); }
|
||||
nav .item.active { color:var(--orca-fg); font-weight:600;
|
||||
border-left-color:var(--orca-accent); }
|
||||
nav .glyph { display:inline-block; width:1.5em; }
|
||||
#content { flex:1; overflow:auto; padding:14px 16px 28px; }
|
||||
|
||||
.tiles { display:grid; grid-template-columns:repeat(auto-fill,minmax(108px,1fr));
|
||||
gap:10px; margin-bottom:12px; }
|
||||
.tile { border:1px solid var(--border); border-radius:8px; padding:10px 12px; }
|
||||
.tile { border:1px solid var(--orca-border); border-radius:8px; padding:10px 12px; }
|
||||
.tile .v { font-size:20px; font-weight:600; font-variant-numeric:tabular-nums; }
|
||||
.tile .l { font-size:11px; color:var(--muted); margin-top:2px; }
|
||||
.tile .l { font-size:11px; color:var(--orca-muted); margin-top:2px; }
|
||||
|
||||
.cards { display:grid; grid-template-columns:repeat(auto-fit,minmax(260px,1fr)); gap:12px; }
|
||||
.card { border:1px solid var(--border); border-radius:8px;
|
||||
.card { border:1px solid var(--orca-border); border-radius:8px;
|
||||
padding:12px 14px; min-width:0; }
|
||||
.card h3 { margin:0 0 8px; font-size:11px; letter-spacing:.07em;
|
||||
text-transform:uppercase; color:var(--muted); }
|
||||
text-transform:uppercase; color:var(--orca-muted); }
|
||||
.card.wide { grid-column:1 / -1; }
|
||||
""" + HUE_CSS + r""" .card.hued { border-top:3px solid var(--hue); }
|
||||
.dot { display:inline-block; width:9px; height:9px; border-radius:50%;
|
||||
background:var(--hue); margin-right:6px; }
|
||||
.pgroup { margin-bottom:18px; }
|
||||
.pgroup-head { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:8px; }
|
||||
.pgroup-head h2 { margin:0; font-size:12px; letter-spacing:.07em; text-transform:uppercase; }
|
||||
.pgroup-head select { flex:0 1 260px; min-width:120px; }
|
||||
.spacer { flex:1; }
|
||||
|
||||
.kv { display:grid; grid-template-columns:minmax(110px,max-content) 1fr;
|
||||
gap:3px 14px; font-size:12px; }
|
||||
.kv .k { color:var(--muted); }
|
||||
.kv .k { color:var(--orca-muted); }
|
||||
.kv .v { font-family:var(--mono); word-break:break-word; }
|
||||
|
||||
.badge { display:inline-block; border:1px solid var(--border);
|
||||
color:var(--muted); border-radius:999px; padding:1px 8px;
|
||||
.badge { display:inline-block; border:1px solid var(--orca-border);
|
||||
color:var(--orca-muted); border-radius:999px; padding:1px 8px;
|
||||
font-size:11px; margin:1px 3px 1px 0; white-space:nowrap; }
|
||||
.badge.on { background:var(--accent); border-color:var(--accent);
|
||||
color:var(--accent-fg); }
|
||||
.badge.on { background:var(--orca-accent); border-color:var(--orca-accent);
|
||||
color:var(--orca-accent-fg); }
|
||||
.swatch { display:inline-block; width:12px; height:12px; border-radius:3px;
|
||||
border:1px solid var(--border); vertical-align:-2px; margin-right:5px; }
|
||||
border:1px solid var(--orca-border); vertical-align:-2px; margin-right:5px; }
|
||||
|
||||
.toolbar { display:flex; gap:8px; align-items:center; margin-bottom:10px; }
|
||||
.toolbar input { flex:1; max-width:340px; }
|
||||
.count { color:var(--muted); font-size:12px; }
|
||||
""" + CFG_TABLE_CSS + r"""
|
||||
.count { color:var(--orca-muted); font-size:12px; }
|
||||
|
||||
table.cfg { width:100%; border-collapse:collapse; font-size:12px; }
|
||||
table.cfg td:first-child { font-family:var(--mono); white-space:nowrap;
|
||||
vertical-align:top; }
|
||||
table.cfg td.val { font-family:var(--mono); white-space:pre-wrap; word-break:break-word; }
|
||||
td.val .more { color:var(--orca-accent); cursor:pointer; }
|
||||
|
||||
.node { margin:1px 0; }
|
||||
.nhead { display:flex; align-items:center; gap:7px; padding:4px 8px;
|
||||
border-radius:6px; cursor:pointer; user-select:none; flex-wrap:wrap; }
|
||||
.nhead:hover { background:var(--border); }
|
||||
.twist { width:1em; flex:none; color:var(--muted); font-size:10px; }
|
||||
.nhead:hover { background:var(--orca-border); }
|
||||
.twist { width:1em; flex:none; color:var(--orca-muted); font-size:10px; }
|
||||
.nname { font-weight:600; }
|
||||
.nbody { display:none; margin-left:14px; padding:4px 0 4px 12px;
|
||||
border-left:1px dotted var(--border); }
|
||||
border-left:1px dotted var(--orca-border); }
|
||||
.node.open > .nhead .twist { transform:rotate(90deg); }
|
||||
.node.open > .nbody { display:block; }
|
||||
.subhead { margin:8px 0 4px; font-size:11px; letter-spacing:.07em;
|
||||
text-transform:uppercase; color:var(--muted); }
|
||||
text-transform:uppercase; color:var(--orca-muted); }
|
||||
|
||||
.banner { border:1px solid var(--border); border-left:3px solid var(--accent);
|
||||
.banner { border:1px solid var(--orca-border); border-left:3px solid var(--orca-accent);
|
||||
border-radius:6px; padding:9px 12px; margin-bottom:12px; font-size:12px; }
|
||||
.muted { color:var(--muted); }
|
||||
.muted { color:var(--orca-muted); }
|
||||
.mono { font-family:var(--mono); }
|
||||
|
||||
canvas.preview { width:100%; height:160px; border:1px solid var(--border);
|
||||
canvas.preview { width:100%; height:160px; border:1px solid var(--orca-border);
|
||||
border-radius:6px; }
|
||||
.previews { display:grid; grid-template-columns:1fr 1fr; gap:10px; margin-top:8px; }
|
||||
.previews .cap { font-size:11px; color:var(--muted); text-align:center; margin-top:2px; }
|
||||
.previews .cap { font-size:11px; color:var(--orca-muted); text-align:center; margin-top:2px; }
|
||||
|
||||
.log { font-family:var(--mono); font-size:12px; border:1px solid var(--border);
|
||||
.log { font-family:var(--mono); font-size:12px; border:1px solid var(--orca-border);
|
||||
border-radius:6px; padding:8px 10px; max-height:190px; overflow:auto; }
|
||||
.log div { padding:1px 0; }
|
||||
fieldset { border:1px solid var(--border); border-radius:6px; margin:0 0 10px; }
|
||||
legend { color:var(--muted); font-size:11px; padding:0 6px; }
|
||||
fieldset { border:1px solid var(--orca-border); border-radius:6px; margin:0 0 10px; }
|
||||
legend { color:var(--orca-muted); font-size:11px; padding:0 6px; }
|
||||
.frow { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin:6px 0; }
|
||||
label { font-size:12px; color:var(--muted); }
|
||||
label { font-size:12px; color:var(--orca-muted); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -642,7 +588,7 @@ const SECTIONS = [
|
||||
['assembly', '⬡', 'Assembly'],
|
||||
['uikit', '▣', 'UI Toolkit'],
|
||||
];
|
||||
const S = { current:'overview', cache:{}, busy:{}, colors:[], uiLog:[], gen:0 };
|
||||
const S = { current:'overview', cache:{}, busy:{}, colors:[], uiLog:[] };
|
||||
|
||||
const $ = id => document.getElementById(id);
|
||||
const esc = s => String(s == null ? '' : s)
|
||||
@@ -652,9 +598,6 @@ const deg = r => num(r * 180 / Math.PI, 1) + '°';
|
||||
const vec3 = (v, d=2) => v ? '(' + v.map(c => num(c, d)).join(', ') + ')' : '—';
|
||||
const yn = b => b ? 'yes' : 'no';
|
||||
const badge = (label, on) => '<span class="badge' + (on ? ' on' : '') + '">' + esc(label) + '</span>';
|
||||
// Colors land in a style attribute, so only literal hex colors pass through.
|
||||
const swatch = c => /^#[0-9a-f]{3,8}$/i.test(c || '')
|
||||
? '<span class="swatch" style="background:' + c + '"></span>' : '';
|
||||
const kv = rows => '<div class="kv">' + rows.map(([k, v]) =>
|
||||
'<div class="k">' + esc(k) + '</div><div class="v">' + v + '</div>').join('') + '</div>';
|
||||
const bboxRows = (label, bb) => bb ? [
|
||||
@@ -665,8 +608,7 @@ const bboxRows = (label, bb) => bb ? [
|
||||
/* ------------------------------------------------------- nav + fetching */
|
||||
function buildNav() {
|
||||
$('nav').innerHTML = SECTIONS.map(([key, glyph, label]) =>
|
||||
'<div class="item" id="nav-' + key + '" role="button" tabindex="0" ' +
|
||||
'onclick="show(\'' + key + '\')">' +
|
||||
'<div class="item" id="nav-' + key + '" onclick="show(\'' + key + '\')">' +
|
||||
'<span class="glyph">' + glyph + '</span>' + label + '</div>').join('');
|
||||
}
|
||||
function show(key) {
|
||||
@@ -680,10 +622,9 @@ function show(key) {
|
||||
function fetchSection(key) {
|
||||
S.busy[key] = true;
|
||||
$('content').innerHTML = '<p class="muted">Loading ' + esc(key) + '…</p>';
|
||||
orca.postMessage({ command:'fetch', section:key, gen:S.gen });
|
||||
orca.postMessage({ command:'fetch', section:key });
|
||||
}
|
||||
function refresh() {
|
||||
S.gen++; // replies to fetches from before this point are dropped
|
||||
S.cache = {};
|
||||
S.busy = {};
|
||||
show(S.current);
|
||||
@@ -719,12 +660,13 @@ function renderOverview(d) {
|
||||
[t.config_keys, 'config keys'],
|
||||
].map(([v, l]) => '<div class="tile"><div class="v">' + v + '</div><div class="l">' + l + '</div></div>').join('');
|
||||
|
||||
const filaments = d.setup.filaments.map(f =>
|
||||
'<div style="margin:3px 0">' + swatch(f.color) +
|
||||
'<b>' + f.slot + '</b> ' + esc(f.preset ? f.preset.name : '<missing preset>') +
|
||||
(f.material ? ' <span class="muted">' + esc(f.material) + '</span>' : '') +
|
||||
(f.preset ? badge(f.preset.is_system ? 'system' : 'user', f.preset.is_system) +
|
||||
(f.preset.is_dirty ? badge('modified', true) : '') : '') +
|
||||
const filaments = d.setup.filaments.map((f, i) =>
|
||||
'<div style="margin:3px 0">' +
|
||||
(f.color ? '<span class="swatch" style="background:' + esc(f.color) + '"></span>' : '') +
|
||||
'<b>' + (i + 1) + '</b> ' + esc(f.name) +
|
||||
(f.type ? ' <span class="muted">' + esc(f.type) + '</span>' : '') +
|
||||
badge(f.is_system ? 'system' : 'user', f.is_system) +
|
||||
(f.is_dirty ? badge('modified', true) : '') +
|
||||
'</div>').join('') || '<span class="muted">none</span>';
|
||||
|
||||
return '<div class="tiles">' + tiles + '</div><div class="cards">' +
|
||||
@@ -767,65 +709,35 @@ function presetBadges(p) {
|
||||
(p.is_visible ? '' : badge('hidden', false));
|
||||
}
|
||||
function renderPresets(d) {
|
||||
const detailCard = g => {
|
||||
const p = g.edited;
|
||||
return '<div class="cards"><div class="card hued">' +
|
||||
'<div style="margin-bottom:6px"><b>' + esc(p.name) + '</b> ' + presetBadges(p) + '</div>' +
|
||||
kv([['label()', esc(p.label)], ['alias', esc(p.alias) || '—'],
|
||||
['type', esc(p.type)], ['bundle', esc(p.bundle_id) || '—'],
|
||||
['edited_preset()', p.config_key_count + ' keys' +
|
||||
(p.is_dirty ? ' · has unsaved changes' : ' · same as saved')],
|
||||
['selected_preset()', esc(g.selected.name) + ' · ' + g.selected.config_key_count + ' keys'],
|
||||
['file', '<span class="muted">' + esc(p.file) + '</span>']]) +
|
||||
'</div></div>';
|
||||
};
|
||||
const slotCard = (s, g) => {
|
||||
const p = s.preset;
|
||||
if (!p) return '<div class="card hued"><h3>Slot ' + s.slot + '</h3>' +
|
||||
'<p class="muted">no preset mounted</p></div>';
|
||||
return '<div class="card hued"><h3>Slot ' + s.slot + '</h3>' +
|
||||
'<div style="margin-bottom:6px">' + swatch(s.color) + '<b>' + esc(p.name) + '</b> ' +
|
||||
badge(p.is_system ? 'system' : 'user', p.is_system) +
|
||||
(p.is_dirty ? badge('modified', true) : '') +
|
||||
(p.name === g.selected_name ? badge('editing', true) : '') + '</div>' +
|
||||
kv([['material', esc(s.material) || '—'],
|
||||
['alias', esc(p.alias) || '—'],
|
||||
['config keys', p.config_key_count],
|
||||
['file', '<span class="muted">' + esc(p.file) + '</span>']]) +
|
||||
'<div class="frow" style="margin-top:8px"><button class="small secondary" ' +
|
||||
'data-act="pcfg" data-coll="filaments" data-name="' + esc(p.name) + '">View config</button></div>' +
|
||||
'</div>';
|
||||
};
|
||||
return '<div class="banner">Each group is one <span class="mono">host.PresetCollection</span>; ' +
|
||||
'the Filament group shows every slot from <span class="mono">current_filament_presets()</span>, ' +
|
||||
'and “editing” marks the preset open in its settings tab. Pick any preset in a group ' +
|
||||
'header — “View config” opens its complete config (via ' +
|
||||
'<span class="mono">find_preset().config_value()</span>) in its own window.</div>' +
|
||||
d.collections.map(g => {
|
||||
const options = g.names.map(n => // explicit value= so odd whitespace in names survives
|
||||
'<option value="' + esc(n) + '"' + (n === g.selected_name ? ' selected' : '') + '>' +
|
||||
return '<div class="banner">Each card is one <span class="mono">host.PresetCollection</span>. ' +
|
||||
'“Edited” is the selected preset plus any unsaved changes. Pick any preset ' +
|
||||
'and load its complete config via <span class="mono">find_preset().config_value()</span>.</div>' +
|
||||
'<div class="cards">' + d.collections.map(c => {
|
||||
const p = c.edited;
|
||||
const options = c.names.map(n => // explicit value= so odd whitespace in names survives
|
||||
'<option value="' + esc(n) + '"' + (n === c.selected_name ? ' selected' : '') + '>' +
|
||||
esc(n) + '</option>').join('');
|
||||
const cards = g.key === 'filaments'
|
||||
? '<div class="cards">' + (d.filament_slots.map(s => slotCard(s, g)).join('') ||
|
||||
'<p class="muted">no filament slots</p>') + '</div>'
|
||||
: detailCard(g);
|
||||
return '<div class="pgroup hue-' + g.key + '">' +
|
||||
'<div class="pgroup-head"><span class="dot"></span><h2>' + esc(g.label) + '</h2>' +
|
||||
'<span class="count">' + g.size + ' presets</span><span class="spacer"></span>' +
|
||||
'<select id="sel-' + g.key + '">' + options + '</select>' +
|
||||
'<button class="small" data-act="pcfg" data-coll="' + g.key + '">View config</button></div>' +
|
||||
cards +
|
||||
(g.truncated ? '<p class="muted">select capped, ' + g.truncated + ' more not listed</p>' : '') +
|
||||
// the config itself opens in a viewer window; this slot only shows failures
|
||||
'<div id="pcfg-' + g.key + '"></div></div>';
|
||||
}).join('');
|
||||
return '<div class="card"><h3>' + esc(c.label) + ' · ' + c.size + ' presets</h3>' +
|
||||
'<div style="margin-bottom:6px"><b>' + esc(p.name) + '</b> ' + presetBadges(p) + '</div>' +
|
||||
kv([['label()', esc(p.label)], ['alias', esc(p.alias) || '—'],
|
||||
['type', esc(p.type)], ['bundle', esc(p.bundle_id) || '—'],
|
||||
['edited_preset()', p.config_key_count + ' keys' +
|
||||
(p.is_dirty ? ' · has unsaved changes' : ' · same as saved')],
|
||||
['selected_preset()', esc(c.selected.name) + ' · ' + c.selected.config_key_count + ' keys'],
|
||||
['file', '<span class="muted">' + esc(p.file) + '</span>']]) +
|
||||
'<div class="frow" style="margin-top:8px">' +
|
||||
'<select id="sel-' + c.key + '" style="flex:1;min-width:0">' + options + '</select>' +
|
||||
'<button class="small" data-act="pcfg" data-coll="' + c.key + '">View config</button>' +
|
||||
'</div>' +
|
||||
(c.truncated ? '<p class="muted">list capped, ' + c.truncated + ' more not shown</p>' : '') +
|
||||
'<div id="pcfg-' + c.key + '"></div></div>';
|
||||
}).join('') + '</div>';
|
||||
}
|
||||
|
||||
function configTable(rows, id) {
|
||||
const body = rows.map(r => {
|
||||
const long = r.v.length > 160;
|
||||
const shown = long ? esc(r.v.slice(0, 160)) +
|
||||
'<span class="more" data-act="expand" role="button" tabindex="0"> … show all</span>'
|
||||
const shown = long ? esc(r.v.slice(0, 160)) + '<span class="more" data-act="expand"> … show all</span>'
|
||||
: esc(r.v);
|
||||
return '<tr data-k="' + esc(r.k.toLowerCase()) + '"><td>' + esc(r.k) + '</td>' +
|
||||
'<td class="val" data-full="' + esc(r.v) + '">' + shown + '</td></tr>';
|
||||
@@ -834,36 +746,37 @@ function configTable(rows, id) {
|
||||
}
|
||||
function renderConfig(d) {
|
||||
return '<div class="toolbar"><input id="cfg-search" placeholder="Filter ' + d.count +
|
||||
' keys…" oninput="filterTable(\'cfg-table\', this.value, \'cfg-count\')">' +
|
||||
' keys…" oninput="filterConfig(this.value)">' +
|
||||
'<span class="count" id="cfg-count">' + d.count + ' keys</span></div>' +
|
||||
'<div class="banner">The merged result of printer + process + filament presets — ' +
|
||||
'exactly what <span class="mono">preset_bundle().full_config_value(key)</span> returns for ' +
|
||||
'every key in <span class="mono">full_config_keys()</span>.</div>' +
|
||||
configTable(d.rows, 'cfg-table');
|
||||
}
|
||||
function filterTable(tableId, q, countId) {
|
||||
function filterConfig(q) {
|
||||
q = q.trim().toLowerCase();
|
||||
let visible = 0;
|
||||
document.querySelectorAll('#' + tableId + ' tr[data-k]').forEach(tr => {
|
||||
document.querySelectorAll('#cfg-table tr[data-k]').forEach(tr => {
|
||||
const hit = !q || tr.dataset.k.includes(q) ||
|
||||
tr.querySelector('.val').dataset.full.toLowerCase().includes(q);
|
||||
tr.style.display = hit ? '' : 'none';
|
||||
if (hit) visible++;
|
||||
});
|
||||
if (countId) $(countId).textContent = visible + ' keys';
|
||||
$('cfg-count').textContent = visible + ' keys';
|
||||
}
|
||||
|
||||
function extruderChip(id) {
|
||||
if (id == null || id < 1) return '<span class="badge">extruder —</span>';
|
||||
return '<span class="badge">' + swatch(S.colors[id - 1]) + 'extruder ' + id + '</span>';
|
||||
const color = S.colors[id - 1];
|
||||
return '<span class="badge">' + (color ? '<span class="swatch" style="background:' +
|
||||
esc(color) + '"></span>' : '') + 'extruder ' + id + '</span>';
|
||||
}
|
||||
const VOL_GLYPH = { ModelPart:'◆', NegativeVolume:'◇', ParameterModifier:'▨',
|
||||
SupportEnforcer:'▲', SupportBlocker:'▽' };
|
||||
|
||||
function node(head, body, open) {
|
||||
return '<div class="node' + (open ? ' open' : '') + '">' +
|
||||
'<div class="nhead" data-act="toggle" role="button" tabindex="0" aria-expanded="' +
|
||||
!!open + '"><span class="twist">▶</span>' + head + '</div>' +
|
||||
'<div class="nhead" data-act="toggle"><span class="twist">▶</span>' + head + '</div>' +
|
||||
'<div class="nbody">' + body + '</div></div>';
|
||||
}
|
||||
function paintedBadges(p) {
|
||||
@@ -1056,7 +969,7 @@ function drawPoints(canvas, pts, ax, ay) {
|
||||
PREVIEWS[canvas.id] = { pts, ax, ay };
|
||||
const scaleDpr = window.devicePixelRatio || 1;
|
||||
const w = canvas.clientWidth * scaleDpr, h = canvas.clientHeight * scaleDpr;
|
||||
if (!w || !h) { delete canvas.dataset.painted; return; } // collapsed; repainted on toggle
|
||||
if (!w || !h) return; // inside a collapsed node; repainted on toggle
|
||||
canvas.dataset.painted = '1';
|
||||
canvas.width = w; canvas.height = h;
|
||||
const ctx = canvas.getContext('2d');
|
||||
@@ -1070,7 +983,7 @@ function drawPoints(canvas, pts, ax, ay) {
|
||||
(h - 2 * pad) / Math.max(maxY - minY, 1e-6));
|
||||
const ox = (w - (maxX - minX) * scale) / 2, oy = (h - (maxY - minY) * scale) / 2;
|
||||
const style = getComputedStyle(document.body);
|
||||
ctx.strokeStyle = style.getPropertyValue('--accent');
|
||||
ctx.strokeStyle = style.getPropertyValue('--orca-accent');
|
||||
ctx.strokeRect(ox, oy, (maxX - minX) * scale, (maxY - minY) * scale);
|
||||
ctx.fillStyle = style.color;
|
||||
ctx.globalAlpha = 0.55;
|
||||
@@ -1079,21 +992,6 @@ function drawPoints(canvas, pts, ax, ay) {
|
||||
ctx.fillRect(ox + (p[ax] - minX) * scale - r / 2,
|
||||
h - oy - (p[ay] - minY) * scale - r / 2, r, r);
|
||||
}
|
||||
// A live theme switch re-stamps data-orca-theme and a resize leaves the bitmap at its
|
||||
// old size — both need every known preview repainted.
|
||||
function repaintPreviews() {
|
||||
document.querySelectorAll('canvas.preview').forEach(c => {
|
||||
const p = PREVIEWS[c.id];
|
||||
if (p) drawPoints(c, p.pts, p.ax, p.ay);
|
||||
});
|
||||
}
|
||||
new MutationObserver(repaintPreviews)
|
||||
.observe(document.documentElement, { attributes:true, attributeFilter:['data-orca-theme'] });
|
||||
let resizeTimer;
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(repaintPreviews, 150);
|
||||
});
|
||||
|
||||
/* ------------------------------------------------------------ UI toolkit */
|
||||
function renderUikit() {
|
||||
@@ -1154,7 +1052,6 @@ $('content').addEventListener('click', e => {
|
||||
if (act === 'toggle') {
|
||||
const node = t.parentElement;
|
||||
node.classList.toggle('open');
|
||||
t.setAttribute('aria-expanded', node.classList.contains('open'));
|
||||
// Repaint any preview whose canvas was hidden (zero-size) when its data arrived.
|
||||
node.querySelectorAll('canvas.preview').forEach(c => {
|
||||
const p = PREVIEWS[c.id];
|
||||
@@ -1165,10 +1062,9 @@ $('content').addEventListener('click', e => {
|
||||
t.textContent = 'Loading…';
|
||||
orca.postMessage({ command:'mesh', object:+t.dataset.o, volume:+t.dataset.v });
|
||||
} else if (act === 'pcfg') {
|
||||
const coll = t.dataset.coll; // slot buttons carry their preset in data-name
|
||||
const coll = t.dataset.coll;
|
||||
orca.postMessage({ command:'preset_config', collection:coll,
|
||||
name:t.dataset.name !== undefined ? t.dataset.name
|
||||
: $('sel-' + coll).value });
|
||||
name:$('sel-' + coll).value });
|
||||
} else if (act === 'expand') {
|
||||
const td = t.closest('td');
|
||||
td.textContent = td.dataset.full;
|
||||
@@ -1176,22 +1072,11 @@ $('content').addEventListener('click', e => {
|
||||
uiAction(t.dataset.ui);
|
||||
}
|
||||
});
|
||||
// Keyboard parity for the click-only elements (nav items, tree toggles, "show all").
|
||||
// Real form controls are skipped so they keep their native keys.
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key !== 'Enter' && e.key !== ' ') return;
|
||||
if (/^(BUTTON|INPUT|SELECT|TEXTAREA)$/.test(e.target.tagName)) return;
|
||||
const t = e.target.closest('.item, [data-act]');
|
||||
if (!t) return;
|
||||
e.preventDefault();
|
||||
t.click();
|
||||
});
|
||||
|
||||
/* ------------------------------------------------------------- messages */
|
||||
orca.onMessage(msg => {
|
||||
if (!msg || !msg.command) return;
|
||||
if (msg.command === 'section') {
|
||||
if (msg.gen !== S.gen) return; // raced a Refresh; the refetch is in flight
|
||||
S.cache[msg.section] = msg;
|
||||
S.busy[msg.section] = false;
|
||||
stamp();
|
||||
@@ -1200,15 +1085,14 @@ orca.onMessage(msg => {
|
||||
const container = $('mesh-' + msg.object + '-' + msg.volume);
|
||||
if (!container) return;
|
||||
if (msg.ok) renderMeshDetail(container, msg.data);
|
||||
else container.innerHTML =
|
||||
'<button class="small secondary" data-act="mesh" data-o="' + msg.object +
|
||||
'" data-v="' + msg.volume + '">Retry</button> <span class="muted">⚠ ' +
|
||||
esc(msg.error) + '</span>';
|
||||
else container.innerHTML = '<span class="muted">⚠ ' + esc(msg.error) + '</span>';
|
||||
} else if (msg.command === 'preset_config') {
|
||||
// Success opened a viewer window; this slot only shows (or clears) failures.
|
||||
const container = $('pcfg-' + msg.collection);
|
||||
if (!container) return;
|
||||
container.innerHTML = msg.ok ? '' : '<p class="muted">⚠ ' + esc(msg.error) + '</p>';
|
||||
container.innerHTML = msg.ok
|
||||
? '<div class="subhead">' + esc(msg.name) + ' · ' + msg.data.rows.length +
|
||||
' keys</div>' + configTable(msg.data.rows, 'pcfg-table-' + msg.collection)
|
||||
: '<span class="muted">⚠ ' + esc(msg.error) + '</span>';
|
||||
} else if (msg.command === 'ui_result') {
|
||||
addLog(msg.ok ? '← ' + msg.action + ': ' + msg.result
|
||||
: '← ' + msg.action + ' failed: ' + msg.error);
|
||||
@@ -1226,33 +1110,28 @@ show('overview');
|
||||
# The modal page demoed from the UI Toolkit tab: create_window(style=WINDOW_MODAL)
|
||||
# keeps the handle alive for callbacks; orca.submit(payload) invokes on_submit.
|
||||
MODAL_PAGE = r"""<!DOCTYPE html>
|
||||
<html lang="en"><head><meta charset="utf-8"><style>""" + BASE_CSS + r"""
|
||||
body { padding:18px; }
|
||||
h3 { margin-top:0; }
|
||||
input { width:100%; }
|
||||
</style></head>
|
||||
<body>
|
||||
<h3>Modal dialog</h3>
|
||||
<html><head><meta charset="utf-8"></head>
|
||||
<body style="padding:18px">
|
||||
<h3 style="margin-top:0">Modal dialog</h3>
|
||||
<p>create_window(style=WINDOW_MODAL) keeps this page interactive until it submits or closes.</p>
|
||||
<p><input id="note" value="hello from the modal"></p>
|
||||
<p><input id="note" style="width:100%" value="hello from the modal"></p>
|
||||
<p style="text-align:right">
|
||||
<button class="secondary" onclick="orca.postMessage({live: document.getElementById('note').value})">Post while open</button>
|
||||
<button class="secondary" onclick="orca.close()">Cancel</button>
|
||||
<button style="background:transparent;color:var(--orca-fg);border-color:var(--orca-border)"
|
||||
onclick="orca.postMessage({live: document.getElementById('note').value})">Post while open</button>
|
||||
<button style="background:transparent;color:var(--orca-fg);border-color:var(--orca-border)"
|
||||
onclick="orca.close()">Cancel</button>
|
||||
<button onclick="orca.submit({note: document.getElementById('note').value})">Submit</button>
|
||||
</p>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
CHILD_PAGE = r"""<!DOCTYPE html>
|
||||
<html lang="en"><head><meta charset="utf-8"><style>""" + BASE_CSS + r"""
|
||||
body { padding:18px; }
|
||||
h3 { margin-top:0; }
|
||||
</style></head>
|
||||
<body>
|
||||
<h3>Child window</h3>
|
||||
<html><head><meta charset="utf-8"></head>
|
||||
<body style="padding:18px">
|
||||
<h3 style="margin-top:0">Child window</h3>
|
||||
<p>Same create_window() API — one plugin, several windows.</p>
|
||||
<p><button onclick="orca.postMessage({command:'ping'})">Ping the plugin</button></p>
|
||||
<div id="log" style="color:var(--muted)"></div>
|
||||
<div id="log" style="color:var(--orca-muted)"></div>
|
||||
<script>
|
||||
var n = 0;
|
||||
orca.onMessage(function (msg) {
|
||||
@@ -1263,75 +1142,6 @@ CHILD_PAGE = r"""<!DOCTYPE html>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
# The per-preset config viewer, opened by "View config" on the Presets tab. Its rows are
|
||||
# baked in as JSON at build time, so the window needs no bridge traffic and stays usable
|
||||
# side by side with the panel until closed.
|
||||
CONFIG_PAGE = r"""<!DOCTYPE html>
|
||||
<html lang="en"><head><meta charset="utf-8"><style>""" + BASE_CSS + CFG_TABLE_CSS + r"""
|
||||
body { padding:12px 16px 20px; border-top:3px solid var(--hue, var(--accent)); }
|
||||
.toolbar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:10px; }
|
||||
.toolbar h3 { margin:0; font-size:13px; }
|
||||
.toolbar input { flex:1; min-width:140px; }
|
||||
.dot { display:inline-block; width:9px; height:9px; border-radius:50%; flex:none;
|
||||
background:var(--hue, var(--accent)); }
|
||||
.count { color:var(--muted); font-size:12px; white-space:nowrap; }
|
||||
</style></head>
|
||||
<body>
|
||||
<div class="toolbar"><span class="dot"></span><h3 id="name"></h3>
|
||||
<span class="count" id="count"></span>
|
||||
<input id="q" placeholder="Filter keys…">
|
||||
<button class="secondary" onclick="orca.close()">Close</button></div>
|
||||
<div id="table"></div>
|
||||
<script>
|
||||
'use strict';
|
||||
const DATA = __DATA__;
|
||||
const $ = id => document.getElementById(id);
|
||||
const esc = s => String(s == null ? '' : s)
|
||||
.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
if (DATA.hue) document.body.style.setProperty('--hue', DATA.hue);
|
||||
$('name').textContent = DATA.name;
|
||||
$('table').innerHTML = '<table class="cfg" id="t"><tr><th>key</th><th>value</th></tr>' +
|
||||
DATA.rows.map(r => {
|
||||
const long = r.v.length > 160;
|
||||
const shown = long ? esc(r.v.slice(0, 160)) +
|
||||
'<span class="more" role="button" tabindex="0"> … show all</span>' : esc(r.v);
|
||||
return '<tr data-k="' + esc(r.k.toLowerCase()) + '"><td>' + esc(r.k) + '</td>' +
|
||||
'<td class="val" data-full="' + esc(r.v) + '">' + shown + '</td></tr>';
|
||||
}).join('') + '</table>';
|
||||
function filter(q) {
|
||||
q = q.trim().toLowerCase();
|
||||
let visible = 0;
|
||||
document.querySelectorAll('#t tr[data-k]').forEach(tr => {
|
||||
const hit = !q || tr.dataset.k.includes(q) ||
|
||||
tr.querySelector('.val').dataset.full.toLowerCase().includes(q);
|
||||
tr.style.display = hit ? '' : 'none';
|
||||
if (hit) visible++;
|
||||
});
|
||||
$('count').textContent = visible + ' keys';
|
||||
}
|
||||
$('q').addEventListener('input', e => filter(e.target.value));
|
||||
$('table').addEventListener('click', e => {
|
||||
const t = e.target.closest('.more');
|
||||
if (t) { const td = t.closest('td'); td.textContent = td.dataset.full; }
|
||||
});
|
||||
document.addEventListener('keydown', e => {
|
||||
if ((e.key === 'Enter' || e.key === ' ') && e.target.classList.contains('more')) {
|
||||
e.preventDefault();
|
||||
e.target.click();
|
||||
}
|
||||
});
|
||||
filter('');
|
||||
</script>
|
||||
</body></html>
|
||||
"""
|
||||
|
||||
|
||||
def config_page(collection, name, rows):
|
||||
"""CONFIG_PAGE with one preset's rows baked in. `</` is escaped so a config
|
||||
value containing e.g. `</script>` cannot break out of the script block."""
|
||||
payload = json.dumps({"name": name, "hue": HUES.get(collection, ""), "rows": rows})
|
||||
return CONFIG_PAGE.replace("__DATA__", payload.replace("</", "<\\/"))
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# the plugin
|
||||
@@ -1340,7 +1150,6 @@ class OrcaInspectorPanel(orca.script.ScriptPluginCapabilityBase):
|
||||
win = None
|
||||
child = None
|
||||
modal = None
|
||||
cfgwin = None
|
||||
|
||||
def get_name(self):
|
||||
return "Orca Inspector"
|
||||
@@ -1357,9 +1166,6 @@ class OrcaInspectorPanel(orca.script.ScriptPluginCapabilityBase):
|
||||
if self.modal is not None:
|
||||
self.modal.close()
|
||||
self.modal = None
|
||||
if self.cfgwin is not None:
|
||||
self.cfgwin.close()
|
||||
self.cfgwin = None
|
||||
# Non-modal: returns immediately. The window is host-owned and lives on
|
||||
# after execute() returns; on_message keeps firing when the page posts.
|
||||
self.win = orca.host.ui.create_window(
|
||||
@@ -1378,11 +1184,11 @@ class OrcaInspectorPanel(orca.script.ScriptPluginCapabilityBase):
|
||||
msg = msg or {}
|
||||
command = msg.get("command")
|
||||
if command == "fetch":
|
||||
self.send_section(msg.get("section", ""), msg.get("gen"))
|
||||
self.send_section(msg.get("section", ""))
|
||||
elif command == "mesh":
|
||||
self.send_mesh(msg.get("object", -1), msg.get("volume", -1))
|
||||
self.send_mesh(int(msg.get("object", -1)), int(msg.get("volume", -1)))
|
||||
elif command == "preset_config":
|
||||
self.open_preset_config(msg.get("collection", ""), msg.get("name", ""))
|
||||
self.send_preset_config(msg.get("collection", ""), msg.get("name", ""))
|
||||
elif command == "ui":
|
||||
self.run_ui_action(msg)
|
||||
|
||||
@@ -1390,15 +1196,11 @@ class OrcaInspectorPanel(orca.script.ScriptPluginCapabilityBase):
|
||||
if self.child is not None:
|
||||
self.child.close()
|
||||
self.child = None
|
||||
if self.cfgwin is not None:
|
||||
self.cfgwin.close()
|
||||
self.cfgwin = None
|
||||
print("Orca Inspector closed")
|
||||
|
||||
def send_section(self, section, gen=None):
|
||||
def send_section(self, section):
|
||||
builder = SECTION_BUILDERS.get(section)
|
||||
# gen echoes the page's refresh counter so a reply that raced a Refresh is dropped there.
|
||||
reply = {"command": "section", "section": section, "gen": gen}
|
||||
reply = {"command": "section", "section": section}
|
||||
if builder is None:
|
||||
self.win.post({**reply, "ok": False, "error": f"unknown section {section!r}"})
|
||||
return
|
||||
@@ -1411,23 +1213,15 @@ class OrcaInspectorPanel(orca.script.ScriptPluginCapabilityBase):
|
||||
reply = {"command": "mesh", "object": object_index, "volume": volume_index}
|
||||
try:
|
||||
self.win.post({**reply, "ok": True,
|
||||
"data": build_mesh(int(object_index), int(volume_index))})
|
||||
"data": build_mesh(object_index, volume_index)})
|
||||
except Exception as exc:
|
||||
self.win.post({**reply, "ok": False, "error": str(exc)})
|
||||
|
||||
def open_preset_config(self, collection, name):
|
||||
# One viewer at a time: a new pick replaces the previous window. The main page
|
||||
# only hears back about failures.
|
||||
def send_preset_config(self, collection, name):
|
||||
reply = {"command": "preset_config", "collection": collection, "name": name}
|
||||
try:
|
||||
rows = build_preset_config(collection, name)
|
||||
if self.cfgwin is not None:
|
||||
self.cfgwin.close()
|
||||
self.cfgwin = orca.host.ui.create_window(
|
||||
title=f"Orca Inspector — {name}",
|
||||
html=config_page(collection, name, rows),
|
||||
width=520, height=640)
|
||||
self.win.post({**reply, "ok": True})
|
||||
self.win.post({**reply, "ok": True,
|
||||
"data": build_preset_config(collection, name)})
|
||||
except Exception as exc:
|
||||
self.win.post({**reply, "ok": False, "error": str(exc)})
|
||||
|
||||
@@ -1473,19 +1267,15 @@ class OrcaInspectorPanel(orca.script.ScriptPluginCapabilityBase):
|
||||
if self.child is not None and self.child.is_open():
|
||||
report("child already open")
|
||||
else:
|
||||
# Not `reply`: the close can also come from "child_close" later, so the
|
||||
# on_close payload is labelled neutrally.
|
||||
self.child = orca.host.ui.create_window(
|
||||
title="Orca Inspector — child", html=CHILD_PAGE,
|
||||
width=380, height=240, on_message=self.on_child_message,
|
||||
on_close=lambda: self.win.post(
|
||||
{"command": "ui_result", "action": "child",
|
||||
"ok": True, "result": "child window closed"}))
|
||||
{**reply, "ok": True, "result": "child window closed"}))
|
||||
report("child opened")
|
||||
elif action == "child_close":
|
||||
if self.child is not None:
|
||||
self.child.close()
|
||||
report("close requested") # the actual close is logged by on_close
|
||||
else:
|
||||
report("no child window")
|
||||
elif action == "child_state":
|
||||
@@ -1,618 +0,0 @@
|
||||
# /// script
|
||||
# requires-python = ">=3.12"
|
||||
#
|
||||
# [tool.orcaslicer.plugin]
|
||||
# name = "Twistify"
|
||||
# description = "Twists, tapers, and wobbles every layer's slice polygons as a function of Z (demo)."
|
||||
# author = "SoftFever"
|
||||
# version = "0.03"
|
||||
# type = "slicing-pipeline"
|
||||
# ///
|
||||
"""Twistify -- twist/taper/wobble any model at slice time.
|
||||
|
||||
At Step.posSlice, every layer's sliced surfaces are transformed by a similarity
|
||||
about the object's bounding-box center as a function of Z -- edited IN PLACE
|
||||
through the host geometry classes (ExPolygon.rotate/scale/translate). Each
|
||||
surface is rotated about the center, then (if tapering) translated to the
|
||||
origin, uniformly scaled, and translated back, so the taper stays centered on
|
||||
the object instead of drifting toward the coordinate origin. An optional X
|
||||
wobble is applied last. After the per-region edits, layer.make_slices()
|
||||
re-derives the layer's merged islands so overhang/bridge/skirt/support stay
|
||||
coherent. The split slice loop runs make_perimeters() right after the hook, so
|
||||
the transform cascades into perimeters, infill, and the final G-code -- the
|
||||
preview corkscrews and the print keeps correct walls/infill/flow.
|
||||
|
||||
Because we edit geometry in place, surface types are preserved automatically
|
||||
(no per-surface type carry needed), and no numpy is required --
|
||||
rotate/scale/translate are host methods. Parameters come from self.get_config()
|
||||
(see _params below); the host seeds them from get_default_config() the first
|
||||
time the plugin loads. The first object layer is untouched (z_rel = 0), so bed
|
||||
adhesion is unaffected.
|
||||
|
||||
The plugin also ships its own configuration UI (has_config_ui/get_config_ui):
|
||||
a self-contained page the Plugins dialog renders instead of the JSON editor,
|
||||
drawing the transform it is about to apply as an isometric stack of layer
|
||||
outlines. It reaches the host only through the injected window.orca bridge --
|
||||
getConfig/saveConfig/restoreDefaults/getContext/onConfig/onTheme -- and styles
|
||||
itself from the --orca-* theme variables the host hands the frame, so it
|
||||
follows OrcaSlicer's light/dark theme.
|
||||
"""
|
||||
import math
|
||||
import json
|
||||
import orca
|
||||
|
||||
_DEFAULTS = {
|
||||
"twist_deg_per_mm": 1.0,
|
||||
"taper_per_mm": 0.0,
|
||||
"wobble_ampl_mm": 0.0,
|
||||
"wobble_period_mm": 20.0,
|
||||
"min_scale": 0.05,
|
||||
}
|
||||
|
||||
|
||||
def _params(self):
|
||||
try:
|
||||
src = json.loads(self.get_config())
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
src = {}
|
||||
out = {}
|
||||
for key, default in _DEFAULTS.items():
|
||||
try:
|
||||
out[key] = float(src[key])
|
||||
except (KeyError, TypeError, ValueError):
|
||||
out[key] = default
|
||||
return out
|
||||
|
||||
|
||||
def _is_identity(p):
|
||||
return p["twist_deg_per_mm"] == 0.0 and p["taper_per_mm"] == 0.0 and p["wobble_ampl_mm"] == 0.0
|
||||
|
||||
|
||||
def _layer_params(z_rel, mm_to_scaled, p):
|
||||
"""(angle_rad, scale, x_offset_scaled) for one layer. Exact identity at z_rel == 0."""
|
||||
theta = math.radians(p["twist_deg_per_mm"] * z_rel)
|
||||
s = max(p["min_scale"], 1.0 + p["taper_per_mm"] * z_rel)
|
||||
ox = 0.0
|
||||
if p["wobble_ampl_mm"] != 0.0 and p["wobble_period_mm"] > 0.0:
|
||||
ox = p["wobble_ampl_mm"] * math.sin(2.0 * math.pi * z_rel / p["wobble_period_mm"]) * mm_to_scaled
|
||||
return theta, s, ox
|
||||
|
||||
|
||||
# The configuration page. Self-contained on purpose: it runs in an iframe sandboxed into an opaque
|
||||
# origin, so it has only the window.orca bridge and the --orca-* theme variables the host injects --
|
||||
# no network, no same-origin access, no shared stylesheet. get_config_ui() substitutes _DEFAULTS for
|
||||
# __ORCA_DEFAULTS__, so Python owns the values and the page adds only presentation.
|
||||
_CONFIG_UI = """
|
||||
<style>
|
||||
:root {
|
||||
--ink: var(--orca-fg, #1f2429);
|
||||
--paper: var(--orca-bg, #ffffff);
|
||||
--rule: var(--orca-border, #d9dee3);
|
||||
--quiet: var(--orca-muted, #6b7580);
|
||||
--live: var(--orca-accent, #009688);
|
||||
--live-ink: var(--orca-accent-fg, #ffffff);
|
||||
--ui: var(--orca-font, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
|
||||
--data: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--ink: var(--orca-fg, #e4e6e8);
|
||||
--paper: var(--orca-bg, #2b2d30);
|
||||
--rule: var(--orca-border, #3d4043);
|
||||
--quiet: var(--orca-muted, #9aa0a6);
|
||||
}
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html, body { height: 100%; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
font: 13px/1.45 var(--ui);
|
||||
}
|
||||
.panel { display: flex; flex-direction: column; height: 100%; }
|
||||
|
||||
.head {
|
||||
display: flex; align-items: baseline; gap: 10px;
|
||||
padding: 12px 16px 10px;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
}
|
||||
.mark {
|
||||
font: 10px/1 var(--data); letter-spacing: .14em; text-transform: uppercase;
|
||||
color: var(--live);
|
||||
}
|
||||
.head-note { font-size: 11px; color: var(--quiet); }
|
||||
|
||||
/* The frame is as narrow as ~400px in a resized dialog and as wide as ~600px; below the
|
||||
breakpoint the drawing moves under the controls rather than squeezing both. */
|
||||
.work {
|
||||
flex: 1; min-height: 0; overflow: auto;
|
||||
display: grid; grid-template-columns: minmax(0, 1fr) 180px; gap: 16px;
|
||||
align-content: start; padding: 14px 16px;
|
||||
}
|
||||
@media (max-width: 470px) {
|
||||
.work { grid-template-columns: minmax(0, 1fr); }
|
||||
.plot { max-width: 210px; }
|
||||
}
|
||||
|
||||
.rows { display: flex; flex-direction: column; gap: 12px; }
|
||||
.row { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 4px 8px; }
|
||||
.row-label {
|
||||
grid-column: 1;
|
||||
font: 10px/1 var(--data); letter-spacing: .12em; text-transform: uppercase;
|
||||
color: var(--quiet);
|
||||
}
|
||||
.row-figure { grid-column: 2; display: flex; align-items: baseline; gap: 4px; }
|
||||
.row-figure input {
|
||||
/* No spinners: the rail is the coarse control, this is the exact one, and the arrows would
|
||||
steal the width the value needs. */
|
||||
-webkit-appearance: textfield; appearance: textfield;
|
||||
width: 76px; padding: 3px 6px; text-align: right;
|
||||
font: 13px/1.2 var(--data); font-variant-numeric: tabular-nums;
|
||||
color: var(--ink); background: transparent;
|
||||
border: 1px solid transparent; border-radius: 3px;
|
||||
}
|
||||
.row-figure input::-webkit-outer-spin-button,
|
||||
.row-figure input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
||||
.row-figure input:hover { border-color: var(--rule); }
|
||||
.row-figure input:focus { outline: none; border-color: var(--live); }
|
||||
.unit { font: 10px/1 var(--data); color: var(--quiet); min-width: 26px; }
|
||||
|
||||
/* The rail carries a detent at the value that means "no deformation", so neutral is a position
|
||||
you can feel for rather than a number you have to read. */
|
||||
.rail { grid-column: 1 / -1; position: relative; height: 16px; }
|
||||
.rail[data-detent]::before {
|
||||
content: ""; position: absolute; left: var(--detent); top: 3px;
|
||||
width: 1px; height: 10px; background: var(--rule);
|
||||
}
|
||||
.rail input {
|
||||
-webkit-appearance: none; appearance: none;
|
||||
position: relative; width: 100%; height: 16px; margin: 0; background: transparent;
|
||||
}
|
||||
.rail input::-webkit-slider-runnable-track { height: 2px; background: var(--rule); border-radius: 1px; }
|
||||
.rail input::-webkit-slider-thumb {
|
||||
-webkit-appearance: none; width: 11px; height: 11px; margin-top: -4.5px;
|
||||
border: none; border-radius: 50%; background: var(--live); cursor: grab;
|
||||
}
|
||||
.rail input:focus { outline: none; }
|
||||
.rail input:focus-visible { outline: 2px solid var(--live); outline-offset: 1px; border-radius: 3px; }
|
||||
.rail input:disabled::-webkit-slider-thumb { background: var(--quiet); cursor: default; }
|
||||
|
||||
.readout {
|
||||
margin-top: 14px; padding-top: 10px; border-top: 1px solid var(--rule);
|
||||
font-size: 11px; color: var(--quiet); min-height: 32px;
|
||||
}
|
||||
.readout b { color: var(--ink); font-weight: 600; font-family: var(--data); font-variant-numeric: tabular-nums; }
|
||||
|
||||
.plot { margin: 0; display: flex; flex-direction: column; gap: 6px; }
|
||||
.plot svg { width: 100%; height: auto; display: block; }
|
||||
.plot figcaption { font: 10px/1.4 var(--data); color: var(--quiet); }
|
||||
/* SVG colours live here rather than in stroke="" attributes: var() is not reliable in a
|
||||
presentation attribute, and this way the drawing re-themes with the rest of the page. */
|
||||
.ring, .helix, .datum { fill: none; }
|
||||
.ring { stroke: var(--live); stroke-width: 1; }
|
||||
.helix { stroke: var(--live); stroke-width: 1; stroke-opacity: .5; stroke-dasharray: 2 2; }
|
||||
.datum { stroke: var(--quiet); stroke-width: 1; stroke-opacity: .7; }
|
||||
.tick { stroke: var(--rule); stroke-width: 1; }
|
||||
.plot text { fill: var(--quiet); font: 8px var(--data); }
|
||||
|
||||
.foot {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 10px 16px; border-top: 1px solid var(--rule);
|
||||
}
|
||||
.state { flex: 1; font-size: 11px; color: var(--quiet); }
|
||||
.state.dirty { color: var(--ink); }
|
||||
button {
|
||||
font: 12px/1 var(--ui); padding: 6px 14px; border-radius: 4px; cursor: pointer;
|
||||
border: 1px solid var(--rule); background: transparent; color: var(--ink);
|
||||
}
|
||||
button.go { border-color: var(--live); background: var(--live); color: var(--live-ink); }
|
||||
button:disabled { opacity: .45; cursor: default; }
|
||||
button:focus-visible { outline: 2px solid var(--live); outline-offset: 2px; }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
|
||||
</style>
|
||||
|
||||
<div class="panel">
|
||||
<header class="head">
|
||||
<span class="mark">Twistify</span>
|
||||
<span class="head-note">Applied to every layer as it is sliced</span>
|
||||
</header>
|
||||
|
||||
<div class="work">
|
||||
<div>
|
||||
<div class="rows" id="rows"></div>
|
||||
<p class="readout" id="readout"></p>
|
||||
</div>
|
||||
|
||||
<figure class="plot">
|
||||
<svg id="plot" viewBox="0 0 180 208" aria-hidden="true"></svg>
|
||||
<figcaption id="plotNote"></figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
<footer class="foot">
|
||||
<span class="state" id="state"></span>
|
||||
<button type="button" id="restore">Restore defaults</button>
|
||||
<button type="button" id="save" class="go">Save</button>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var DEFAULTS = __ORCA_DEFAULTS__;
|
||||
|
||||
// Presentation only. `neutral` marks the value at which the control contributes no deformation;
|
||||
// rows without one (period, floor) are shape parameters, not amounts.
|
||||
var FIELDS = [
|
||||
{ key: "twist_deg_per_mm", label: "Twist", unit: "\\u00b0/mm", min: -20, max: 20, step: 0.1, dp: 1, neutral: 0,
|
||||
hint: "Rotation added per millimetre of height." },
|
||||
{ key: "taper_per_mm", label: "Taper", unit: "/mm", min: -0.05, max: 0.05, step: 0.001, dp: 3, neutral: 0,
|
||||
hint: "Scale added per millimetre. Negative narrows the model as it rises." },
|
||||
{ key: "wobble_ampl_mm", label: "Wobble", unit: "mm", min: 0, max: 10, step: 0.1, dp: 1, neutral: 0,
|
||||
hint: "How far each layer slides in X, from centre to peak." },
|
||||
{ key: "wobble_period_mm", label: "Wobble period", unit: "mm", min: 1, max: 120, step: 1, dp: 0,
|
||||
hint: "Height of one full wobble cycle." },
|
||||
{ key: "min_scale", label: "Scale floor", unit: "\\u00d7", min: 0.01, max: 1, step: 0.01, dp: 2,
|
||||
hint: "Taper never shrinks a layer past this, so a tall model cannot collapse." }
|
||||
];
|
||||
|
||||
// The preview simulates this much height. It never reads the real model, so the caption says so
|
||||
// rather than implying the drawing is to scale with the plate.
|
||||
var Z_SPAN = 40;
|
||||
var Z_STEP = 2;
|
||||
|
||||
var form = {};
|
||||
var stored = {};
|
||||
var context = (window.orca && window.orca.getContext) ? window.orca.getContext() : { scope: "global" };
|
||||
var savedNoteTimer = 0;
|
||||
|
||||
function num(value, fallback) {
|
||||
var n = parseFloat(value);
|
||||
return isFinite(n) ? n : fallback;
|
||||
}
|
||||
|
||||
function merged(config) {
|
||||
var out = {};
|
||||
Object.keys(DEFAULTS).forEach(function (key) {
|
||||
out[key] = num(config ? config[key] : undefined, DEFAULTS[key]);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
function isDirty() {
|
||||
return Object.keys(DEFAULTS).some(function (key) { return form[key] !== stored[key]; });
|
||||
}
|
||||
|
||||
// --- the transform, exactly as _layer_params() applies it -------------------------------------
|
||||
|
||||
function layerAt(z) {
|
||||
var theta = form.twist_deg_per_mm * z * Math.PI / 180;
|
||||
var scale = Math.max(form.min_scale, 1 + form.taper_per_mm * z);
|
||||
var shift = 0;
|
||||
if (form.wobble_ampl_mm !== 0 && form.wobble_period_mm > 0)
|
||||
shift = form.wobble_ampl_mm * Math.sin(2 * Math.PI * z / form.wobble_period_mm);
|
||||
return { theta: theta, scale: scale, shift: shift };
|
||||
}
|
||||
|
||||
function deforms() {
|
||||
return form.twist_deg_per_mm !== 0 || form.taper_per_mm !== 0 || form.wobble_ampl_mm !== 0;
|
||||
}
|
||||
|
||||
// --- drawing ----------------------------------------------------------------------------------
|
||||
|
||||
var ISO_X = 0.866, ISO_Y = 0.5; // isometric: plan outlines stacked by height
|
||||
var ORIGIN_X = 108, ORIGIN_Y = 182; // the z = 0 outline's centre, in viewBox units
|
||||
var PLAN = 20; // half-width of the sample contour
|
||||
var Z_UNITS = 3.3; // viewBox units per millimetre of height
|
||||
var MM = 1.5; // viewBox units per millimetre of wobble travel
|
||||
var REACH = 62; // how far sideways the drawing may run before it is scaled down
|
||||
|
||||
// Plan-view corners of the layer at `z`, before projection.
|
||||
function planOutline(z) {
|
||||
var layer = layerAt(z);
|
||||
var half = PLAN * layer.scale;
|
||||
var cos = Math.cos(layer.theta), sin = Math.sin(layer.theta);
|
||||
return [[-half, -half], [half, -half], [half, half], [-half, half]].map(function (corner) {
|
||||
return [corner[0] * cos - corner[1] * sin + layer.shift * MM,
|
||||
corner[0] * sin + corner[1] * cos];
|
||||
});
|
||||
}
|
||||
|
||||
function points(plan, z, fit) {
|
||||
return plan.map(function (p) {
|
||||
var x = ORIGIN_X + (p[0] - p[1]) * ISO_X * fit;
|
||||
var y = ORIGIN_Y + (p[0] + p[1]) * ISO_Y * fit - z * Z_UNITS;
|
||||
return x.toFixed(1) + "," + y.toFixed(1);
|
||||
}).join(" ");
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var svg = [];
|
||||
var layers = [];
|
||||
var reach = 0;
|
||||
for (var z = 0; z <= Z_SPAN + 0.001; z += Z_STEP) {
|
||||
var plan = planOutline(z);
|
||||
layers.push({ z: z, plan: plan });
|
||||
plan.forEach(function (p) { reach = Math.max(reach, Math.abs(p[0] - p[1]) * ISO_X); });
|
||||
}
|
||||
// A wide taper or a big wobble would run out of the frame, so scale down to fit rather than
|
||||
// clipping. Z keeps its scale, so the ruler stays true.
|
||||
var fit = reach > REACH ? REACH / reach : 1;
|
||||
|
||||
// Z ruler: the drawing is a height plot, so it gets an axis.
|
||||
for (var mark = 0; mark <= Z_SPAN; mark += 10) {
|
||||
var y = ORIGIN_Y - mark * Z_UNITS;
|
||||
svg.push('<line class="tick" x1="24" y1="' + y.toFixed(1) + '" x2="30" y2="' + y.toFixed(1) + '"/>');
|
||||
svg.push('<text x="20" y="' + (y + 3).toFixed(1) + '" text-anchor="end">' + mark + '</text>');
|
||||
}
|
||||
svg.push('<text x="30" y="' + (ORIGIN_Y - Z_SPAN * Z_UNITS - 11).toFixed(1) +
|
||||
'" text-anchor="end">mm</text>');
|
||||
|
||||
// The stack, faint at the bed and full strength at the top: the deformation grows with Z.
|
||||
var trace = [];
|
||||
layers.forEach(function (layer) {
|
||||
var ring = points(layer.plan, layer.z, fit);
|
||||
svg.push('<polygon class="ring" points="' + ring + '" stroke-opacity="' +
|
||||
(0.18 + 0.82 * layer.z / Z_SPAN).toFixed(2) + '"/>');
|
||||
trace.push(ring.split(" ")[1]);
|
||||
});
|
||||
|
||||
// One corner followed all the way up: on a twisted model, the helix the walls will run in.
|
||||
svg.push('<polyline class="helix" points="' + trace.join(" ") + '"/>');
|
||||
|
||||
// The datum: the first layer is never transformed, so it doubles as the reference outline.
|
||||
svg.push('<polygon class="datum" points="' + points(layers[0].plan, 0, fit) + '"/>');
|
||||
|
||||
document.getElementById("plot").innerHTML = svg.join("");
|
||||
document.getElementById("plotNote").textContent =
|
||||
deforms() ? "Sample contour, " + Z_SPAN + " mm of Z" : "Sample contour, undeformed";
|
||||
}
|
||||
|
||||
// --- readout ----------------------------------------------------------------------------------
|
||||
|
||||
function describe() {
|
||||
if (!deforms())
|
||||
return "No deformation. Twistify passes every layer through unchanged.";
|
||||
|
||||
var top = layerAt(Z_SPAN);
|
||||
var parts = [];
|
||||
if (form.twist_deg_per_mm !== 0)
|
||||
parts.push("<b>" + (form.twist_deg_per_mm * Z_SPAN).toFixed(0) + "\\u00b0</b> of rotation");
|
||||
if (form.taper_per_mm !== 0)
|
||||
parts.push("<b>" + top.scale.toFixed(2) + "\\u00d7</b> scale" +
|
||||
(top.scale === form.min_scale ? " (at the floor)" : ""));
|
||||
if (form.wobble_ampl_mm !== 0)
|
||||
parts.push("<b>\\u00b1" + form.wobble_ampl_mm.toFixed(1) + "</b> mm of wobble");
|
||||
return "At " + Z_SPAN + " mm above the first layer: " + parts.join(", ") + ".";
|
||||
}
|
||||
|
||||
function setReadout(html) { document.getElementById("readout").innerHTML = html; }
|
||||
|
||||
// Whatever the pointer or the keyboard is on explains itself; with neither, report the totals.
|
||||
function restoreReadout() {
|
||||
var id = document.activeElement ? document.activeElement.id : "";
|
||||
for (var i = 0; i < FIELDS.length; i++)
|
||||
if (id === "n-" + FIELDS[i].key || id === "r-" + FIELDS[i].key)
|
||||
return setReadout(FIELDS[i].hint);
|
||||
setReadout(describe());
|
||||
}
|
||||
|
||||
function refreshState() {
|
||||
var dirty = isDirty();
|
||||
document.getElementById("save").disabled = context.readOnly || !dirty;
|
||||
|
||||
if (savedNoteTimer)
|
||||
return; // the "Saved" note owns the line until it expires
|
||||
var state = document.getElementById("state");
|
||||
state.textContent = context.readOnly ? "This configuration is read-only."
|
||||
: dirty ? "Unsaved changes" : "";
|
||||
state.classList.toggle("dirty", dirty && !context.readOnly);
|
||||
}
|
||||
|
||||
function announce(message) {
|
||||
var state = document.getElementById("state");
|
||||
state.textContent = message;
|
||||
state.classList.remove("dirty");
|
||||
clearTimeout(savedNoteTimer);
|
||||
savedNoteTimer = setTimeout(function () { savedNoteTimer = 0; refreshState(); }, 2200);
|
||||
}
|
||||
|
||||
// --- controls ---------------------------------------------------------------------------------
|
||||
|
||||
function detent(field) {
|
||||
if (field.neutral === undefined) return "";
|
||||
return ((field.neutral - field.min) / (field.max - field.min) * 100).toFixed(2) + "%";
|
||||
}
|
||||
|
||||
function clamp(field, value) { return Math.min(field.max, Math.max(field.min, value)); }
|
||||
|
||||
// `echo` rewrites the number field with the clamped, formatted value. Off while the user is still
|
||||
// typing: "-" and "0.0" are valid partial input, and rewriting them mid-keystroke fights the
|
||||
// typist. The drawing still follows every keystroke.
|
||||
function apply(field, value, echo) {
|
||||
form[field.key] = clamp(field, value);
|
||||
if (echo)
|
||||
document.getElementById("n-" + field.key).value = form[field.key].toFixed(field.dp);
|
||||
document.getElementById("r-" + field.key).value = form[field.key];
|
||||
draw();
|
||||
// A control explains itself while you approach it, but once a value moves what matters is the
|
||||
// total it adds up to.
|
||||
setReadout(describe());
|
||||
refreshState();
|
||||
}
|
||||
|
||||
function buildRows() {
|
||||
var host = document.getElementById("rows");
|
||||
FIELDS.forEach(function (field) {
|
||||
var row = document.createElement("div");
|
||||
row.className = "row";
|
||||
// The number input is the labelled, keyboard-reachable control; the rail is the same value by
|
||||
// pointer, so it stays out of the tab order and the accessibility tree.
|
||||
row.innerHTML =
|
||||
'<label class="row-label" for="n-' + field.key + '">' + field.label + '</label>' +
|
||||
'<span class="row-figure">' +
|
||||
'<input id="n-' + field.key + '" type="number" step="' + field.step + '" ' +
|
||||
'min="' + field.min + '" max="' + field.max + '">' +
|
||||
'<span class="unit">' + field.unit + '</span>' +
|
||||
'</span>' +
|
||||
'<span class="rail"' + (field.neutral === undefined ? "" : ' data-detent style="--detent:' + detent(field) + '"') + '>' +
|
||||
'<input id="r-' + field.key + '" type="range" step="' + field.step + '" ' +
|
||||
'min="' + field.min + '" max="' + field.max + '" tabindex="-1" aria-hidden="true">' +
|
||||
'</span>';
|
||||
host.appendChild(row);
|
||||
|
||||
var number = row.querySelector("#n-" + field.key);
|
||||
var range = row.querySelector("#r-" + field.key);
|
||||
number.addEventListener("input", function () { apply(field, num(number.value, form[field.key]), false); });
|
||||
number.addEventListener("change", function () { apply(field, num(number.value, DEFAULTS[field.key]), true); });
|
||||
range.addEventListener("input", function () { apply(field, num(range.value, form[field.key]), true); });
|
||||
|
||||
[number, range].forEach(function (input) {
|
||||
input.addEventListener("focus", function () { setReadout(field.hint); });
|
||||
input.addEventListener("mouseenter", function () { setReadout(field.hint); });
|
||||
input.addEventListener("blur", restoreReadout);
|
||||
input.addEventListener("mouseleave", restoreReadout);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function load(config) {
|
||||
stored = merged(config);
|
||||
form = merged(config);
|
||||
FIELDS.forEach(function (field) {
|
||||
document.getElementById("n-" + field.key).value = form[field.key].toFixed(field.dp);
|
||||
document.getElementById("r-" + field.key).value = form[field.key];
|
||||
});
|
||||
draw();
|
||||
restoreReadout();
|
||||
refreshState();
|
||||
labelRestore();
|
||||
setInputsEnabled(!context.readOnly);
|
||||
}
|
||||
|
||||
// "Restore defaults" writes the plugin's own defaults globally, but in a preset it drops that
|
||||
// preset's override and falls back to the global configuration. Say which one the button does.
|
||||
function labelRestore() {
|
||||
var restore = document.getElementById("restore");
|
||||
var preset = context.scope === "preset";
|
||||
restore.textContent = preset ? "Use global settings" : "Restore defaults";
|
||||
restore.title = preset ? "Discard this preset's override and use the global configuration"
|
||||
: "Replace the saved configuration with Twistify's defaults";
|
||||
restore.disabled = context.readOnly || (preset && !context.hasPresetOverride);
|
||||
}
|
||||
|
||||
function setInputsEnabled(enabled) {
|
||||
FIELDS.forEach(function (field) {
|
||||
document.getElementById("n-" + field.key).disabled = !enabled;
|
||||
document.getElementById("r-" + field.key).disabled = !enabled;
|
||||
});
|
||||
}
|
||||
|
||||
buildRows();
|
||||
load(window.orca ? window.orca.getConfig() : {});
|
||||
|
||||
document.getElementById("save").addEventListener("click", function () {
|
||||
if (!window.orca) return;
|
||||
window.orca.saveConfig(form);
|
||||
});
|
||||
document.getElementById("restore").addEventListener("click", function () {
|
||||
if (window.orca) window.orca.restoreDefaults();
|
||||
});
|
||||
|
||||
if (window.orca && window.orca.onConfig) {
|
||||
var first = true;
|
||||
window.orca.onConfig(function (config) {
|
||||
// Fires once immediately (already handled above) and then on every host save: reload from
|
||||
// what was stored, never from what was typed.
|
||||
if (first) { first = false; return; }
|
||||
if (window.orca.getContext) context = window.orca.getContext();
|
||||
load(config);
|
||||
announce("Saved");
|
||||
});
|
||||
}
|
||||
// No theme handler needed: every colour on the page, the drawing included, resolves through the
|
||||
// --orca-* variables the host rewrites in place.
|
||||
})();
|
||||
</script>
|
||||
"""
|
||||
|
||||
|
||||
class Twistify(orca.slicing.SlicingPipelineCapabilityBase):
|
||||
def get_name(self):
|
||||
return "Twistify"
|
||||
|
||||
def get_default_config(self):
|
||||
return _DEFAULTS
|
||||
|
||||
def has_config_ui(self):
|
||||
return True
|
||||
|
||||
def get_config_ui(self):
|
||||
return _CONFIG_UI.replace("__ORCA_DEFAULTS__", json.dumps(_DEFAULTS))
|
||||
|
||||
def execute(self, ctx):
|
||||
if ctx.step != orca.slicing.Step.posSlice or ctx.object is None:
|
||||
return orca.ExecutionResult.success()
|
||||
|
||||
p = _params(self)
|
||||
if _is_identity(p):
|
||||
return orca.ExecutionResult.success("Twistify: identity parameters, nothing to do")
|
||||
|
||||
mm_to_scaled = 1.0 / orca.slicing.unscale(1)
|
||||
|
||||
layers = ctx.object.layers()
|
||||
if not layers:
|
||||
return orca.ExecutionResult.success("Twistify: object has no layers")
|
||||
|
||||
# Twist/taper axis = the object's bounding-box center (scaled coords, same frame
|
||||
# as the slice polygons), so each object on the plate transforms about its own
|
||||
# center. Keep the float center for translate-to-origin/back around scale(), and
|
||||
# a rounded-to-Point center for rotate() (which takes an integer Point).
|
||||
min_x, min_y, max_x, max_y = ctx.object.bounding_box()
|
||||
cx = (min_x + max_x) / 2.0
|
||||
cy = (min_y + max_y) / 2.0
|
||||
center = orca.host.Point(int(round(cx)), int(round(cy)))
|
||||
z0 = float(layers[0].print_z) # z_rel = 0 on the first layer -> footprint untouched
|
||||
|
||||
layers_touched = 0
|
||||
for layer in layers:
|
||||
if ctx.cancelled():
|
||||
break
|
||||
z_rel = float(layer.print_z) - z0
|
||||
theta, s, ox = _layer_params(z_rel, mm_to_scaled, p)
|
||||
if theta == 0.0 and s == 1.0 and ox == 0.0:
|
||||
continue # exact identity (always the first layer)
|
||||
|
||||
edited = False
|
||||
for region in layer.regions():
|
||||
for surface in region.slices.surfaces:
|
||||
ex = surface.expolygon
|
||||
ex.rotate(theta, center) # rotate about the object center (in place)
|
||||
if s != 1.0:
|
||||
# scale() scales about the coordinate ORIGIN, so re-center the
|
||||
# geometry on the origin first and translate back after, making
|
||||
# this a true similarity transform about the object's center.
|
||||
ex.translate(-cx, -cy)
|
||||
ex.scale(s)
|
||||
ex.translate(cx, cy)
|
||||
if ox != 0.0:
|
||||
ex.translate(ox, 0.0) # wobble in X
|
||||
edited = True
|
||||
if edited:
|
||||
# Re-derive the merged islands from the twisted region slices.
|
||||
layer.make_slices()
|
||||
layers_touched += 1
|
||||
|
||||
name = ctx.object.model_object().name or "object"
|
||||
return orca.ExecutionResult.success(
|
||||
f"Twistify: transformed {layers_touched} layer(s) of '{name}' "
|
||||
f"(twist {p['twist_deg_per_mm']} deg/mm, taper {p['taper_per_mm']}/mm, "
|
||||
f"wobble {p['wobble_ampl_mm']} mm)")
|
||||
|
||||
|
||||
@orca.plugin
|
||||
class TwistifyPackage(orca.base):
|
||||
def register_capabilities(self):
|
||||
orca.register_capability(Twistify)
|
||||