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)