-
+
|
diff --git a/docs/gallery/assets/bmesh-gear-hero.webp b/docs/gallery/assets/bmesh-gear-hero.webp
index db4cf33..da96193 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 ae7af27..5c61029 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 558cc03..056fbde 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/bmesh-gear/index.html b/docs/gallery/bmesh-gear/index.html
index 066d61e..25983f7 100644
--- a/docs/gallery/bmesh-gear/index.html
+++ b/docs/gallery/bmesh-gear/index.html
@@ -295,15 +295,31 @@ Source
scene = bpy.context.scene
for poly in obj.data.polygons:
poly.use_smooth = False # crisp machined facets
- mat = bpy.data.materials.new("Steel")
+ # machined brass: a flat metal face lit by area lights renders as one
+ # featureless gradient, so the finish carries the design — concentric
+ # turning marks (a RINGS wave driving roughness) break the face into
+ # rings that each catch the key at a different angle
+ mat = bpy.data.materials.new("MachinedBrass")
mat.use_nodes = True
- bsdf = mat.node_tree.nodes["Principled BSDF"]
- bsdf.inputs["Base Color"].default_value = (0.75, 0.77, 0.8, 1.0)
+ nt = mat.node_tree
+ bsdf = nt.nodes["Principled BSDF"]
+ bsdf.inputs["Base Color"].default_value = (0.72, 0.44, 0.16, 1.0)
bsdf.inputs["Metallic"].default_value = 1.0
- bsdf.inputs["Roughness"].default_value = 0.45
+ rings = nt.nodes.new("ShaderNodeTexWave")
+ rings.wave_type = 'RINGS'
+ rings.rings_direction = 'SPHERICAL' # concentric from center, not X-axis bands
+ rings.inputs["Scale"].default_value = 16.0
+ rings.inputs["Distortion"].default_value = 0.5
+ rough = nt.nodes.new("ShaderNodeMapRange")
+ rough.inputs["From Min"].default_value = 0.0
+ rough.inputs["From Max"].default_value = 1.0
+ rough.inputs["To Min"].default_value = 0.14
+ rough.inputs["To Max"].default_value = 0.36
+ nt.links.new(rings.outputs["Fac"], rough.inputs["Value"])
+ nt.links.new(rough.outputs["Result"], bsdf.inputs["Roughness"])
obj.data.materials.append(mat)
obj.location = (0.0, 0.0, 0.85)
- obj.rotation_euler = (math.radians(38), 0.0, math.radians(22))
+ obj.rotation_euler = (math.radians(46), 0.0, math.radians(22))
floor_me = bpy.data.meshes.new("Floor")
bm = bmesh.new()
@@ -341,9 +357,11 @@ Source
# 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("Key", (-3.5, -4.5, 5.5), 550.0, 4.5, (1.0, 0.96, 0.9), (48, 0, -35))
+ light("Fill", (5.0, -3.5, 2.5), 130.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))
+ # a small hot glint: brass needs one crisp specular streak to read machined
+ light("Glint", (1.8, -5.2, 6.2), 900.0, 0.9, (1.0, 0.9, 0.7), (40, 0, 18))
cam_data = bpy.data.cameras.new("Cam")
cam_data.lens = 55.0
diff --git a/docs/gallery/contact-sheets/bmesh-gear-contact-sheet.webp b/docs/gallery/contact-sheets/bmesh-gear-contact-sheet.webp
new file mode 100644
index 0000000..a1d5854
Binary files /dev/null and b/docs/gallery/contact-sheets/bmesh-gear-contact-sheet.webp differ
diff --git a/docs/gallery/contact-sheets/depsgraph-export-contact-sheet.webp b/docs/gallery/contact-sheets/depsgraph-export-contact-sheet.webp
new file mode 100644
index 0000000..cb7cbf0
Binary files /dev/null and b/docs/gallery/contact-sheets/depsgraph-export-contact-sheet.webp differ
diff --git a/docs/gallery/contact-sheets/gn-sdf-remesh-contact-sheet.webp b/docs/gallery/contact-sheets/gn-sdf-remesh-contact-sheet.webp
new file mode 100644
index 0000000..5358da6
Binary files /dev/null and b/docs/gallery/contact-sheets/gn-sdf-remesh-contact-sheet.webp differ
diff --git a/docs/gallery/depsgraph-export/index.html b/docs/gallery/depsgraph-export/index.html
index 86522ba..2f89930 100644
--- a/docs/gallery/depsgraph-export/index.html
+++ b/docs/gallery/depsgraph-export/index.html
@@ -287,11 +287,18 @@ Source
"""Base cube beside its evaluated form — the two counts the check compares."""
scene = bpy.context.scene
+ # one hero material on BOTH objects: the two silhouettes share a color so
+ # the only visible difference is the thing the check measures — base-mesh
+ # geometry versus the depsgraph-evaluated subsurf
+ hero = principled("Teal", (0.012, 0.30, 0.34, 1.0), 0.0, 0.2)
+
# left: the base mesh, modifier-free (a plain copy of the datablock)
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.09, 0.10, 0.12, 1.0), 0.0, 0.55))
+ # turned off camera-parallel so the key rakes the front face into a
+ # gradient instead of one flat tone
+ base_obj.rotation_euler = (0.0, 0.0, math.radians(32))
+ base_obj.data.materials.append(hero)
bpy.context.collection.objects.link(base_obj)
# right: the SUBSURF object — what the depsgraph evaluates and the OBJ ships.
@@ -302,8 +309,7 @@ Source
bottom = min(v.co.z for v in em.vertices)
obj.evaluated_get(dg).to_mesh_clear()
obj.location = (1.8, 0.0, -bottom)
- obj.data.materials.append(
- principled("EvalGreen", (0.03, 0.32, 0.10, 1.0), 0.0, 0.15))
+ obj.data.materials.append(hero)
for poly in obj.data.polygons:
poly.use_smooth = True
@@ -345,10 +351,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 = 46.0
+ cam_data.lens = 50.0
cam = bpy.data.objects.new("Cam", cam_data)
- cam.location = (0.0, -8.2, 2.7)
- cam.rotation_euler = (math.radians(78), 0.0, 0.0)
+ cam.location = (0.0, -7.4, 3.0)
+ cam.rotation_euler = (math.radians(76), 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 e6e3cac..7f853ec 100644
--- a/docs/gallery/gn-sdf-remesh/index.html
+++ b/docs/gallery/gn-sdf-remesh/index.html
@@ -252,8 +252,8 @@ Source
p.use_smooth = True
mat = bpy.data.materials.new("Ceramic"); mat.use_nodes = True
b = mat.node_tree.nodes.get('Principled BSDF')
- b.inputs['Base Color'].default_value = (0.42, 0.028, 0.045, 1) # crimson ceramic
- b.inputs['Roughness'].default_value = 0.22
+ b.inputs['Base Color'].default_value = (0.45, 0.025, 0.05, 1) # crimson ceramic
+ b.inputs['Roughness'].default_value = 0.16 # glossy: the SDF facets read by their glints
obj.data.materials.append(mat)
return obj
@@ -275,12 +275,15 @@ Source
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.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)
+ aim = bpy.data.objects.new("Aim", None); aim.location = (0, 0, 0.5); bpy.context.collection.objects.link(aim)
+ # off-axis and low: the remesh facets are the API evidence, and they only
+ # read when a raking key skims them from the side
+ cam = bpy.data.objects.new("cam", bpy.data.cameras.new("cam")); cam.location = (2.6, -5.2, 1.5)
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'
- # 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)),
+ # low raking warm key so every facet catches a distinct glint, faint cool
+ # fill (docs/VISUAL-STYLE.md)
+ for nm, loc, en, sz, col in [("K", (-5, -3.5, 2.8), 480, 3.0, (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)
diff --git a/examples/bmesh-gear/bmesh_gear.py b/examples/bmesh-gear/bmesh_gear.py
index 982a398..3c17888 100644
--- a/examples/bmesh-gear/bmesh_gear.py
+++ b/examples/bmesh-gear/bmesh_gear.py
@@ -93,15 +93,31 @@ def render_still(obj, path, engine):
scene = bpy.context.scene
for poly in obj.data.polygons:
poly.use_smooth = False # crisp machined facets
- mat = bpy.data.materials.new("Steel")
+ # machined brass: a flat metal face lit by area lights renders as one
+ # featureless gradient, so the finish carries the design — concentric
+ # turning marks (a RINGS wave driving roughness) break the face into
+ # rings that each catch the key at a different angle
+ mat = bpy.data.materials.new("MachinedBrass")
mat.use_nodes = True
- bsdf = mat.node_tree.nodes["Principled BSDF"]
- bsdf.inputs["Base Color"].default_value = (0.75, 0.77, 0.8, 1.0)
+ nt = mat.node_tree
+ bsdf = nt.nodes["Principled BSDF"]
+ bsdf.inputs["Base Color"].default_value = (0.72, 0.44, 0.16, 1.0)
bsdf.inputs["Metallic"].default_value = 1.0
- bsdf.inputs["Roughness"].default_value = 0.45
+ rings = nt.nodes.new("ShaderNodeTexWave")
+ rings.wave_type = 'RINGS'
+ rings.rings_direction = 'SPHERICAL' # concentric from center, not X-axis bands
+ rings.inputs["Scale"].default_value = 16.0
+ rings.inputs["Distortion"].default_value = 0.5
+ rough = nt.nodes.new("ShaderNodeMapRange")
+ rough.inputs["From Min"].default_value = 0.0
+ rough.inputs["From Max"].default_value = 1.0
+ rough.inputs["To Min"].default_value = 0.14
+ rough.inputs["To Max"].default_value = 0.36
+ nt.links.new(rings.outputs["Fac"], rough.inputs["Value"])
+ nt.links.new(rough.outputs["Result"], bsdf.inputs["Roughness"])
obj.data.materials.append(mat)
obj.location = (0.0, 0.0, 0.85)
- obj.rotation_euler = (math.radians(38), 0.0, math.radians(22))
+ obj.rotation_euler = (math.radians(46), 0.0, math.radians(22))
floor_me = bpy.data.meshes.new("Floor")
bm = bmesh.new()
@@ -139,9 +155,11 @@ def light(name, loc, energy, size, col, rot):
# 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("Key", (-3.5, -4.5, 5.5), 550.0, 4.5, (1.0, 0.96, 0.9), (48, 0, -35))
+ light("Fill", (5.0, -3.5, 2.5), 130.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))
+ # a small hot glint: brass needs one crisp specular streak to read machined
+ light("Glint", (1.8, -5.2, 6.2), 900.0, 0.9, (1.0, 0.9, 0.7), (40, 0, 18))
cam_data = bpy.data.cameras.new("Cam")
cam_data.lens = 55.0
diff --git a/examples/bmesh-gear/preview.webp b/examples/bmesh-gear/preview.webp
index 72b0eb8..147d448 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 a6ac5ec..5eaa836 100644
--- a/examples/depsgraph-export/depsgraph_export.py
+++ b/examples/depsgraph-export/depsgraph_export.py
@@ -82,11 +82,18 @@ def render_still(obj, path, engine):
"""Base cube beside its evaluated form — the two counts the check compares."""
scene = bpy.context.scene
+ # one hero material on BOTH objects: the two silhouettes share a color so
+ # the only visible difference is the thing the check measures — base-mesh
+ # geometry versus the depsgraph-evaluated subsurf
+ hero = principled("Teal", (0.012, 0.30, 0.34, 1.0), 0.0, 0.2)
+
# left: the base mesh, modifier-free (a plain copy of the datablock)
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.09, 0.10, 0.12, 1.0), 0.0, 0.55))
+ # turned off camera-parallel so the key rakes the front face into a
+ # gradient instead of one flat tone
+ base_obj.rotation_euler = (0.0, 0.0, math.radians(32))
+ base_obj.data.materials.append(hero)
bpy.context.collection.objects.link(base_obj)
# right: the SUBSURF object — what the depsgraph evaluates and the OBJ ships.
@@ -97,8 +104,7 @@ def render_still(obj, path, engine):
bottom = min(v.co.z for v in em.vertices)
obj.evaluated_get(dg).to_mesh_clear()
obj.location = (1.8, 0.0, -bottom)
- obj.data.materials.append(
- principled("EvalGreen", (0.03, 0.32, 0.10, 1.0), 0.0, 0.15))
+ obj.data.materials.append(hero)
for poly in obj.data.polygons:
poly.use_smooth = True
@@ -140,10 +146,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 = 46.0
+ cam_data.lens = 50.0
cam = bpy.data.objects.new("Cam", cam_data)
- cam.location = (0.0, -8.2, 2.7)
- cam.rotation_euler = (math.radians(78), 0.0, 0.0)
+ cam.location = (0.0, -7.4, 3.0)
+ cam.rotation_euler = (math.radians(76), 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 496294a..7f62723 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 b315bf4..6ab360a 100644
--- a/examples/gn-sdf-remesh/gn_sdf_remesh.py
+++ b/examples/gn-sdf-remesh/gn_sdf_remesh.py
@@ -49,8 +49,8 @@ def build():
p.use_smooth = True
mat = bpy.data.materials.new("Ceramic"); mat.use_nodes = True
b = mat.node_tree.nodes.get('Principled BSDF')
- b.inputs['Base Color'].default_value = (0.42, 0.028, 0.045, 1) # crimson ceramic
- b.inputs['Roughness'].default_value = 0.22
+ b.inputs['Base Color'].default_value = (0.45, 0.025, 0.05, 1) # crimson ceramic
+ b.inputs['Roughness'].default_value = 0.16 # glossy: the SDF facets read by their glints
obj.data.materials.append(mat)
return obj
@@ -72,12 +72,15 @@ def render_still(obj, path, engine):
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.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)
+ aim = bpy.data.objects.new("Aim", None); aim.location = (0, 0, 0.5); bpy.context.collection.objects.link(aim)
+ # off-axis and low: the remesh facets are the API evidence, and they only
+ # read when a raking key skims them from the side
+ cam = bpy.data.objects.new("cam", bpy.data.cameras.new("cam")); cam.location = (2.6, -5.2, 1.5)
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'
- # 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)),
+ # low raking warm key so every facet catches a distinct glint, faint cool
+ # fill (docs/VISUAL-STYLE.md)
+ for nm, loc, en, sz, col in [("K", (-5, -3.5, 2.8), 480, 3.0, (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)
diff --git a/examples/gn-sdf-remesh/preview.webp b/examples/gn-sdf-remesh/preview.webp
index aec33a9..f0289ec 100644
Binary files a/examples/gn-sdf-remesh/preview.webp and b/examples/gn-sdf-remesh/preview.webp differ
|