Skip to content

Add export cleanup candidate inspection#94

Merged
Ker102 merged 1 commit into
mainfrom
feature/blender-export-cleanup-candidates
Jun 18, 2026
Merged

Add export cleanup candidate inspection#94
Ker102 merged 1 commit into
mainfrom
feature/blender-export-cleanup-candidates

Conversation

@Ker102

@Ker102 Ker102 commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add inspect_export_cleanup_candidates to report helper/reference/hidden/construction objects before export.
  • Protect explicit export targets and objects referenced by parents, constraints, Boolean object targets, and Boolean collection targets.
  • Support explicit actions: report, hide, move_to_collection, or delete, with report as the default.
  • Wire addon, public addon, LangChain tool, registry/filtering, system prompt, export guide, scene management guide, guide coverage, and capability inventory.

Validation

  • npx tsx scripts/test/test-blender-export-cleanup-candidates.ts
  • python -m py_compile desktop/assets/vipermesh-addon.py public/downloads/vipermesh-addon.py
  • npx tsx scripts/test/test-blender-hierarchy-inspection-tool.ts
  • npx tsx scripts/test/test-blender-uv-export-validation.ts
  • npx tsx scripts/test/test-tool-guide-trigger-coverage.ts
  • npx tsx scripts/test/test-blender-capability-inventory.ts
  • npx tsc --noEmit --incremental false
  • npm run lint (passes; existing baseline-browser-mapping age warning)
  • git diff --check (passes; Windows line-ending warnings only)

Summary by CodeRabbit

  • New Features

    • Added a new export cleanup inspection tool that identifies helper, reference, and construction objects in scenes before export.
    • Supports multiple actions: report candidates, hide them, move to a collection, or delete them.
    • Protects referenced objects and modifier dependencies from accidental removal.
  • Documentation

    • Updated export and scene management guides with cleanup inspection instructions and best practices.
    • Added tool to system prompts and orchestration registry for improved discoverability.

@github-actions github-actions Bot added documentation Improvements or additions to documentation backend desktop scripts labels Jun 18, 2026
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds the inspect_export_cleanup_candidates command end-to-end: a new Python method in both Blender addon copies detects and optionally mutates helper/reference/construction objects while protecting referenced targets; a TypeScript LangChain tool definition, registry entry, and filter rules wire it into the agent; system prompt and tool guide docs reference it; and a new test script validates the full stack.

Changes

inspect_export_cleanup_candidates Feature

Layer / File(s) Summary
Blender addon implementation (desktop + public)
desktop/assets/vipermesh-addon.py, public/downloads/vipermesh-addon.py
Both addon copies register inspect_export_cleanup_candidates in the command dispatch table and implement the method: builds a protected-object set from parents/constraints/modifiers, collects candidates via helper-pattern matching, and executes report/hide/move_to_collection/delete actions, returning structured candidate metadata and counts.
TypeScript tool definition, registry, and filter wiring
lib/orchestration/tool-registry.ts, lib/ai/agents.ts, lib/orchestration/tool-filter.ts
TOOL_REGISTRY gains the new tool entry with parameter schema; agents.ts adds the Zod-validated LangChain tool calling the MCP client and registers it in ALL_TOOLS; tool-filter.ts adds export_object to the pipeline category and extends the keyword regex to match cleanup/helper/construction object phrasing.
Agent system prompt and capability inventory
lib/orchestration/prompts/blender-agent-system.md, docs/blender-mcp-capability-inventory.md
System prompt adds inspect_export_cleanup_candidates to the tool reference list and "When to Use Which Tool" table. Capability inventory increments command count to 97 and appends the new command.
Tool guide updates
data/tool-guides/export-guide.md, data/tool-guides/scene-management-guide.md
Export guide frontmatter, pre-export checklist, and recommended tool sequence now include inspect_export_cleanup_candidates as an explicit step before cleanup. Scene-management guide frontmatter and Collections rules replace the prior set_visibility limitation note with organize_collection_hierarchy guidance and a inspect_export_cleanup_candidates prerequisite.
Test suite
scripts/test/test-blender-export-cleanup-candidates.ts, scripts/test/test-tool-guide-trigger-coverage.ts
New test script validates both addon sources expose the command with required implementation details, checks tool filter output and prompt content, and verifies agent/guide markdown references. Trigger-coverage test gains an assertion that the new tool appears in at least one guide's triggered_by metadata.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

documentation, backend, desktop, scripts

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding a new export cleanup candidate inspection feature.
Description check ✅ Passed The description provides a comprehensive summary of changes, validation steps, and objectives, though it deviates from the template structure by not using the standard sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/blender-export-cleanup-candidates

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed: dependency version conflict. Check your lock file or package.json.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 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/export-guide.md`:
- Line 35: The description for the `inspect_export_cleanup_candidates` function
on line 35 is incomplete and only mentions modifier/constraint targets. Update
the wording to include all types of protected targets that the feature actually
protects: parent-linked targets, Boolean-referenced targets, and
modifier/constraint targets. Expand the description to comprehensively list
these three categories so it accurately reflects the actual behavior and
prevents unsafe cleanup assumptions.

In `@desktop/assets/vipermesh-addon.py`:
- Around line 5242-5256: After parsing the names parameter with parse_names()
and creating requested_set, add validation to verify that all requested target
names actually exist in the scene or context being operated on. If any names in
requested_set cannot be found, return an error indicating which names are
unresolved or missing. This prevents typos from silently being accepted and
leaving intended targets unprotected when performing hide, move_to_collection,
or delete operations.
- Around line 5226-5240: The pattern_matches function currently allows
wildcard-only patterns like "*" to match everything because after stripping
asterisks an empty string matches all content, which is dangerous with delete
operations. In the pattern_matches function, add a validation check after
normalizing the token to reject patterns that consist only of wildcards by
checking if token.strip("*") results in an empty string, and skip those patterns
before executing any matching logic.
- Around line 5258-5274: The loop that builds referenced_object_names iterates
through bpy.data.objects which includes all objects across all scenes in the
.blend file, causing unintended mutations to objects outside the active export
context. Replace the iteration over bpy.data.objects with either
bpy.context.scene.objects to scope to the active scene, or filter the objects
based on the provided names parameter to ensure only the intended objects are
processed. This will prevent the subsequent hide, move_to_collection, and delete
operations from affecting objects in other scenes.

In `@lib/orchestration/tool-filter.ts`:
- Line 209: The keyword regex pattern in the pipeline category is missing
"reference object(s)" keywords, which causes requests about cleaning up
reference objects to miss the correct routing path even though
inspect_export_cleanup_candidates handles them. Add "reference object(s)" (or
"reference objects?") to the keywords regex pattern alongside the existing
cleanup, helper, and construction object phrases to ensure proper
categorization.

In `@public/downloads/vipermesh-addon.py`:
- Around line 5208-5215: The parse_names function currently returns an empty
list for invalid input types (the final return [] statement), which silently
treats malformed names as "no protected targets" and allows mutating actions to
proceed unsafely. Instead of returning an empty list as a fallback for unhandled
input types, raise an exception (such as ValueError or TypeError) with a clear
message indicating that the names value is invalid. This ensures that invalid or
unexpected data types are rejected outright rather than silently bypassing
protection mechanisms.
- Around line 5285-5286: The code at lines 5285-5286 classifies any object with
type CAMERA or LIGHT as a cleanup candidate based solely on its type, which
incorrectly flags legitimate scene cameras and lights. Fix this by removing
CAMERA and LIGHT from the type check in the condition (keep only EMPTY for
type-only matching), and instead add cameras and lights to the reasons list only
when they meet additional criteria such as matching a helper naming pattern or
being marked as hidden.
- Around line 5270-5273: The Boolean modifier collection iteration uses
collection.objects which only includes directly-contained objects and misses
nested cutter/reference objects in child collections. Replace collection.objects
with collection.all_objects in the for loop that iterates over collection_obj to
ensure the full collection hierarchy is evaluated when building the
referenced_object_names set.
- Around line 5292-5293: The hidden candidate detection in the condition
checking include_hidden with obj.hide_viewport and obj.hide_render only
evaluates global visibility properties and misses view-layer-specific hidden
state that can be accessed via hide_get(). Replace the current visibility check
by using visible_get() instead of directly checking hide_viewport and
hide_render properties, since visible_get() comprehensively evaluates all
visibility constraints including view-layer-specific hidden state, collection
visibility, and other settings. Update the condition where
reasons.append("hidden") is executed to leverage visible_get() for complete
hidden candidate detection.
🪄 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: 4fbe4a0b-eb1b-4a1a-928a-a508b6aca2cc

📥 Commits

Reviewing files that changed from the base of the PR and between 16eb8d9 and 3a079f0.

📒 Files selected for processing (11)
  • data/tool-guides/export-guide.md
  • data/tool-guides/scene-management-guide.md
  • desktop/assets/vipermesh-addon.py
  • docs/blender-mcp-capability-inventory.md
  • lib/ai/agents.ts
  • lib/orchestration/prompts/blender-agent-system.md
  • lib/orchestration/tool-filter.ts
  • lib/orchestration/tool-registry.ts
  • public/downloads/vipermesh-addon.py
  • scripts/test/test-blender-export-cleanup-candidates.ts
  • scripts/test/test-tool-guide-trigger-coverage.ts

2. **Apply all modifiers** (if you want clean geometry) — SubSurf, mirrors, booleans become permanent
3. **Set correct origin** — Usually center of geometry or bottom center for grounding
4. **Clean up** — Remove unused objects, helper empties, construction geometry
4. **Inspect cleanup candidates** (`inspect_export_cleanup_candidates`) — Find helper empties, reference objects, hidden construction geometry, and protected modifier/constraint targets before cleanup

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Broaden the protected-target wording to match actual behavior.

Line 35 only mentions modifier/constraint targets, but the feature also protects parent-linked and Boolean-referenced targets. Tightening this wording avoids unsafe cleanup assumptions.

Suggested doc fix
-4. **Inspect cleanup candidates** (`inspect_export_cleanup_candidates`) — Find helper empties, reference objects, hidden construction geometry, and protected modifier/constraint targets before cleanup
+4. **Inspect cleanup candidates** (`inspect_export_cleanup_candidates`) — Find helper empties, reference objects, hidden construction geometry, and protected dependency targets (parents, constraints, Boolean references, and explicit export targets) before cleanup
📝 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.

Suggested change
4. **Inspect cleanup candidates** (`inspect_export_cleanup_candidates`) — Find helper empties, reference objects, hidden construction geometry, and protected modifier/constraint targets before cleanup
4. **Inspect cleanup candidates** (`inspect_export_cleanup_candidates`) — Find helper empties, reference objects, hidden construction geometry, and protected dependency targets (parents, constraints, Boolean references, and explicit export targets) before cleanup
🤖 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/export-guide.md` at line 35, The description for the
`inspect_export_cleanup_candidates` function on line 35 is incomplete and only
mentions modifier/constraint targets. Update the wording to include all types of
protected targets that the feature actually protects: parent-linked targets,
Boolean-referenced targets, and modifier/constraint targets. Expand the
description to comprehensively list these three categories so it accurately
reflects the actual behavior and prevents unsafe cleanup assumptions.

Comment on lines +5226 to +5240
def pattern_matches(value, patterns):
normalized = str(value or "").lower()
for pattern in patterns:
token = str(pattern).strip().lower()
if not token:
continue
if token.startswith("*") and token.endswith("*") and token.strip("*") in normalized:
return True
if token.endswith("*") and normalized.startswith(token[:-1]):
return True
if token.startswith("*") and normalized.endswith(token[1:]):
return True
if normalized == token or token in normalized:
return True
return False

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject wildcard-only helper patterns before matching.

helper_patterns=["*"] currently makes every unprotected object a candidate because an empty string matches every name; with action="delete", that can remove non-helper scene content.

Proposed fix
             def pattern_matches(value, patterns):
                 normalized = str(value or "").lower()
                 for pattern in patterns:
                     token = str(pattern).strip().lower()
                     if not token:
                         continue
-                    if token.startswith("*") and token.endswith("*") and token.strip("*") in normalized:
+                    wildcard_core = token.strip("*")
+                    if "*" in token and not wildcard_core:
+                        continue
+                    if token.startswith("*") and token.endswith("*") and wildcard_core in normalized:
                         return True
                     if token.endswith("*") and normalized.startswith(token[:-1]):
                         return True
🧰 Tools
🪛 Ruff (0.15.17)

[warning] 5226-5226: Missing return type annotation for private function pattern_matches

Add return type annotation: bool

(ANN202)

🤖 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 5226 - 5240, The
pattern_matches function currently allows wildcard-only patterns like "*" to
match everything because after stripping asterisks an empty string matches all
content, which is dangerous with delete operations. In the pattern_matches
function, add a validation check after normalizing the token to reject patterns
that consist only of wildcards by checking if token.strip("*") results in an
empty string, and skip those patterns before executing any matching logic.

Comment on lines +5242 to +5256
requested_names = parse_names(names)
requested_set = set(requested_names)
if helper_patterns is None:
helper_patterns = [
"helper", "helpers", "reference", "ref_", "guide", "construction",
"cutter", "boolean", "temp", "tmp", "proxy", "debug", "draft"
]
elif isinstance(helper_patterns, str):
helper_patterns = [part.strip() for part in helper_patterns.split(",") if part.strip()]
elif not isinstance(helper_patterns, (list, tuple)):
return {"error": "helper_patterns must be an array, a comma-separated string, or omitted"}

action = str(action or "report").lower().replace("-", "_")
if action not in {"report", "hide", "move_to_collection", "delete"}:
return {"error": "action must be report, hide, move_to_collection, or delete"}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail mutating cleanup when protected target names are missing.

names is the explicit export-target protection list, but unresolved entries are silently accepted. A typo can leave the intended target unprotected for hide, move_to_collection, or delete.

Proposed fix
             requested_names = parse_names(names)
             requested_set = set(requested_names)
@@
             action = str(action or "report").lower().replace("-", "_")
             if action not in {"report", "hide", "move_to_collection", "delete"}:
                 return {"error": "action must be report, hide, move_to_collection, or delete"}
+
+            missing_requested_names = [
+                name for name in requested_names
+                if bpy.data.objects.get(name) is None
+            ]
+            if missing_requested_names and action != "report":
+                return {
+                    "error": "Cannot mutate cleanup candidates until all protected target names resolve.",
+                    "missing_objects": missing_requested_names,
+                }
@@
                 "referenced_object_names": sorted(referenced_object_names),
                 "skipped_protected_count": len(set(skipped_protected)),
+                "missing_objects": missing_requested_names,
                 "helper_patterns": list(helper_patterns),

Also applies to: 5332-5344

🤖 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 5242 - 5256, After parsing
the names parameter with parse_names() and creating requested_set, add
validation to verify that all requested target names actually exist in the scene
or context being operated on. If any names in requested_set cannot be found,
return an error indicating which names are unresolved or missing. This prevents
typos from silently being accepted and leaving intended targets unprotected when
performing hide, move_to_collection, or delete operations.

Comment on lines +5258 to +5274
referenced_object_names = set()
for obj in bpy.data.objects:
if obj.parent:
referenced_object_names.add(obj.parent.name)
for constraint in getattr(obj, "constraints", []):
target = getattr(constraint, "target", None)
if target:
referenced_object_names.add(target.name)
for modifier in getattr(obj, "modifiers", []):
target = getattr(modifier, "object", None)
if target:
referenced_object_names.add(target.name)
collection = getattr(modifier, "collection", None)
if collection:
for collection_obj in collection.objects:
referenced_object_names.add(collection_obj.name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n desktop/assets/vipermesh-addon.py | sed -n '5250,5340p'

Repository: Ker102/ViperMesh

Length of output: 5334


🏁 Script executed:

# Check the function context and see where this code is called from
cat -n desktop/assets/vipermesh-addon.py | sed -n '5200,5250p'

Repository: Ker102/ViperMesh

Length of output: 2713


🏁 Script executed:

# Check what happens after line 5330 to see the mutations
cat -n desktop/assets/vipermesh-addon.py | sed -n '5310,5350p'

Repository: Ker102/ViperMesh

Length of output: 2451


🏁 Script executed:

# Find the function definition and its name
rg "def.*helper.*reference" desktop/assets/vipermesh-addon.py -A 2

Repository: Ker102/ViperMesh

Length of output: 42


🏁 Script executed:

# Search for calls to this function
rg "helper.*reference|export.*cleanup" desktop/assets/vipermesh-addon.py

Repository: Ker102/ViperMesh

Length of output: 458


🏁 Script executed:

# Check if this function is part of a class and understand context better
sed -n '5150,5210p' desktop/assets/vipermesh-addon.py

Repository: Ker102/ViperMesh

Length of output: 2348


🏁 Script executed:

# Find where inspect_export_cleanup_candidates is called
rg "inspect_export_cleanup_candidates" desktop/assets/vipermesh-addon.py

Repository: Ker102/ViperMesh

Length of output: 192


🏁 Script executed:

# Check if there are any export-related functions that call this
rg "export_asset_package|validate_export_readiness" desktop/assets/vipermesh-addon.py -B 5 -A 5

Repository: Ker102/ViperMesh

Length of output: 4168


🏁 Script executed:

# Search for documentation or comments about scene scope
rg -i "scene|scope|blend" desktop/assets/vipermesh-addon.py | grep -i "cleanup\|helper\|reference" | head -20

Repository: Ker102/ViperMesh

Length of output: 101


🏁 Script executed:

# Check if the 'names' parameter is used in the function at all
sed -n '5198,5350p' desktop/assets/vipermesh-addon.py | grep -n "names\|requested_set"

Repository: Ker102/ViperMesh

Length of output: 1099


🏁 Script executed:

# Check the validate_export_readiness function to see how it uses names parameter
rg "def validate_export_readiness" -A 50 desktop/assets/vipermesh-addon.py | head -70

Repository: Ker102/ViperMesh

Length of output: 2541


🏁 Script executed:

# Check if there are any tests or examples showing usage
rg "inspect_export_cleanup_candidates" -B 5 -A 10 desktop/assets/vipermesh-addon.py | grep -A 10 "example\|test\|usage" -i

Repository: Ker102/ViperMesh

Length of output: 42


🏁 Script executed:

# Look for how export_asset_package uses this function in its workflow
sed -n '5500,5650p' desktop/assets/vipermesh-addon.py | head -100

Repository: Ker102/ViperMesh

Length of output: 3842


🏁 Script executed:

# Check if there are any scene-specific operations in the codebase
rg "bpy.context.scene" desktop/assets/vipermesh-addon.py | wc -l

Repository: Ker102/ViperMesh

Length of output: 63


🏁 Script executed:

# Search for any multi-scene handling or scene switching
rg "bpy.context.window.scene\|bpy.context.scene =" desktop/assets/vipermesh-addon.py

Repository: Ker102/ViperMesh

Length of output: 42


🏁 Script executed:

# Check the README or documentation for context about this function
find desktop -name "README*" -o -name "*.md" | head -5

Repository: Ker102/ViperMesh

Length of output: 78


🏁 Script executed:

# Search for any comments about scene scope in the file
rg -B 2 -A 2 "scene\|Scene" desktop/assets/vipermesh-addon.py | grep -A 3 -B 3 "cleanup\|helper\|export"

Repository: Ker102/ViperMesh

Length of output: 42


🏁 Script executed:

# Check what export_asset_package does with object selection
rg "def export_asset_package" -A 100 desktop/assets/vipermesh-addon.py | head -120

Repository: Ker102/ViperMesh

Length of output: 4854


Scope cleanup candidates to the active export scene or use the provided names parameter.

The scan uses bpy.data.objects, then mutates those candidates with hide, move_to_collection, and delete, so helpers from other scenes in the same .blend can be affected even when the function is called with specific object names. The function accepts a names parameter but does not use it to constrain the scan. Use either bpy.context.scene.objects or filter candidates based on the names parameter to limit scope to the active export context.

Proposed fix (active scene scope)
-            referenced_object_names = set()
-            for obj in bpy.data.objects:
+            scene_objects = list(bpy.context.scene.objects)
+
+            referenced_object_names = set()
+            for obj in scene_objects:
                 if obj.parent:
                     referenced_object_names.add(obj.parent.name)
@@
-            for obj in bpy.data.objects:
+            for obj in scene_objects:
                 reasons = []
                 if obj.name in protected_names:
                     skipped_protected.append(obj.name)
🤖 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 5258 - 5274, The loop that
builds referenced_object_names iterates through bpy.data.objects which includes
all objects across all scenes in the .blend file, causing unintended mutations
to objects outside the active export context. Replace the iteration over
bpy.data.objects with either bpy.context.scene.objects to scope to the active
scene, or filter the objects based on the provided names parameter to ensure
only the intended objects are processed. This will prevent the subsequent hide,
move_to_collection, and delete operations from affecting objects in other
scenes.

{ keywords: GEOMETRY_KEYWORDS, categories: ["geometry"] },
{ keywords: /(color|material|texture|paint|shade|metallic)/i, categories: ["materials"] },
{ keywords: /(uv|unwrap|pack island|export|glb|gltf|fbx|obj|stl|file format|package|texture dependenc|missing image|empty filepath|risky texture)/i, categories: ["pipeline"] },
{ keywords: /(uv|unwrap|pack island|export|glb|gltf|fbx|obj|stl|file format|package|texture dependenc|missing image|empty filepath|risky texture|cleanup candidates?|helper objects?|construction objects?)/i, categories: ["pipeline"] },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Include “reference object(s)” in pipeline keyword matching.

inspect_export_cleanup_candidates handles reference objects too, but the new keyword regex only adds cleanup/helper/construction phrases. Requests like “clean up reference objects before export” may miss this routing path.

Suggested patch
-  { keywords: /(uv|unwrap|pack island|export|glb|gltf|fbx|obj|stl|file format|package|texture dependenc|missing image|empty filepath|risky texture|cleanup candidates?|helper objects?|construction objects?)/i, categories: ["pipeline"] },
+  { keywords: /(uv|unwrap|pack island|export|glb|gltf|fbx|obj|stl|file format|package|texture dependenc|missing image|empty filepath|risky texture|cleanup candidates?|helper objects?|reference objects?|construction objects?)/i, categories: ["pipeline"] },
🤖 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 209, The keyword regex pattern in
the pipeline category is missing "reference object(s)" keywords, which causes
requests about cleaning up reference objects to miss the correct routing path
even though inspect_export_cleanup_candidates handles them. Add "reference
object(s)" (or "reference objects?") to the keywords regex pattern alongside the
existing cleanup, helper, and construction object phrases to ensure proper
categorization.

Comment on lines +5208 to +5215
def parse_names(value):
if value is None:
return []
if isinstance(value, str):
return [part.strip() for part in value.split(",") if part.strip()]
if isinstance(value, (list, tuple)):
return [str(name).strip() for name in value if str(name).strip()]
return []

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject invalid names instead of treating them as “no protected targets”.

A malformed names value currently becomes [], so mutating actions can proceed without protecting the intended export targets.

🛡️ Proposed fix
             def parse_names(value):
                 if value is None:
                     return []
                 if isinstance(value, str):
                     return [part.strip() for part in value.split(",") if part.strip()]
                 if isinstance(value, (list, tuple)):
                     return [str(name).strip() for name in value if str(name).strip()]
-                return []
+                raise ValueError("names must be an array of object names, a comma-separated string, or omitted")
📝 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.

Suggested change
def parse_names(value):
if value is None:
return []
if isinstance(value, str):
return [part.strip() for part in value.split(",") if part.strip()]
if isinstance(value, (list, tuple)):
return [str(name).strip() for name in value if str(name).strip()]
return []
def parse_names(value):
if value is None:
return []
if isinstance(value, str):
return [part.strip() for part in value.split(",") if part.strip()]
if isinstance(value, (list, tuple)):
return [str(name).strip() for name in value if str(name).strip()]
raise ValueError("names must be an array of object names, a comma-separated string, or omitted")
🧰 Tools
🪛 Ruff (0.15.17)

[warning] 5208-5208: Missing return type annotation for private function parse_names

(ANN202)

🤖 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 5208 - 5215, The
parse_names function currently returns an empty list for invalid input types
(the final return [] statement), which silently treats malformed names as "no
protected targets" and allows mutating actions to proceed unsafely. Instead of
returning an empty list as a fallback for unhandled input types, raise an
exception (such as ValueError or TypeError) with a clear message indicating that
the names value is invalid. This ensures that invalid or unexpected data types
are rejected outright rather than silently bypassing protection mechanisms.

Comment on lines +5270 to +5273
collection = getattr(modifier, "collection", None)
if collection:
for collection_obj in collection.objects:
referenced_object_names.add(collection_obj.name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Blender Python API Collection.objects vs Collection.all_objects Boolean modifier collection operand nested collections

💡 Result:

In the Blender Python API, there is a critical distinction between a Collection's objects and its all_objects properties [1][2]: 1. Collection.objects: Returns only the objects directly contained within that specific collection [1][3]. It does not include objects located in nested (child) collections [3]. 2. Collection.all_objects: Returns all objects located within that collection, including those residing in its child and descendant collections [1][3]. Important Iteration Gotcha When iterating over all_objects, you should never modify the collection structure or object properties that trigger a cache rebuild (e.g., changing hide_viewport, hide_select, or deleting objects) while looping directly over the iterator [4]. Doing so will invalidate the memory accessed by the iterator and cause crashes [4]. Always convert the collection to a list or tuple first [4][5]: # Correct approach to avoid crashes for obj in collection.all_objects[:]: obj.hide_viewport = True Boolean Modifier Collection Operand When using the Boolean modifier in 'COLLECTION' mode, you specify a collection as the operand [6][7]. The Boolean modifier operates on the target collection, and it effectively uses the contents of that collection to perform the operation [8]. Using a collection operand allows you to group multiple mesh objects to act as a single boolean source [6][8]. If you need to perform a boolean operation specifically on a subset of objects including nested ones, ensure the collection assigned to the modifier's collection property contains all necessary sub-collections, as the modifier will evaluate the entire tree of the assigned collection [8]. Top Results: [1] https://docs.blender.org/api/master/bpy.types.Collection.html [4] https://docs.blender.org/api/latest/info_gotchas_crashes.html [6] https://docs.blender.org/api/latest/bpy.types.BooleanModifier.html [8] https://docs.blender.org/manual/en/latest/modeling/modifiers/generate/booleans.html [3] https://b3d.interplanety.org/en/selecting-objects-in-a-collection/ [5] https://blender.stackexchange.com/questions/279443/python-script-not-iterating-through-all-objects-in-collections

Citations:


Protect nested Boolean collection operands with recursive collection membership.

The Boolean modifier evaluates the entire tree of its assigned collection. Currently, collection.objects only includes directly-contained objects; nested cutter/reference objects in child collections can be missed during classification, and thus incorrectly mutated or deleted. Use all_objects to include the full collection hierarchy.

Proposed fix
                     collection = getattr(modifier, "collection", None)
                     if collection:
-                        for collection_obj in collection.objects:
+                        for collection_obj in getattr(collection, "all_objects", collection.objects):
                             referenced_object_names.add(collection_obj.name)
📝 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.

Suggested change
collection = getattr(modifier, "collection", None)
if collection:
for collection_obj in collection.objects:
referenced_object_names.add(collection_obj.name)
collection = getattr(modifier, "collection", None)
if collection:
for collection_obj in getattr(collection, "all_objects", collection.objects):
referenced_object_names.add(collection_obj.name)
🤖 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 5270 - 5273, The Boolean
modifier collection iteration uses collection.objects which only includes
directly-contained objects and misses nested cutter/reference objects in child
collections. Replace collection.objects with collection.all_objects in the for
loop that iterates over collection_obj to ensure the full collection hierarchy
is evaluated when building the referenced_object_names set.

Source: MCP tools

Comment on lines +5285 to +5286
if obj.type in {"EMPTY", "CAMERA", "LIGHT"}:
reasons.append(f"type:{obj.type}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not classify every camera and light as cleanup by type alone.

This makes normal scene cameras/lights candidates for hide/move/delete even when they are not helper/reference/construction objects. Restrict type-only matching to EMPTY, or require cameras/lights to match a helper pattern or be hidden.

🛡️ Proposed fix
-                if obj.type in {"EMPTY", "CAMERA", "LIGHT"}:
+                if obj.type == "EMPTY":
                     reasons.append(f"type:{obj.type}")

If cameras/lights should still be reported, add them only when another signal applies:

                 if pattern_matches(obj.name, helper_patterns):
                     reasons.append("name_pattern")
                 collection_names = [collection.name for collection in obj.users_collection]
                 if any(pattern_matches(collection_name, helper_patterns) for collection_name in collection_names):
                     reasons.append("collection_pattern")
                 if include_hidden and (obj.hide_viewport or obj.hide_render):
                     reasons.append("hidden")
+                if obj.type in {"CAMERA", "LIGHT"} and reasons:
+                    reasons.append(f"type:{obj.type}")
📝 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.

Suggested change
if obj.type in {"EMPTY", "CAMERA", "LIGHT"}:
reasons.append(f"type:{obj.type}")
if obj.type == "EMPTY":
reasons.append(f"type:{obj.type}")
🤖 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 5285 - 5286, The code at
lines 5285-5286 classifies any object with type CAMERA or LIGHT as a cleanup
candidate based solely on its type, which incorrectly flags legitimate scene
cameras and lights. Fix this by removing CAMERA and LIGHT from the type check in
the condition (keep only EMPTY for type-only matching), and instead add cameras
and lights to the reasons list only when they meet additional criteria such as
matching a helper naming pattern or being marked as hidden.

Comment on lines +5292 to +5293
if include_hidden and (obj.hide_viewport or obj.hide_render):
reasons.append("hidden")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Blender Python Object.hide_get Object.hide_set hide_viewport hide_render visible_get API

💡 Result:

In the Blender Python API, managing object visibility involves distinct properties and methods on the bpy.types.Object class [1][2]. Key Properties and Methods: hide_viewport: A boolean property that controls whether the object is hidden in the 3D viewport [1][3]. hide_render: A boolean property that controls whether the object is hidden during rendering [1][2]. hide_get(view_layer=None): A method that returns the hidden state of an object for a specific view layer [1][4]. hide_set(state, *, view_layer=None): A method that sets the hidden state of an object for a specific view layer [1][5]. visible_get(view_layer=None, viewport=None): A method that returns whether an object is effectively visible in the 3D viewport, taking into account all relevant visibility settings such as viewport hiding, collection visibility, and local view [2][4]. Important Distinctions: The hide_viewport and hide_render properties are typically used for direct state toggling [1]. Conversely, hide_get and hide_set provide a more robust interface that supports view layer-specific control [1][2]. The visible_get method is the recommended way to check if an object is truly visible to the user in the viewport, as it aggregates the complex criteria (like object constraints, parent visibility, and viewport isolation) that determine final visibility [2][6]. Note: These methods and properties differ from operators like bpy.ops.object.hide_view_set, which are intended for UI-driven interactions and perform actions equivalent to user keypresses [7]. For scripted object manipulation, modifying the properties or using the _set / _get methods directly is the standard approach [1].

Citations:


Include view-layer-specific hidden state in hidden candidate detection.

The current check only examines hide_viewport and hide_render, which are global visibility settings. Objects can also be hidden per-view-layer via hide_get(), which isn't currently checked. This causes incomplete detection when objects are hidden only in specific view layers. Since the codebase already uses visible_get() for comprehensive visibility checking, consider using it here as well—it accounts for view-layer-specific hidden state, collection visibility, and other constraints.

🤖 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 5292 - 5293, The hidden
candidate detection in the condition checking include_hidden with
obj.hide_viewport and obj.hide_render only evaluates global visibility
properties and misses view-layer-specific hidden state that can be accessed via
hide_get(). Replace the current visibility check by using visible_get() instead
of directly checking hide_viewport and hide_render properties, since
visible_get() comprehensively evaluates all visibility constraints including
view-layer-specific hidden state, collection visibility, and other settings.
Update the condition where reasons.append("hidden") is executed to leverage
visible_get() for complete hidden candidate detection.

@Ker102 Ker102 merged commit 865837d into main Jun 18, 2026
6 checks passed
@Ker102 Ker102 deleted the feature/blender-export-cleanup-candidates branch June 18, 2026 19:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend desktop documentation Improvements or additions to documentation scripts

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant