Skip to content

Version 2026.7.0 - #393

Merged
Mustard2 merged 33 commits into
mainfrom
2026.7.0
Aug 8, 2026
Merged

Version 2026.7.0#393
Mustard2 merged 33 commits into
mainfrom
2026.7.0

Conversation

@Mustard2

@Mustard2 Mustard2 commented Jul 31, 2026

Copy link
Copy Markdown
Owner

This version adds a new tool to generate high-res physics cages.

Moreover it adds support for Blender 5.2: in particular cloth dynamics in the Physics panel and hair dynamics in the Hair panel.

Changelog

  • Feature: Tool to generate high-res cages with tris, which can be used in case detailed collisions or deformations are needed (supports multi-cage, multi-boundaries). See a preview of the tool in this Twitter video (NSFW!).
  • Feature: Creator tools have now different default physics presets (more to come).
  • Feature: Some big operators now have the possibility to setup some presets.
  • Feature Outfit masks #397: Masks can now be set on Outfits/Hair and their extras similarly to how they can be now set for the Body. For instance an Hair might need to mask part of an outfit, and now it is possible adding a mask modifier to the outfit and calling it with the exact name of the Hair piece.
  • Feature: Geometry Nodes in Physics and Hair panels, with the possibility to add custom Geometry Nodes.
  • Feature: Custom Properties filters for Configuration mode lists (filter by Outfit/Hair, Outfit Piece, Body Section).
  • Fix: Missing Geometry Nodes in Body panel in Blender 5.2.
  • Fix: Some options in Eevee fast normals are not restored correctly.
  • Fix: Various issues with physics and Geometry Nodes.
  • Fix Hair switcher doesn't hide the selected hair armature when hiding unused hair option #396: Hair switcher objects armature not disabled.
  • Fix: Hair can be summoned on top of the hair switched hair, selecting a new one from the Hair list.
  • Fix Volume preserve check box doesn't affect objects parented to armature #395: Volume Preserve does not affect outfits/objects parented to armature.
  • Fix Properties smart check resets INT Min/Max values to default every time #398: Properties Smart Check resets settings to default. A new option is added to preserve current settings if needed.
  • Fix: Tons of small bugs fixed.
  • Deprecation: The old way to create hair cages has been revised. No need to summon a new operator to add physics, this is done directly in one step.

@Mustard2 Mustard2 self-assigned this Jul 31, 2026
@Mustard2 Mustard2 added the enhancement New feature or request label Jul 31, 2026
@Mustard2 Mustard2 added the bug Something isn't working label Aug 1, 2026
@Mustard2

Mustard2 commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

@claude Review

@Mustard2 Mustard2 closed this Aug 1, 2026
@Mustard2 Mustard2 reopened this Aug 1, 2026
@claude

claude Bot commented Aug 1, 2026

Copy link
Copy Markdown

Claude finished @Mustard2's task in 3m 13s —— View job


Review

  • Gather context (PR diff, changed files, CLAUDE.md)
  • Understand the request
  • Review new files (geometry_nodes.py, physics/definitions_nodes.py, ops_jiggle_accurate.py, ops_physics_assign.py, physics_presets.py)
  • Review modified files (menu_hair.py, menu_physics.py, custom_properties/ui_list.py, etc.)
  • Check registration wiring (register/unregister) for new modules
  • Post final review

Overall this is a well-organized PR — the new tools_creators/physics_presets.py module is a good consolidation (the old duplicated cloth-settings blocks in ops_jiggle.py are gone), the misc/geometry_nodes.py helper correctly branches on the Blender 5.2 modifier-input API change (modifier.properties.inputs vs. flat custom properties), and registration order in physics/__init__.py / tools_creators/__init__.py looks correct (new definitions_nodes, ops_jiggle_accurate, ops_physics_assign modules are registered/unregistered symmetrically). CI (lint + Blender 4.2/4.5/5.1 build matrix) is passing.

One real inconsistency worth fixing, plus a couple of minor notes:

1. physics/ui_list.py:67-76 — "missing physics" warning doesn't recognize custom Geometry Nodes physics

if item.type == "CAGE":
    found = any(
        mod.type in ["CLOTH", "SOFT_BODY"]
        or (
            mod.type == "NODES"
            and mod.node_group
            and mod.node_group.name.startswith(CLOTH_DYNAMICS_NODE_GROUP)
        )
        for mod in item.object.modifiers
    )

This only counts a NODES modifier as "physics found" if its node group is the built-in Cloth Dynamics (Experimental) group. Everywhere else that detects a physics setup — menu_physics.py's find_geometry_nodes_physics_modifier (any NODES modifier with a node_group), physics_presets.find_physics_modifier/remove_physics, and outfits/definitions.py's physics-toggle check — accepts any Geometry Nodes modifier with a node group, which matches this PR's stated feature of supporting custom Geometry Nodes. As written, a Physics Item using a custom (non-default) Geometry Nodes physics setup will show a spurious "no physics modifier" warning icon in the Physics list even though the Cloth Dynamics settings panel correctly displays it as a generic Geometry Nodes physics setup.

Fix this →

2. Minor — broadened "has physics" detection to any Geometry Nodes modifier
outfits/definitions.py:37 and physics/ops_outfits_setup.py (both the Surface Deform setup and the intersecting-objects check) now treat any NODES modifier with a node_group as "physics", not just recognized physics node groups. This is presumably intentional given the new "custom Geometry Nodes" support, but it does mean an outfit piece with an unrelated (non-physics) Geometry Nodes modifier — e.g. a decorative/array setup — would now be skipped by the Surface Deform auto-setup and would have its visibility toggled by the physics on/off button. Worth confirming this broad match is intended.

3. Nit — custom_properties/ui_list.py line-ending change
The diff shows the whole file as rewritten (410+/210-) because origin/main's copy has CRLF line endings and the new one uses LF; the actual logic delta (new filter_items, section search field, outfit/hair filters, draw_filter) is much smaller. Not a problem, just noting it so it's not mistaken for a bigger rewrite than it is — might be worth normalizing line endings repo-wide separately to avoid this on future PRs.

Nothing else stood out — the removed operators (mustardui.tools_creators_create_jiggle_preset, and ops_hair_cage_add_cloth.py's mustardui.tools_creators_add_cloth_to_hair) have no leftover references, and the layout.enabledlayout.active change across the Physics panels is applied consistently.
· Branch: 2026.7.0

@Mustard2

Mustard2 commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

@claude Review the latest changes

@claude

claude Bot commented Aug 1, 2026

Copy link
Copy Markdown

Claude finished @Mustard2's task in 3m 6s —— View job


Review of latest changes

  • Identify commits since my last review (db71138, c1092d7, 9f55b09, 52bb976)
  • Verify db71138 addresses the previous review's CAGE warning inconsistency
  • Review c1092d7 — physics item filter for the configure-side UI list
  • Review 9f55b09 + 52bb976 — multi-cage generation and multi-boundary pinning in ops_jiggle_accurate.py
  • Post final review

db71138 (fix: warning for missing physics mods) correctly resolves the finding from my previous review: physics/ui_list.py's CAGE warning check now treats any NODES modifier with a node_group as physics found, consistent with menu_physics.py, physics_presets.find_physics_modifier, and remove_physics. Good fix, and it simplified the file by dropping the now-unused CLOTH_DYNAMICS_NODE_GROUP import.

c1092d7 (feat: filter for physics items) adds a name-search + outfit-item-visibility filter to MUSTARDUI_UL_PhysicsItems_UIList_Menu in physics/ui_list_menu.py. Logic checks out (filter_name, use_filter_invert, use_filter_sort_alpha/reverse, and the new filter_show_outfit_items toggle all combine correctly via the standard flt_flags/flt_neworder UIList contract), and item.outfit_enable is a real property on the physics item (physics/settings_item.py:94). Minor nit: the name filter is implemented with a manual search not in name.lower() loop instead of bpy.types.UI_UL_list.filter_items_by_name(...), which is what custom_properties/ui_list.py's filter_items_by_type (added earlier in this same PR) uses and which additionally supports Blender's standard multi-token/wildcard search syntax. Not a bug, just an inconsistency with the sibling implementation shipped in this PR — worth aligning if this filter gets copy/pasted elsewhere.

9f55b09 + 52bb976 (multi-cage generation, multi-boundary pinning in tools_creators/ops_jiggle_accurate.py) is a substantial, well-documented rework (the docstrings clearly explain the why for each step, e.g. why border loops are simplified loop-by-loop and not globally, why the border is measured on the cage and not the source mesh, why OUTSIDE_SURFACE and not ABOVE_SURFACE is used for the shrinkwrap). I traced the full flow end-to-end:

  • Island detection (selection_islands) correctly falls back to a single merged island when merge_cages is set, otherwise does a proper BFS over neighbors restricted to the selection.
  • Per-island cage generation, vertex-group transfer, Pin-group weighting, and the new structural-stiffness group are all correctly scoped per loop iteration — no cross-iteration state leakage found (e.g. pin_group, border_indices, cage_border_loops are all freshly created per island).
  • pinned_border's new multiple_pin_boundaries branch (52bb976) returns every recorded loop instead of picking the single widest one attached to the rest of the model — correctly gated, and the existing "pick the widest attached border" heuristic is preserved unchanged for the default case.
  • The new loop-aware simplify_border (via border_loops) budgets collapses per-loop (room = len(loop) - MINIMUM_BORDER_EDGES) instead of globally, which correctly protects narrow borders (e.g. an ankle/wrist end) from being collapsed shut — this was called out in the previous single-loop version as a latent risk and is now fixed.
  • Modifier setup (Surface Deform bind-with-retry, Corrective Smooth, Armature modifier copy, Displace/Inflate driver, physics preset application) is applied per-cage with the active object correctly re-targeted before each bpy.ops.object.* call that depends on it.

One minor edge case, not introduced by these commits but touched by them: in execute(), the structural-stiffness vertex group is only built when self.physics_engine == "CLOTH" (line ~819), but the physics actually applied is decided later via physics_presets.selected_preset(self), which itself falls back from NODES to CLOTH when Cloth Dynamics isn't available (e.g. an operator redo-panel preset saved on Blender 5.2 and reused on 5.1). In that fallback path the Cloth modifier ends up applied without a structural group even though structural_enable was requested, since the group was never built. Low impact (requires an engine-unavailable fallback combined with structural stiffness enabled) and pre-existing in spirit, just flagging for awareness rather than requesting a change.

No registration/wiring issues, no leftover dead code, and nothing else stood out. CI is green for the commits up to 19:55 UTC (c1092d7 and earlier); I don't see a completed CI run yet covering 9f55b09/52bb976 (pushed at 21:34/21:55), so worth confirming the workflow triggered for those before merging.
· Branch: 2026.7.0

@Mustard2 Mustard2 linked an issue Aug 5, 2026 that may be closed by this pull request
…moned on top of the hair switched changing hair_list
…moned on top of the hair switched changing hair_list
@Mustard2 Mustard2 linked an issue Aug 5, 2026 that may be closed by this pull request
@Mustard2

Mustard2 commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

@claude Review, especially if the new masks are correctly implemented and all the possible cases (also with locked outfits) are considered.

@claude

claude Bot commented Aug 5, 2026

Copy link
Copy Markdown

Claude finished @Mustard2's task in 6m 9s —— View job


Review

  • Gather context (PR diff, latest mask commits cffdd27 + d166c80)
  • Understand the mask system (host objects, driving pieces, locked outfits)
  • Review d166c80 (mask system works also on outfits/extras/hair/hair extras)
  • Review cffdd27 (function renamed in preparation of changes)
  • Check all call sites for correctness (outfits/ops_visibility.py, outfits/ops_visibility_full.py, settings/rig.py, hair/helper_functions.py, hair/ops_hair_visibility.py, configuration/ops_smartcheck.py)
  • Post final review

I traced the whole mask refactor: outfits/helper_functions.py's new get_mask_objects (body + Outfits + Extras + Hair + Hair Extras meshes), update_outfit_obj_masks/update_outfit_masks (generic {piece name: visible} driver dict instead of the old body-only, single-piece-name signature), and update_global_masks. All call sites were migrated consistently — no leftover references to the old update_outfit_body_masks/update_global_body_mask signature.

Locked outfits are handled correctly in the two places that matter:

  • outfits/ops_visibility_full.py:161mask_visibility[obj.name] = (is_active or locked) and show_obj and mask, which mirrors the show_obj computation just above it (itself lock-aware via locked_collection/hidden_flag), and this dict is applied once after every outfit + extras + hair piece is resolved (including the hair-switch pass), so shared ("A|B") masks correctly stay on if either driving piece ends up visible.
  • settings/rig.py's outfits_global_options_update(is_extras or collection.name == self.outfits_list or obj.MustardUI_outfit_lock) and not obj.hide_viewport and self.outfits_global_mask, consistent with the above.
  • The single-object toggle in outfits/ops_visibility.py doesn't need lock-awareness itself (it just reflects the object's own now-updated hide_viewport), and shared modifiers whose other driving piece isn't in the (intentionally partial) visibility dict correctly fall back to a live context.scene.objects.get(...) lookup (outfits/helper_functions.py:104-110), so they don't get clobbered.
  • The "modifier named after its own host object" exclusion (outfits/helper_functions.py:93-98) is a sensible, well-commented way to keep self-named masks under the blanket global-modifier toggle instead of the per-piece driver logic.

Hair wiring (hair/helper_functions.py's get_hair_mask_visibility/update_hair_masks, called from both Hair visibility operators, plus the new hair_global_mask/hair_enable_global_mask properties in settings/rig.py and their propagation through menu_hair.py, menu_configure_hair.py, tools/simplify.py, hair/ops_optimize.py) all looks correctly threaded through, including the hair-switcher-forces-hidden case.

One real bug found:

configuration/ops_smartcheck.py:455 — Smart Check's Hair MASK branch sets the wrong property

elif m.type == "PARTICLE_SYSTEM":
    rig_settings.hair_enable_global_particles = True
elif m.type == "MASK":
    rig_settings.hair_enable_global_particles = True

This MASK branch is a copy-paste of the PARTICLE_SYSTEM one above it — it should set the new hair_enable_global_mask property (added in this same PR for the Hair mask switcher) instead of hair_enable_global_particles again. As written, Smart Check will never auto-enable the Hair mask switcher even when hair objects have MASK modifiers, and will incorrectly force-enable the Particle Hair switcher whenever a MASK modifier is found on a hair object, even if there's no particle system at all. (The equivalent Outfits/Extras branch a few lines above, elif m.type == "MASK": rig_settings.outfits_enable_global_mask = True, is correct — this is the model to follow.)

Fix this →

Nothing else stood out — no registration/wiring issues, and the rest of the mask logic (including the locked-outfit and shared-modifier edge cases specifically asked about) checks out.
· Branch: 2026.7.0

@Mustard2

Mustard2 commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

@claude Review also the new changes

@claude

claude Bot commented Aug 5, 2026

Copy link
Copy Markdown

Claude finished @Mustard2's task in 4m 19s —— View job


Review of latest changes

  • Identify commits since my last review (30e1bc9, df31322, on top of the already-reviewed d166c80)
  • Verify the previously-flagged ops_smartcheck.py MASK-branch bug is fixed
  • Review the mask-system rewrite in outfits/helper_functions.py (get_mask_pieces/get_mask_objects/get_mask_visibility/update_obj_masks/update_masks)
  • Trace all call sites for the cross-category (Outfits vs. Hair) mask-switch case
  • Post final review

Good news first: the configuration/ops_smartcheck.py bug I flagged in my previous review (MASK branch incorrectly setting hair_enable_global_particles instead of hair_enable_global_mask) is fixed correctly in d166c80.

Since then, 30e1bc9 + df31322 did a substantial consolidation of the whole mask system: the separate hair/helper_functions.py mask helpers (get_hair_mask_visibility/update_hair_masks) and the bespoke per-piece visibility logic that used to live inline in settings/rig.py (outfits_global_options_update) were all merged into one generic set of functions in outfits/helper_functions.py: get_mask_pieces, get_mask_objects, get_mask_visibility, update_obj_masks, update_masks. This is a nice simplification and all call sites (outfits/ops_visibility.py, outfits/ops_visibility_full.py, hair/ops_hair_visibility.py, settings/rig.py) were migrated consistently, with no leftover references to the removed functions. The locked-outfit handling I verified last time (show_obj computed before the mask pass, then masks read the already-final hide_viewport state) still holds correctly in ops_visibility_full.py.

One real regression introduced by this consolidation — the master mask switch is now applied per host instead of per driving piece:

outfits/helper_functions.py:60-93 (get_mask_pieces/get_mask_objects) pairs every mask-hosting object with a single "mask switch" value based on which collection the host itself belongs to — the Body and every Outfit/Extras piece get outfits_global_mask, every Hair/Hair-Extras piece gets hair_global_mask (or falls back to outfits_global_mask if hair_enable_global_mask is off). update_masks (line 147-158) then calls update_obj_masks(context, obj, visibility, mask) with that host-level switch.

The problem: update_obj_masks (line 101-131) uses this single mask flag to gate every modifier hosted by that object, regardless of which piece's name actually drives that specific modifier (driving = [x for x in names if x in visibility], should_show = mask and any(visibility[x] for x in driving)). So a MASK/VERTEX_WEIGHT_MIX modifier hosted on the Body but named after a Hair piece (e.g. a scalp mask that activates only for a specific hairstyle) is gated by outfits_global_mask, not hair_global_mask — even though the whole point of the separate hair_enable_global_mask/hair_global_mask switch (exposed in menu_hair.py:691-692 / menu_configure_hair.py:58) is to let the Hair mask switcher control masks driven by Hair pieces independently of the Outfits one. The same problem exists symmetrically for a Hair object hosting a mask driven by an Outfit piece.

This is a real regression vs. the previous version (d166c80 and earlier): there, hair/helper_functions.py's get_hair_mask_visibility(rig_settings, mask) baked the switch into each hair piece's own dict entry (visibility[obj.name] = not obj.hide_viewport and mask), and update_outfit_masks applied that dict uniformly across all mask objects — so a modifier's visibility correctly followed whichever switch belongs to the piece that names it, no matter which object hosts the modifier. That per-driving-piece gating was dropped when get_mask_visibility (line 96-98) was simplified to plain {obj.name: not obj.hide_viewport} with no switch baked in.

Concrete failure scenario: creator enables the separate Hair mask switcher (hair_enable_global_mask = True), body has a MASK modifier named "Ponytail" to hide part of the scalp when that hairstyle is worn. User turns the Hair "Mask" switch off (hair_global_mask = False) but leaves the Outfits "Mask" switch on (outfits_global_mask = True, the default). Expected: the Ponytail scalp mask should also disable. Actual: it stays driven by outfits_global_mask (since its host, the Body, always gets outfits_global_mask) and ignores hair_global_mask entirely.

Fix this →

Minor, related note: update_obj_masks line 112-116 — for a MASK modifier whose name matches no known Outfit/Hair piece at all (unrelated/manual mask), the code force-disables it whenever the host's own category switch (mask) is off, but never re-enables it when the switch is turned back on. This is a one-directional side effect on modifiers the mask system otherwise treats as "not associated to Outfits/Hair" (per the comment on line 110) — worth double-checking this is intended, since it means toggling the master switch off-and-on can leave unrelated Mask modifiers stuck disabled.

Nothing else stood out in this round — registration/wiring is unaffected (no new classes in this batch of commits), and the rest of the previously-reviewed mask logic (locked outfits, shared "A|B" modifiers, hair-switcher-forces-hidden) is unchanged and still correct.
· Branch: 2026.7.0

@Mustard2

Mustard2 commented Aug 5, 2026

Copy link
Copy Markdown
Owner Author

@claude Is the latest fix solving the issue?

@claude

claude Bot commented Aug 5, 2026

Copy link
Copy Markdown

Claude finished @Mustard2's task in 1m 37s —— View job


Review

  • Identify the latest commit (a56201b "fix: Claude review")
  • Verify it addresses the mask-switch regression flagged in my previous review
  • Check the minor one-directional side-effect note as well
  • Post final answer

Yesa56201b correctly fixes the regression I flagged (per-host mask switch instead of per-driving-piece).

outfits/helper_functions.py:

  • get_mask_visibility (line 96-98) now bakes each piece's own category switch into its value: {obj.name: not obj.hide_viewport and mask for obj, mask in get_mask_pieces(rig_settings)}, where mask comes from get_mask_pieces (outfits_global_mask for Outfits/Extras pieces, hair_global_mask/hair_enable_global_mask fallback for Hair/Hair Extras pieces) — i.e. the switch is now attached to the driving piece, not the host.
  • update_obj_masks (line 118) now does should_show = any(visibility[x] for x in driving) without re-multiplying by the host's own mask flag, so a modifier's visibility follows whichever switch belongs to the category of the piece named in the modifier, regardless of which object hosts it.

This exactly resolves the concrete scenario from my last review: a MASK modifier hosted on the Body but named after a Hair piece (e.g. "Ponytail") is now correctly gated by hair_global_mask/hair_enable_global_mask (baked into visibility["Ponytail"]) instead of the Body's outfits_global_mask.

outfits/ops_visibility.py:152-155 also got a necessary companion fix: previously visibility.update(switched) would have overwritten the freshly-baked, switch-aware values from get_mask_visibility with the raw unbaked switched entries (not o.hide_viewport, no switch applied) for the piece(s) toggled by this operator — reintroducing the same bug for that specific piece. It's now visibility.setdefault(name, visible and rig_settings.outfits_global_mask), which only fills in objects not already covered by get_mask_visibility (e.g. non-collection children touched via the shift-click recursive toggle) and correctly gates them with the Outfits switch, since this operator only ever handles Outfits/Extras pieces.

I re-traced the rest of the call chain (ops_visibility_full.py, hair/ops_hair_visibility.py, settings/rig.py's outfits_global_options_update/hair_global_mask_update) — all call update_masks either with no override (fresh recompute via the fixed get_mask_visibility) or via the now-correct merge in ops_visibility.py, so the fix is applied consistently everywhere.

One pre-existing minor item from my last review is still unaddressed (not the regression itself, so likely not what "the issue" refers to, but noting for completeness): update_obj_masks lines 110-116 still force-disable a MASK modifier with no recognized driving piece whenever the host's own category switch is off, but never re-enable it when the switch is turned back on — a one-directional side effect on unrelated/manual masks.
· Branch: 2026.7.0

@Mustard2 Mustard2 linked an issue Aug 6, 2026 that may be closed by this pull request
@Mustard2

Mustard2 commented Aug 8, 2026

Copy link
Copy Markdown
Owner Author

@claude Final review please

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude finished @Mustard2's task in 2m 4s —— View job


Final Review


Reviewing the two commits added since my last pass (a56201b), on top of the previously-confirmed mask-regression fix.

4006240 (fix #398 — preserve Custom Properties settings on Smart Check) looks correctly implemented. add_custom_property now takes skip_existing and, when a tracked entry already matches (rna, path), returns early without recreating it (custom_properties/ops_smartcheck.py:84-86); the caller no longer purges index_to_remove when skip_existing is on (line 221-224), so previously-added properties are never deleted-then-recreated (which is what reset their settings before). k/preserved counters and the new dialog UI (configuration/ops_smartcheck.py) are wired correctly, and the default (True) is a sensible, backward-safe choice. One theoretical edge case, low impact: if a property is tracked in custom_props but was manually deleted from the object (cp.prop_name not in obj.keys()), skip_existing=True still returns early and won't recreate it, so it stays permanently missing until the user disables "Preserve". Not a regression from before this PR in the normal flow (preserve mode never purges the tracking entries, so this only bites if a user hand-deletes the id-property outside MustardUI); just flagging for awareness, not requesting a change.

83b0a64 (fix #394 — error message for invalid custom property addition) — one thing worth double-checking:

custom_properties/ops_add.py:71-76 replaced

try:
    bpy.ops.ui.copy_data_path_button(full_path=True)
except Exception:
    self.report({"ERROR"}, "MustardUI - Invalid selection.")
    return {"FINISHED"}

with

result = bpy.ops.ui.copy_data_path_button(full_path=True)
if "CANCELLED" in result:
    self.report({"ERROR"}, "MustardUI - This property does not support being added...")
    return {"FINISHED"}

This correctly adds a friendlier message for the case where the operator runs but can't resolve a path (returns {'CANCELLED'}), and the added "." not in clipboard guard is a nice extra safety net. However, the try/except around the call itself is gone — if bpy.ops.ui.copy_data_path_button()'s poll() fails for the selected UI element (which raises RuntimeError, not a {'CANCELLED'} return), that exception is no longer caught anywhere in execute(), so instead of the old graceful "Invalid selection" report the operator would now raise an unhandled exception. Since #394 is specifically about "invalid" property additions, it's worth confirming in-Blender that the specific case reported in #394 only ever hits the CANCELLED path and never the poll-failure path — if it can still hit poll failure (e.g. right-clicking a non-copyable button), this change would trade a clean error message for a Python traceback in some scenarios.

Fix this →

Overall PR state: CI is green (lint + Blender 4.2/4.5/5.1 build matrix, run 31218744903). All previously-flagged issues across earlier review rounds (CAGE physics-warning inconsistency, Smart Check MASK-branch typo, the mask-switch per-host-vs-per-driving-piece regression) are confirmed fixed as of a56201b, and I found no new registration/wiring issues in this final delta. The only outstanding item is the try/except removal noted above — worth a quick manual check in Blender before merge, otherwise this PR looks ready.
· Branch: 2026.7.0

@Mustard2
Mustard2 merged commit 72d7f64 into main Aug 8, 2026
7 checks passed
@Mustard2
Mustard2 deleted the 2026.7.0 branch August 8, 2026 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

1 participant