diff --git a/openpype/hosts/blender/plugins/load/load_blend.py b/openpype/hosts/blender/plugins/load/load_blend.py index b64793566f..53933a1934 100644 --- a/openpype/hosts/blender/plugins/load/load_blend.py +++ b/openpype/hosts/blender/plugins/load/load_blend.py @@ -20,7 +20,7 @@ from openpype.hosts.blender.api.pipeline import ( class BlendLoader(plugin.AssetLoader): """Load assets from a .blend file.""" - families = ["model", "rig", "layout"] + families = ["model", "rig", "layout", "camera"] representations = ["blend"] label = "Load Blend" diff --git a/openpype/hosts/blender/plugins/load/load_camera_blend.py b/openpype/hosts/blender/plugins/load/load_camera_blend.py deleted file mode 100644 index f00027f0b4..0000000000 --- a/openpype/hosts/blender/plugins/load/load_camera_blend.py +++ /dev/null @@ -1,256 +0,0 @@ -"""Load a camera asset in Blender.""" - -import logging -from pathlib import Path -from pprint import pformat -from typing import Dict, List, Optional - -import bpy - -from openpype.pipeline import ( - get_representation_path, - AVALON_CONTAINER_ID, -) -from openpype.hosts.blender.api import plugin -from openpype.hosts.blender.api.pipeline import ( - AVALON_CONTAINERS, - AVALON_PROPERTY, -) - -logger = logging.getLogger("openpype").getChild( - "blender").getChild("load_camera") - - -class BlendCameraLoader(plugin.AssetLoader): - """Load a camera from a .blend file. - - Warning: - Loading the same asset more then once is not properly supported at the - moment. - """ - - families = ["camera"] - representations = ["blend"] - - label = "Link Camera (Blend)" - icon = "code-fork" - color = "orange" - - def _remove(self, asset_group): - objects = list(asset_group.children) - - for obj in objects: - if obj.type == 'CAMERA': - bpy.data.cameras.remove(obj.data) - - def _process(self, libpath, asset_group, group_name): - with bpy.data.libraries.load( - libpath, link=True, relative=False - ) as (data_from, data_to): - data_to.objects = data_from.objects - - parent = bpy.context.scene.collection - - empties = [obj for obj in data_to.objects if obj.type == 'EMPTY'] - - container = None - - for empty in empties: - if empty.get(AVALON_PROPERTY): - container = empty - break - - assert container, "No asset group found" - - # Children must be linked before parents, - # otherwise the hierarchy will break - objects = [] - nodes = list(container.children) - - for obj in nodes: - obj.parent = asset_group - - for obj in nodes: - objects.append(obj) - nodes.extend(list(obj.children)) - - objects.reverse() - - for obj in objects: - parent.objects.link(obj) - - for obj in objects: - local_obj = plugin.prepare_data(obj, group_name) - - if local_obj.type != 'EMPTY': - plugin.prepare_data(local_obj.data, group_name) - - if not local_obj.get(AVALON_PROPERTY): - local_obj[AVALON_PROPERTY] = dict() - - avalon_info = local_obj[AVALON_PROPERTY] - avalon_info.update({"container_name": group_name}) - - objects.reverse() - - bpy.data.orphans_purge(do_local_ids=False) - - plugin.deselect_all() - - return objects - - def process_asset( - self, context: dict, name: str, namespace: Optional[str] = None, - options: Optional[Dict] = None - ) -> Optional[List]: - """ - Arguments: - name: Use pre-defined name - namespace: Use pre-defined namespace - context: Full parenthood of representation to load - options: Additional settings dictionary - """ - libpath = self.fname - asset = context["asset"]["name"] - subset = context["subset"]["name"] - - asset_name = plugin.asset_name(asset, subset) - unique_number = plugin.get_unique_number(asset, subset) - group_name = plugin.asset_name(asset, subset, unique_number) - namespace = namespace or f"{asset}_{unique_number}" - - avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) - if not avalon_container: - avalon_container = bpy.data.collections.new(name=AVALON_CONTAINERS) - bpy.context.scene.collection.children.link(avalon_container) - - asset_group = bpy.data.objects.new(group_name, object_data=None) - asset_group.empty_display_type = 'SINGLE_ARROW' - avalon_container.objects.link(asset_group) - - objects = self._process(libpath, asset_group, group_name) - - bpy.context.scene.collection.objects.link(asset_group) - - asset_group[AVALON_PROPERTY] = { - "schema": "openpype:container-2.0", - "id": AVALON_CONTAINER_ID, - "name": name, - "namespace": namespace or '', - "loader": str(self.__class__.__name__), - "representation": str(context["representation"]["_id"]), - "libpath": libpath, - "asset_name": asset_name, - "parent": str(context["representation"]["parent"]), - "family": context["representation"]["context"]["family"], - "objectName": group_name - } - - self[:] = objects - return objects - - def exec_update(self, container: Dict, representation: Dict): - """Update the loaded asset. - - This will remove all children of the asset group, load the new ones - and add them as children of the group. - """ - object_name = container["objectName"] - asset_group = bpy.data.objects.get(object_name) - libpath = Path(get_representation_path(representation)) - extension = libpath.suffix.lower() - - self.log.info( - "Container: %s\nRepresentation: %s", - pformat(container, indent=2), - pformat(representation, indent=2), - ) - - assert asset_group, ( - f"The asset is not loaded: {container['objectName']}" - ) - assert libpath, ( - "No existing library file found for {container['objectName']}" - ) - assert libpath.is_file(), ( - f"The file doesn't exist: {libpath}" - ) - assert extension in plugin.VALID_EXTENSIONS, ( - f"Unsupported file: {libpath}" - ) - - metadata = asset_group.get(AVALON_PROPERTY) - group_libpath = metadata["libpath"] - - normalized_group_libpath = ( - str(Path(bpy.path.abspath(group_libpath)).resolve()) - ) - normalized_libpath = ( - str(Path(bpy.path.abspath(str(libpath))).resolve()) - ) - self.log.debug( - "normalized_group_libpath:\n %s\nnormalized_libpath:\n %s", - normalized_group_libpath, - normalized_libpath, - ) - if normalized_group_libpath == normalized_libpath: - self.log.info("Library already loaded, not updating...") - return - - # Check how many assets use the same library - count = 0 - for obj in bpy.data.collections.get(AVALON_CONTAINERS).objects: - if obj.get(AVALON_PROPERTY).get('libpath') == group_libpath: - count += 1 - - mat = asset_group.matrix_basis.copy() - - self._remove(asset_group) - - # If it is the last object to use that library, remove it - if count == 1: - library = bpy.data.libraries.get(bpy.path.basename(group_libpath)) - if library: - bpy.data.libraries.remove(library) - - self._process(str(libpath), asset_group, object_name) - - asset_group.matrix_basis = mat - - metadata["libpath"] = str(libpath) - metadata["representation"] = str(representation["_id"]) - metadata["parent"] = str(representation["parent"]) - - def exec_remove(self, container: Dict) -> bool: - """Remove an existing container from a Blender scene. - - Arguments: - container (openpype:container-1.0): Container to remove, - from `host.ls()`. - - Returns: - bool: Whether the container was deleted. - """ - object_name = container["objectName"] - asset_group = bpy.data.objects.get(object_name) - libpath = asset_group.get(AVALON_PROPERTY).get('libpath') - - # Check how many assets use the same library - count = 0 - for obj in bpy.data.collections.get(AVALON_CONTAINERS).objects: - if obj.get(AVALON_PROPERTY).get('libpath') == libpath: - count += 1 - - if not asset_group: - return False - - self._remove(asset_group) - - bpy.data.objects.remove(asset_group) - - # If it is the last object to use that library, remove it - if count == 1: - library = bpy.data.libraries.get(bpy.path.basename(libpath)) - bpy.data.libraries.remove(library) - - return True