forked from blender/blender-addons
-
Notifications
You must be signed in to change notification settings - Fork 0
/
append_link.py
400 lines (329 loc) · 14.2 KB
/
append_link.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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
from blenderkit import utils, ui
import bpy
import uuid
def append_brush(file_name, brushname=None, link=False, fake_user=True):
'''append a brush'''
with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to):
for m in data_from.brushes:
if m == brushname or brushname is None:
data_to.brushes = [m]
brushname = m
brush = bpy.data.brushes[brushname]
if fake_user:
brush.use_fake_user = True
return brush
def append_material(file_name, matname=None, link=False, fake_user=True):
'''append a material type asset'''
# first, we have to check if there is a material with same name
# in previous step there's check if the imported material
# is already in the scene, so we know same name != same material
mats_before = bpy.data.materials[:]
try:
with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to):
found = False
for m in data_from.materials:
if m == matname or matname is None:
data_to.materials = [m]
# print(m, type(m))
matname = m
found = True
break;
#not found yet? probably some name inconsistency then.
if not found and len(data_from.materials)>0:
data_to.materials = [data_from.materials[0]]
matname = data_from.materials[0]
print(f"the material wasn't found under the exact name, appended another one: {matname}")
# print('in the appended file the name is ', matname)
except Exception as e:
print(e)
print('failed to open the asset file')
# we have to find the new material , due to possible name changes
mat = None
for m in bpy.data.materials:
if m not in mats_before:
mat = m
break;
#still not found?
if mat is None:
mat = bpy.data.materials.get(matname)
if fake_user:
mat.use_fake_user = True
return mat
def append_scene(file_name, scenename=None, link=False, fake_user=False):
'''append a scene type asset'''
with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to):
for s in data_from.scenes:
if s == scenename or scenename is None:
data_to.scenes = [s]
scenename = s
scene = bpy.data.scenes[scenename]
if fake_user:
scene.use_fake_user = True
# scene has to have a new uuid, so user reports aren't screwed.
scene['uuid'] = str(uuid.uuid4())
#reset ui_props of the scene to defaults:
ui_props = bpy.context.scene.blenderkitUI
ui_props.down_up = 'SEARCH'
return scene
def get_node_sure(node_tree, ntype=''):
'''
Gets a node of certain type, but creates a new one if not pre
'''
node = None
for n in node_tree.nodes:
if ntype == n.bl_rna.identifier:
node = n
return node
if not node:
node = node_tree.nodes.new(type=ntype)
return node
def hdr_swap(name, hdr):
'''
Try to replace the hdr in current world setup. If this fails, create a new world.
:param name: Name of the resulting world (renamse the current one if swap is successfull)
:param hdr: Image type
:return: None
'''
w = bpy.context.scene.world
if w:
w.use_nodes = True
w.name = name
nt = w.node_tree
for n in nt.nodes:
if 'ShaderNodeTexEnvironment' == n.bl_rna.identifier:
env_node = n
env_node.image = hdr
return
new_hdr_world(name,hdr)
def new_hdr_world(name, hdr):
'''
creates a new world, links in the hdr with mapping node, and links the world to scene
:param name: Name of the world datablock
:param hdr: Image type
:return: None
'''
w = bpy.data.worlds.new(name=name)
w.use_nodes = True
bpy.context.scene.world = w
nt = w.node_tree
env_node = nt.nodes.new(type='ShaderNodeTexEnvironment')
env_node.image = hdr
background = get_node_sure(nt, 'ShaderNodeBackground')
tex_coord = get_node_sure(nt, 'ShaderNodeTexCoord')
mapping = get_node_sure(nt, 'ShaderNodeMapping')
nt.links.new(env_node.outputs['Color'], background.inputs['Color'])
nt.links.new(tex_coord.outputs['Generated'], mapping.inputs['Vector'])
nt.links.new(mapping.outputs['Vector'], env_node.inputs['Vector'])
env_node.location.x = -400
mapping.location.x = -600
tex_coord.location.x = -800
def load_HDR(file_name, name):
'''Load a HDR into file and link it to scene world. '''
already_linked = False
for i in bpy.data.images:
if i.filepath == file_name:
hdr = i
already_linked = True
break;
if not already_linked:
hdr = bpy.data.images.load(file_name)
hdr_swap(name, hdr)
return hdr
def link_collection(file_name, obnames=[], location=(0, 0, 0), link=False, parent = None, **kwargs):
'''link an instanced group - model type asset'''
sel = utils.selection_get()
with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to):
scols = []
for col in data_from.collections:
if col == kwargs['name']:
data_to.collections = [col]
rotation = (0, 0, 0)
if kwargs.get('rotation') is not None:
rotation = kwargs['rotation']
bpy.ops.object.empty_add(type='PLAIN_AXES', location=location, rotation=rotation)
main_object = bpy.context.view_layer.objects.active
main_object.instance_type = 'COLLECTION'
if parent is not None:
main_object.parent = bpy.data.objects.get(parent)
main_object.matrix_world.translation = location
for col in bpy.data.collections:
if col.library is not None:
fp = bpy.path.abspath(col.library.filepath)
fp1 = bpy.path.abspath(file_name)
if fp == fp1:
main_object.instance_collection = col
break;
#sometimes, the lib might already be without the actual link.
if not main_object.instance_collection and kwargs['name']:
col = bpy.data.collections.get(kwargs['name'])
if col:
main_object.instance_collection = col
main_object.name = main_object.instance_collection.name
# bpy.ops.wm.link(directory=file_name + "/Collection/", filename=kwargs['name'], link=link, instance_collections=True,
# autoselect=True)
# main_object = bpy.context.view_layer.objects.active
# if kwargs.get('rotation') is not None:
# main_object.rotation_euler = kwargs['rotation']
# main_object.location = location
utils.selection_set(sel)
return main_object, []
def append_particle_system(file_name, obnames=[], location=(0, 0, 0), link=False, **kwargs):
'''link an instanced group - model type asset'''
pss = []
with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to):
for ps in data_from.particles:
pss.append(ps)
data_to.particles = pss
s = bpy.context.scene
sel = utils.selection_get()
target_object = bpy.context.scene.objects.get(kwargs['target_object'])
if target_object is not None and target_object.type == 'MESH':
target_object.select_set(True)
bpy.context.view_layer.objects.active = target_object
for ps in pss:
# now let's tune this ps to the particular objects area:
totarea = 0
for p in target_object.data.polygons:
totarea += p.area
count = int(ps.count * totarea)
if ps.child_type in ('INTERPOLATED', 'SIMPLE'):
total_count = count * ps.rendered_child_count
disp_count = count * ps.child_nbr
else:
total_count = count
bbox_threshold = 25000
display_threshold = 200000
total_max_threshold = 2000000
# emitting too many parent particles just kills blender now.
#this part tuned child count, we'll leave children to artists only.
# if count > total_max_threshold:
# ratio = round(count / total_max_threshold)
#
# if ps.child_type in ('INTERPOLATED', 'SIMPLE'):
# ps.rendered_child_count *= ratio
# else:
# ps.child_type = 'INTERPOLATED'
# ps.rendered_child_count = ratio
# count = max(2, int(count / ratio))
#1st level of optimizaton - switch t bounding boxes.
if total_count>bbox_threshold:
target_object.display_type = 'BOUNDS'
# 2nd level of optimization - reduce percentage of displayed particles.
ps.display_percentage = min(ps.display_percentage, max(1, int(100 * display_threshold / total_count)))
#here we can also tune down number of children displayed.
#set the count
ps.count = count
#add the modifier
bpy.ops.object.particle_system_add()
# 3rd level - hide particle system from viewport - is done on the modifier..
if total_count > total_max_threshold:
target_object.modifiers[-1].show_viewport = False
target_object.particle_systems[-1].settings = ps
target_object.select_set(False)
utils.selection_set(sel)
return target_object, []
def append_objects(file_name, obnames=[], location=(0, 0, 0), link=False, **kwargs):
'''append objects into scene individually'''
#simplified version of append
if kwargs.get('name'):
# by now used for appending into scene
scene = bpy.context.scene
sel = utils.selection_get()
bpy.ops.object.select_all(action='DESELECT')
path = file_name + "\\Collection\\"
collection_name = kwargs.get('name')
fc = utils.get_fake_context(bpy.context, area_type='VIEW_3D')
bpy.ops.wm.append(fc, filename=collection_name, directory=path)
return_obs = []
to_hidden_collection = []
collection = None
for ob in bpy.context.scene.objects:
if ob.select_get():
return_obs.append(ob)
if not ob.parent:
main_object = ob
ob.location = location
# check for object that should be hidden
if ob.users_collection[0].name == collection_name:
collection = ob.users_collection[0]
else:
to_hidden_collection.append(ob)
if kwargs.get('rotation'):
main_object.rotation_euler = kwargs['rotation']
if kwargs.get('parent') is not None:
main_object.parent = bpy.data.objects[kwargs['parent']]
main_object.matrix_world.translation = location
#move objects that should be hidden to a sub collection
if len(to_hidden_collection)>0 and collection is not None:
hidden_collection_name = collection_name+'_hidden'
h_col = bpy.data.collections.new(name = hidden_collection_name)
collection.children.link(h_col)
for ob in to_hidden_collection:
ob.users_collection[0].objects.unlink(ob)
h_col.objects.link(ob)
utils.exclude_collection(hidden_collection_name)
bpy.ops.object.select_all(action='DESELECT')
utils.selection_set(sel)
return main_object, return_obs
#this is used for uploads:
with bpy.data.libraries.load(file_name, link=link, relative=True) as (data_from, data_to):
sobs = []
# for col in data_from.collections:
# if col == kwargs.get('name'):
for ob in data_from.objects:
if ob in obnames or obnames == []:
sobs.append(ob)
data_to.objects = sobs
# data_to.objects = data_from.objects#[name for name in data_from.objects if name.startswith("house")]
# link them to scene
scene = bpy.context.scene
sel = utils.selection_get()
bpy.ops.object.select_all(action='DESELECT')
return_obs = [] # this might not be needed, but better be sure to rewrite the list.
main_object = None
hidden_objects = []
#
for obj in data_to.objects:
if obj is not None:
# if obj.name not in scene.objects:
scene.collection.objects.link(obj)
if obj.parent is None:
obj.location = location
main_object = obj
obj.select_set(True)
# we need to unhide object so make_local op can use those too.
if link == True:
if obj.hide_viewport:
hidden_objects.append(obj)
obj.hide_viewport = False
return_obs.append(obj)
# Only after all objects are in scene! Otherwise gets broken relationships
if link == True:
bpy.ops.object.make_local(type='SELECT_OBJECT')
for ob in hidden_objects:
ob.hide_viewport = True
if kwargs.get('rotation') is not None:
main_object.rotation_euler = kwargs['rotation']
if kwargs.get('parent') is not None:
main_object.parent = bpy.data.objects[kwargs['parent']]
main_object.matrix_world.translation = location
bpy.ops.object.select_all(action='DESELECT')
utils.selection_set(sel)
return main_object, return_obs