From 9c8513c56a705d9527dad0a65f01374303c5c223 Mon Sep 17 00:00:00 2001 From: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Date: Sun, 4 Oct 2026 20:48:41 -0400 Subject: [PATCH] fix(skills): name the silent bake failure and check the bake result bake-high-to-low said a missing UV layer or a non-active image node "finishes without error". Measured on 4.5.11 LTS and 5.2.1 LTS: a missing UV layer raises RuntimeError, a lone Image Texture node that is not nodes.active is baked into anyway, and the case that really is silent is a material with no Image Texture node, which returns {'CANCELLED'} and raises nothing. The canonical call assigned `result` and never looked at it. The skill now tabulates the three measured behaviours, and both the skill's canonical call and snippets/bake_normal_high_to_low.py raise when the result is not {'FINISHED'}. Closes #356 Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 --- skills/bake-high-to-low/SKILL.md | 13 ++++++++++++- snippets/bake_normal_high_to_low.py | 7 ++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/skills/bake-high-to-low/SKILL.md b/skills/bake-high-to-low/SKILL.md index a50de1c2..76cd2b93 100644 --- a/skills/bake-high-to-low/SKILL.md +++ b/skills/bake-high-to-low/SKILL.md @@ -19,7 +19,15 @@ This skill is the bake step. It composes `ai-mesh-cleanup` (identity scale, appl ## The core misunderstanding -Baking is not a render. EEVEE has no bake path. The operator writes into the **active Image Texture node** of the **active** object's material, not into a file and not into "the selected image datablock." A missing UV layer, the wrong object active, or a node that is not `nodes.active` finishes without error and leaves a blank or unchanged image. +Baking is not a render. EEVEE has no bake path. The operator writes into an **Image Texture node** in the **active** object's material (the active one, `nodes.active`, when the material has several), not into a file and not into "the selected image datablock." The failure modes are not equally loud. Measured on 4.5.11 LTS and 5.2.1 LTS: + +| Setup mistake | What `bpy.ops.object.bake` does | +| --- | --- | +| Target mesh has no UV layer | Raises `RuntimeError: No active UV layer found in the object "Low"` | +| Target material has no Image Texture node | Returns `{'CANCELLED'}`, **raises nothing**, writes nothing | +| The only Image Texture node is not `nodes.active` | Bakes into it anyway (`{'FINISHED'}`) | + +So a headless job must check the returned set: `{'CANCELLED'}` is the silent one. With several Image Texture nodes, set `nodes.active` to the one you mean. The pass type RNA is `type`, not `bake_type`. `bake_type` is not on `bpy.ops.object.bake`. Passing it is a TypeError. @@ -104,6 +112,9 @@ result = bpy.ops.object.bake( use_clear=True, target="IMAGE_TEXTURES", ) +if result != {'FINISHED'}: + # {'CANCELLED'} raises nothing: e.g. no Image Texture node in the material. + raise RuntimeError(f"bake did not finish: {result}") ``` `cage_extrusion` inflates the active object along its normals so rays hit the high mesh. It must clear the high-frequency amplitude (ribs at 0.10 need extrusion > 0.10; 0.20 is the worked value). If extrusion alone skims past concavities, set `use_cage=True` and `cage_object="CageName"` (the object's `.name`). Do not pass the Object. diff --git a/snippets/bake_normal_high_to_low.py b/snippets/bake_normal_high_to_low.py index d59decef..9f6938aa 100644 --- a/snippets/bake_normal_high_to_low.py +++ b/snippets/bake_normal_high_to_low.py @@ -2,6 +2,8 @@ # normal map. Cycles CPU only. High must be selected; low must be active. # Operator RNA is `type`, not `bake_type`. cage_object is a string name. # Identifiers match on 4.5 LTS, 5.1, and 5.2 LTS — no version shim. +# A missing UV layer raises, but a material with no Image Texture node +# returns {'CANCELLED'} without raising, so check the result. # # Reference: # https://docs.blender.org/api/current/bpy.ops.object.html#bpy.ops.object.bake @@ -18,7 +20,7 @@ def bake_normal_high_to_low(high, low, cage_extrusion=0.20, margin=16): high.select_set(True) low.select_set(True) bpy.context.view_layer.objects.active = low - return bpy.ops.object.bake( + result = bpy.ops.object.bake( type="NORMAL", use_selected_to_active=True, cage_extrusion=cage_extrusion, @@ -29,3 +31,6 @@ def bake_normal_high_to_low(high, low, cage_extrusion=0.20, margin=16): use_clear=True, target="IMAGE_TEXTURES", ) + if result != {"FINISHED"}: + raise RuntimeError(f"bake did not finish: {result}") + return result