4646BALE_Y = 0.48
4747BALE_Z = 0.38
4848# Baling twine is a cord, not a strap. At 16 x 24 mm the wrap read as
49- # packing tape on a parcel; 8 x 10 mm reads as cord pulled into the straw.
50- TWINE_T = 0.008
51- TWINE_W = 0.010
49+ # packing tape on a parcel. At 8 x 10 mm, 4 mm of it stood proud of a
50+ # waisted loaf and read as a scored groove; 10 x 14 mm stands 6 mm proud
51+ # and reads as cord pulled into the straw.
52+ TWINE_T = 0.010
53+ TWINE_W = 0.014
5254TWINE_EMBED = 0.004
55+ # The knot: a tight loop standing on the cord in the belt's own plane. A
56+ # 60 mm ring lying flat on the top read as a printed symbol, not a tie.
57+ KNOT_MAJOR = 0.013
5358BELT_XS = (- 0.225 , 0.225 )
5459LOAF_CUTS = 8
5560# The high mesh carries the straw; the bake moves it onto the low mesh. A
5964FLAKE_W = 0.075
6065FLAKE_AMP = 0.011
6166BULGE = 0.055
62- RIDGE_AMP = 0.014
67+ # Longitudinal straw ridge. At 14 mm it crumpled the top edge in profile,
68+ # and the bale read as a stuffed sack; the flakes on the high mesh carry
69+ # the straw texture through the bake instead.
70+ RIDGE_AMP = 0.006
6371BBOX_TOL = 0.01
6472# Fitted after locking geometry. Recomputed from bound_box.
65- OUTER_SIZE = (0.922 , 0.530 , 0.409 )
73+ OUTER_SIZE = (0.921 , 0.508 , 0.417 )
6674
6775# The belt follows the loaf's own cross-section, sampled by raycast, instead
6876# of being a constant rectangle: a rectangle stands proud at the middle of
9098UV_EPS = 1e-4
9199UV_OVERLAP_MAX = 1e-5
92100COLLIDER_TRIS_MAX = 400
93- BAKE_RES = 256
94- CAGE_EXTRUSION = 0.04
101+ # One UV cell per face: at 256 px each cell got 5.6 texels, and the
102+ # bilinear lookup smeared neighbouring cells' normals into streaks across
103+ # the straw (tavern-stool's defect). 1024 px, with a floor asserted.
104+ BAKE_RES = 1024
105+ LOW_BAKE_RES = 256
106+ TEXEL_MIN = 12.0
107+ # The cage covers the flake relief on the high mesh (11 mm) and the bevel
108+ # difference; at 0.04 it reached the twine standing proud of the loaf.
109+ CAGE_EXTRUSION = 0.02
95110HAY_FACES_MIN = 24
96111TWINE_FACES_MIN = 24
97112
137152CINCH_MAX = 0.020
138153# Loaf length and height against the stated real-world bale. Y is left to
139154# the cinch budget above, since the pillow bulge dominates it.
140- LOAF_XZ = (0.922 , 0.409 )
155+ LOAF_XZ = (0.921 , 0.393 )
141156LOAF_XZ_TOL = 0.02
142157
143- FLOAT_BELT_Z = 0.02
144- SLACK_BELT = 0.010
158+ # Sized to their own budgets and kept inside BBOX_TOL: with the ridge at
159+ # 6 mm the knot sets the envelope top, and 20 / 10 mm moved it past the
160+ # bounding box, which then fired (exit 8) instead of 16 / 18.
161+ FLOAT_BELT_Z = 0.008
162+ SLACK_BELT = 0.006
145163SKEW_BELT_X = 0.004
146164
147165HAY_IDX = 0
@@ -735,17 +753,18 @@ def build_bale_mesh(name, bevel_offset, bevel_segments, cuts=LOAF_CUTS,
735753 # alongside its belt. Standing it upright made a wire hoop that
736754 # broke the silhouette; laying it in XY reads as tied cord.
737755 top_z = max (pz for _py , pz in profile )
756+ # The knot loop stands in the belt's plane (YZ), centred on the
757+ # belt's station, its foot sunk to the cord's mid-line so it grows
758+ # out of the wrap. The tube is the same cord as the wrap.
759+ minor = TWINE_T * 0.39
738760 add_rim (
739761 bm ,
740- # Offset outward from the centre, not along +X: a bare
741- # `x + TWINE_W` put the two loops at -0.201 and +0.249.
742- (x + math .copysign (TWINE_W , x ), 0.052 ,
743- top_z - TWINE_EMBED + TWINE_T * 0.45 ),
744- 0.030 ,
745- # The loop is the same cord as the wrap: tube sized from it.
746- TWINE_T * 0.39 ,
762+ (x , 0.052 ,
763+ top_z - TWINE_EMBED + TWINE_T * 0.5 + KNOT_MAJOR + minor * 0.5 ),
764+ KNOT_MAJOR ,
765+ minor ,
747766 TWINE_IDX ,
748- euler = (0.0 , 0 .0 , 0.0 ),
767+ euler = (0.0 , math . pi / 2 .0 , 0.0 ),
749768 n_major = 12 ,
750769 n_minor = 6 ,
751770 )
@@ -897,9 +916,11 @@ def straw_material(name):
897916def bale_materials ():
898917 """(hay, twine): shared by the check, the render and inspection."""
899918 hay = straw_material ("BaleHay" )
919+ # Orange polypropylene baling twine. A dark brown cord lying in the
920+ # straw's waist read as a scored cut in every view, not as a tie.
900921 twine = principled (
901- "BaleTwine" , (0.30 , 0.22 , 0.11 , 1.0 ), 0.0 , 0.62 ,
902- noise_scale = 60.0 , wear = (0.17 , 0.12 , 0.06 , 1.0 ),
922+ "BaleTwine" , (0.62 , 0.20 , 0.035 , 1.0 ), 0.0 , 0.48 ,
923+ noise_scale = 60.0 , wear = (0.44 , 0.13 , 0.025 , 1.0 ),
903924 )
904925 return hay , twine
905926
@@ -1039,8 +1060,26 @@ def export_unity(path, objects):
10391060 )
10401061
10411062
1063+ def texel_audit (mesh , img ):
1064+ """Smallest UV cell, in baked texels along its longer side.
1065+
1066+ Copied from showcase/tavern-stool (do not import across pieces).
1067+ """
1068+ uv = mesh .uv_layers .active
1069+ if uv is None or img is None :
1070+ return 0.0
1071+ res = min (img .size [0 ], img .size [1 ])
1072+ data = uv .data
1073+ worst = 1e9
1074+ for poly in mesh .polygons :
1075+ us = [data [i ].uv [0 ] for i in poly .loop_indices ]
1076+ vs = [data [i ].uv [1 ] for i in poly .loop_indices ]
1077+ worst = min (worst , max (max (us ) - min (us ), max (vs ) - min (vs )) * res )
1078+ return worst
1079+
1080+
10421081def check (skip_decimate , lift_z = False , stray_vert = False , slack_belt = False ,
1043- float_belts = False , skew_belt = False , odd_loaf = False ):
1082+ float_belts = False , skew_belt = False , odd_loaf = False , low_bake = False ):
10441083 bpy .ops .wm .read_factory_settings (use_empty = True )
10451084 low = build_bale_mesh (
10461085 "BaleLow" , bevel_offset = 0.016 , bevel_segments = 2 ,
@@ -1088,10 +1127,12 @@ def check(skip_decimate, lift_z=False, stray_vert=False, slack_belt=False,
10881127 size_y = bb [4 ] - bb [1 ]
10891128 size_z = bb [5 ] - bb [2 ]
10901129
1091- img , tex = setup_bake_image (low , hay )
1130+ img , tex = setup_bake_image (low , hay , LOW_BAKE_RES if low_bake else BAKE_RES )
10921131 if img is None :
10931132 return fail ("hay bale has no UV layer" , 3 ), None , None , None , None , None
10941133 bake_result = bake_normal (high , low )
1134+ texel = texel_audit (low .data , img )
1135+ print (f"measured bake_res={ img .size [0 ]} texel_min={ texel :.2f} " )
10951136
10961137 lod1 = make_lod (low , "BaleLOD1" , LOD1_TARGET , skip_decimate )
10971138 lod2 = make_lod (low , "BaleLOD2" , LOD2_TARGET , skip_decimate )
@@ -1390,6 +1431,12 @@ def half_y_at(x0, half_window):
13901431 f"{ LOAF_XZ } m bale" ,
13911432 19 ,
13921433 ), None , None , None , None , None
1434+ if texel < TEXEL_MIN :
1435+ return fail (
1436+ f"bake texel density { texel :.2f} px per UV cell < { TEXEL_MIN } "
1437+ "(--low-bake is the designed fail)" ,
1438+ 20 ,
1439+ ), None , None , None , None , None
13931440 return 0 , low , high , hay , tex , collider
13941441
13951442
@@ -1556,6 +1603,12 @@ def main():
15561603 help = "falsification: use the odd sin() ridge term so the loaf stops "
15571604 "being mirror-symmetric in X" ,
15581605 )
1606+ p .add_argument (
1607+ "--low-bake" ,
1608+ action = "store_true" ,
1609+ help = "falsification: bake the normal map at 256 px, so the "
1610+ "texel-density budget fails" ,
1611+ )
15591612 args = p .parse_args (argv )
15601613
15611614 code , low , _high , hay , tex , _col = check (
@@ -1566,6 +1619,7 @@ def main():
15661619 slack_belt = args .slack_belt ,
15671620 skew_belt = args .skew_belt ,
15681621 odd_loaf = args .odd_loaf ,
1622+ low_bake = args .low_bake ,
15691623 )
15701624 if code :
15711625 return code
0 commit comments