blender-addons/blenderkit/append_link.py
Vilem Duha ae7be84e2d BlenderKit: fix an error when trying to assign material to unsupported object type
-do same warning for drag-drop although that can be now further improved since it seems ray cast to other object types works already)
-remove some old prints
2021-07-26 08:11:52 +02:00

382 lines
13 KiB
Python

# ##### 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\\"
object_name = kwargs.get('name')
fc = utils.get_fake_context(bpy.context, area_type='VIEW_3D')
bpy.ops.wm.append(fc, filename=object_name, directory=path)
return_obs = []
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
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
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