diff --git a/docs/gallery/assets/bmesh-gear-hero.webp b/docs/gallery/assets/bmesh-gear-hero.webp
index 3550b1c..db4cf33 100644
Binary files a/docs/gallery/assets/bmesh-gear-hero.webp and b/docs/gallery/assets/bmesh-gear-hero.webp differ
diff --git a/docs/gallery/assets/depsgraph-export-hero.webp b/docs/gallery/assets/depsgraph-export-hero.webp
index 9276917..ae7af27 100644
Binary files a/docs/gallery/assets/depsgraph-export-hero.webp and b/docs/gallery/assets/depsgraph-export-hero.webp differ
diff --git a/docs/gallery/assets/gn-sdf-remesh-hero.webp b/docs/gallery/assets/gn-sdf-remesh-hero.webp
index 4130651..558cc03 100644
Binary files a/docs/gallery/assets/gn-sdf-remesh-hero.webp and b/docs/gallery/assets/gn-sdf-remesh-hero.webp differ
diff --git a/docs/gallery/assets/turntable-hero.webp b/docs/gallery/assets/turntable-hero.webp
index 8666547..ca5e30b 100644
Binary files a/docs/gallery/assets/turntable-hero.webp and b/docs/gallery/assets/turntable-hero.webp differ
diff --git a/docs/gallery/bmesh-gear/index.html b/docs/gallery/bmesh-gear/index.html
index 028cc4a..066d61e 100644
--- a/docs/gallery/bmesh-gear/index.html
+++ b/docs/gallery/bmesh-gear/index.html
@@ -315,8 +315,8 @@
Source
fmat = bpy.data.materials.new("Studio")
fmat.use_nodes = True
fb = fmat.node_tree.nodes["Principled BSDF"]
- fb.inputs["Base Color"].default_value = (0.055, 0.06, 0.07, 1.0)
- fb.inputs["Roughness"].default_value = 0.5
+ fb.inputs["Base Color"].default_value = (0.03, 0.032, 0.037, 1.0)
+ fb.inputs["Roughness"].default_value = 0.7
floor_me.materials.append(fmat)
floor = bpy.data.objects.new("Floor", floor_me)
scene.collection.objects.link(floor)
@@ -328,7 +328,7 @@ Source
world = bpy.data.worlds.new("World")
world.use_nodes = True
# metals reflect the environment: keep a faint cool ambient so flanks never go black
- world.node_tree.nodes["Background"].inputs["Color"].default_value = (0.035, 0.04, 0.05, 1.0)
+ world.node_tree.nodes["Background"].inputs["Color"].default_value = (0.02, 0.022, 0.028, 1.0)
scene.world = world
def light(name, loc, energy, size, col, rot):
@@ -339,10 +339,11 @@ Source
ob.rotation_euler = tuple(math.radians(a) for a in rot)
scene.collection.objects.link(ob)
- # metals live on reflections: big soft key, strong cool fill, warm rim kept low
- light("Key", (-3.5, -4.5, 5.5), 1400.0, 7.0, (1.0, 0.98, 0.94), (48, 0, -35))
- light("Fill", (5.0, -3.5, 2.5), 600.0, 9.0, (0.8, 0.87, 1.0), (65, 0, 50))
- light("Rim", (1.5, 4.5, 2.2), 700.0, 4.0, (1.0, 0.7, 0.4), (-82, 0, 165))
+ # metals live on reflections: soft warm key, restrained cool fill, and the
+ # warm wedge raking the back wall (docs/VISUAL-STYLE.md)
+ light("Key", (-3.5, -4.5, 5.5), 700.0, 6.0, (1.0, 0.96, 0.9), (48, 0, -35))
+ light("Fill", (5.0, -3.5, 2.5), 160.0, 9.0, (0.75, 0.85, 1.0), (65, 0, 50))
+ light("Wedge", (2.5, 5.5, 4.0), 380.0, 6.0, (1.0, 0.76, 0.5), (-68, 0, 190))
cam_data = bpy.data.cameras.new("Cam")
cam_data.lens = 55.0
@@ -364,6 +365,8 @@ Source
scene.render.resolution_y = 720
scene.render.image_settings.file_format = 'PNG'
scene.render.filepath = path
+ # AgX would flatten the steel toward chalk (docs/VISUAL-STYLE.md)
+ 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/depsgraph-export/index.html b/docs/gallery/depsgraph-export/index.html
index 42f0325..86522ba 100644
--- a/docs/gallery/depsgraph-export/index.html
+++ b/docs/gallery/depsgraph-export/index.html
@@ -291,7 +291,7 @@ Source
base_obj = bpy.data.objects.new("Base", obj.data.copy())
base_obj.location = (-1.7, 0.0, 1.0)
base_obj.data.materials.append(
- principled("Graphite", (0.16, 0.17, 0.19, 1.0), 0.0, 0.7))
+ principled("Graphite", (0.09, 0.10, 0.12, 1.0), 0.0, 0.55))
bpy.context.collection.objects.link(base_obj)
# right: the SUBSURF object — what the depsgraph evaluates and the OBJ ships.
@@ -345,10 +345,10 @@ Source
light("Wedge", (2.5, 5.5, 4.0), 380.0, 6.0, (1.0, 0.76, 0.5), (-68, 0, 190))
cam_data = bpy.data.cameras.new("Cam")
- cam_data.lens = 42.0
+ cam_data.lens = 46.0
cam = bpy.data.objects.new("Cam", cam_data)
- cam.location = (0.0, -9.0, 3.1)
- cam.rotation_euler = (math.radians(76), 0.0, 0.0)
+ cam.location = (0.0, -8.2, 2.7)
+ cam.rotation_euler = (math.radians(78), 0.0, 0.0)
scene.collection.objects.link(cam)
scene.camera = cam
diff --git a/docs/gallery/gn-sdf-remesh/index.html b/docs/gallery/gn-sdf-remesh/index.html
index 3481ba5..e6e3cac 100644
--- a/docs/gallery/gn-sdf-remesh/index.html
+++ b/docs/gallery/gn-sdf-remesh/index.html
@@ -217,7 +217,7 @@ Source
blender --background --python gn_sdf_remesh.py -- --output r.png # also render the result
blender --background --python gn_sdf_remesh.py -- --output r.png --engine cycles # GPU-less
"""
-import bpy, sys, os, argparse
+import bpy, sys, os, math, argparse
def get_eevee_engine_id():
return 'BLENDER_EEVEE' if bpy.app.version >= (5, 0, 0) else 'BLENDER_EEVEE_NEXT'
@@ -267,25 +267,28 @@ Source
bm.free()
fmat = bpy.data.materials.new("Studio"); fmat.use_nodes = True
fb = fmat.node_tree.nodes.get('Principled BSDF')
- fb.inputs['Base Color'].default_value = (0.055, 0.06, 0.07, 1) # dark graphite studio
- fb.inputs['Roughness'].default_value = 0.55
+ fb.inputs['Base Color'].default_value = (0.03, 0.032, 0.037, 1) # dark staged studio
+ fb.inputs['Roughness'].default_value = 0.7
fme.materials.append(fmat)
floor = bpy.data.objects.new("Floor", fme); bpy.context.collection.objects.link(floor)
wall = bpy.data.objects.new("Wall", fme.copy()); wall.location = (0, 9.0, 0)
wall.rotation_euler = (1.5708, 0, 0); bpy.context.collection.objects.link(wall)
w = bpy.data.worlds.new("W"); w.use_nodes = True
- w.node_tree.nodes["Background"].inputs[0].default_value = (0.01, 0.011, 0.014, 1); sc.world = w
+ w.node_tree.nodes["Background"].inputs[0].default_value = (0.02, 0.021, 0.025, 1); sc.world = w
aim = bpy.data.objects.new("Aim", None); aim.location = (0, 0, 0.55); bpy.context.collection.objects.link(aim)
cam = bpy.data.objects.new("cam", bpy.data.cameras.new("cam")); cam.location = (0, -6.5, 2.2)
bpy.context.collection.objects.link(cam); sc.camera = cam
c = cam.constraints.new('TRACK_TO'); c.target = aim; c.track_axis = 'TRACK_NEGATIVE_Z'; c.up_axis = 'UP_Y'
- # key, cool fill, warm rim
- for nm, loc, en, sz, col in [("K", (-4, -5, 7), 1100, 5.0, (1.0, 0.98, 0.95)),
- ("F2", (5, -4, 2), 220, 7.0, (0.85, 0.9, 1.0)),
- ("R", (2.5, 6, 4), 700, 3.0, (1.0, 0.72, 0.45))]:
+ # shaped warm key, faint cool fill (docs/VISUAL-STYLE.md)
+ for nm, loc, en, sz, col in [("K", (-4, -5, 7), 650, 4.5, (1.0, 0.96, 0.9)),
+ ("F2", (5, -4, 2), 110, 7.0, (0.75, 0.85, 1.0))]:
ld = bpy.data.lights.new(nm, 'AREA'); ld.energy = en; ld.size = sz; ld.color = col
lo = bpy.data.objects.new(nm, ld); lo.location = loc; bpy.context.collection.objects.link(lo)
lc = lo.constraints.new('TRACK_TO'); lc.target = aim; lc.track_axis = 'TRACK_NEGATIVE_Z'; lc.up_axis = 'UP_Y'
+ # warm wedge raking the back wall, aimed past the torus at the wall
+ wd = bpy.data.lights.new("Wedge", 'AREA'); wd.energy = 380; wd.size = 6.0; wd.color = (1.0, 0.76, 0.5)
+ wo = bpy.data.objects.new("Wedge", wd); wo.location = (2.5, 5.5, 4.0)
+ wo.rotation_euler = (math.radians(-68), 0, math.radians(190)); bpy.context.collection.objects.link(wo)
sc.render.engine = 'CYCLES' if engine == 'cycles' else get_eevee_engine_id()
if sc.render.engine == 'CYCLES':
try: sc.cycles.samples = 32
@@ -295,6 +298,8 @@ Source
except Exception: pass
sc.render.resolution_x = 1280; sc.render.resolution_y = 720
sc.render.image_settings.file_format = 'PNG'; sc.render.filepath = path
+ # AgX would wash the crimson toward brick (docs/VISUAL-STYLE.md)
+ sc.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/turntable/index.html b/docs/gallery/turntable/index.html
index 9a0b11d..81caa9c 100644
--- a/docs/gallery/turntable/index.html
+++ b/docs/gallery/turntable/index.html
@@ -235,8 +235,7 @@ Source
bpy.ops.wm.read_factory_settings(use_empty=True)
bpy.ops.mesh.primitive_monkey_add(location=(0, 0, 1.0))
obj = bpy.context.active_object
- for p in obj.data.polygons:
- p.use_smooth = True
+ obj.data.shade_smooth()
mat = bpy.data.materials.new("M"); mat.use_nodes = True
b = mat.node_tree.nodes.get('Principled BSDF')
b.inputs['Base Color'].default_value = (0.85, 0.35, 0.10, 1)
@@ -309,7 +308,10 @@ Source
else:
try: sc.eevee.taa_render_samples = 16
except Exception: pass
- sc.frame_set(FRAMES // 4)
+ # pin the still to a deliberate pose: frame 4 is ~31 degrees of turn, a
+ # three-quarter view where ears and brow read instantly as Suzanne. An
+ # arbitrary turntable frame can land face-away and read as broken geometry.
+ sc.frame_set(4)
sc.render.resolution_x = 1280; sc.render.resolution_y = 720
sc.render.image_settings.file_format = 'PNG'; sc.render.filepath = path
# AgX would wash the copper toward beige (docs/VISUAL-STYLE.md)
diff --git a/examples/bmesh-gear/bmesh_gear.py b/examples/bmesh-gear/bmesh_gear.py
index 3175383..982a398 100644
--- a/examples/bmesh-gear/bmesh_gear.py
+++ b/examples/bmesh-gear/bmesh_gear.py
@@ -113,8 +113,8 @@ def render_still(obj, path, engine):
fmat = bpy.data.materials.new("Studio")
fmat.use_nodes = True
fb = fmat.node_tree.nodes["Principled BSDF"]
- fb.inputs["Base Color"].default_value = (0.055, 0.06, 0.07, 1.0)
- fb.inputs["Roughness"].default_value = 0.5
+ fb.inputs["Base Color"].default_value = (0.03, 0.032, 0.037, 1.0)
+ fb.inputs["Roughness"].default_value = 0.7
floor_me.materials.append(fmat)
floor = bpy.data.objects.new("Floor", floor_me)
scene.collection.objects.link(floor)
@@ -126,7 +126,7 @@ def render_still(obj, path, engine):
world = bpy.data.worlds.new("World")
world.use_nodes = True
# metals reflect the environment: keep a faint cool ambient so flanks never go black
- world.node_tree.nodes["Background"].inputs["Color"].default_value = (0.035, 0.04, 0.05, 1.0)
+ world.node_tree.nodes["Background"].inputs["Color"].default_value = (0.02, 0.022, 0.028, 1.0)
scene.world = world
def light(name, loc, energy, size, col, rot):
@@ -137,10 +137,11 @@ def light(name, loc, energy, size, col, rot):
ob.rotation_euler = tuple(math.radians(a) for a in rot)
scene.collection.objects.link(ob)
- # metals live on reflections: big soft key, strong cool fill, warm rim kept low
- light("Key", (-3.5, -4.5, 5.5), 1400.0, 7.0, (1.0, 0.98, 0.94), (48, 0, -35))
- light("Fill", (5.0, -3.5, 2.5), 600.0, 9.0, (0.8, 0.87, 1.0), (65, 0, 50))
- light("Rim", (1.5, 4.5, 2.2), 700.0, 4.0, (1.0, 0.7, 0.4), (-82, 0, 165))
+ # metals live on reflections: soft warm key, restrained cool fill, and the
+ # warm wedge raking the back wall (docs/VISUAL-STYLE.md)
+ light("Key", (-3.5, -4.5, 5.5), 700.0, 6.0, (1.0, 0.96, 0.9), (48, 0, -35))
+ light("Fill", (5.0, -3.5, 2.5), 160.0, 9.0, (0.75, 0.85, 1.0), (65, 0, 50))
+ light("Wedge", (2.5, 5.5, 4.0), 380.0, 6.0, (1.0, 0.76, 0.5), (-68, 0, 190))
cam_data = bpy.data.cameras.new("Cam")
cam_data.lens = 55.0
@@ -162,6 +163,8 @@ def light(name, loc, energy, size, col, rot):
scene.render.resolution_y = 720
scene.render.image_settings.file_format = 'PNG'
scene.render.filepath = path
+ # AgX would flatten the steel toward chalk (docs/VISUAL-STYLE.md)
+ 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/examples/bmesh-gear/preview.webp b/examples/bmesh-gear/preview.webp
index 1ea7ea3..72b0eb8 100644
Binary files a/examples/bmesh-gear/preview.webp and b/examples/bmesh-gear/preview.webp differ
diff --git a/examples/depsgraph-export/depsgraph_export.py b/examples/depsgraph-export/depsgraph_export.py
index 8206c36..a6ac5ec 100644
--- a/examples/depsgraph-export/depsgraph_export.py
+++ b/examples/depsgraph-export/depsgraph_export.py
@@ -86,7 +86,7 @@ def render_still(obj, path, engine):
base_obj = bpy.data.objects.new("Base", obj.data.copy())
base_obj.location = (-1.7, 0.0, 1.0)
base_obj.data.materials.append(
- principled("Graphite", (0.16, 0.17, 0.19, 1.0), 0.0, 0.7))
+ principled("Graphite", (0.09, 0.10, 0.12, 1.0), 0.0, 0.55))
bpy.context.collection.objects.link(base_obj)
# right: the SUBSURF object — what the depsgraph evaluates and the OBJ ships.
@@ -140,10 +140,10 @@ def light(name, loc, energy, size, col, rot):
light("Wedge", (2.5, 5.5, 4.0), 380.0, 6.0, (1.0, 0.76, 0.5), (-68, 0, 190))
cam_data = bpy.data.cameras.new("Cam")
- cam_data.lens = 42.0
+ cam_data.lens = 46.0
cam = bpy.data.objects.new("Cam", cam_data)
- cam.location = (0.0, -9.0, 3.1)
- cam.rotation_euler = (math.radians(76), 0.0, 0.0)
+ cam.location = (0.0, -8.2, 2.7)
+ cam.rotation_euler = (math.radians(78), 0.0, 0.0)
scene.collection.objects.link(cam)
scene.camera = cam
diff --git a/examples/depsgraph-export/preview.webp b/examples/depsgraph-export/preview.webp
index 3e1f10f..496294a 100644
Binary files a/examples/depsgraph-export/preview.webp and b/examples/depsgraph-export/preview.webp differ
diff --git a/examples/gn-sdf-remesh/gn_sdf_remesh.py b/examples/gn-sdf-remesh/gn_sdf_remesh.py
index 102dc9f..b315bf4 100644
--- a/examples/gn-sdf-remesh/gn_sdf_remesh.py
+++ b/examples/gn-sdf-remesh/gn_sdf_remesh.py
@@ -14,7 +14,7 @@
blender --background --python gn_sdf_remesh.py -- --output r.png # also render the result
blender --background --python gn_sdf_remesh.py -- --output r.png --engine cycles # GPU-less
"""
-import bpy, sys, os, argparse
+import bpy, sys, os, math, argparse
def get_eevee_engine_id():
return 'BLENDER_EEVEE' if bpy.app.version >= (5, 0, 0) else 'BLENDER_EEVEE_NEXT'
@@ -64,25 +64,28 @@ def render_still(obj, path, engine):
bm.free()
fmat = bpy.data.materials.new("Studio"); fmat.use_nodes = True
fb = fmat.node_tree.nodes.get('Principled BSDF')
- fb.inputs['Base Color'].default_value = (0.055, 0.06, 0.07, 1) # dark graphite studio
- fb.inputs['Roughness'].default_value = 0.55
+ fb.inputs['Base Color'].default_value = (0.03, 0.032, 0.037, 1) # dark staged studio
+ fb.inputs['Roughness'].default_value = 0.7
fme.materials.append(fmat)
floor = bpy.data.objects.new("Floor", fme); bpy.context.collection.objects.link(floor)
wall = bpy.data.objects.new("Wall", fme.copy()); wall.location = (0, 9.0, 0)
wall.rotation_euler = (1.5708, 0, 0); bpy.context.collection.objects.link(wall)
w = bpy.data.worlds.new("W"); w.use_nodes = True
- w.node_tree.nodes["Background"].inputs[0].default_value = (0.01, 0.011, 0.014, 1); sc.world = w
+ w.node_tree.nodes["Background"].inputs[0].default_value = (0.02, 0.021, 0.025, 1); sc.world = w
aim = bpy.data.objects.new("Aim", None); aim.location = (0, 0, 0.55); bpy.context.collection.objects.link(aim)
cam = bpy.data.objects.new("cam", bpy.data.cameras.new("cam")); cam.location = (0, -6.5, 2.2)
bpy.context.collection.objects.link(cam); sc.camera = cam
c = cam.constraints.new('TRACK_TO'); c.target = aim; c.track_axis = 'TRACK_NEGATIVE_Z'; c.up_axis = 'UP_Y'
- # key, cool fill, warm rim
- for nm, loc, en, sz, col in [("K", (-4, -5, 7), 1100, 5.0, (1.0, 0.98, 0.95)),
- ("F2", (5, -4, 2), 220, 7.0, (0.85, 0.9, 1.0)),
- ("R", (2.5, 6, 4), 700, 3.0, (1.0, 0.72, 0.45))]:
+ # shaped warm key, faint cool fill (docs/VISUAL-STYLE.md)
+ for nm, loc, en, sz, col in [("K", (-4, -5, 7), 650, 4.5, (1.0, 0.96, 0.9)),
+ ("F2", (5, -4, 2), 110, 7.0, (0.75, 0.85, 1.0))]:
ld = bpy.data.lights.new(nm, 'AREA'); ld.energy = en; ld.size = sz; ld.color = col
lo = bpy.data.objects.new(nm, ld); lo.location = loc; bpy.context.collection.objects.link(lo)
lc = lo.constraints.new('TRACK_TO'); lc.target = aim; lc.track_axis = 'TRACK_NEGATIVE_Z'; lc.up_axis = 'UP_Y'
+ # warm wedge raking the back wall, aimed past the torus at the wall
+ wd = bpy.data.lights.new("Wedge", 'AREA'); wd.energy = 380; wd.size = 6.0; wd.color = (1.0, 0.76, 0.5)
+ wo = bpy.data.objects.new("Wedge", wd); wo.location = (2.5, 5.5, 4.0)
+ wo.rotation_euler = (math.radians(-68), 0, math.radians(190)); bpy.context.collection.objects.link(wo)
sc.render.engine = 'CYCLES' if engine == 'cycles' else get_eevee_engine_id()
if sc.render.engine == 'CYCLES':
try: sc.cycles.samples = 32
@@ -92,6 +95,8 @@ def render_still(obj, path, engine):
except Exception: pass
sc.render.resolution_x = 1280; sc.render.resolution_y = 720
sc.render.image_settings.file_format = 'PNG'; sc.render.filepath = path
+ # AgX would wash the crimson toward brick (docs/VISUAL-STYLE.md)
+ sc.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/examples/gn-sdf-remesh/preview.webp b/examples/gn-sdf-remesh/preview.webp
index f5823e0..aec33a9 100644
Binary files a/examples/gn-sdf-remesh/preview.webp and b/examples/gn-sdf-remesh/preview.webp differ
diff --git a/examples/turntable/preview.webp b/examples/turntable/preview.webp
index c893dbf..185ac57 100644
Binary files a/examples/turntable/preview.webp and b/examples/turntable/preview.webp differ
diff --git a/examples/turntable/turntable.py b/examples/turntable/turntable.py
index c5f4fb7..65290ac 100644
--- a/examples/turntable/turntable.py
+++ b/examples/turntable/turntable.py
@@ -33,8 +33,7 @@ def build():
bpy.ops.wm.read_factory_settings(use_empty=True)
bpy.ops.mesh.primitive_monkey_add(location=(0, 0, 1.0))
obj = bpy.context.active_object
- for p in obj.data.polygons:
- p.use_smooth = True
+ obj.data.shade_smooth()
mat = bpy.data.materials.new("M"); mat.use_nodes = True
b = mat.node_tree.nodes.get('Principled BSDF')
b.inputs['Base Color'].default_value = (0.85, 0.35, 0.10, 1)
@@ -107,7 +106,10 @@ def render_still(obj, path, engine):
else:
try: sc.eevee.taa_render_samples = 16
except Exception: pass
- sc.frame_set(FRAMES // 4)
+ # pin the still to a deliberate pose: frame 4 is ~31 degrees of turn, a
+ # three-quarter view where ears and brow read instantly as Suzanne. An
+ # arbitrary turntable frame can land face-away and read as broken geometry.
+ sc.frame_set(4)
sc.render.resolution_x = 1280; sc.render.resolution_y = 720
sc.render.image_settings.file_format = 'PNG'; sc.render.filepath = path
# AgX would wash the copper toward beige (docs/VISUAL-STYLE.md)