Skip to content

Commit 521cb91

Browse files
TMHSDigitalclaude
andcommitted
fix(showcase): give hay-bale packed flakes and a stem-mat end instead of felt and gashes
The bale read rough: its straw was a fibre field streaked along the length like brushed felt, the baked normal cut dark gashes along the top edge, and the end face baked into horizontal cake layers. - straw_material: stems lie across the bale in each flake's plane, sharpened into distinct stems; a noise-warped saw wave along X cuts the flake seams, darkened and grooved into the bump; waxy stems, matte gaps. - Baked normal at NRM_STRENGTH 0.55. The per-face UV cells the zero-AABB- overlap budget requires break the bake's tangent frame at every cell; at 1.0 those breaks were the gashes. - End relief is a product of slow y and z terms instead of sin(z) strata. Hull changed: collider 140 -> 100 tris (README table, witnessesFix). Every budget passes on 5.2.1, 5.1.2 and 4.5.11 (4.5 render path too); all eight falsifiers keep their exits: --skip-decimate 9, --stray-vert 15, --lift-z 16, --float-belts 16, --slack-belt 18, --skew-belt 19, --odd-loaf 19, --low-bake 20. Contact-sheet luma 0.297 (was 0.306; stage unchanged). Link gate: 122 pages, 0 failures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 0956197 commit 521cb91

9 files changed

Lines changed: 251 additions & 89 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ Budget-conformance props. Not examples. Conventions: [`showcase/README.md`](show
134134
<a href="showcase/tavern-stool/"><img src="showcase/tavern-stool/preview.webp" width="24%" alt="Tavern stool: a dished round wooden seat holding a pewter tankard, on turned splayed legs with stretchers at two heights and iron ferrules on flat treads, on a dark studio floor" /></a>
135135
<a href="showcase/iron-cauldron/"><img src="showcase/iron-cauldron/preview.webp" width="24%" alt="Iron cauldron: a round-bellied cast-iron pot hanging by its bail from a timber tripod on a dark studio floor" /></a>
136136
<a href="showcase/wooden-ladder/"><img src="showcase/wooden-ladder/preview.webp" width="24%" alt="Wooden ladder: an oak ladder with six pale turned hickory rungs through-tenoned into its rails, iron tie-rods under the end rungs and iron shoes, leaning on a barn-board wall with a rope coil on a rung" /></a>
137-
<a href="showcase/hay-bale/"><img src="showcase/hay-bale/preview.webp" width="24%" alt="Hay bale: a bound straw bale with two sisal twine belts on a dark studio floor" /></a>
137+
<a href="showcase/hay-bale/"><img src="showcase/hay-bale/preview.webp" width="24%" alt="Hay bale: a bound straw bale of packed flakes with two orange twine belts on a dark studio floor" /></a>
138138
<a href="showcase/crate-stack/"><img src="showcase/crate-stack/preview.webp" width="24%" alt="Crate stack: three slatted shipping crates stacked and yawed, each with iron corner straps and runners, on a dark studio floor" /></a>
139139
<a href="showcase/stone-archway/"><img src="showcase/stone-archway/preview.webp" width="24%" alt="Stone archway: a semicircular masonry arch with coursed piers, projecting imposts, nine voussoirs and a proud keystone, on a dark studio floor" /></a>
140140
<a href="showcase/rope-bridge/"><img src="showcase/rope-bridge/preview.webp" width="24%" alt="Rope bridge: a sagging plank deck on foot ropes between two pairs of log posts, with lashed hemp hand ropes, suspenders, and ropes staked to the ground, on a dark studio floor" /></a>
@@ -228,7 +228,7 @@ Budget-conformance props. Not examples. Conventions: [`showcase/README.md`](show
228228

229229
[`wooden-ladder`](showcase/wooden-ladder/) — procedural timber ladder with raked stiles, evenly pitched rungs, and iron shoes through the same pipeline. Falsifiers `--skip-decimate` exits 9, `--drift-rungs` exits 19.
230230

231-
[`hay-bale`](showcase/hay-bale/) — procedural bound hay bale with sisal twine through the same pipeline. Falsifier `--skip-decimate` exits 9.
231+
[`hay-bale`](showcase/hay-bale/) — procedural bound hay bale with orange polypropylene twine through the same pipeline. Falsifier `--skip-decimate` exits 9.
232232

233233
[`rope-bridge`](showcase/rope-bridge/) — procedural rope footbridge whose plank deck is fitted to a parabola recomputed from the plank tops, with lashed hand ropes and staked foot ropes, through the same pipeline. Falsifiers `--vee-deck` exits 19, `--float-suspenders` exits 18.
234234

55.3 KB
Loading
14.3 KB
Loading

‎docs/gallery/hay-bale/index.html‎

Lines changed: 145 additions & 43 deletions
Large diffs are not rendered by default.

‎docs/gallery/index.html‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1301,12 +1301,12 @@ <h2><a href="wooden-ladder/">wooden-ladder</a></h2>
13011301
</article>
13021302
<article class="card" data-tags="mesh export showcase" data-kind="showcase" data-category="village" data-name="hay-bale">
13031303
<a class="card-media" href="hay-bale/" tabindex="-1" aria-hidden="true">
1304-
<img src="assets/hay-bale-hero.webp" alt="A golden fibrous hay bale, square-edged and slightly bulging, bound by two orange twine cords with a small knot loop standing on each." width="1280" height="720" loading="lazy" decoding="async" />
1304+
<img src="assets/hay-bale-hero.webp" alt="A golden hay bale of packed flakes, its stems and flake seams showing on the flank and a stem mat on the end, bound by two orange twine cords with a small knot loop standing on each." width="1280" height="720" loading="lazy" decoding="async" />
13051305
</a>
13061306
<div class="card-body">
13071307
<h2><a href="hay-bale/">hay-bale</a></h2>
13081308
<p class="teaches">A bound hay bale whose twine belts follow the loaf&#x27;s own raycast cross-section, asserting recomputed geometry budgets, not an API contract.</p>
1309-
<p class="witnesses"><span class="tag">witnesses</span> Recomputed: 2044 tris, two materials with 1436 hay and 304 twine faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.921x0.508x0.417 m, zmin 0, hygiene and cross-shell z-fight 0, 5 shells, each belt wrapping under the loaf at zmin 1.5 mm, banded belt seat depth 4.0-11.1 mm over 20/20 stations on both belts, belt mirror error 0, cinch depth 10.5 mm, loaf 0.921x0.393 m, 22.4 bake texels per UV cell, LOD ratios in band, convex collider 140 tris, non-empty glTF. --skip-decimate exits 9; --stray-vert 15; --lift-z and --float-belts 16; --slack-belt 18; --skew-belt and --odd-loaf 19; --low-bake 20.</p>
1309+
<p class="witnesses"><span class="tag">witnesses</span> Recomputed: 2044 tris, two materials with 1436 hay and 304 twine faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.921x0.508x0.417 m, zmin 0, hygiene and cross-shell z-fight 0, 5 shells, each belt wrapping under the loaf at zmin 1.5 mm, banded belt seat depth 4.0-11.1 mm over 20/20 stations on both belts, belt mirror error 0, cinch depth 10.5 mm, loaf 0.921x0.393 m, 22.4 bake texels per UV cell, LOD ratios in band, convex collider 100 tris, non-empty glTF. --skip-decimate exits 9; --stray-vert 15; --lift-z and --float-belts 16; --slack-belt 18; --skew-belt and --odd-loaf 19; --low-bake 20.</p>
13101310
<a class="card-link" href="hay-bale/">View showcase piece<span class="sr-only"> hay-bale</span> <span aria-hidden="true">&rarr;</span></a>
13111311
</div>
13121312
</article>

‎showcase/gallery.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,8 @@
365365
"dir": "showcase/hay-bale",
366366
"category": "village",
367367
"teaches": "A bound hay bale whose twine belts follow the loaf's own raycast cross-section, asserting recomputed geometry budgets, not an API contract.",
368-
"alt": "A golden fibrous hay bale, square-edged and slightly bulging, bound by two orange twine cords with a small knot loop standing on each.",
369-
"witnessesFix": "Recomputed: 2044 tris, two materials with 1436 hay and 304 twine faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.921x0.508x0.417 m, zmin 0, hygiene and cross-shell z-fight 0, 5 shells, each belt wrapping under the loaf at zmin 1.5 mm, banded belt seat depth 4.0-11.1 mm over 20/20 stations on both belts, belt mirror error 0, cinch depth 10.5 mm, loaf 0.921x0.393 m, 22.4 bake texels per UV cell, LOD ratios in band, convex collider 140 tris, non-empty glTF. --skip-decimate exits 9; --stray-vert 15; --lift-z and --float-belts 16; --slack-belt 18; --skew-belt and --odd-loaf 19; --low-bake 20.",
368+
"alt": "A golden hay bale of packed flakes, its stems and flake seams showing on the flank and a stem mat on the end, bound by two orange twine cords with a small knot loop standing on each.",
369+
"witnessesFix": "Recomputed: 2044 tris, two materials with 1436 hay and 304 twine faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.921x0.508x0.417 m, zmin 0, hygiene and cross-shell z-fight 0, 5 shells, each belt wrapping under the loaf at zmin 1.5 mm, banded belt seat depth 4.0-11.1 mm over 20/20 stations on both belts, belt mirror error 0, cinch depth 10.5 mm, loaf 0.921x0.393 m, 22.4 bake texels per UV cell, LOD ratios in band, convex collider 100 tris, non-empty glTF. --skip-decimate exits 9; --stray-vert 15; --lift-z and --float-belts 16; --slack-belt 18; --skew-belt and --odd-loaf 19; --low-bake 20.",
370370
"hero": "docs/gallery/assets/hay-bale-hero.webp",
371371
"preview": "showcase/hay-bale/preview.webp",
372372
"tags": [

‎showcase/hay-bale/README.md‎

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Hay bale
22

33
A showcase piece, not an example. Procedural bound straw bale (cinched
4-
pillow loaf, end-grain strata, two orange polypropylene belts that
4+
pillow loaf, a lumpy stem-mat end, two orange polypropylene belts that
55
wrap the loaf's own cross-section, a knot loop standing on each) then the shipped pipeline: unique-cell
66
UVs, Cycles high-to-low normal bake, LOD chain, convex collider, Unity
77
glTF export.
@@ -65,7 +65,7 @@ RNG and no version-dependent operator.
6565
| **Cinch depth** | mid-span minus waist in [0.006, 0.020] m | 0.01053 |
6666
| Loaf X × Z | (0.921, 0.393) m ± 0.02 | (0.9208, 0.3931) |
6767
| Hay–twine gap | BVH surface < 0.008 m | 0.00009 |
68-
| Collider tris | ≤ 400 | 140 |
68+
| Collider tris | ≤ 400 | 100 |
6969
| Bake texels | smallest UV cell ≥ 12 px at the baked resolution | 22.43 px at 1024 px |
7070
| Export | written, size > 0 | 203508 / 203508 / 203496 bytes |
7171

@@ -181,7 +181,7 @@ the whole bale erased every ridge and nap in it and left a featureless
181181
pillow that read as a bar of soap. Twine is cord, so the belts and
182182
hitch loops stay smooth.
183183

184-
The low mesh carries the form — bulge, waist, ridges, end strata — and
184+
The low mesh carries the form — bulge, waist, ridges, end relief — and
185185
the **high mesh carries the straw**: `LOAF_CUTS_HIGH` subdivision plus
186186
per-band flake offsets, moved onto the low mesh by the bake stage. A low
187187
mesh dense enough to model flakes directly would cost thousands of
@@ -190,15 +190,24 @@ triangles for detail this piece already has a bake for.
190190
## Surface
191191

192192
The bale used to read as a wrapped parcel: flat mustard hay, and a wide
193-
dark band for twine. The hay is now `straw_material`:
194-
- a dense fibre field stretched along the bale (noise with X compressed),
195-
and a weaker crossing field for the strands that lie the other way;
196-
- colour pulled between pale straw and dark tan by the fibres, with a
197-
faint green cast from low-frequency noise;
198-
- roughness and a bump from the same fibres, so every strand catches
199-
light.
200-
201-
The baked normal is chained under that bump (`wire_normal`). The twine
193+
dark band for twine. Later it read as an upholstered cushion: its fibres
194+
ran along the length like brushed felt. The hay is now `straw_material`:
195+
- stems lying *across* the bale, in the plane of each packed flake (noise
196+
with Y compressed, a weaker folded set with Z compressed), sharpened by
197+
a map range into distinct light and dark stems;
198+
- flake seams: a saw wave along X at about 2.6 × `FLAKE_W`, warped by
199+
noise so the seams wander, darkened and grooved into the bump;
200+
- colour from dark tan through gold to pale straw, with a faint green cast
201+
from low-frequency noise; waxy stems, matte gaps.
202+
203+
It is all object-space, so it runs continuously across the per-face UV
204+
cells. The baked normal is chained under that bump (`wire_normal`) at
205+
`NRM_STRENGTH` 0.55. The UV budget (zero per-face AABB overlap) gives every
206+
face its own cell, so the bake's tangent frame breaks at every cell edge; at
207+
full strength those breaks cut dark gashes along the top edge. The end face
208+
used to be `sin(z)` strata, which baked into cake layers gouged across the
209+
end. It is now a product of slow terms in y and z, which reads as the stem mat of
210+
the last flake. That changed the hull: the collider went from 140 to 100 tris. The twine
202211
is a 10 × 14 mm orange polypropylene cord standing 6 mm proud. As an
203212
8 × 10 mm dark brown cord it stood 4 mm proud of the waisted loaf and read
204213
as a scored cut in every view. The longitudinal ridge dropped from 14 mm to

‎showcase/hay-bale/hay_bale.py‎

Lines changed: 79 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -648,12 +648,14 @@ def _shape_loaf(verts, flake=False):
648648
# flared spikes. The ripple is slow (7, not 14) — at 14 it aliased
649649
# against the subdivision grid and read as a checkerboard quilt.
650650
if abs(nx) > 0.82:
651-
# Strata in Z, not rings in r. A cut bale end shows the flake
652-
# layers edge-on; a concentric ripple is not what that looks
653-
# like, and at BAKE_RES it resolved into a pixelated bullseye
654-
# on the hero that read as a printed target.
651+
# The end is the face of the last flake: a mat of stems, lumpy
652+
# in both directions. Concentric rings baked into a printed
653+
# target; horizontal strata (sin in z alone) baked into cake
654+
# layers gouged across the end. A product of slow terms in y and
655+
# z reads as packed straw and stays even in x.
655656
edge = 1.0 - min(1.0, max(abs(ny), abs(nz))) ** 3
656-
strata = math.sin((z / BALE_Z) * math.pi * 6.0)
657+
strata = (math.sin((z / BALE_Z) * math.pi * 3.0 + 0.7)
658+
* math.cos((y / BALE_Y) * math.pi * 2.4))
657659
v.co.x += math.copysign(NAP_AMP * strata * edge, x)
658660

659661

@@ -862,33 +864,68 @@ def _stretched_noise(nt, coord, stretch, scale, detail):
862864

863865

864866
def straw_material(name):
865-
"""Straw: fibres, colour break-up and a fibre bump, not a flat mustard.
866-
867-
The bale read as a wrapped parcel because its hay was one flat colour
868-
with a soft noise. Straw is streaks: a dense fibre field stretched
869-
along the bale, a weaker crossing field for the strands that lie the
870-
other way, colour pulled between pale straw and dark tan by the fibres,
871-
a faint green cast from low-frequency noise, and a bump from the same
872-
fibres so every strand catches light.
867+
"""Straw: stems, flake seams, colour break-up and a bump, not felt.
868+
869+
Stems lie across the bale in the plane of each packed flake, sharpened
870+
into distinct light and dark lines; a warped saw wave along X cuts the
871+
seams between flakes, darkened and grooved; colour runs from dark tan
872+
through gold to pale straw with a faint green cast from low-frequency
873+
noise. All object-space, so it is continuous across the per-face UV
874+
cells the bake uses.
873875
"""
874876
mat = bpy.data.materials.new(name)
875877
mat.use_nodes = True
876878
nt = mat.node_tree
877879
bsdf = nt.nodes["Principled BSDF"]
878880
coord = nt.nodes.new("ShaderNodeTexCoord")
879-
along = _stretched_noise(nt, coord, (0.07, 1.0, 1.0), 110.0, 6.0)
880-
across = _stretched_noise(nt, coord, (1.0, 0.09, 1.0), 90.0, 4.0)
881+
# Stems lie ACROSS the bale, in the plane of each flake the plunger
882+
# packed: long along Y, a weaker folded set along Z. Fibres streaked
883+
# along X (the old field) ran with the length like brushed felt, and the
884+
# bale read as an upholstered cushion. Sharpened by the map range so each
885+
# stem is a crisp light/dark line, not a soft smear.
886+
along = _stretched_noise(nt, coord, (1.0, 0.05, 1.0), 150.0, 3.0)
887+
across = _stretched_noise(nt, coord, (1.0, 1.0, 0.08), 120.0, 3.0)
881888
fib = nt.nodes.new("ShaderNodeMix")
882889
fib.data_type = "FLOAT"
883-
_sock(fib.inputs, "Factor_Float").default_value = 0.32
890+
_sock(fib.inputs, "Factor_Float").default_value = 0.35
884891
nt.links.new(along.outputs["Fac"], _sock(fib.inputs, "A_Float"))
885892
nt.links.new(across.outputs["Fac"], _sock(fib.inputs, "B_Float"))
893+
sharp = nt.nodes.new("ShaderNodeMapRange")
894+
sharp.inputs["From Min"].default_value = 0.38
895+
sharp.inputs["From Max"].default_value = 0.62
896+
nt.links.new(_sock(fib.outputs, "Result_Float"), sharp.inputs["Value"])
897+
# Flake seams: a saw wave along X at the flake pitch, its drop warped by
898+
# noise so the seams wander like packed slices instead of ruled lines.
899+
wave = nt.nodes.new("ShaderNodeTexWave")
900+
wave.wave_type = "BANDS"
901+
wave.bands_direction = "X"
902+
wave.wave_profile = "SAW"
903+
wave.inputs["Scale"].default_value = 1.0 / FLAKE_W / 2.6
904+
wave.inputs["Distortion"].default_value = 2.4
905+
wave.inputs["Detail"].default_value = 2.0
906+
wave.inputs["Detail Scale"].default_value = 1.5
907+
nt.links.new(coord.outputs["Object"], wave.inputs["Vector"])
908+
seam = nt.nodes.new("ShaderNodeMapRange")
909+
seam.interpolation_type = "SMOOTHSTEP"
910+
seam.inputs["From Min"].default_value = 0.0
911+
seam.inputs["From Max"].default_value = 0.09
912+
seam.inputs["To Min"].default_value = 1.0
913+
seam.inputs["To Max"].default_value = 0.0
914+
nt.links.new(wave.outputs["Fac"], seam.inputs["Value"])
886915
ramp = nt.nodes.new("ShaderNodeValToRGB")
887-
ramp.color_ramp.elements[0].position = 0.34
888-
ramp.color_ramp.elements[0].color = (0.36, 0.25, 0.075, 1.0)
889-
ramp.color_ramp.elements[1].position = 0.66
890-
ramp.color_ramp.elements[1].color = (0.88, 0.71, 0.31, 1.0)
891-
nt.links.new(_sock(fib.outputs, "Result_Float"), ramp.inputs["Fac"])
916+
ramp.color_ramp.elements[0].position = 0.0
917+
ramp.color_ramp.elements[0].color = (0.30, 0.21, 0.065, 1.0)
918+
mid = ramp.color_ramp.elements.new(0.55)
919+
mid.color = (0.66, 0.50, 0.19, 1.0)
920+
ramp.color_ramp.elements[-1].position = 1.0
921+
ramp.color_ramp.elements[-1].color = (0.93, 0.79, 0.42, 1.0)
922+
nt.links.new(sharp.outputs["Result"], ramp.inputs["Fac"])
923+
shade = nt.nodes.new("ShaderNodeMix")
924+
shade.data_type = "RGBA"
925+
shade.blend_type = "MULTIPLY"
926+
nt.links.new(seam.outputs["Result"], _sock(shade.inputs, "Factor_Float"))
927+
nt.links.new(ramp.outputs["Color"], _sock(shade.inputs, "A_Color"))
928+
_sock(shade.inputs, "B_Color").default_value = (0.38, 0.33, 0.25, 1.0)
892929
patch = nt.nodes.new("ShaderNodeTexNoise")
893930
patch.inputs["Scale"].default_value = 4.0
894931
patch.inputs["Detail"].default_value = 2.0
@@ -897,18 +934,25 @@ def straw_material(name):
897934
green.data_type = "RGBA"
898935
green.blend_type = "MULTIPLY"
899936
nt.links.new(patch.outputs["Fac"], _sock(green.inputs, "Factor_Float"))
900-
nt.links.new(ramp.outputs["Color"], _sock(green.inputs, "A_Color"))
937+
nt.links.new(_sock(shade.outputs, "Result_Color"), _sock(green.inputs, "A_Color"))
901938
_sock(green.inputs, "B_Color").default_value = (0.90, 0.95, 0.74, 1.0)
902939
nt.links.new(_sock(green.outputs, "Result_Color"), bsdf.inputs["Base Color"])
940+
# Dry stems carry a waxy sheen; the gaps between them are matte.
903941
rough = nt.nodes.new("ShaderNodeMapRange")
904-
rough.inputs["To Min"].default_value = 0.95
905-
rough.inputs["To Max"].default_value = 0.70
906-
nt.links.new(_sock(fib.outputs, "Result_Float"), rough.inputs["Value"])
942+
rough.inputs["To Min"].default_value = 0.92
943+
rough.inputs["To Max"].default_value = 0.58
944+
nt.links.new(sharp.outputs["Result"], rough.inputs["Value"])
907945
nt.links.new(rough.outputs["Result"], bsdf.inputs["Roughness"])
946+
# Stem relief minus the seam groove, one height field for the bump.
947+
height = nt.nodes.new("ShaderNodeMath")
948+
height.operation = "MULTIPLY_ADD"
949+
nt.links.new(seam.outputs["Result"], height.inputs[0])
950+
height.inputs[1].default_value = -1.2
951+
nt.links.new(sharp.outputs["Result"], height.inputs[2])
908952
bump = nt.nodes.new("ShaderNodeBump")
909-
bump.inputs["Strength"].default_value = 0.65
953+
bump.inputs["Strength"].default_value = 0.8
910954
bump.inputs["Distance"].default_value = 0.004
911-
nt.links.new(_sock(fib.outputs, "Result_Float"), bump.inputs["Height"])
955+
nt.links.new(height.outputs["Value"], bump.inputs["Height"])
912956
nt.links.new(bump.outputs["Normal"], bsdf.inputs["Normal"])
913957
return mat
914958

@@ -1440,12 +1484,19 @@ def half_y_at(x0, half_window):
14401484
return 0, low, high, hay, tex, collider
14411485

14421486

1487+
# The bake lands in per-face UV cells (the zero-AABB-overlap budget), so its
1488+
# tangent frame breaks at every cell. At full strength those breaks cut dark
1489+
# gashes along the top edge; the procedural stems carry the fine detail, and
1490+
# the bake keeps the flake relief at this weight.
1491+
NRM_STRENGTH = 0.55
1492+
1493+
14431494
def wire_normal(mat, tex):
14441495
"""Baked normal map into the BSDF, under the fibre bump if there is one."""
14451496
nt = mat.node_tree
14461497
bsdf = nt.nodes["Principled BSDF"]
14471498
nrm = nt.nodes.new("ShaderNodeNormalMap")
1448-
nrm.inputs["Strength"].default_value = 1.0
1499+
nrm.inputs["Strength"].default_value = NRM_STRENGTH
14491500
nt.links.new(tex.outputs["Color"], nrm.inputs["Color"])
14501501
bump = next((n for n in nt.nodes if n.bl_idname == "ShaderNodeBump"), None)
14511502
if bump is not None:

‎showcase/hay-bale/preview.webp‎

46.9 KB
Loading

0 commit comments

Comments
 (0)