-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathconvert_selection.py
200 lines (154 loc) · 7.03 KB
/
convert_selection.py
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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# copyright (c) 2018- polygoniq xyz s.r.o.
import bpy
import typing
import logging
from . import polib
from . import mapr
from . import hatchery
from . import asset_registry
from . import preferences
logger = logging.getLogger(f"polygoniq.{__name__}")
MODULE_CLASSES: typing.List[typing.Any] = []
# The 'make_selection_linked' implementation is here because we need to access the 'asset_registry'
# instance. The 'make_selection_editable' is in the 'polib.asset_pack_bpy' module, where it can
# be used without dependency on the 'mapr' module.
# If we ever need 'make_selection_linked' in other places, let's move it to 'polib' and add the
# dependency to 'mapr'.
def make_selection_linked(
context: bpy.types.Context,
asset_provider: mapr.asset_provider.AssetProvider,
file_provider: mapr.file_provider.FileProvider,
) -> typing.List[bpy.types.Object]:
previous_active_obj_name = context.active_object.name if context.active_object else None
converted_objects = []
spawner = mapr.blender_asset_spawner.AssetSpawner(asset_provider, file_provider)
for obj in polib.asset_pack_bpy.find_root_objects(context.selected_objects):
if obj.instance_type == 'COLLECTION':
continue
id_from_object = obj.get(mapr.blender_asset_spawner.ASSET_ID_PROP_NAME, None)
if id_from_object is None:
# Object can have missing id if it comes from pre-engon asset pack
logger.error(f"Object '{obj.name}' has no asset id, cannot convert to linked.")
continue
asset = asset_provider.get_asset(id_from_object)
if asset is None:
# This can happen if the asset id of the object present in scene is not known
# to engon - e.g. if corresponding asset pack is not loaded.
logger.error(
f"Asset with id '{id_from_object}' not found in any installed or registered "
"Asset Pack, cannot convert to linked."
)
continue
if asset.type_ != mapr.asset_data.AssetDataType.blender_model:
continue
old_model_matrix = obj.matrix_world.copy()
old_collections = list(obj.users_collection)
old_color = tuple(obj.color)
old_parent = obj.parent
# This way old object names won't interfere with the new ones
hierarchy_objects = polib.asset_pack_bpy.get_hierarchy(obj)
for hierarchy_obj in hierarchy_objects:
hierarchy_obj.name = polib.utils_bpy.generate_unique_name(
f"del_{hierarchy_obj.name}", bpy.data.objects
)
# Spawn the asset if its mapr id is found
spawned_data = spawner.spawn(
context,
asset,
hatchery.spawn.ModelSpawnOptions(parent_collection=None, select_spawned=False),
)
if spawned_data is None:
logger.error(f"Failed to spawn asset {asset.id_}")
continue
assert isinstance(spawned_data, hatchery.spawn.ModelSpawnedData)
instance_root = spawned_data.instancer
instance_root.matrix_world = old_model_matrix
instance_root.parent = old_parent
instance_root.color = old_color
for coll in old_collections:
if instance_root.name not in coll.objects:
coll.objects.link(instance_root)
converted_objects.append(instance_root)
bpy.data.batch_remove(hierarchy_objects)
# Force Blender to evaluate view_layer data after programmatically removing/linking objects.
# https://docs.blender.org/api/current/info_gotcha.html#no-updates-after-setting-values
context.view_layer.update()
# Select root instances of the newly created objects, user had to have them selected before,
# otherwise they wouldn't be converted at all.
for obj in converted_objects:
obj.select_set(True)
if (
previous_active_obj_name is not None
and previous_active_obj_name in context.view_layer.objects
):
context.view_layer.objects.active = bpy.data.objects[previous_active_obj_name]
return converted_objects
@polib.log_helpers_bpy.logged_operator
class MakeSelectionEditable(bpy.types.Operator):
bl_idname = "engon.make_selection_editable"
bl_label = "Convert to Editable"
bl_description = "Converts Collections into Mesh Data with Editable Materials"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context: bpy.types.Context) -> bool:
return context.mode == 'OBJECT' and len(context.selected_objects) > 0
@polib.utils_bpy.blender_cursor('WAIT')
def execute(self, context: bpy.types.Context):
selected_objects_and_parents_names = polib.asset_pack_bpy.make_selection_editable(
context, True, keep_selection=True, keep_active=True
)
pack_paths = asset_registry.instance.get_packs_paths()
logger.info(f"Resulting objects and parents: {selected_objects_and_parents_names}")
prefs = preferences.prefs_utils.get_preferences(context).browser_preferences
if prefs.spawn_options.remove_duplicates:
filters = [polib.remove_duplicates_bpy.polygoniq_duplicate_data_filter]
polib.remove_duplicates_bpy.remove_duplicate_datablocks(
bpy.data.materials, filters, pack_paths
)
polib.remove_duplicates_bpy.remove_duplicate_datablocks(
bpy.data.images, filters, pack_paths
)
polib.remove_duplicates_bpy.remove_duplicate_datablocks(
bpy.data.node_groups, filters, pack_paths
)
return {'FINISHED'}
MODULE_CLASSES.append(MakeSelectionEditable)
@polib.log_helpers_bpy.logged_operator
class MakeSelectionLinked(bpy.types.Operator):
bl_idname = "engon.make_selection_linked"
bl_label = "Convert to Linked"
bl_description = (
"Converts selected objects to their linked variants from "
"engon asset packs. WARNING: This operation removes "
"all local changes. Doesn't work on particle systems, "
"only polygoniq assets are supported by this operator"
)
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context: bpy.types.Context):
return (
context.mode == 'OBJECT'
and next(
polib.asset_pack_bpy.get_polygoniq_objects(
context.selected_objects, include_linked=False
),
None,
)
is not None
)
@polib.utils_bpy.blender_cursor('WAIT')
def execute(self, context: bpy.types.Context):
converted_objects = make_selection_linked(
context,
asset_registry.instance.master_asset_provider,
asset_registry.instance.master_file_provider,
)
self.report({'INFO'}, f"Converted {len(converted_objects)} object(s) to linked")
return {'FINISHED'}
MODULE_CLASSES.append(MakeSelectionLinked)
def register():
for cls in MODULE_CLASSES:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(MODULE_CLASSES):
bpy.utils.unregister_class(cls)