This repository has been archived on 2023-05-13. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
powerlib/linking.py
Andy Goralczyk 08d73b3f6f Initial conversion to work with 2.8. collections are used instead of the old group system.
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.
2020-06-12 10:44:52 +02:00

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)