ob.rotation_euler = tuple(math.radians(a) for a in rot)
scene.collection.objects.link(ob)
- light("Key", (-4.0, -5.0, 6.0), 1500.0, 7.0, (1.0, 0.97, 0.92), (48, 0, -38))
- light("Fill", (5.0, -4.0, 3.0), 500.0, 9.0, (0.75, 0.85, 1.0), (62, 0, 50))
- light("Rim", (0.5, 4.5, 5.0), 900.0, 5.0, (0.55, 0.75, 1.0), (-55, 0, 175))
+ # a shaped warm key from the upper left, a faint cool fill so the shadow
+ # side of each tube stays legible, a cool rim to lift the silhouettes off
+ # the backdrop, and a warm wedge raking the back wall — the falloff pool
+ # behind the subjects that the rest of the gallery stages against.
+ light("Key", (-4.0, -5.0, 6.0), 650.0, 4.5, (1.0, 0.96, 0.9), (48, 0, -38))
+ light("Fill", (5.0, -4.0, 3.0), 110.0, 9.0, (0.75, 0.85, 1.0), (62, 0, 50))
+ light("Rim", (0.5, 4.5, 5.0), 350.0, 4.0, (0.6, 0.78, 1.0), (-55, 0, 175))
+ light("Wedge", (2.5, 3.5, 4.2), 520.0, 6.0, (1.0, 0.76, 0.5), (-72, 0, 195))
cam_data = bpy.data.cameras.new("Cam")
cam_data.lens = 45.0
@@ -481,6 +486,9 @@
Source
scene.render.resolution_y = 720
scene.render.image_settings.file_format = 'PNG'
scene.render.filepath = path
+ # AgX (the 4.x/5.x default) desaturates the BoneTint bands toward pastel,
+ # hiding exactly the weight boundaries the LBS check asserts.
+ scene.view_settings.view_transform = 'Standard'
bpy.ops.render.render(write_still=True)
return os.path.exists(path) and os.path.getsize(path) > 0
diff --git a/docs/gallery/assets/armature-bend-hero.webp b/docs/gallery/assets/armature-bend-hero.webp
index b69c8a7..1866425 100644
Binary files a/docs/gallery/assets/armature-bend-hero.webp and b/docs/gallery/assets/armature-bend-hero.webp differ
diff --git a/docs/gallery/assets/color-attribute-wheel-hero.webp b/docs/gallery/assets/color-attribute-wheel-hero.webp
index 405971f..e8867fb 100644
Binary files a/docs/gallery/assets/color-attribute-wheel-hero.webp and b/docs/gallery/assets/color-attribute-wheel-hero.webp differ
diff --git a/docs/gallery/color-attribute-wheel/index.html b/docs/gallery/color-attribute-wheel/index.html
index 113fd03..7ecbea0 100644
--- a/docs/gallery/color-attribute-wheel/index.html
+++ b/docs/gallery/color-attribute-wheel/index.html
@@ -244,11 +244,15 @@
Source
"""Vertex coords and per-vertex (h, s, v) in the same order as vidx()."""
coords = [(0.0, 0.0, 0.0)]
hsv = [(0.0, 0.0, 1.0)] # center: fully desaturated, white
+ # the hue origin is rotated off the picture horizontal: red is the
+ # perceptually sharpest hue transition, and the 0/360-degree wrap reads
+ # as a seam artifact when it lies level in frame
+ angle0 = math.radians(-52.0)
for r in range(1, RINGS + 1):
radius = R_OUTER * r / RINGS
sat = min(1.0, (r / RINGS) * 1.4)
for s in range(SEGMENTS):
- angle = 2.0 * math.pi * s / SEGMENTS
+ angle = angle0 + 2.0 * math.pi * s / SEGMENTS
coords.append((radius * math.cos(angle), radius * math.sin(angle), 0.0))
hsv.append((s / SEGMENTS, sat, 1.0))
return coords, hsv
@@ -360,7 +364,11 @@
Source
if"Emission Color"in bsdf.inputs: # Principled gained built-in emission in 4.x+
nt.links.new(attr_node.outputs["Color"], bsdf.inputs["Emission Color"])
bsdf.inputs["Emission Strength"].default_value = 0.12
- bsdf.inputs["Roughness"].default_value = 0.62
+ # fully matte: any specular component reflects the wall/floor horizon as
+ # a hard line across the disc face
+ bsdf.inputs["Roughness"].default_value = 0.85
+ if"Specular IOR Level"in bsdf.inputs:
+ bsdf.inputs["Specular IOR Level"].default_value = 0.0# The step AI code most often skips: the attribute must actually be wired# into the shader, not just present on the mesh.
@@ -385,8 +393,11 @@
Source
if mat isNone:
returnFalse
obj.data.materials.append(mat)
- obj.location = (0.0, 0.0, 0.55)
- obj.rotation_euler = (math.radians(14), 0.0, math.radians(12))
+ # stand the disc up toward the camera like an easel: the wheel is the
+ # subject, so it should present nearly face-on and fill the frame instead
+ # of lying foreshortened on the floor.
+ obj.location = (0.0, 0.0, 1.34)
+ obj.rotation_euler = (math.radians(52), 0.0, math.radians(10))
floor_me = bpy.data.meshes.new("Floor")
bm = bmesh.new()
@@ -424,9 +435,15 @@
Source
# a bright soft key from above reads the hue ring clearly; a low cool fill# keeps the shadow side legible; a faint warm rim separates the disc edge# from the dark backdrop without washing out the attribute colors.
- light("Key", (-2.0, -3.0, 5.5), 380.0, 11.0, (1.0, 0.98, 0.96), (58, 0, -28))
- light("Fill", (4.5, -2.5, 1.6), 140.0, 9.0, (0.78, 0.86, 1.0), (68, 0, 55))
+ light("Key", (-2.0, -3.0, 5.5), 320.0, 8.0, (1.0, 0.98, 0.96), (58, 0, -28))
+ light("Fill", (4.5, -2.5, 1.6), 90.0, 9.0, (0.78, 0.86, 1.0), (68, 0, 55))
light("Rim", (0.5, 3.6, 2.2), 170.0, 4.0, (1.0, 0.78, 0.55), (-70, 0, 175))
+ # a warm wedge raking the back wall — the falloff pool behind the subject
+ # the rest of the gallery stages against.
+ # placed between the disc and the back wall so it can only rake the wall:
+ # from any position in front, its grazing terminator draws a hard line
+ # across the flat disc face.
+ light("Wedge", (2.0, 5.2, 3.6), 220.0, 6.0, (1.0, 0.76, 0.5), (-68, 0, 190))
aim = bpy.data.objects.new("Aim", None)
aim.location = obj.location
@@ -435,7 +452,7 @@