|
| 1 | +import re |
1 | 2 | import time |
2 | 3 |
|
3 | 4 | import bpy |
@@ -331,6 +332,87 @@ def rename_data_if_enabled(scene, entity): |
331 | 332 | entity.data.name = entity.name |
332 | 333 |
|
333 | 334 |
|
| 335 | +_AUTO_SUFFIX_RE = re.compile(r'\.\d{3,}$') |
| 336 | + |
| 337 | + |
| 338 | +def _detect_name_conflict(intended, actual): |
| 339 | + """Return a short warning reason if Blender's auto-uniquify changed the |
| 340 | + name the caller asked for, else False. The caller (the popup) already |
| 341 | + shows the intended vs. actual name side by side, so this only needs to |
| 342 | + say why, not repeat either name. Strips any pre-existing numeric tail |
| 343 | + from both sides before comparing, so an intended name that already ends |
| 344 | + in e.g. '.005' (numerate/rename-by-index) is still recognized correctly |
| 345 | + when Blender bumps it to '.006' instead of leaving it untouched.""" |
| 346 | + if intended == actual: |
| 347 | + return False |
| 348 | + if _AUTO_SUFFIX_RE.sub('', intended) == _AUTO_SUFFIX_RE.sub('', actual): |
| 349 | + return "name already in use" |
| 350 | + return "could not apply name exactly" |
| 351 | + |
| 352 | + |
| 353 | +def apply_rename(scene, entity, new_name, msg, old_name=None, obType=False, obIcon=False): |
| 354 | + """Assign entity.name, detect any Blender-side conflict, and log a message. |
| 355 | + Returns (actual_name, warning_or_None, is_protected). |
| 356 | +
|
| 357 | + If Blender can't give the entity exactly the requested name, the rename |
| 358 | + is reverted rather than kept under whatever fallback name Blender's |
| 359 | + auto-uniquify picked — accepting that fallback is how a batch full of |
| 360 | + conflicts turns into a confusing cascade (Foo.002 silently becoming |
| 361 | + Foo.001, etc., see issue #233). A conflicting entity is left untouched |
| 362 | + and reported as skipped instead. |
| 363 | +
|
| 364 | + is_protected distinguishes a rename that Blender refused outright (the ID |
| 365 | + is linked from an external library, or it's a built-in/intrinsic item |
| 366 | + Blender hard-protects from renaming — e.g. mesh attributes like |
| 367 | + "position"; nothing changed) from a plain name conflict (the rename is |
| 368 | + attempted, found to collide, and reverted) — different failure modes |
| 369 | + callers should track/report separately. |
| 370 | +
|
| 371 | + old_name overrides what gets reported/used for driver fixups — needed by |
| 372 | + name_replace's enumerate path, which parks entities under a temporary |
| 373 | + placeholder name before calling this, so entity.name at call time is |
| 374 | + that placeholder, not the real original name. |
| 375 | + """ |
| 376 | + reported_old_name = old_name if old_name is not None else entity.name |
| 377 | + try: |
| 378 | + entity.name = new_name |
| 379 | + except AttributeError: |
| 380 | + warning = "name is protected, could not rename" |
| 381 | + msg.add_message(reported_old_name, reported_old_name, obType, obIcon, warning=warning) |
| 382 | + return reported_old_name, warning, True |
| 383 | + |
| 384 | + warning = _detect_name_conflict(new_name, entity.name) |
| 385 | + if warning: |
| 386 | + if entity.name != reported_old_name: |
| 387 | + try: |
| 388 | + entity.name = reported_old_name |
| 389 | + except AttributeError: |
| 390 | + pass |
| 391 | + msg.add_message(reported_old_name, reported_old_name, obType, obIcon, warning=warning) |
| 392 | + return reported_old_name, warning, False |
| 393 | + |
| 394 | + rename_data_if_enabled(scene, entity) |
| 395 | + if isinstance(entity, (bpy.types.Bone, bpy.types.EditBone, bpy.types.PoseBone)): |
| 396 | + update_bone_drivers(reported_old_name, entity.name) |
| 397 | + |
| 398 | + msg.add_message(reported_old_name, entity.name, obType, obIcon, warning=False) |
| 399 | + return entity.name, False, False |
| 400 | + |
| 401 | + |
| 402 | +def report_rename_warnings(op, conflict_count, protected_count=0): |
| 403 | + """Surface a status-bar/Info-log warning so conflicts are visible even |
| 404 | + when the popup preference (renamingPanel_showPopup) is off.""" |
| 405 | + parts = [] |
| 406 | + if conflict_count: |
| 407 | + noun = "item" if conflict_count == 1 else "items" |
| 408 | + parts.append(f"{conflict_count} {noun} skipped due to a naming conflict") |
| 409 | + if protected_count: |
| 410 | + noun = "item" if protected_count == 1 else "items" |
| 411 | + parts.append(f"{protected_count} protected {noun} could not be renamed") |
| 412 | + if parts: |
| 413 | + op.report({'WARNING'}, "; ".join(parts) + ". See the Rename Info popup for details.") |
| 414 | + |
| 415 | + |
334 | 416 | def update_bone_drivers(old_name, new_name): |
335 | 417 | """Update all driver paths that reference a renamed bone.""" |
336 | 418 | if old_name == new_name: |
|
0 commit comments