As an additional asset component type, Non-instanced collections have been added back (this used to be supported by the pre-blender institute version developed by Bassam Kurdali afaik.
185 lines
5.7 KiB
Python
185 lines
5.7 KiB
Python
import os
|
|
import bpy
|
|
|
|
|
|
VERBOSE = False # enable this for debugging
|
|
|
|
|
|
def debug_print(*args):
|
|
"""Print debug messages"""
|
|
if VERBOSE:
|
|
print(*args)
|
|
|
|
|
|
def relative_path_to_file(filepath):
|
|
"""Makes a path relative to the current file"""
|
|
return bpy.path.relpath(filepath)
|
|
|
|
|
|
def absolute_path_from_file(rel_filepath):
|
|
return bpy.path.abspath(rel_filepath)
|
|
|
|
|
|
def relative_path_to_lib(filepath):
|
|
"""Makes a path relative to the current library"""
|
|
filepath = absolute_path_from_file(filepath)
|
|
libpath = os.path.dirname(
|
|
absolute_path_from_file(bpy.context.scene['lib_path']))
|
|
rel_path = os.path.relpath(filepath, libpath)
|
|
return rel_path
|
|
|
|
|
|
def bottom_up_from_idblock(idblock):
|
|
"""Generator, yields datablocks from the bottom (i.e. uses nothing) upward.
|
|
|
|
Stupid in that it doesn't detect cycles yet.
|
|
|
|
:param idblock: the idblock whose users to yield.
|
|
"""
|
|
|
|
visited = set()
|
|
|
|
def visit(idblock):
|
|
# Prevent visiting the same idblock multiple times
|
|
if idblock in visited:
|
|
return
|
|
visited.add(idblock)
|
|
|
|
user_map = bpy.data.user_map([idblock])
|
|
# There is only one entry here, for the idblock we requested.
|
|
for user in user_map[idblock]:
|
|
yield from visit(user)
|
|
yield idblock
|
|
|
|
yield from visit(idblock)
|
|
|
|
|
|
def make_local(ob):
|
|
# make local like a boss (using the patch from Sybren Stuvel)
|
|
for idblock in bottom_up_from_idblock(ob):
|
|
|
|
if idblock.library is None:
|
|
# Already local
|
|
continue
|
|
|
|
debug_print('Should make %r local: ' % idblock)
|
|
debug_print(' - result: %s' % idblock.make_local(clear_proxy=True))
|
|
|
|
# this shouldn't happen, but it does happen :/
|
|
if idblock.library:
|
|
pass
|
|
|
|
|
|
def treat_ob(ob, grp):
|
|
"""Remap existing ob to the new ob"""
|
|
ob_name = ob.name
|
|
debug_print(f'Processing {ob_name}')
|
|
|
|
try:
|
|
existing = bpy.data.objects[ob_name, None]
|
|
|
|
except KeyError:
|
|
debug_print('Not yet in Blender, just linking to scene.')
|
|
bpy.context.scene.collection.objects.link(ob)
|
|
|
|
make_local(ob)
|
|
ob = bpy.data.objects[ob_name, None]
|
|
|
|
debug_print('GRP: ', grp.name)
|
|
grp.objects.link(ob)
|
|
|
|
else:
|
|
debug_print(f'Updating {ob.name}')
|
|
# when an object already exists:
|
|
# - find local version
|
|
# - user_remap() it
|
|
existing.user_remap(ob)
|
|
existing.name = f'(PRE-SPLODE LOCAL) {existing.name}'
|
|
|
|
# Preserve visible or hidden state
|
|
ob.hide_viewport = existing.hide_viewport
|
|
|
|
# Preserve animation (used to place the instance in the scene)
|
|
if existing.animation_data:
|
|
ob.animation_data_create()
|
|
ob.animation_data.action = existing.animation_data.action
|
|
|
|
bpy.data.objects.remove(existing)
|
|
make_local(ob)
|
|
|
|
|
|
def load_collection_reference_objects(filepath, collection_names):
|
|
# We load one collection at a time
|
|
debug_print(f'Loading collections {filepath} : {collection_names}')
|
|
rel_path = relative_path_to_file(filepath)
|
|
|
|
# Road a object scene we know the name of.
|
|
with bpy.data.libraries.load(rel_path, link=True) as (data_from, data_to):
|
|
data_to.collections = collection_names
|
|
|
|
data = {}
|
|
for collection in data_to.collections:
|
|
debug_print(f'Handling collection {collection.name}')
|
|
ref_collection_name = f'__REF{collection.name}'
|
|
|
|
if ref_collection_name in bpy.data.collections:
|
|
object_names_from = [ob.name for ob in collection.objects]
|
|
object_names_to = [
|
|
ob.name for ob in bpy.data.collections[ref_collection_name].objects]
|
|
object_names_diff = list(
|
|
set(object_names_to) - set(object_names_from))
|
|
|
|
# Delete removed objects
|
|
for ob in object_names_diff:
|
|
# bpy.data.objects[ob].select_set(True)
|
|
# bpy.ops.object.delete()
|
|
bpy.data.objects.remove(bpy.data.objects[ob])
|
|
else:
|
|
bpy.ops.collection.create(name=ref_collection_name)
|
|
|
|
# store all the objects that are in the collection
|
|
data[bpy.data.collections[ref_collection_name]] = [
|
|
ob for ob in collection.objects]
|
|
|
|
# remove the collections
|
|
bpy.data.collections.remove(collection, do_unlink=True)
|
|
|
|
# add the new objects and make them local
|
|
process_collection_reference_objects(data)
|
|
|
|
|
|
def process_collection_reference_objects(data):
|
|
for collection, objects in data.items():
|
|
for ob in objects:
|
|
treat_ob(ob, collection)
|
|
|
|
|
|
def load_instance_collections(filepath, collection_names):
|
|
debug_print(f'Loading collections {filepath} : {collection_names}')
|
|
rel_path = relative_path_to_file(filepath)
|
|
|
|
# Load an object scene we know the name of.
|
|
with bpy.data.libraries.load(rel_path, link=True) as (data_from, data_to):
|
|
data_to.collections = collection_names
|
|
|
|
scene = bpy.context.scene
|
|
for collection in collection_names:
|
|
instance = bpy.data.objects.new(collection.name, None)
|
|
instance.instance_type = 'COLLECTION'
|
|
instance.empty_display_size = 0.01
|
|
instance.instance_collection = collection
|
|
scene.collection.objects.link(instance)
|
|
|
|
|
|
def load_non_instance_collections(filepath, collection_names):
|
|
debug_print(f'Loading collections {filepath} : {collection_names}')
|
|
rel_path = relative_path_to_file(filepath)
|
|
|
|
# Load an object scene we know the name of.
|
|
with bpy.data.libraries.load(rel_path, link=True) as (data_from, data_to):
|
|
data_to.collections = collection_names
|
|
|
|
scene = bpy.context.scene
|
|
for collection in collection_names:
|
|
scene.collection.children.link(collection)
|