diff --git a/openpype/hosts/blender/plugins/create/create_blender_scene.py b/openpype/hosts/blender/plugins/create/create_blender_scene.py deleted file mode 100644 index b63ed4fd3f..0000000000 --- a/openpype/hosts/blender/plugins/create/create_blender_scene.py +++ /dev/null @@ -1,37 +0,0 @@ -import bpy - -from openpype.pipeline import legacy_io -from openpype.hosts.blender.api import plugin, lib, ops -from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES - - -class CreateBlenderScene(plugin.Creator): - """Raw Blender Scene file export""" - - name = "blenderScene" - label = "Blender Scene" - family = "blenderScene" - icon = "file-archive-o" - - def process(self): - """ Run the creator on Blender main thread""" - mti = ops.MainThreadItem(self._process) - ops.execute_in_main_thread(mti) - - def _process(self): - # Get Instance Container or create it if it does not exist - instances = bpy.data.collections.get(AVALON_INSTANCES) - if not instances: - instances = bpy.data.collections.new(name=AVALON_INSTANCES) - bpy.context.scene.collection.children.link(instances) - - asset = self.data["asset"] - subset = self.data["subset"] - name = plugin.asset_name(asset, subset) - - asset_group = bpy.data.collections.new(name=name) - instances.children.link(asset_group) - self.data['task'] = legacy_io.Session.get('AVALON_TASK') - lib.imprint(asset_group, self.data) - - return asset_group diff --git a/openpype/hosts/blender/plugins/load/load_layout_blend.py b/openpype/hosts/blender/plugins/load/load_layout_blend.py deleted file mode 100644 index 7d2fd23444..0000000000 --- a/openpype/hosts/blender/plugins/load/load_layout_blend.py +++ /dev/null @@ -1,469 +0,0 @@ -"""Load a layout in Blender.""" - -from pathlib import Path -from pprint import pformat -from typing import Dict, List, Optional - -import bpy - -from openpype.pipeline import ( - legacy_create, - get_representation_path, - AVALON_CONTAINER_ID, -) -from openpype.pipeline.create import get_legacy_creator_by_name -from openpype.hosts.blender.api import plugin -from openpype.hosts.blender.api.pipeline import ( - AVALON_CONTAINERS, - AVALON_PROPERTY, -) - - -class BlendLayoutLoader(plugin.AssetLoader): - """Load layout from a .blend file.""" - - families = ["layout"] - representations = ["blend"] - - label = "Link Layout" - icon = "code-fork" - color = "orange" - - def _remove(self, asset_group): - objects = list(asset_group.children) - - for obj in objects: - if obj.type == 'MESH': - for material_slot in list(obj.material_slots): - if material_slot.material: - bpy.data.materials.remove(material_slot.material) - bpy.data.meshes.remove(obj.data) - elif obj.type == 'ARMATURE': - objects.extend(obj.children) - bpy.data.armatures.remove(obj.data) - elif obj.type == 'CURVE': - bpy.data.curves.remove(obj.data) - elif obj.type == 'EMPTY': - objects.extend(obj.children) - bpy.data.objects.remove(obj) - - def _remove_asset_and_library(self, asset_group): - if not asset_group.get(AVALON_PROPERTY): - return - - libpath = asset_group.get(AVALON_PROPERTY).get('libpath') - - if not libpath: - return - - # Check how many assets use the same library - count = 0 - for obj in bpy.data.collections.get(AVALON_CONTAINERS).all_objects: - if obj.get(AVALON_PROPERTY).get('libpath') == libpath: - count += 1 - - 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)) - if library: - bpy.data.libraries.remove(library) - - def _process( - self, libpath, asset_group, group_name, asset, representation, - actions, anim_instances - ): - 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) and - empty.get(AVALON_PROPERTY).get('family') == 'layout'): - 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) - - allowed_types = ['ARMATURE', 'MESH', 'EMPTY'] - - for obj in nodes: - if obj.type in allowed_types: - obj.parent = asset_group - - for obj in nodes: - if obj.type in allowed_types: - objects.append(obj) - nodes.extend(list(obj.children)) - - objects.reverse() - - constraints = [] - - armatures = [obj for obj in objects if obj.type == 'ARMATURE'] - - for armature in armatures: - for bone in armature.pose.bones: - for constraint in bone.constraints: - if hasattr(constraint, 'target'): - constraints.append(constraint) - - for obj in objects: - parent.objects.link(obj) - - for obj in objects: - local_obj = plugin.prepare_data(obj) - - action = None - - if actions: - action = actions.get(local_obj.name, None) - - if local_obj.type == 'MESH': - plugin.prepare_data(local_obj.data) - - if obj != local_obj: - for constraint in constraints: - if constraint.target == obj: - constraint.target = local_obj - - for material_slot in local_obj.material_slots: - if material_slot.material: - plugin.prepare_data(material_slot.material) - elif local_obj.type == 'ARMATURE': - plugin.prepare_data(local_obj.data) - - if action: - if local_obj.animation_data is None: - local_obj.animation_data_create() - local_obj.animation_data.action = action - elif (local_obj.animation_data and - local_obj.animation_data.action): - plugin.prepare_data( - local_obj.animation_data.action) - - # Set link the drivers to the local object - if local_obj.data.animation_data: - for d in local_obj.data.animation_data.drivers: - for v in d.driver.variables: - for t in v.targets: - t.id = local_obj - - elif local_obj.type == 'EMPTY': - if (not anim_instances or - (anim_instances and - local_obj.name not in anim_instances.keys())): - avalon = local_obj.get(AVALON_PROPERTY) - if avalon and avalon.get('family') == 'rig': - creator_plugin = get_legacy_creator_by_name( - "CreateAnimation") - if not creator_plugin: - raise ValueError( - "Creator plugin \"CreateAnimation\" was " - "not found.") - - legacy_create( - creator_plugin, - name=local_obj.name.split(':')[-1] + "_animation", - asset=asset, - options={"useSelection": False, - "asset_group": local_obj}, - data={"dependencies": representation} - ) - - 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() - - armatures = [ - obj for obj in bpy.data.objects - if obj.type == 'ARMATURE' and obj.library is None] - arm_act = {} - - # The armatures with an animation need to be at the center of the - # scene to be hooked correctly by the curves modifiers. - for armature in armatures: - if armature.animation_data and armature.animation_data.action: - arm_act[armature] = armature.animation_data.action - armature.animation_data.action = None - armature.location = (0.0, 0.0, 0.0) - for bone in armature.pose.bones: - bone.location = (0.0, 0.0, 0.0) - bone.rotation_euler = (0.0, 0.0, 0.0) - - curves = [obj for obj in data_to.objects if obj.type == 'CURVE'] - - for curve in curves: - curve_name = curve.name.split(':')[0] - curve_obj = bpy.data.objects.get(curve_name) - - local_obj = plugin.prepare_data(curve) - plugin.prepare_data(local_obj.data) - - # Curves need to reset the hook, but to do that they need to be - # in the view layer. - parent.objects.link(local_obj) - plugin.deselect_all() - local_obj.select_set(True) - bpy.context.view_layer.objects.active = local_obj - if local_obj.library is None: - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.object.hook_reset() - bpy.ops.object.mode_set(mode='OBJECT') - parent.objects.unlink(local_obj) - - local_obj.use_fake_user = True - - for mod in local_obj.modifiers: - mod.object = bpy.data.objects.get(f"{mod.object.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}) - - local_obj.parent = curve_obj - objects.append(local_obj) - - for armature in armatures: - if arm_act.get(armature): - armature.animation_data.action = arm_act[armature] - - while bpy.data.orphans_purge(do_local_ids=False): - pass - - 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"] - representation = str(context["representation"]["_id"]) - - 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, asset, representation, - None, None) - - for child in asset_group.children: - if child.get(AVALON_PROPERTY): - avalon_container.objects.link(child) - - 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 update(self, container: Dict, representation: Dict): - """Update the loaded asset. - - This will remove all objects of the current collection, load the new - ones and add them to the collection. - If the objects of the collection are used in another collection they - will not be removed, only unlinked. Normally this should not be the - case though. - - Warning: - No nested collections are supported at the moment! - """ - 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 - - actions = {} - anim_instances = {} - - for obj in asset_group.children: - obj_meta = obj.get(AVALON_PROPERTY) - if obj_meta.get('family') == 'rig': - # Get animation instance - collections = list(obj.users_collection) - for c in collections: - avalon = c.get(AVALON_PROPERTY) - if avalon and avalon.get('family') == 'animation': - anim_instances[obj.name] = c.name - break - - # Get armature's action - rig = None - for child in obj.children: - if child.type == 'ARMATURE': - rig = child - break - if not rig: - raise Exception("No armature in the rig asset group.") - if rig.animation_data and rig.animation_data.action: - instance_name = obj_meta.get('instance_name') - actions[instance_name] = rig.animation_data.action - - mat = asset_group.matrix_basis.copy() - - # Remove the children of the asset_group first - for child in list(asset_group.children): - self._remove_asset_and_library(child) - - # 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 - - 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) - - asset = container.get("asset_name").split("_")[0] - - self._process( - str(libpath), asset_group, object_name, asset, - str(representation.get("_id")), actions, anim_instances - ) - - # Link the new objects to the animation collection - for inst in anim_instances.keys(): - try: - obj = bpy.data.objects[inst] - bpy.data.collections[anim_instances[inst]].objects.link(obj) - except KeyError: - self.log.info(f"Object {inst} does not exist anymore.") - coll = bpy.data.collections.get(anim_instances[inst]) - if (coll): - bpy.data.collections.remove(coll) - - avalon_container = bpy.data.collections.get(AVALON_CONTAINERS) - for child in asset_group.children: - if child.get(AVALON_PROPERTY): - avalon_container.objects.link(child) - - asset_group.matrix_basis = mat - - metadata["libpath"] = str(libpath) - metadata["representation"] = str(representation["_id"]) - - 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. - - Warning: - No nested collections are supported at the moment! - """ - object_name = container["objectName"] - asset_group = bpy.data.objects.get(object_name) - - if not asset_group: - return False - - # Remove the children of the asset_group first - for child in list(asset_group.children): - self._remove_asset_and_library(child) - - self._remove_asset_and_library(asset_group) - - return True diff --git a/openpype/hosts/blender/plugins/load/load_model.py b/openpype/hosts/blender/plugins/load/load_model.py deleted file mode 100644 index 0a5d98ffa0..0000000000 --- a/openpype/hosts/blender/plugins/load/load_model.py +++ /dev/null @@ -1,296 +0,0 @@ -"""Load a model asset in Blender.""" - -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, -) - - -class BlendModelLoader(plugin.AssetLoader): - """Load models from a .blend file. - - Because they come from a .blend file we can simply link the collection that - contains the model. There is no further need to 'containerise' it. - """ - - families = ["model"] - representations = ["blend"] - - label = "Link Model" - icon = "code-fork" - color = "orange" - - def _remove(self, asset_group): - objects = list(asset_group.children) - - for obj in objects: - if obj.type == 'MESH': - for material_slot in list(obj.material_slots): - bpy.data.materials.remove(material_slot.material) - bpy.data.meshes.remove(obj.data) - elif obj.type == 'EMPTY': - objects.extend(obj.children) - bpy.data.objects.remove(obj) - - 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) - - for material_slot in local_obj.material_slots: - if material_slot.material: - plugin.prepare_data(material_slot.material, 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) - - plugin.deselect_all() - - if options is not None: - parent = options.get('parent') - transform = options.get('transform') - - if parent and transform: - location = transform.get('translation') - rotation = transform.get('rotation') - scale = transform.get('scale') - - asset_group.location = ( - location.get('x'), - location.get('y'), - location.get('z') - ) - asset_group.rotation_euler = ( - rotation.get('x'), - rotation.get('y'), - rotation.get('z') - ) - asset_group.scale = ( - scale.get('x'), - scale.get('y'), - scale.get('z') - ) - - bpy.context.view_layer.objects.active = parent - asset_group.select_set(True) - - bpy.ops.object.parent_set(keep_transform=True) - - plugin.deselect_all() - - 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 objects of the current collection, load the new - ones and add them to the collection. - If the objects of the collection are used in another collection they - will not be removed, only unlinked. Normally this should not be the - case though. - """ - 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 diff --git a/openpype/hosts/blender/plugins/load/load_rig.py b/openpype/hosts/blender/plugins/load/load_rig.py deleted file mode 100644 index 1d23a70061..0000000000 --- a/openpype/hosts/blender/plugins/load/load_rig.py +++ /dev/null @@ -1,417 +0,0 @@ -"""Load a rig asset in Blender.""" - -from pathlib import Path -from pprint import pformat -from typing import Dict, List, Optional - -import bpy - -from openpype.pipeline import ( - legacy_create, - get_representation_path, - AVALON_CONTAINER_ID, -) -from openpype.pipeline.create import get_legacy_creator_by_name -from openpype.hosts.blender.api import ( - plugin, - get_selection, -) -from openpype.hosts.blender.api.pipeline import ( - AVALON_CONTAINERS, - AVALON_PROPERTY, -) - - -class BlendRigLoader(plugin.AssetLoader): - """Load rigs from a .blend file.""" - - families = ["rig"] - representations = ["blend"] - - label = "Link Rig" - icon = "code-fork" - color = "orange" - - def _remove(self, asset_group): - objects = list(asset_group.children) - - for obj in objects: - if obj.type == 'MESH': - for material_slot in list(obj.material_slots): - if material_slot.material: - bpy.data.materials.remove(material_slot.material) - bpy.data.meshes.remove(obj.data) - elif obj.type == 'ARMATURE': - objects.extend(obj.children) - bpy.data.armatures.remove(obj.data) - elif obj.type == 'CURVE': - bpy.data.curves.remove(obj.data) - elif obj.type == 'EMPTY': - objects.extend(obj.children) - bpy.data.objects.remove(obj) - - def _process(self, libpath, asset_group, group_name, action): - 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) - - allowed_types = ['ARMATURE', 'MESH'] - - for obj in nodes: - if obj.type in allowed_types: - obj.parent = asset_group - - for obj in nodes: - if obj.type in allowed_types: - objects.append(obj) - nodes.extend(list(obj.children)) - - objects.reverse() - - constraints = [] - - armatures = [obj for obj in objects if obj.type == 'ARMATURE'] - - for armature in armatures: - for bone in armature.pose.bones: - for constraint in bone.constraints: - if hasattr(constraint, 'target'): - constraints.append(constraint) - - for obj in objects: - parent.objects.link(obj) - - for obj in objects: - local_obj = plugin.prepare_data(obj, group_name) - - if local_obj.type == 'MESH': - plugin.prepare_data(local_obj.data, group_name) - - if obj != local_obj: - for constraint in constraints: - if constraint.target == obj: - constraint.target = local_obj - - for material_slot in local_obj.material_slots: - if material_slot.material: - plugin.prepare_data(material_slot.material, group_name) - elif local_obj.type == 'ARMATURE': - plugin.prepare_data(local_obj.data, group_name) - - if action is not None: - if local_obj.animation_data is None: - local_obj.animation_data_create() - local_obj.animation_data.action = action - elif (local_obj.animation_data and - local_obj.animation_data.action is not None): - plugin.prepare_data( - local_obj.animation_data.action, group_name) - - # Set link the drivers to the local object - if local_obj.data.animation_data: - for d in local_obj.data.animation_data.drivers: - for v in d.driver.variables: - for t in v.targets: - t.id = local_obj - - 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() - - curves = [obj for obj in data_to.objects if obj.type == 'CURVE'] - - for curve in curves: - local_obj = plugin.prepare_data(curve, group_name) - plugin.prepare_data(local_obj.data, group_name) - - local_obj.use_fake_user = True - - for mod in local_obj.modifiers: - mod_target_name = mod.object.name - mod.object = bpy.data.objects.get( - f"{group_name}:{mod_target_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}) - - local_obj.parent = asset_group - objects.append(local_obj) - - while bpy.data.orphans_purge(do_local_ids=False): - pass - - 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) - - action = None - - plugin.deselect_all() - - create_animation = False - anim_file = None - - if options is not None: - parent = options.get('parent') - transform = options.get('transform') - action = options.get('action') - create_animation = options.get('create_animation') - anim_file = options.get('animation_file') - - if parent and transform: - location = transform.get('translation') - rotation = transform.get('rotation') - scale = transform.get('scale') - - asset_group.location = ( - location.get('x'), - location.get('y'), - location.get('z') - ) - asset_group.rotation_euler = ( - rotation.get('x'), - rotation.get('y'), - rotation.get('z') - ) - asset_group.scale = ( - scale.get('x'), - scale.get('y'), - scale.get('z') - ) - - bpy.context.view_layer.objects.active = parent - asset_group.select_set(True) - - bpy.ops.object.parent_set(keep_transform=True) - - plugin.deselect_all() - - objects = self._process(libpath, asset_group, group_name, action) - - if create_animation: - creator_plugin = get_legacy_creator_by_name("CreateAnimation") - if not creator_plugin: - raise ValueError("Creator plugin \"CreateAnimation\" was " - "not found.") - - asset_group.select_set(True) - - animation_asset = options.get('animation_asset') - - legacy_create( - creator_plugin, - name=namespace + "_animation", - # name=f"{unique_number}_{subset}_animation", - asset=animation_asset, - options={"useSelection": False, "asset_group": asset_group}, - data={"dependencies": str(context["representation"]["_id"])} - ) - - plugin.deselect_all() - - if anim_file: - bpy.ops.import_scene.fbx(filepath=anim_file, anim_offset=0.0) - - imported = get_selection() - - armature = [ - o for o in asset_group.children if o.type == 'ARMATURE'][0] - - imported_group = [ - o for o in imported if o.type == 'EMPTY'][0] - - for obj in imported: - if obj.type == 'ARMATURE': - if not armature.animation_data: - armature.animation_data_create() - armature.animation_data.action = obj.animation_data.action - - self._remove(imported_group) - bpy.data.objects.remove(imported_group) - - 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 - - # Get the armature of the rig - objects = asset_group.children - armature = [obj for obj in objects if obj.type == 'ARMATURE'][0] - - action = None - if armature.animation_data and armature.animation_data.action: - action = armature.animation_data.action - - 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)) - bpy.data.libraries.remove(library) - - self._process(str(libpath), asset_group, object_name, action) - - 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 asset group from a Blender scene. - - Arguments: - container (openpype:container-1.0): Container to remove, - from `host.ls()`. - - Returns: - bool: Whether the asset group 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 diff --git a/openpype/hosts/blender/plugins/publish/extract_blender_scene_raw.py b/openpype/hosts/blender/plugins/publish/extract_blender_scene_raw.py deleted file mode 100644 index b3f6f6460c..0000000000 --- a/openpype/hosts/blender/plugins/publish/extract_blender_scene_raw.py +++ /dev/null @@ -1,49 +0,0 @@ -import os - -import bpy - -from openpype.pipeline import publish - - -class ExtractBlenderSceneRaw(publish.Extractor): - """Extract as Blender Scene (raw). - - This will preserve all references, construction history, etc. - """ - - label = "Blender Scene (Raw)" - hosts = ["blender"] - families = ["blenderScene", "layout"] - scene_type = "blend" - - def process(self, instance): - # Define extract output file path - dir_path = self.staging_dir(instance) - filename = "{0}.{1}".format(instance.name, self.scene_type) - path = os.path.join(dir_path, filename) - - # We need to get all the data blocks for all the blender objects. - # The following set will contain all the data blocks from version - # 2.93 of Blender. - data_blocks = set() - - for attr in dir(bpy.data): - data_block = getattr(bpy.data, attr) - if isinstance(data_block, bpy.types.bpy_prop_collection): - data_blocks |= {*data_block} - - # Write the datablocks in a new blend file. - bpy.data.libraries.write(path, data_blocks) - - if "representations" not in instance.data: - instance.data["representations"] = [] - - representation = { - 'name': self.scene_type, - 'ext': self.scene_type, - 'files': filename, - "stagingDir": dir_path - } - instance.data["representations"].append(representation) - - self.log.info("Extracted instance '%s' to: %s" % (instance.name, path)) diff --git a/openpype/plugins/publish/integrate_legacy.py b/openpype/plugins/publish/integrate_legacy.py index f773a50ad2..c238cca633 100644 --- a/openpype/plugins/publish/integrate_legacy.py +++ b/openpype/plugins/publish/integrate_legacy.py @@ -127,8 +127,7 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin): "mvLook", "mvUsdComposition", "mvUsdOverride", - "simpleUnrealTexture", - "blenderScene" + "simpleUnrealTexture" ] exclude_families = ["render.farm"] db_representation_context_keys = [