@@ -927,14 +927,16 @@ def grain_material():
927927 nt ,
928928 wave .outputs ["Fac" ],
929929 (
930- (0.30 , (0.600 , 0.450 , 0.265 )),
931- (0.72 , (0.520 , 0.370 , 0.205 )),
932- (0.95 , (0.400 , 0.268 , 0.140 )),
930+ (0.30 , (0.400 , 0.300 , 0.190 )),
931+ (0.72 , (0.335 , 0.238 , 0.140 )),
932+ (0.95 , (0.255 , 0.168 , 0.090 )),
933933 ),
934934 )
935935 weather = noise (nt , coord , 6.0 , 4.0 , 0.55 )
936+ # A working block's top is weathered grey-brown, not fresh-sawn pine:
937+ # the pale face read as a cheese wheel set on a stump.
936938 base = mix_color (
937- nt , rings , (0.330 , 0.250 , 0.170 ), remap (nt , weather , 0.45 , 0.75 , 0.0 , 0.45 )
939+ nt , rings , (0.235 , 0.205 , 0.170 ), remap (nt , weather , 0.40 , 0.75 , 0.20 , 0.65 )
938940 )
939941 # Radial checks at the angles the geometry notches, widening toward the
940942 # rim and fading out before the pith, as a drying check does.
@@ -957,8 +959,16 @@ def grain_material():
957959 line = remap (nt , nearest , 0.0 , 1.0 , 1.0 , 0.0 )
958960 fade = remap (nt , rad , 0.22 * BLOCK_R , 0.55 * BLOCK_R , 0.0 , 1.0 )
959961 crack = math_node (nt , "MULTIPLY" , line , fade )
962+ cracked = mix_color (nt , base , (0.050 , 0.032 , 0.020 ), crack )
963+ # Only faces that look up are end grain. The top chamfer and the rim
964+ # chips lean out toward the bark, and pale end grain wrapping down them
965+ # drew a lid on a canister: there they take the bark's cambium brown.
966+ geo = nt .nodes .new ("ShaderNodeNewGeometry" )
967+ gsep = nt .nodes .new ("ShaderNodeSeparateXYZ" )
968+ nt .links .new (geo .outputs ["Normal" ], gsep .inputs ["Vector" ])
969+ side = remap (nt , gsep .outputs ["Z" ], 0.80 , 0.93 , 1.0 , 0.0 )
960970 nt .links .new (
961- mix_color (nt , base , (0.050 , 0.032 , 0.020 ), crack ), bsdf .inputs ["Base Color" ]
971+ mix_color (nt , cracked , (0.085 , 0.055 , 0.032 ), side ), bsdf .inputs ["Base Color" ]
962972 )
963973 nt .links .new (
964974 remap (nt , wave .outputs ["Fac" ], 0.0 , 1.0 , 0.58 , 0.74 ), bsdf .inputs ["Roughness" ]
@@ -978,15 +988,17 @@ def iron_material():
978988 nt ,
979989 blot ,
980990 (
981- (0.40 , (0.085 , 0.082 , 0.079 )),
982- (0.60 , (0.140 , 0.136 , 0.131 )),
983- (0.72 , (0.130 , 0.100 , 0.076 )),
984- (0.82 , (0.180 , 0.096 , 0.052 )),
991+ (0.40 , (0.205 , 0.200 , 0.192 )),
992+ (0.60 , (0.330 , 0.322 , 0.310 )),
993+ (0.72 , (0.215 , 0.165 , 0.120 )),
994+ (0.82 , (0.240 , 0.130 , 0.070 )),
985995 ),
986996 ),
987997 bsdf .inputs ["Base Color" ],
988998 )
989- nt .links .new (remap (nt , blot , 0.35 , 0.8 , 0.34 , 0.60 ), bsdf .inputs ["Roughness" ])
999+ # Albedo lifted from ~0.1 to ~0.3: at 0.1 a metallic head mirrored the
1000+ # black stage and the axe read as a hole cut in the frame.
1001+ nt .links .new (remap (nt , blot , 0.35 , 0.8 , 0.30 , 0.55 ), bsdf .inputs ["Roughness" ])
9901002 nt .links .new (remap (nt , blot , 0.66 , 0.80 , 1.0 , 0.35 ), bsdf .inputs ["Metallic" ])
9911003 return mat
9921004
@@ -1792,96 +1804,145 @@ def wire_normal(mat, tex):
17921804
17931805
17941806def _dressing_materials ():
1795- """Split face and bark for the render-only kindling and chips. Their own
1796- materials: the block's are wired to its baked normal map through UVs the
1797- dressing does not have."""
1798- def mat (name , base , rough , stripes ):
1807+ """Split face, end grain, bark for the render-only kindling and chips.
1808+ Their own materials: the block's are wired to its baked normal map
1809+ through UVs the dressing does not have. All object-space, and each billet
1810+ is its own object, so its end rings centre on its own pith."""
1811+ def mat (name ):
17991812 m = bpy .data .materials .new (name )
18001813 m .use_nodes = True
18011814 nt = m .node_tree
1802- b = nt .nodes ["Principled BSDF" ]
1803- b .inputs ["Roughness" ].default_value = rough
1804- coord = nt .nodes .new ("ShaderNodeTexCoord" )
1805- wave = nt .nodes .new ("ShaderNodeTexWave" )
1806- wave .wave_type = "BANDS"
1807- wave .bands_direction = "Z" if stripes else "X"
1808- wave .inputs ["Scale" ].default_value = 6.0 if stripes else 2.0
1809- wave .inputs ["Distortion" ].default_value = 6.0
1810- wave .inputs ["Detail" ].default_value = 3.0
1811- nt .links .new (coord .outputs ["Object" ], wave .inputs ["Vector" ])
1812- ramp = nt .nodes .new ("ShaderNodeValToRGB" )
1813- ramp .color_ramp .elements [0 ].color = tuple (c * 0.72 for c in base ) + (1.0 ,)
1814- ramp .color_ramp .elements [1 ].color = base + (1.0 ,)
1815- nt .links .new (wave .outputs ["Fac" ], ramp .inputs ["Fac" ])
1816- nt .links .new (ramp .outputs ["Color" ], b .inputs ["Base Color" ])
1817- return m
1818- split = mat ("KindlingSplit" , (0.62 , 0.46 , 0.28 ), 0.72 , True )
1819- bark = mat ("KindlingBark" , (0.16 , 0.11 , 0.075 ), 0.9 , False )
1820- return split , bark
1815+ return m , nt , nt .nodes ["Principled BSDF" ], nt .nodes .new ("ShaderNodeTexCoord" )
1816+
1817+ # Split face: fibres torn along the billet (noise squeezed across it).
1818+ split , nt , b , tc = mat ("KindlingSplit" )
1819+ fib = noise (nt , mapping (nt , tc .outputs ["Object" ], scale = (0.06 , 1.0 , 1.0 )), 60.0 , 5.0 , 0.6 )
1820+ nt .links .new (ramp (nt , fib , ((0.35 , (0.30 , 0.21 , 0.12 )), (0.52 , (0.47 , 0.34 , 0.20 )),
1821+ (0.70 , (0.58 , 0.44 , 0.27 )))), b .inputs ["Base Color" ])
1822+ b .inputs ["Roughness" ].default_value = 0.78
1823+ bump = nt .nodes .new ("ShaderNodeBump" )
1824+ bump .inputs ["Strength" ].default_value = 0.5
1825+ nt .links .new (fib , bump .inputs ["Height" ])
1826+ nt .links .new (bump .outputs ["Normal" ], b .inputs ["Normal" ])
1827+
1828+ # End grain: rings about the billet's own axis (local X), darker than the
1829+ # split face, as a sawn end is.
1830+ ends , nt , b , tc = mat ("KindlingEnd" )
1831+ wave = nt .nodes .new ("ShaderNodeTexWave" )
1832+ wave .wave_type = "RINGS"
1833+ wave .rings_direction = "SPHERICAL"
1834+ wave .inputs ["Scale" ].default_value = 26.0
1835+ wave .inputs ["Distortion" ].default_value = 2.0
1836+ nt .links .new (mapping (nt , tc .outputs ["Object" ], scale = (0.0 , 1.0 , 1.0 )), wave .inputs ["Vector" ])
1837+ nt .links .new (ramp (nt , wave .outputs ["Fac" ], ((0.3 , (0.48 , 0.34 , 0.19 )),
1838+ (0.9 , (0.33 , 0.22 , 0.12 )))),
1839+ b .inputs ["Base Color" ])
1840+ b .inputs ["Roughness" ].default_value = 0.72
1841+
1842+ # Bark: plated and fissured, the block's palette.
1843+ bark , nt , b , tc = mat ("KindlingBark" )
1844+ plates = noise (nt , mapping (nt , tc .outputs ["Object" ], scale = (2.0 , 9.0 , 9.0 )), 6.0 , 8.0 , 0.64 )
1845+ nt .links .new (ramp (nt , plates , ((0.40 , (0.020 , 0.013 , 0.008 )), (0.55 , (0.075 , 0.048 , 0.028 )),
1846+ (0.80 , (0.150 , 0.100 , 0.062 )))), b .inputs ["Base Color" ])
1847+ b .inputs ["Roughness" ].default_value = 0.88
1848+ bump = nt .nodes .new ("ShaderNodeBump" )
1849+ bump .inputs ["Strength" ].default_value = 0.8
1850+ nt .links .new (plates , bump .inputs ["Height" ])
1851+ nt .links .new (bump .outputs ["Normal" ], b .inputs ["Normal" ])
1852+ return split , ends , bark
18211853
18221854
18231855def add_split_kindling (scene ):
18241856 """Render-only dressing: split billets and chips at the block's foot.
18251857
18261858 A chopping block is read by what it splits. Each billet is a quarter of
1827- a small round (two split faces meeting at the pith, bark on the arc),
1828- laid on the floor; chips are thin seeded slivers. None of it is part of
1829- the asset: no budget reads it, the export does not carry it, and the
1830- asset sheet renders the block alone.
1859+ a small round (two split faces meeting at the pith, bark on the arc,
1860+ sawn ends showing rings), laid on the floor; chips are tapered wedges
1861+ tilted as they fell. None of it is part of the asset: no budget reads
1862+ it, the export does not carry it, and the asset sheet renders the block
1863+ alone. Returns the dressing objects.
18311864 """
1832- split , bark = _dressing_materials ()
1865+ split , ends , bark = _dressing_materials ()
18331866 rng = random .Random (71 )
1834- me = bpy .data .meshes .new ("Kindling" )
1835- me .materials .append (split )
1836- me .materials .append (bark )
1837- bm = bmesh .new ()
1838- try :
1839- def billet (cx , cy , yaw , r , length , roll ):
1840- segs = 6
1867+ obs = []
1868+
1869+ def emit (name , build , loc , rot ):
1870+ me = bpy .data .meshes .new (name )
1871+ for m in (split , ends , bark ):
1872+ me .materials .append (m )
1873+ bm = bmesh .new ()
1874+ try :
1875+ build (bm )
1876+ bmesh .ops .recalc_face_normals (bm , faces = bm .faces )
1877+ bm .to_mesh (me )
1878+ finally :
1879+ bm .free ()
1880+ ob = bpy .data .objects .new (name , me )
1881+ ob .location = loc
1882+ ob .rotation_euler = rot
1883+ scene .collection .objects .link (ob )
1884+ obs .append (ob )
1885+
1886+ def billet (name , cx , cy , yaw , r , length , roll ):
1887+ # Quarter round about local X; roll 0 rests it on one split face,
1888+ # 90 degrees on the other, so one shows bark up and one a split face.
1889+ segs = 10
1890+
1891+ def build (bm ):
18411892 rings = []
18421893 for end in (- 0.5 , 0.5 ):
1843- ring = [Vector ((end * length , 0.0 , 0.0 ))]
1894+ ring = [bm . verts . new ((end * length , 0.0 , 0.0 ))]
18441895 for k in range (segs + 1 ):
18451896 a = (math .pi / 2.0 ) * k / segs
1846- ring .append (Vector ((end * length , r * math .cos (a ), r * math .sin (a ))))
1897+ # a little out of round, as split wood is
1898+ rr = r * (1.0 + 0.04 * math .sin (3.0 * a + end ))
1899+ ring .append (bm .verts .new ((end * length , rr * math .cos (a ), rr * math .sin (a ))))
18471900 rings .append (ring )
1848- rot = Matrix .Rotation (yaw , 3 , "Z" ) @ Matrix .Rotation (roll , 3 , "X" )
1849- vs = [[bm .verts .new (rot @ p + Vector ((cx , cy , 0.0 ))) for p in ring ] for ring in rings ]
1850- a , b = vs
1901+ a , b = rings
18511902 n = len (a )
18521903 for k in range (n ):
18531904 j = (k + 1 ) % n
18541905 f = bm .faces .new ((a [k ], a [j ], b [j ], b [k ]))
1855- # faces between arc vertices carry bark; the two through the
1856- # pith are split faces
1857- f .material_index = 1 if (k >= 1 and j >= 2 ) else 0
1858- bm .faces .new (list (reversed (a ))).material_index = 0
1859- bm .faces .new (b ).material_index = 0
1860-
1861- # three billets beside the foot, two left and one right;
1862- # roll 0 or +90 degrees lays a billet on one split face or the other, so\n # some show their bark and some their split face
1863- billet (- 0.45 , - 0.02 , math .radians (62.0 ), 0.088 , 0.31 , 0.0 )
1864- billet (- 0.43 , - 0.19 , math .radians (38.0 ), 0.080 , 0.28 , math .radians (90.0 ))
1865- billet (0.42 , - 0.12 , math .radians (118.0 ), 0.084 , 0.29 , math .radians (90.0 ))
1866- for i in range (16 ):
1867- ang = rng .uniform (math .radians (200.0 ), math .radians (340.0 ))
1868- d = rng .uniform (0.31 , 0.42 )
1906+ arc = k >= 1 and j >= 2
1907+ f .material_index = 2 if arc else 0
1908+ f .smooth = arc
1909+ bm .faces .new (list (reversed (a ))).material_index = 1
1910+ bm .faces .new (b ).material_index = 1
1911+
1912+ # rolled about local X, then yawed; roll 90 lifts the arc off the floor
1913+ emit (name , build , (cx , cy , 0.0 ), (roll , 0.0 , yaw ))
1914+
1915+ billet ("Billet.A" , - 0.45 , - 0.02 , math .radians (62.0 ), 0.088 , 0.31 , 0.0 )
1916+ billet ("Billet.B" , - 0.43 , - 0.19 , math .radians (38.0 ), 0.080 , 0.28 , math .radians (90.0 ))
1917+ billet ("Billet.C" , 0.42 , - 0.12 , math .radians (118.0 ), 0.084 , 0.29 , math .radians (90.0 ))
1918+
1919+ def chips (bm ):
1920+ for i in range (12 ):
1921+ ang = rng .uniform (math .radians (205.0 ), math .radians (335.0 ))
1922+ d = rng .uniform (0.30 , 0.46 )
18691923 cx , cy = d * math .cos (ang ), d * math .sin (ang )
1870- sx , sy , sz = rng .uniform (0.020 , 0.045 ), rng .uniform (0.008 , 0.018 ), 0.003
1871- geo = bmesh .ops .create_cube (bm , size = 1.0 )
1872- rot = Matrix .Rotation (rng .uniform (0.0 , math .pi ), 3 , "Z" )
1873- for v in geo ["verts" ]:
1874- v .co = rot @ Vector ((v .co .x * sx , v .co .y * sy , v .co .z * sz + sz / 2.0 )) \
1875- + Vector ((cx , cy , 0.0 ))
1876- for f in {f for v in geo ["verts" ] for f in v .link_faces }:
1877- f .material_index = 0
1878- bmesh .ops .recalc_face_normals (bm , faces = bm .faces )
1879- bm .to_mesh (me )
1880- finally :
1881- bm .free ()
1882- ob = bpy .data .objects .new ("Kindling" , me )
1883- scene .collection .objects .link (ob )
1884- return ob
1924+ lx , ly = rng .uniform (0.035 , 0.070 ), rng .uniform (0.016 , 0.030 )
1925+ t0 , t1 = rng .uniform (0.007 , 0.011 ), 0.0018
1926+ # a wedge: full thickness at the struck end, feathered at the tip
1927+ pts = [(- lx / 2 , - ly / 2 , 0.0 ), (lx / 2 , - ly / 2 , 0.0 ),
1928+ (lx / 2 , ly / 2 , 0.0 ), (- lx / 2 , ly / 2 , 0.0 ),
1929+ (- lx / 2 , - ly / 2 , t0 ), (lx / 2 , - ly * 0.4 , t1 ),
1930+ (lx / 2 , ly * 0.4 , t1 ), (- lx / 2 , ly / 2 , t0 )]
1931+ tilt = Matrix .Rotation (rng .uniform (- 0.25 , 0.25 ), 3 , "X" )
1932+ rot = Matrix .Rotation (rng .uniform (0.0 , math .pi ), 3 , "Z" ) @ tilt
1933+ vs = [bm .verts .new (rot @ Vector (p ) + Vector ((cx , cy , 0.0 ))) for p in pts ]
1934+ lift = - min (v .co .z for v in vs )
1935+ for v in vs :
1936+ v .co .z += lift
1937+ quads = ((0 , 3 , 2 , 1 ), (4 , 5 , 6 , 7 ), (0 , 1 , 5 , 4 ),
1938+ (1 , 2 , 6 , 5 ), (2 , 3 , 7 , 6 ), (3 , 0 , 4 , 7 ))
1939+ for q , quad in enumerate (quads ):
1940+ f = bm .faces .new ([vs [k ] for k in quad ])
1941+ # every third chip carries its bark on top
1942+ f .material_index = 2 if (q == 1 and i % 3 == 0 ) else 0
1943+
1944+ emit ("Chips" , chips , (0.0 , 0.0 , 0.0 ), (0.0 , 0.0 , 0.0 ))
1945+ return obs
18851946
18861947
18871948def render_still (low , wood , tex , path , engine ):
@@ -1974,7 +2035,7 @@ def light(name, loc, energy, size, col, rot):
19742035 scene .view_settings .view_transform = "Standard"
19752036
19762037 fcode = gallery_framing .check_framing (
1977- scene , cam , hero = [low ], elements = [low , kindling ], stage = [floor , wall ],
2038+ scene , cam , hero = [low ], elements = [low , * kindling ], stage = [floor , wall ],
19782039 )
19792040 if fcode :
19802041 return fcode
0 commit comments