-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathreplace_objects.py
More file actions
122 lines (95 loc) · 3.94 KB
/
replace_objects.py
File metadata and controls
122 lines (95 loc) · 3.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"""
Replace Objects - Swaps selected objects with linked copies of the active object.
The classic set-dressing workflow: block out a scene with simple placeholder
shapes, then replace them all at once with the final prop (the active object).
Each replaced object's position, rotation and optionally scale are preserved.
"""
import bpy
from typing import Optional
def replace_objects(context, keep_scale: bool) -> Optional[str]:
"""Replace every non-active selected object with a linked copy of the active.
Args:
context: Current Blender context.
keep_scale: If True each replacement inherits the original's scale.
If False the template's scale is used instead.
Returns:
Error string on failure, None on success.
"""
template = context.active_object
if not template:
return "No active object — make the desired replacement prop the active object"
targets = [o for o in context.selected_objects if o != template]
if not targets:
return (
"Only one object selected — select the objects you want to replace "
"and make the replacement template the active object"
)
new_objects = []
for obj in targets:
# Capture transform and collection membership before removal
obj_loc = obj.location.copy()
obj_rot = obj.rotation_euler.copy()
obj_scale = obj.scale.copy()
obj_cols = list(obj.users_collection)
# Create a linked duplicate (shares mesh/curve data with template)
new_obj = template.copy()
new_obj.location = obj_loc
new_obj.rotation_euler = obj_rot
new_obj.scale = obj_scale if keep_scale else template.scale.copy()
# Link to the same collection(s) as the original
if obj_cols:
for col in obj_cols:
col.objects.link(new_obj)
else:
context.scene.collection.objects.link(new_obj)
new_objects.append(new_obj)
# Remove the original object from the file
bpy.data.objects.remove(obj, do_unlink=True)
# Select newly created objects; make the last one active
bpy.ops.object.select_all(action='DESELECT')
for obj in new_objects:
obj.select_set(True)
if new_objects:
context.view_layer.objects.active = new_objects[-1]
return None
class OBJECT_OT_replace_objects(bpy.types.Operator):
"""Replace selected objects with linked copies of the active object"""
bl_idname = "object.replace_objects"
bl_label = "Replace with Active"
bl_description = (
"Replace every selected object (except the active one) with a linked "
"copy of the active object, preserving each replaced object's transform. "
"Ideal for swapping placeholder geometry with the final prop"
)
bl_options = {'REGISTER', 'UNDO'}
keep_scale: bpy.props.BoolProperty(
name="Keep Original Scale",
description=(
"Preserve each replaced object's scale. "
"Disable to use the template's scale instead"
),
default=True,
)
@classmethod
def poll(cls, context):
return (
context.mode == 'OBJECT' and
context.active_object is not None and
len(context.selected_objects) >= 2
)
def execute(self, context):
error = replace_objects(context, self.keep_scale)
if error:
self.report({'WARNING'}, error)
return {'CANCELLED'}
return {'FINISHED'}
# ─── Registration ─────────────────────────────────────────────────────────────
classes = (
OBJECT_OT_replace_objects,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)