Add Blender material preset tools#41
Conversation
📝 WalkthroughWalkthroughThis PR introduces deterministic material preset creation to the ViperMesh Blender MCP addon. Two new commands— ChangesMaterial Preset Tooling & Deterministic Principled BSDF Creation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsTimed out fetching pipeline failures after 30000ms Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@data/tool-guides/materials-guide.md`:
- Around line 54-55: Add blank lines immediately before the "### Special
Materials" heading and after the table block (the lines containing "| Surface |
Key Properties | Notes |") so the heading and its table are separated from
surrounding content; update the "Special Materials" heading and the adjacent
table block in the markdown to ensure an empty line above the "### Special
Materials" line and an empty line after the table rows to resolve MD022/MD058
warnings.
In `@desktop/assets/vipermesh-addon.py`:
- Around line 1204-1210: The _as_rgba helper currently always clamps each
component to [0,1], which wrongly strips HDR emission values; change _as_rgba to
accept an optional clamp=True parameter and only perform the min/max clamping
when clamp is True, leaving value normalization/conversion and alpha handling
intact; then update the emission assignment call socket_values["Emission Color"]
= self._as_rgba(resolved_emission, defaults["color"]) to pass clamp=False so HDR
emission_color values are preserved while other callers keep default clamping.
In `@lib/orchestration/tool-filter.ts`:
- Line 6: Remove "execute_code" from the materials category array in
tool-filter.ts so material-intent requests no longer match that tool; update the
materials entry (materials: ["create_material_preset",
"inspect_material_node_graph", "set_texture"]) by deleting the "execute_code"
token and keep execute_code available only in its appropriate category(s) if
needed (refer to the materials array and any routing logic that reads it to
verify deterministic routing behavior).
In `@lib/orchestration/tool-registry.ts`:
- Around line 100-102: The create_material_preset tool metadata currently lists
only a subset of material fields; update the parameters string for
create_material_preset in lib/orchestration/tool-registry.ts to enumerate all
supported options (e.g., add alpha?: number, emission?: number, emission_color?:
number[], transmission?: number, ior?: number, replacement?: boolean|string,
slot?: string, and any slot/texture control objects) alongside existing keys
(name, preset, object_name, base_color, metallic, roughness, texture_maps) so
the planner sees full capabilities; ensure the syntax matches the existing
metadata format (string describing keys and types) and include the new keys in
the same comma-separated list.
In `@public/downloads/vipermesh-addon.py`:
- Around line 1293-1324: The material setup currently ignores the alpha channel
in base_color unless the separate alpha parameter is provided; update the logic
so that when alpha is None we derive alpha from base_color (resolved_color[3])
and use that to (1) set uses_transparency (treat as transparent if preset_key ==
"glass" OR resolved alpha < 1.0), (2) choose the proper blend/surface render
mode (existing uses_transparency assignment), and (3) populate
socket_values["Alpha"] from resolved_color[3] when no explicit alpha is given;
update the code around variables preset_key, alpha, base_color, resolved_color,
uses_transparency, and socket_values to read resolved_color[3] as the fallback
alpha.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: d20f3196-75ab-45d5-a4bc-3bcb6bad9007
📒 Files selected for processing (11)
data/tool-guides/materials-guide.mddesktop/assets/vipermesh-addon.pydocs/blender-mcp-capability-inventory.mddocs/plans/2026-05-05-blender-docs-skill-pipeline.mdlib/ai/agents.tslib/blender/capability-inventory.tslib/orchestration/prompts/blender-agent-system.mdlib/orchestration/tool-filter.tslib/orchestration/tool-registry.tspublic/downloads/vipermesh-addon.pyscripts/test/test-blender-capability-inventory.ts
| ### Special Materials | ||
| | Surface | Key Properties | Notes | |
There was a problem hiding this comment.
Add blank lines around the “Special Materials” heading/table block.
This currently triggers markdownlint warnings (MD022/MD058).
🧹 Suggested formatting fix
### Non-Metals (metallic = 0.0)
| Surface | Color [R,G,B] | Roughness | Notes |
|---|---|---|---|
...
| Fabric/cloth | [varies] | 0.85–0.95 | Very matte |
### Special Materials
+
| Surface | Key Properties | Notes |
|---|---|---|
| Glass | Transmission Weight = 0.9–1.0, IOR = 1.45–1.52, Roughness = 0.0–0.05 | Use `create_material_preset` preset `glass` |
| Water | Transmission Weight = 0.9, IOR = 1.33, Roughness = 0.02 | Use `create_material_preset` preset `glass` with IOR override |
| Skin | Subsurface Weight = 0.3, Subsurface Radius = [0.1, 0.05, 0.02] | |
| Emissive | Emission Color + Emission Strength | Use `create_material_preset` preset `emissive` |
+
## BLENDER 5.x SOCKET NAME CHANGES📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ### Special Materials | |
| | Surface | Key Properties | Notes | | |
| ### Special Materials | |
| | Surface | Key Properties | Notes | | |
| |---|---|---| | |
| | Glass | Transmission Weight = 0.9–1.0, IOR = 1.45–1.52, Roughness = 0.0–0.05 | Use `create_material_preset` preset `glass` | | |
| | Water | Transmission Weight = 0.9, IOR = 1.33, Roughness = 0.02 | Use `create_material_preset` preset `glass` with IOR override | | |
| | Skin | Subsurface Weight = 0.3, Subsurface Radius = [0.1, 0.05, 0.02] | | | |
| | Emissive | Emission Color + Emission Strength | Use `create_material_preset` preset `emissive` | | |
| ## BLENDER 5.x SOCKET NAME CHANGES |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 54-54: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
[warning] 55-55: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@data/tool-guides/materials-guide.md` around lines 54 - 55, Add blank lines
immediately before the "### Special Materials" heading and after the table block
(the lines containing "| Surface | Key Properties | Notes |") so the heading and
its table are separated from surrounding content; update the "Special Materials"
heading and the adjacent table block in the markdown to ensure an empty line
above the "### Special Materials" line and an empty line after the table rows to
resolve MD022/MD058 warnings.
There was a problem hiding this comment.
Actionable comments posted: 6
♻️ Duplicate comments (1)
lib/orchestration/tool-filter.ts (1)
6-6:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMaterial intents still reintroduce
execute_codethrough the geometry matcher.Removing it from
materialshelps, but prompts like “create a glass material” still match the genericcreate|add|generate|buildrule and pull ingeometry, which addsexecute_codeback into the shortlist. That keeps routing non-deterministic against this PR’s migration goal.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/orchestration/tool-filter.ts` at line 6, The material intents still trigger the generic geometry matcher and reintroduce execute_code; update the shortlist/matching logic so that when an intent is in the materials array (materials: ["create_material_preset","inspect_material_node_graph","set_texture"]) the geometry matcher (the "geometry" matcher / rule that uses the generic create|add|generate|build pattern) is not applied — either by adding an explicit exclusion in the geometry matcher or by short-circuiting the matching flow: detect material intents first and return/skip adding geometry, so material prompts cannot fall through to the geometry rule and re-add execute_code.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@desktop/assets/vipermesh-addon.py`:
- Around line 1409-1415: The code currently treats assignment warnings as
non-fatal and still reports success and returns assigned_object even when
self.assign_material(object_name, ...) returned an error; change the flow so
that after calling self.assign_material you treat assignment.get("error") as a
failure: do not set assigned_slot or assigned_object when assignment contains an
error, and set the overall result success flag to False (and include the error
message) in that case; update the logic around assigned_slot, assigned_object
and the final response construction (references: assigned_slot,
self.assign_material, assignment.get("error"), and assigned_object) so a failed
assignment produces success=False and does not echo an assigned_object.
- Around line 1288-1293: The current removal of the existing material
(bpy.data.materials.remove(existing)) causes all users to lose the datablock
before a replacement exists; instead either mutate the existing datablock in
place or, if you must create a new datablock, create the new material first and
rebind users to it before removing the old one. Concretely: in the
replace_existing branch avoid calling bpy.data.materials.remove(existing)
immediately — either reuse the found material object (existing) as mat and
rebuild/clear its node_tree in place, or create new_mat =
bpy.data.materials.new(name=name), iterate every object and material slot that
references existing and set slot.material = new_mat to rebind users, then safely
remove(existing). Update the code paths that set mat (the existing/or-new
assignment and subsequent node tree rebuild) to operate on the rebound/updated
material.
- Around line 1303-1311: Compute uses_transparency and set the refraction flags
after the transmission override is resolved and treat materials as refractive
not only for preset_key == "glass" but also when transmission_weight > 0 (and a
physical IOR > 1.0) or when resolved_alpha < 1.0; specifically, update the
condition that sets uses_transparency (currently using preset_key == "glass" or
resolved_alpha < 1.0) to also consider transmission_weight > 0 and ior > 1.0,
and move the blocks that set mat.surface_render_method/mat.blend_method and
mat.use_raytrace_refraction/mat.use_screen_refraction to run after transmission
resolution so the refraction booleans honor the transmission override (also
apply the same change to the other occurrence around the 1330–1333 region).
- Around line 1330-1342: The code uses Blender 4.0+ Principled BSDF socket names
("Transmission Weight", "Emission Color", "Coat Weight", "Sheen Weight") but the
addon claims Blender 3.x support, so sockets are not found on 3.x; update the
socket population logic that builds socket_values (and the places that call
_set_socket_value and use self._as_rgba) to resolve legacy aliases based on
bpy.app.version (or probe node.inputs): map "Transmission Weight" ->
"Transmission", "Emission Color" -> "Emission", "Coat Weight" -> "Clearcoat",
"Sheen Weight" -> "Sheen" when running under Blender < (4,0,0) or when the
preferred name is missing, and use the first existing name when setting
socket_values so _set_socket_value succeeds without silent warnings.
In `@public/downloads/vipermesh-addon.py`:
- Around line 1288-1315: The current logic always forces mat.use_nodes = True
and immediately calls nodes.clear() even when an existing material is being
reused (existing variable) and replace_existing is False; change the behavior so
that if existing is found and replace_existing is False you do not mutate the
existing material's node tree—either return early (preserve nodes) or add a new
boolean update_existing flag and only run mat.use_nodes = True and nodes.clear()
when update_existing is True; additionally, when you overwrite a non-empty node
tree you must surface a warning/flag in the function response (e.g., include
reused_existing=False and an overwrite_warning) so callers know nodes were
replaced; locate references to replace_existing, existing, mat.use_nodes,
nodes.clear and the function's return path to implement this guard and the new
flag/warning.
- Around line 1395-1401: When wiring a ShaderNodeDisplacement in the
displacement branch (role == "displacement") ensure the material's Cycles
displacement mode is set: obtain the material (mat) used for this node setup,
check cycles via getattr(mat, "cycles", None) and if present set
mat.cycles.displacement_method = "BOTH" or "DISPLACEMENT" before linking
displacement.outputs["Displacement"] to output.inputs["Displacement"]; this
ensures the connection produces real displacement (remember true displacement
still requires a subdivided mesh).
---
Duplicate comments:
In `@lib/orchestration/tool-filter.ts`:
- Line 6: The material intents still trigger the generic geometry matcher and
reintroduce execute_code; update the shortlist/matching logic so that when an
intent is in the materials array (materials:
["create_material_preset","inspect_material_node_graph","set_texture"]) the
geometry matcher (the "geometry" matcher / rule that uses the generic
create|add|generate|build pattern) is not applied — either by adding an explicit
exclusion in the geometry matcher or by short-circuiting the matching flow:
detect material intents first and return/skip adding geometry, so material
prompts cannot fall through to the geometry rule and re-add execute_code.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 15ef8775-d4bd-4f60-80bb-0185ba884c35
📒 Files selected for processing (5)
data/tool-guides/materials-guide.mddesktop/assets/vipermesh-addon.pylib/orchestration/tool-filter.tslib/orchestration/tool-registry.tspublic/downloads/vipermesh-addon.py
| existing = bpy.data.materials.get(name) | ||
| if existing and replace_existing: | ||
| bpy.data.materials.remove(existing) | ||
| existing = None | ||
|
|
||
| mat = existing or bpy.data.materials.new(name=name) |
There was a problem hiding this comment.
replace_existing=True currently strips the material from every current user.
bpy.data.materials.remove(existing) deletes the datablock before the replacement is created, so any other object already using this material loses its assignment. Since the node tree is rebuilt a few lines later anyway, this should either mutate existing in place or explicitly rebind former users to the new datablock.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@desktop/assets/vipermesh-addon.py` around lines 1288 - 1293, The current
removal of the existing material (bpy.data.materials.remove(existing)) causes
all users to lose the datablock before a replacement exists; instead either
mutate the existing datablock in place or, if you must create a new datablock,
create the new material first and rebind users to it before removing the old
one. Concretely: in the replace_existing branch avoid calling
bpy.data.materials.remove(existing) immediately — either reuse the found
material object (existing) as mat and rebuild/clear its node_tree in place, or
create new_mat = bpy.data.materials.new(name=name), iterate every object and
material slot that references existing and set slot.material = new_mat to rebind
users, then safely remove(existing). Update the code paths that set mat (the
existing/or-new assignment and subsequent node tree rebuild) to operate on the
rebound/updated material.
| uses_transparency = preset_key == "glass" or resolved_alpha < 1.0 | ||
| if hasattr(mat, "surface_render_method"): | ||
| mat.surface_render_method = "BLENDED" if uses_transparency else "DITHERED" | ||
| elif hasattr(mat, "blend_method"): | ||
| mat.blend_method = "BLEND" if uses_transparency else "OPAQUE" | ||
| if hasattr(mat, "use_raytrace_refraction"): | ||
| mat.use_raytrace_refraction = preset_key == "glass" | ||
| elif hasattr(mat, "use_screen_refraction"): | ||
| mat.use_screen_refraction = preset_key == "glass" |
There was a problem hiding this comment.
Honor transmission_weight when deciding whether the material is refractive.
uses_transparency and the refraction flags are computed before the transmission override is resolved and only special-case the glass preset. A call like preset="plastic", transmission_weight=1.0, ior=1.49 will set the BSDF socket but still leave the material configured as opaque/non-refractive.
Suggested fix
- uses_transparency = preset_key == "glass" or resolved_alpha < 1.0
+ resolved_transmission = float(
+ defaults.get("transmission", 0.0) if transmission_weight is None else transmission_weight
+ )
+ uses_transparency = resolved_alpha < 1.0 or resolved_transmission > 0.0
if hasattr(mat, "surface_render_method"):
mat.surface_render_method = "BLENDED" if uses_transparency else "DITHERED"
elif hasattr(mat, "blend_method"):
mat.blend_method = "BLEND" if uses_transparency else "OPAQUE"
if hasattr(mat, "use_raytrace_refraction"):
- mat.use_raytrace_refraction = preset_key == "glass"
+ mat.use_raytrace_refraction = resolved_transmission > 0.0
elif hasattr(mat, "use_screen_refraction"):
- mat.use_screen_refraction = preset_key == "glass"
+ mat.use_screen_refraction = resolved_transmission > 0.0
...
if "transmission" in defaults or transmission_weight is not None:
- socket_values["Transmission Weight"] = float(defaults.get("transmission", 0.0) if transmission_weight is None else transmission_weight)
+ socket_values["Transmission Weight"] = resolved_transmissionAlso applies to: 1330-1333
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@desktop/assets/vipermesh-addon.py` around lines 1303 - 1311, Compute
uses_transparency and set the refraction flags after the transmission override
is resolved and treat materials as refractive not only for preset_key == "glass"
but also when transmission_weight > 0 (and a physical IOR > 1.0) or when
resolved_alpha < 1.0; specifically, update the condition that sets
uses_transparency (currently using preset_key == "glass" or resolved_alpha <
1.0) to also consider transmission_weight > 0 and ior > 1.0, and move the blocks
that set mat.surface_render_method/mat.blend_method and
mat.use_raytrace_refraction/mat.use_screen_refraction to run after transmission
resolution so the refraction booleans honor the transmission override (also
apply the same change to the other occurrence around the 1330–1333 region).
| if "transmission" in defaults or transmission_weight is not None: | ||
| socket_values["Transmission Weight"] = float(defaults.get("transmission", 0.0) if transmission_weight is None else transmission_weight) | ||
| if "ior" in defaults or ior is not None: | ||
| socket_values["IOR"] = float(defaults.get("ior", 1.45) if ior is None else ior) | ||
| if "coat" in defaults: | ||
| socket_values["Coat Weight"] = float(defaults["coat"]) | ||
| if "sheen" in defaults: | ||
| socket_values["Sheen Weight"] = float(defaults["sheen"]) | ||
|
|
||
| resolved_emission = emission_color if emission_color is not None else defaults.get("emission_color") | ||
| if resolved_emission is not None: | ||
| socket_values["Emission Color"] = self._as_rgba(resolved_emission, defaults["color"], clamp=False) | ||
| socket_values["Emission Strength"] = float(defaults.get("emission_strength", 1.0) if emission_strength is None else emission_strength) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Blender 3.x versus 4.x/5.x Principled BSDF, what are the input socket names for Transmission, Emission, Clearcoat, and Sheen, and when were they renamed to Transmission Weight, Emission Color, Coat Weight, and Sheen Weight?
💡 Result:
In Blender 3.x (e.g., 3.6), the Principled BSDF input socket names were Transmission (factor), Emission (color), Clearcoat (factor), and Sheen (factor or with Tint) [1][2][3]. In Blender 4.x/5.x (e.g., 4.0+), they are Transmission Weight, Emission Color (or Emission), Coat Weight, and Sheen Weight [4][5][6][7]. These renames, part of a major revamp to support more material types with layered weights and shortened names, occurred in Blender 4.0 [8][9][10]. Emission rename is explicitly noted in development tasks [9], Transmission/Clearcoat/Sheen changed from direct factors to explicit Weight sockets [10][11].
Citations:
- 1: https://www.blender.org/manual/ar/3.6/render/shader_nodes/shader/principled.html
- 2: https://brandon3d.com/blender-principled-bsdf-shader-guide/
- 3: https://docs.blender.org/manual/id/3.2/render/shader_nodes/shader/principled.html
- 4: https://www.blender.org/manual/ar/4.0/render/shader_nodes/shader/principled.html
- 5: https://docs.blender.org/manual/en/5.1/render/shader_nodes/shader/principled.html
- 6: https://www.blender.org/manual/de/render/shader_nodes/shader/principled.html
- 7: https://docs.blender.org/manual/fr/4.2/render/shader_nodes/shader/principled.html
- 8: https://developer.blender.org/docs/release_notes/4.0/shading/
- 9: https://projects.blender.org/blender/blender/issues/99447
- 10: https://www.reddit.com/r/blenderhelp/comments/1csxtr3/they_changed_the_bdsf_node_transparency_sliders/
- 11: https://blender.stackexchange.com/questions/320141/transmission-weight-of-1-0-in-principled-bsdf-doesnt-let-light-pass-through
🏁 Script executed:
# Find the file and check its structure
find . -name "vipermesh-addon.py" -type fRepository: Ker102/ViperMesh
Length of output: 134
🏁 Script executed:
# Get the file size to understand how to read it
wc -l desktop/assets/vipermesh-addon.pyRepository: Ker102/ViperMesh
Length of output: 99
🏁 Script executed:
# Check addon metadata (typically at top of file) for Blender version
head -50 desktop/assets/vipermesh-addon.pyRepository: Ker102/ViperMesh
Length of output: 1465
🏁 Script executed:
# Examine the _set_socket_value method to understand error handling
rg "_set_socket_value" -A 10 desktop/assets/vipermesh-addon.pyRepository: Ker102/ViperMesh
Length of output: 977
🏁 Script executed:
# Check lines 1330-1342 in detail
sed -n '1325,1345p' desktop/assets/vipermesh-addon.pyRepository: Ker102/ViperMesh
Length of output: 1526
🏁 Script executed:
# Check lines 1471-1482 to confirm they have the same issue
sed -n '1466,1487p' desktop/assets/vipermesh-addon.pyRepository: Ker102/ViperMesh
Length of output: 858
🏁 Script executed:
# Search for version checks or legacy/compatibility handling
rg "blender_version|3\.|4\.|Transmission|Clearcoat|Coat Weight" -B 2 -A 2 desktop/assets/vipermesh-addon.py | head -100Repository: Ker102/ViperMesh
Length of output: 4141
🏁 Script executed:
# Double-check lines around 1471-1482 to see full context of inspection code
sed -n '1465,1495p' desktop/assets/vipermesh-addon.pyRepository: Ker102/ViperMesh
Length of output: 1310
Use only Blender 4.0+ socket names, but addon claims Blender 3.0 minimum support.
The addon advertises blender: (3, 0, 0) in bl_info but uses Blender 4.0+ Principled BSDF input names: Transmission Weight, Emission Color, Coat Weight, Sheen Weight. In Blender 3.x, these sockets are named Transmission, Emission, Clearcoat, and Sheen respectively. When _set_socket_value() cannot find these sockets, it silently appends a warning and continues, causing material presets and inspection results to be silently broken on supported versions.
Affects both material application (lines 1330–1342) and inspection (lines 1471–1482). Add legacy aliases, version checks, or update the minimum Blender version to 4.0.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@desktop/assets/vipermesh-addon.py` around lines 1330 - 1342, The code uses
Blender 4.0+ Principled BSDF socket names ("Transmission Weight", "Emission
Color", "Coat Weight", "Sheen Weight") but the addon claims Blender 3.x support,
so sockets are not found on 3.x; update the socket population logic that builds
socket_values (and the places that call _set_socket_value and use self._as_rgba)
to resolve legacy aliases based on bpy.app.version (or probe node.inputs): map
"Transmission Weight" -> "Transmission", "Emission Color" -> "Emission", "Coat
Weight" -> "Clearcoat", "Sheen Weight" -> "Sheen" when running under Blender <
(4,0,0) or when the preferred name is missing, and use the first existing name
when setting socket_values so _set_socket_value succeeds without silent
warnings.
| assigned_slot = None | ||
| if object_name: | ||
| assignment = self.assign_material(object_name, mat.name, slot_index) | ||
| if assignment.get("error"): | ||
| warnings.append(assignment["error"]) | ||
| else: | ||
| assigned_slot = assignment.get("slot") |
There was a problem hiding this comment.
Don't report a successful preset result when assignment failed.
When object_name is provided, a bad target or unsupported object type only appends a warning here, but the tool still reports success: True and echoes assigned_object. That makes the create+assign flow look complete even though no material slot was updated.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@desktop/assets/vipermesh-addon.py` around lines 1409 - 1415, The code
currently treats assignment warnings as non-fatal and still reports success and
returns assigned_object even when self.assign_material(object_name, ...)
returned an error; change the flow so that after calling self.assign_material
you treat assignment.get("error") as a failure: do not set assigned_slot or
assigned_object when assignment contains an error, and set the overall result
success flag to False (and include the error message) in that case; update the
logic around assigned_slot, assigned_object and the final response construction
(references: assigned_slot, self.assign_material, assignment.get("error"), and
assigned_object) so a failed assignment produces success=False and does not echo
an assigned_object.
| existing = bpy.data.materials.get(name) | ||
| if existing and replace_existing: | ||
| bpy.data.materials.remove(existing) | ||
| existing = None | ||
|
|
||
| mat = existing or bpy.data.materials.new(name=name) | ||
| mat.use_nodes = True | ||
|
|
||
| resolved_color = self._as_rgba(base_color, defaults["color"]) | ||
| if alpha is not None: | ||
| resolved_alpha = float(max(0.0, min(1.0, alpha))) | ||
| resolved_color = (resolved_color[0], resolved_color[1], resolved_color[2], resolved_alpha) | ||
| else: | ||
| resolved_alpha = float(max(0.0, min(1.0, resolved_color[3]))) | ||
|
|
||
| uses_transparency = preset_key == "glass" or resolved_alpha < 1.0 | ||
| if hasattr(mat, "surface_render_method"): | ||
| mat.surface_render_method = "BLENDED" if uses_transparency else "DITHERED" | ||
| elif hasattr(mat, "blend_method"): | ||
| mat.blend_method = "BLEND" if uses_transparency else "OPAQUE" | ||
| if hasattr(mat, "use_raytrace_refraction"): | ||
| mat.use_raytrace_refraction = preset_key == "glass" | ||
| elif hasattr(mat, "use_screen_refraction"): | ||
| mat.use_screen_refraction = preset_key == "glass" | ||
|
|
||
| nodes = mat.node_tree.nodes | ||
| links = mat.node_tree.links | ||
| nodes.clear() |
There was a problem hiding this comment.
replace_existing=False still wipes the existing material's node tree.
When a material with name already exists and replace_existing=False, the code intentionally reuses the data-block (mat = existing or ...), but immediately forces mat.use_nodes = True and nodes.clear() and rebuilds from scratch. Any user-authored nodes/links/properties on that material are silently destroyed, while reused_existing: True is reported back as if the material was preserved.
This is a footgun for an "agent-driven" tool that is likely to be invoked repeatedly with the same material name. Consider either:
- Treating
replace_existing=Falseas "do not touch an existing material" (return early with a clear message), or - Adding a distinct
update_existingflag and at minimum surfacing a warning in the response when a non-empty pre-existing node tree is overwritten.
🛡️ Suggested guard
existing = bpy.data.materials.get(name)
if existing and replace_existing:
bpy.data.materials.remove(existing)
existing = None
+ elif existing:
+ had_nodes = bool(existing.use_nodes and existing.node_tree and len(existing.node_tree.nodes) > 0)
+ if had_nodes:
+ warnings.append(
+ f"Existing material '{name}' was rebuilt in place; previous node graph was discarded. "
+ "Pass replace_existing=true for a fresh data-block, or use a different name to preserve it."
+ )🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@public/downloads/vipermesh-addon.py` around lines 1288 - 1315, The current
logic always forces mat.use_nodes = True and immediately calls nodes.clear()
even when an existing material is being reused (existing variable) and
replace_existing is False; change the behavior so that if existing is found and
replace_existing is False you do not mutate the existing material's node
tree—either return early (preserve nodes) or add a new boolean update_existing
flag and only run mat.use_nodes = True and nodes.clear() when update_existing is
True; additionally, when you overwrite a non-empty node tree you must surface a
warning/flag in the function response (e.g., include reused_existing=False and
an overwrite_warning) so callers know nodes were replaced; locate references to
replace_existing, existing, mat.use_nodes, nodes.clear and the function's return
path to implement this guard and the new flag/warning.
| if role == "displacement": | ||
| displacement = nodes.new(type="ShaderNodeDisplacement") | ||
| displacement.location = (-160, tex.location.y) | ||
| links.new(tex.outputs["Color"], displacement.inputs["Height"]) | ||
| if output.inputs.get("Displacement"): | ||
| links.new(displacement.outputs["Displacement"], output.inputs["Displacement"]) | ||
| continue |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Blender Cycles material displacement_method default value bump only
💡 Result:
In Blender Cycles, the material displacement setting “displacement_method” defaults to bump-only. Details 1) Default value The Cycles material displacement method enum is documented as an enum with default value ‘BUMP’ (Bump Only). [1][2] 2) What “Bump Only” means The Blender manual describes Bump Only as using a modified surface normal during shading instead of actually moving mesh vertices (i.e., it’s an approximation and does not change geometry). [3][4] 3) What you need for “displacement only / displacement and bump” To get true geometry displacement, you must use the corresponding displacement method setting and provide enough geometric subdivision (the manual notes true displacement requires a finely subdivided mesh). [3][5] So if you only change a displacement input but the displacement_method is still at its default, you will see bump/normal shading but not actual geometric variation. [1][3]
Citations:
- 1: https://www.blender.org/api/current/bpy.types.Material.html
- 2: https://www.blender.org/documentation/blender_python_api_current/bpy.types.CyclesMaterialSettings.html
- 3: https://docs.blender.org/manual/id/3.5/render/cycles/material_settings.html
- 4: https://www.blender.org/manual/ar/3.6/render/cycles/material_settings.html
- 5: https://docs.blender.org/manual/id/dev/render/materials/components/displacement.html
🏁 Script executed:
# Find the file and check context around lines 1395-1401
fd -t f "vipermesh-addon.py" --exec wc -l {} \;Repository: Ker102/ViperMesh
Length of output: 144
🏁 Script executed:
# List the file if found
fd -t f "vipermesh-addon.py"Repository: Ker102/ViperMesh
Length of output: 130
🏁 Script executed:
# Try broader search for the file
find . -type f -name "*vipermesh*" 2>/dev/nullRepository: Ker102/ViperMesh
Length of output: 299
🏁 Script executed:
# Read context around the displacement code
sed -n '1380,1420p' ./public/downloads/vipermesh-addon.pyRepository: Ker102/ViperMesh
Length of output: 2047
🏁 Script executed:
# Check for warnings variable - search for its initialization
rg "warnings\s*=" ./public/downloads/vipermesh-addon.py -A 2 -B 2 | head -50Repository: Ker102/ViperMesh
Length of output: 313
🏁 Script executed:
# Also check how other similar issues are handled in this function
sed -n '1300,1410p' ./public/downloads/vipermesh-addon.py | head -150Repository: Ker102/ViperMesh
Length of output: 5822
🌐 Web query:
Blender bpy.types.CyclesMaterialSettings displacement_method enum values
💡 Result:
The enum values for Blender’s Cycles material displacement_method (bpy.types.CyclesMaterialSettings) are: - 'BUMP' (Bump Only) - 'DISPLACEMENT' (Displacement Only) - 'BOTH' (Displacement and Bump) These are defined as an enum in ['BUMP', 'DISPLACEMENT', 'BOTH'] with default 'BUMP'. [1][2] Example (to set): mat = bpy.context.object.active_material mat.cycles.displacement_method = 'DISPLACEMENT'
Citations:
- 1: https://www.blender.org/api/current/bpy.types.Material.html
- 2: https://docs.blender.org/api/2.80/bpy.types.CyclesMaterialSettings.html
Displacement texture wiring has no visible effect without setting cycles.displacement_method.
Material Output's Displacement socket defaults to bump-only in Cycles, so wiring a ShaderNodeDisplacement to output.Displacement without setting mat.cycles.displacement_method won't actually displace geometry—the connection appears to work but only applies bump/normal shading. The material will also report the link as functional.
Set mat.cycles.displacement_method to 'BOTH' or 'DISPLACEMENT' when a displacement map is wired. Use getattr(mat, "cycles", None) to safely access since mat.cycles only exists when Cycles' RNA is registered. Also note that true displacement requires a subdivided mesh.
Suggested change
if role == "displacement":
displacement = nodes.new(type="ShaderNodeDisplacement")
displacement.location = (-160, tex.location.y)
links.new(tex.outputs["Color"], displacement.inputs["Height"])
if output.inputs.get("Displacement"):
links.new(displacement.outputs["Displacement"], output.inputs["Displacement"])
+ cycles_settings = getattr(mat, "cycles", None)
+ if cycles_settings is not None and hasattr(cycles_settings, "displacement_method"):
+ try:
+ cycles_settings.displacement_method = "BOTH"
+ except Exception as exc:
+ warnings.append(f"Could not set displacement_method: {exc!s}")
+ else:
+ warnings.append(
+ "Displacement node wired, but Cycles displacement_method is unavailable; "
+ "ensure the Cycles addon is enabled and the mesh is subdivided for true displacement."
+ )
continue🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@public/downloads/vipermesh-addon.py` around lines 1395 - 1401, When wiring a
ShaderNodeDisplacement in the displacement branch (role == "displacement")
ensure the material's Cycles displacement mode is set: obtain the material (mat)
used for this node setup, check cycles via getattr(mat, "cycles", None) and if
present set mat.cycles.displacement_method = "BOTH" or "DISPLACEMENT" before
linking displacement.outputs["Displacement"] to output.inputs["Displacement"];
this ensures the connection produces real displacement (remember true
displacement still requires a subdivided mesh).
Summary
create_material_presetandinspect_material_node_graphas LangChain toolsValidation
python -m py_compile desktop/assets/vipermesh-addon.py public/downloads/vipermesh-addon.pynpx tsx scripts/test/test-blender-capability-inventory.tsnpx tsx scripts/test/test-blender-rag-skill-migration.tsnpx tsc --noEmit --incremental falsenpm run lint(existing baseline-browser-mapping freshness warning only)Notes
surface_render_method/use_raytrace_refractionwith guarded fallbacks.Summary by CodeRabbit