Skip to content

Commit 721e535

Browse files
committed
#233 Feedback on skipped naming
1 parent 5e62f8c commit 721e535

12 files changed

Lines changed: 306 additions & 104 deletions

operators/add_pre_suffix.py

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import bpy
44

55
from .renaming_operators import switch_to_edit_mode
6-
from ..operators.renaming_utilities import get_renaming_list, call_renaming_popup, call_error_popup, rename_data_if_enabled, update_bone_drivers, log_timing
6+
from ..operators.renaming_utilities import get_renaming_list, call_renaming_popup, call_error_popup, apply_rename, report_rename_warnings, log_timing
77
from ..variable_replacer.variable_replacer import VariableReplacer
88

99

@@ -27,6 +27,8 @@ def execute(self, context):
2727

2828
t_start = time.perf_counter()
2929
msg = wm.renaming_messages
30+
conflicts = 0
31+
protected = 0
3032

3133
VariableReplacer.reset()
3234
VariableReplacer.prepare(context)
@@ -35,18 +37,18 @@ def execute(self, context):
3537
if entity is not None:
3638
suffix = VariableReplacer.replaceInputString(context, wm.renaming_suffix, entity)
3739
if not entity.name.endswith(suffix):
38-
oldName = entity.name
3940
new_name = entity.name + suffix
40-
entity.name = new_name
41-
rename_data_if_enabled(wm, entity)
42-
if wm.renaming_object_types == 'BONE':
43-
update_bone_drivers(oldName, entity.name)
44-
msg.add_message(oldName, entity.name)
41+
_, warning, is_protected = apply_rename(wm, entity, new_name, msg)
42+
if is_protected:
43+
protected += 1
44+
elif warning:
45+
conflicts += 1
4546
else:
46-
msg.add_message(None, None, "Insert Valid String")
47+
msg.add_message(None, None, warning="Insert Valid String")
4748
if switch_edit_mode:
4849
switch_to_edit_mode(context)
4950
log_timing(context, "add_suffix", t_start, len(renaming_list))
51+
report_rename_warnings(self, conflicts, protected)
5052
call_renaming_popup(context)
5153
return {'FINISHED'}
5254

@@ -61,6 +63,8 @@ def execute(self, context):
6163
wm = context.scene
6264

6365
msg = wm.renaming_messages
66+
conflicts = 0
67+
protected = 0
6468

6569
renaming_list, switch_edit_mode, errMsg = get_renaming_list(context)
6670

@@ -79,15 +83,17 @@ def execute(self, context):
7983
if entity is not None:
8084
pre = VariableReplacer.replaceInputString(context, wm.renaming_prefix, entity)
8185
if not entity.name.startswith(pre):
82-
oldName = entity.name
8386
new_name = pre + entity.name
84-
entity.name = new_name
85-
rename_data_if_enabled(wm, entity)
86-
if wm.renaming_object_types == 'BONE':
87-
update_bone_drivers(oldName, entity.name)
88-
msg.add_message(oldName, entity.name)
87+
_, warning, is_protected = apply_rename(wm, entity, new_name, msg)
88+
if is_protected:
89+
protected += 1
90+
elif warning:
91+
conflicts += 1
92+
else:
93+
msg.add_message(None, None, warning="Insert Valid String")
8994

9095
log_timing(context, "add_prefix", t_start, len(renaming_list))
96+
report_rename_warnings(self, conflicts, protected)
9197
call_renaming_popup(context)
9298
if switch_edit_mode:
9399
switch_to_edit_mode(context)

operators/case_transform.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import bpy
44

55
from .renaming_operators import switch_to_edit_mode
6-
from ..operators.renaming_utilities import get_renaming_list, call_renaming_popup, call_error_popup, rename_data_if_enabled, update_bone_drivers
6+
from ..operators.renaming_utilities import get_renaming_list, call_renaming_popup, call_error_popup, apply_rename, report_rename_warnings
77

88

99
# ---------------------------------------------------------------------------
@@ -86,15 +86,18 @@ def execute(self, context):
8686
return {'CANCELLED'}
8787

8888
msg = scene.renaming_messages
89+
conflicts = 0
90+
protected = 0
8991
for entity in renaming_list:
9092
if entity is not None:
91-
old_name = entity.name
92-
entity.name = self._transform(entity.name)
93-
rename_data_if_enabled(scene, entity)
94-
if scene.renaming_object_types == 'BONE':
95-
update_bone_drivers(old_name, entity.name)
96-
msg.add_message(old_name, entity.name)
97-
93+
new_name = self._transform(entity.name)
94+
_, warning, is_protected = apply_rename(scene, entity, new_name, msg)
95+
if is_protected:
96+
protected += 1
97+
elif warning:
98+
conflicts += 1
99+
100+
report_rename_warnings(self, conflicts, protected)
98101
call_renaming_popup(context)
99102
if switch_edit_mode:
100103
switch_to_edit_mode(context)

operators/name_from_data.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import bpy
22

33
from .renaming_operators import switch_to_edit_mode
4-
from .renaming_utilities import get_renaming_list, call_error_popup, call_renaming_popup
4+
from .renaming_utilities import get_renaming_list, call_error_popup, call_renaming_popup, apply_rename, report_rename_warnings
55

66

77
class VIEW3D_OT_use_objectname_for_data(bpy.types.Operator):
@@ -23,14 +23,19 @@ def execute(self, context):
2323
call_error_popup(context)
2424
return {'CANCELLED'}
2525

26+
conflicts = 0
27+
protected = 0
2628
for obj in renaming_list:
2729

2830
if obj.data:
29-
oldName = obj.data.name
3031
new_name = obj.name + suffix_data
31-
obj.data.name = new_name
32-
msg.add_message(oldName, obj.data.name)
32+
_, warning, is_protected = apply_rename(wm, obj.data, new_name, msg)
33+
if is_protected:
34+
protected += 1
35+
elif warning:
36+
conflicts += 1
3337

38+
report_rename_warnings(self, conflicts, protected)
3439
call_renaming_popup(context)
3540
if switch_edit_mode:
3641
switch_to_edit_mode(context)

operators/name_replace.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .renaming_operators import getAllModifiers, \
66
getAllParticleNames, getAllParticleSettingsNames, getAllDataNames
77
from .renaming_operators import switch_to_edit_mode, numerate_entity_name
8-
from ..operators.renaming_utilities import get_renaming_list, call_renaming_popup, call_error_popup, rename_data_if_enabled, update_bone_drivers, log_timing
8+
from ..operators.renaming_utilities import get_renaming_list, call_renaming_popup, call_error_popup, apply_rename, report_rename_warnings, log_timing
99
from ..variable_replacer.variable_replacer import VariableReplacer
1010

1111

@@ -33,6 +33,8 @@ def execute(self, context):
3333

3434
# settings for numerating the new name
3535
msg = scene.renaming_messages
36+
conflicts = 0
37+
protected = 0
3638

3739
per_object_types = {'SHAPEKEYS', 'VERTEXGROUPS', 'UVMAPS', 'COLORATTRIBUTES', 'ATTRIBUTES', 'BONE'}
3840
per_obj_owner_items = {
@@ -121,18 +123,15 @@ def execute(self, context):
121123
else:
122124
replaceName = VariableReplacer.replaceInputString(context, scene.renaming_new_name, entity)
123125

124-
oldName = real_old_names.get(id(entity), entity.name)
126+
oldName = real_old_names.get(id(entity))
125127
new_name = ''
126128

127129
if not scene.renaming_use_enumerate:
128-
try:
129-
entity.name = replaceName
130-
rename_data_if_enabled(scene, entity)
131-
if scene.renaming_object_types == 'BONE':
132-
update_bone_drivers(oldName, entity.name)
133-
msg.add_message(oldName, entity.name)
134-
except AttributeError:
135-
print("Attribute {} is read only".format(replaceName))
130+
_, warning, is_protected = apply_rename(scene, entity, replaceName, msg, old_name=oldName)
131+
if is_protected:
132+
protected += 1
133+
elif warning:
134+
conflicts += 1
136135

137136
else: # if scene.renaming_use_enumerate == True
138137

@@ -178,20 +177,18 @@ def execute(self, context):
178177
particleSettingsList, entity.name,
179178
return_type_list=True)
180179

181-
try:
182-
entity.name = new_name
183-
rename_data_if_enabled(scene, entity)
184-
if scene.renaming_object_types == 'BONE':
185-
update_bone_drivers(oldName, entity.name)
186-
msg.add_message(oldName, entity.name)
187-
except AttributeError:
188-
print("Attribute {} is read only".format(new_name))
180+
_, warning, is_protected = apply_rename(scene, entity, new_name, msg, old_name=oldName)
181+
if is_protected:
182+
protected += 1
183+
elif warning:
184+
conflicts += 1
189185

190186

191187
else: # len(str(replaceName)) <= 0
192-
msg.add_message(None, None, "Insert a valid string to replace names")
188+
msg.add_message(None, None, warning="Insert a valid string to replace names")
193189

194190
log_timing(context, "name_replace", t_start, len(renaming_list))
191+
report_rename_warnings(self, conflicts, protected)
195192
call_renaming_popup(context)
196193
if switch_edit_mode:
197194
switch_to_edit_mode(context)

operators/numerate.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from .renaming_operators import switch_to_edit_mode
66
from .. import __package__ as base_package
7-
from ..operators.renaming_utilities import get_renaming_list, call_renaming_popup, call_error_popup, rename_data_if_enabled, update_bone_drivers, log_timing
7+
from ..operators.renaming_utilities import get_renaming_list, call_renaming_popup, call_error_popup, apply_rename, report_rename_warnings, log_timing
88

99

1010
class VIEW3D_OT_renaming_numerate(bpy.types.Operator):
@@ -25,6 +25,8 @@ def execute(self, context):
2525
digits = prefs.numerate_digits
2626

2727
msg = wm.renaming_messages
28+
conflicts = 0
29+
protected = 0
2830

2931
renaming_list, switch_edit_mode, errMsg = get_renaming_list(context)
3032

@@ -48,17 +50,17 @@ def execute(self, context):
4850
if owner != current_owner:
4951
current_owner = owner
5052
i = 0
51-
oldName = entity.name
5253
new_name = entity.name + separator + (
5354
'{num:{fill}{width}}'.format(num=(i * step) + start_number, fill='0', width=digits))
54-
entity.name = new_name
55-
rename_data_if_enabled(wm, entity)
56-
if obj_type == 'BONE':
57-
update_bone_drivers(oldName, entity.name)
58-
msg.add_message(oldName, entity.name)
55+
_, warning, is_protected = apply_rename(wm, entity, new_name, msg)
56+
if is_protected:
57+
protected += 1
58+
elif warning:
59+
conflicts += 1
5960
i = i + 1
6061

6162
log_timing(context, "numerate", t_start, len(renaming_list))
63+
report_rename_warnings(self, conflicts, protected)
6264
call_renaming_popup(context)
6365
if switch_edit_mode:
6466
switch_to_edit_mode(context)

operators/rename_by_index.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import bpy
44

5-
from ..operators.renaming_utilities import call_renaming_popup, call_error_popup, log_timing
5+
from ..operators.renaming_utilities import call_renaming_popup, call_error_popup, apply_rename, report_rename_warnings, log_timing
66

77
INDEXED_TYPES = ('UVMAPS', 'COLORATTRIBUTES', 'ATTRIBUTES', 'VERTEXGROUPS', 'SHAPEKEYS')
88

@@ -42,21 +42,26 @@ def execute(self, context):
4242

4343
t_start = time.perf_counter()
4444
renamed = 0
45+
conflicts = 0
46+
protected = 0
4547
for obj in obj_list:
4648
if obj.type != 'MESH':
4749
continue
4850
try:
4951
items = list(get_collection(obj))
5052
if target_index < len(items):
5153
item = items[target_index]
52-
old_name = item.name
53-
item.name = new_name
54-
msg.add_message(old_name, item.name)
54+
_, warning, is_protected = apply_rename(scene, item, new_name, msg)
55+
if is_protected:
56+
protected += 1
57+
elif warning:
58+
conflicts += 1
5559
renamed += 1
5660
except Exception as e:
5761
self.report({'WARNING'}, f"Skipped {obj.name}: {e}")
5862
continue
5963

6064
log_timing(context, "rename_by_index", t_start, renamed)
65+
report_rename_warnings(self, conflicts, protected)
6166
call_renaming_popup(context)
6267
return {'FINISHED'}

operators/renaming_utilities.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
import time
23

34
import bpy
@@ -331,6 +332,87 @@ def rename_data_if_enabled(scene, entity):
331332
entity.data.name = entity.name
332333

333334

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+
334416
def update_bone_drivers(old_name, new_name):
335417
"""Update all driver paths that reference a renamed bone."""
336418
if old_name == new_name:

0 commit comments

Comments
 (0)