diff --git a/pype/blender/plugin.py b/pype/blender/plugin.py index b441714c0d..5e98d8314b 100644 --- a/pype/blender/plugin.py +++ b/pype/blender/plugin.py @@ -10,14 +10,16 @@ from avalon import api VALID_EXTENSIONS = [".blend"] -def asset_name(asset: str, subset: str, namespace: Optional[str] = None) -> str: +def asset_name( + asset: str, subset: str, namespace: Optional[str] = None +) -> str: """Return a consistent name for an asset.""" name = f"{asset}_{subset}" if namespace: name = f"{namespace}:{name}" return name -def create_blender_context( obj: Optional[bpy.types.Object] = None ): +def create_blender_context(obj: Optional[bpy.types.Object] = None): """Create a new Blender context. If an object is passed as parameter, it is set as selected and active. """ @@ -27,16 +29,16 @@ def create_blender_context( obj: Optional[bpy.types.Object] = None ): for region in area.regions: if region.type == 'WINDOW': override_context = { - 'window': win, - 'screen': win.screen, - 'area': area, - 'region': region, + 'window': win, + 'screen': win.screen, + 'area': area, + 'region': region, 'scene': bpy.context.scene, 'active_object': obj, 'selected_objects': [obj] } return override_context - raise Exception( "Could not create a custom Blender context." ) + raise Exception("Could not create a custom Blender context.") class AssetLoader(api.Loader): """A basic AssetLoader for Blender diff --git a/pype/plugins/blender/create/create_action.py b/pype/plugins/blender/create/create_action.py index 68e2a50b61..6c24065f81 100644 --- a/pype/plugins/blender/create/create_action.py +++ b/pype/plugins/blender/create/create_action.py @@ -27,8 +27,8 @@ class CreateAction(Creator): if (self.options or {}).get("useSelection"): for obj in lib.get_selection(): - if (obj.animation_data is not None - and obj.animation_data.action is not None): + if (obj.animation_data is not None and + obj.animation_data.action is not None): empty_obj = bpy.data.objects.new(name=name, object_data=None) diff --git a/pype/plugins/blender/create/create_animation.py b/pype/plugins/blender/create/create_animation.py index 6b7616bbfd..3a5985d7a2 100644 --- a/pype/plugins/blender/create/create_animation.py +++ b/pype/plugins/blender/create/create_animation.py @@ -37,7 +37,7 @@ class CreateAnimation(Creator): for obj in lib.get_selection(): - objects_to_link.add( obj ) + objects_to_link.add(obj) if obj.type == 'ARMATURE': diff --git a/pype/plugins/blender/create/create_rig.py b/pype/plugins/blender/create/create_rig.py index d28e854232..dc97d8b4ce 100644 --- a/pype/plugins/blender/create/create_rig.py +++ b/pype/plugins/blender/create/create_rig.py @@ -15,23 +15,6 @@ class CreateRig(Creator): family = "rig" icon = "wheelchair" - # @staticmethod - # def _find_layer_collection(self, layer_collection, collection): - - # found = None - - # if (layer_collection.collection == collection): - - # return layer_collection - - # for layer in layer_collection.children: - - # found = self._find_layer_collection(layer, collection) - - # if found: - - # return found - def process(self): asset = self.data["asset"] @@ -54,7 +37,7 @@ class CreateRig(Creator): for obj in lib.get_selection(): - objects_to_link.add( obj ) + objects_to_link.add(obj) if obj.type == 'ARMATURE': @@ -62,30 +45,6 @@ class CreateRig(Creator): objects_to_link.add(subobj) - # Create a new collection and link the widgets that - # the rig uses. - # custom_shapes = set() - - # for posebone in obj.pose.bones: - - # if posebone.custom_shape is not None: - - # custom_shapes.add( posebone.custom_shape ) - - # if len( custom_shapes ) > 0: - - # widgets_collection = bpy.data.collections.new(name="Widgets") - - # collection.children.link(widgets_collection) - - # for custom_shape in custom_shapes: - - # widgets_collection.objects.link( custom_shape ) - - # layer_collection = self._find_layer_collection(bpy.context.view_layer.layer_collection, widgets_collection) - - # layer_collection.exclude = True - for obj in objects_to_link: collection.objects.link(obj) diff --git a/pype/plugins/blender/load/load_action.py b/pype/plugins/blender/load/load_action.py index e185bff7a8..303d1ead4d 100644 --- a/pype/plugins/blender/load/load_action.py +++ b/pype/plugins/blender/load/load_action.py @@ -69,11 +69,11 @@ class BlendActionLoader(pype.blender.plugin.AssetLoader): ) as (_, data_to): data_to.collections = [lib_container] - scene = bpy.context.scene + collection = bpy.context.scene.collection - scene.collection.children.link(bpy.data.collections[lib_container]) + collection.children.link(bpy.data.collections[lib_container]) - animation_container = scene.collection.children[lib_container].make_local() + animation_container = collection.children[lib_container].make_local() objects_list = [] @@ -84,9 +84,11 @@ class BlendActionLoader(pype.blender.plugin.AssetLoader): obj = obj.make_local() - if obj.animation_data is not None and obj.animation_data.action is not None: + anim_data = obj.animation_data - obj.animation_data.action.make_local() + if anim_data is not None and anim_data.action is not None: + + anim_data.action.make_local() if not obj.get(blender.pipeline.AVALON_PROPERTY): @@ -173,8 +175,12 @@ class BlendActionLoader(pype.blender.plugin.AssetLoader): strips = [] for obj in collection_metadata["objects"]: + + # Get all the strips that use the action + arm_objs = [ + arm for arm in bpy.data.objects if arm.type == 'ARMATURE'] - for armature_obj in [ objj for objj in bpy.data.objects if objj.type == 'ARMATURE' ]: + for armature_obj in arm_objs: if armature_obj.animation_data is not None: @@ -203,25 +209,27 @@ class BlendActionLoader(pype.blender.plugin.AssetLoader): scene.collection.children.link(bpy.data.collections[lib_container]) - animation_container = scene.collection.children[lib_container].make_local() + anim_container = scene.collection.children[lib_container].make_local() objects_list = [] # Link meshes first, then armatures. # The armature is unparented for all the non-local meshes, # when it is made local. - for obj in animation_container.objects: + for obj in anim_container.objects: obj = obj.make_local() - if obj.animation_data is not None and obj.animation_data.action is not None: + anim_data = obj.animation_data - obj.animation_data.action.make_local() + if anim_data is not None and anim_data.action is not None: + + anim_data.action.make_local() for strip in strips: - strip.action = obj.animation_data.action - strip.action_frame_end = obj.animation_data.action.frame_range[1] + strip.action = anim_data.action + strip.action_frame_end = anim_data.action.frame_range[1] if not obj.get(blender.pipeline.AVALON_PROPERTY): @@ -232,7 +240,7 @@ class BlendActionLoader(pype.blender.plugin.AssetLoader): objects_list.append(obj) - animation_container.pop(blender.pipeline.AVALON_PROPERTY) + anim_container.pop(blender.pipeline.AVALON_PROPERTY) # Save the list of objects in the metadata container collection_metadata["objects"] = objects_list @@ -271,7 +279,11 @@ class BlendActionLoader(pype.blender.plugin.AssetLoader): for obj in objects: - for armature_obj in [ objj for objj in bpy.data.objects if objj.type == 'ARMATURE' ]: + # Get all the strips that use the action + arm_objs = [ + arm for arm in bpy.data.objects if arm.type == 'ARMATURE'] + + for armature_obj in arm_objs: if armature_obj.animation_data is not None: diff --git a/pype/plugins/blender/load/load_animation.py b/pype/plugins/blender/load/load_animation.py index ec3e24443f..395684a3ba 100644 --- a/pype/plugins/blender/load/load_animation.py +++ b/pype/plugins/blender/load/load_animation.py @@ -10,7 +10,8 @@ import bpy import pype.blender.plugin -logger = logging.getLogger("pype").getChild("blender").getChild("load_animation") +logger = logging.getLogger("pype").getChild( + "blender").getChild("load_animation") class BlendAnimationLoader(pype.blender.plugin.AssetLoader): @@ -53,10 +54,11 @@ class BlendAnimationLoader(pype.blender.plugin.AssetLoader): scene.collection.children.link(bpy.data.collections[lib_container]) - animation_container = scene.collection.children[lib_container].make_local() + anim_container = scene.collection.children[lib_container].make_local() - meshes = [obj for obj in animation_container.objects if obj.type == 'MESH'] - armatures = [obj for obj in animation_container.objects if obj.type == 'ARMATURE'] + meshes = [obj for obj in anim_container.objects if obj.type == 'MESH'] + armatures = [ + obj for obj in anim_container.objects if obj.type == 'ARMATURE'] # Should check if there is only an armature? @@ -71,9 +73,11 @@ class BlendAnimationLoader(pype.blender.plugin.AssetLoader): obj.data.make_local() - if obj.animation_data is not None and obj.animation_data.action is not None: + anim_data = obj.animation_data - obj.animation_data.action.make_local() + if anim_data is not None and anim_data.action is not None: + + anim_data.action.make_local() if not obj.get(blender.pipeline.AVALON_PROPERTY): @@ -84,7 +88,7 @@ class BlendAnimationLoader(pype.blender.plugin.AssetLoader): objects_list.append(obj) - animation_container.pop( blender.pipeline.AVALON_PROPERTY ) + anim_container.pop(blender.pipeline.AVALON_PROPERTY) bpy.ops.object.select_all(action='DESELECT') @@ -126,7 +130,8 @@ class BlendAnimationLoader(pype.blender.plugin.AssetLoader): container_metadata["libpath"] = libpath container_metadata["lib_container"] = lib_container - objects_list = self._process(self, libpath, lib_container, container_name) + objects_list = self._process( + self, libpath, lib_container, container_name) # Save the list of objects in the metadata container container_metadata["objects"] = objects_list @@ -206,7 +211,8 @@ class BlendAnimationLoader(pype.blender.plugin.AssetLoader): self._remove(self, objects, lib_container) - objects_list = self._process(self, str(libpath), lib_container, collection.name) + objects_list = self._process( + self, str(libpath), lib_container, collection.name) # Save the list of objects in the metadata container collection_metadata["objects"] = objects_list diff --git a/pype/plugins/blender/load/load_model.py b/pype/plugins/blender/load/load_model.py index b8b6b9b956..ff7c6c49c2 100644 --- a/pype/plugins/blender/load/load_model.py +++ b/pype/plugins/blender/load/load_model.py @@ -75,7 +75,7 @@ class BlendModelLoader(pype.blender.plugin.AssetLoader): objects_list.append(obj) - model_container.pop( blender.pipeline.AVALON_PROPERTY ) + model_container.pop(blender.pipeline.AVALON_PROPERTY) bpy.ops.object.select_all(action='DESELECT') @@ -117,7 +117,8 @@ class BlendModelLoader(pype.blender.plugin.AssetLoader): container_metadata["libpath"] = libpath container_metadata["lib_container"] = lib_container - objects_list = self._process(self, libpath, lib_container, container_name) + objects_list = self._process( + self, libpath, lib_container, container_name) # Save the list of objects in the metadata container container_metadata["objects"] = objects_list @@ -190,7 +191,8 @@ class BlendModelLoader(pype.blender.plugin.AssetLoader): self._remove(self, objects, lib_container) - objects_list = self._process(self, str(libpath), lib_container, collection.name) + objects_list = self._process( + self, str(libpath), lib_container, collection.name) # Save the list of objects in the metadata container collection_metadata["objects"] = objects_list diff --git a/pype/plugins/blender/load/load_rig.py b/pype/plugins/blender/load/load_rig.py index 44d47b41a1..d14a868722 100644 --- a/pype/plugins/blender/load/load_rig.py +++ b/pype/plugins/blender/load/load_rig.py @@ -58,7 +58,8 @@ class BlendRigLoader(pype.blender.plugin.AssetLoader): rig_container = scene.collection.children[lib_container].make_local() meshes = [obj for obj in rig_container.objects if obj.type == 'MESH'] - armatures = [obj for obj in rig_container.objects if obj.type == 'ARMATURE'] + armatures = [ + obj for obj in rig_container.objects if obj.type == 'ARMATURE'] objects_list = [] @@ -86,7 +87,7 @@ class BlendRigLoader(pype.blender.plugin.AssetLoader): objects_list.append(obj) - rig_container.pop( blender.pipeline.AVALON_PROPERTY ) + rig_container.pop(blender.pipeline.AVALON_PROPERTY) bpy.ops.object.select_all(action='DESELECT') @@ -128,7 +129,8 @@ class BlendRigLoader(pype.blender.plugin.AssetLoader): container_metadata["libpath"] = libpath container_metadata["lib_container"] = lib_container - objects_list = self._process(self, libpath, lib_container, container_name, None) + objects_list = self._process( + self, libpath, lib_container, container_name, None) # Save the list of objects in the metadata container container_metadata["objects"] = objects_list @@ -209,7 +211,8 @@ class BlendRigLoader(pype.blender.plugin.AssetLoader): self._remove(self, objects, lib_container) - objects_list = self._process(self, str(libpath), lib_container, collection.name, action) + objects_list = self._process( + self, str(libpath), lib_container, collection.name, action) # Save the list of objects in the metadata container collection_metadata["objects"] = objects_list diff --git a/pype/plugins/blender/publish/collect_action.py b/pype/plugins/blender/publish/collect_action.py index 9a54045cea..a8ceed9c82 100644 --- a/pype/plugins/blender/publish/collect_action.py +++ b/pype/plugins/blender/publish/collect_action.py @@ -1,9 +1,7 @@ -import typing from typing import Generator import bpy -import avalon.api import pyblish.api from avalon.blender.pipeline import AVALON_PROPERTY @@ -25,8 +23,8 @@ class CollectAction(pyblish.api.ContextPlugin): """ for collection in bpy.data.collections: avalon_prop = collection.get(AVALON_PROPERTY) or dict() - if (avalon_prop.get('family') == 'action' - and not avalon_prop.get('representation')): + if (avalon_prop.get('family') == 'action' and + not avalon_prop.get('representation')): yield collection def process(self, context): diff --git a/pype/plugins/blender/publish/collect_animation.py b/pype/plugins/blender/publish/collect_animation.py index 109ae98e6f..50d49692b8 100644 --- a/pype/plugins/blender/publish/collect_animation.py +++ b/pype/plugins/blender/publish/collect_animation.py @@ -1,9 +1,7 @@ -import typing from typing import Generator import bpy -import avalon.api import pyblish.api from avalon.blender.pipeline import AVALON_PROPERTY @@ -20,13 +18,13 @@ class CollectAnimation(pyblish.api.ContextPlugin): """Return all 'animation' collections. Check if the family is 'animation' and if it doesn't have the - representation set. If the representation is set, it is a loaded animation - and we don't want to publish it. + representation set. If the representation is set, it is a loaded + animation and we don't want to publish it. """ for collection in bpy.data.collections: avalon_prop = collection.get(AVALON_PROPERTY) or dict() - if (avalon_prop.get('family') == 'animation' - and not avalon_prop.get('representation')): + if (avalon_prop.get('family') == 'animation' and + not avalon_prop.get('representation')): yield collection def process(self, context): diff --git a/pype/plugins/blender/publish/collect_current_file.py b/pype/plugins/blender/publish/collect_current_file.py index 926d290b31..72976c490b 100644 --- a/pype/plugins/blender/publish/collect_current_file.py +++ b/pype/plugins/blender/publish/collect_current_file.py @@ -15,4 +15,5 @@ class CollectBlenderCurrentFile(pyblish.api.ContextPlugin): current_file = bpy.data.filepath context.data['currentFile'] = current_file - assert current_file != '', "Current file is empty. Save the file before continuing." + assert current_file != '', "Current file is empty. " \ + "Save the file before continuing." diff --git a/pype/plugins/blender/publish/collect_model.py b/pype/plugins/blender/publish/collect_model.py index ee10eaf7f2..df5c1e709a 100644 --- a/pype/plugins/blender/publish/collect_model.py +++ b/pype/plugins/blender/publish/collect_model.py @@ -1,9 +1,7 @@ -import typing from typing import Generator import bpy -import avalon.api import pyblish.api from avalon.blender.pipeline import AVALON_PROPERTY @@ -25,8 +23,8 @@ class CollectModel(pyblish.api.ContextPlugin): """ for collection in bpy.data.collections: avalon_prop = collection.get(AVALON_PROPERTY) or dict() - if (avalon_prop.get('family') == 'model' - and not avalon_prop.get('representation')): + if (avalon_prop.get('family') == 'model' and + not avalon_prop.get('representation')): yield collection def process(self, context): diff --git a/pype/plugins/blender/publish/collect_rig.py b/pype/plugins/blender/publish/collect_rig.py index a4b30541f6..01958da37a 100644 --- a/pype/plugins/blender/publish/collect_rig.py +++ b/pype/plugins/blender/publish/collect_rig.py @@ -1,9 +1,7 @@ -import typing from typing import Generator import bpy -import avalon.api import pyblish.api from avalon.blender.pipeline import AVALON_PROPERTY @@ -25,8 +23,8 @@ class CollectRig(pyblish.api.ContextPlugin): """ for collection in bpy.data.collections: avalon_prop = collection.get(AVALON_PROPERTY) or dict() - if (avalon_prop.get('family') == 'rig' - and not avalon_prop.get('representation')): + if (avalon_prop.get('family') == 'rig' and + not avalon_prop.get('representation')): yield collection def process(self, context): diff --git a/pype/plugins/blender/publish/extract_blend.py b/pype/plugins/blender/publish/extract_blend.py index 032f85897d..5f3fdac293 100644 --- a/pype/plugins/blender/publish/extract_blend.py +++ b/pype/plugins/blender/publish/extract_blend.py @@ -43,4 +43,5 @@ class ExtractBlend(pype.api.Extractor): } instance.data["representations"].append(representation) - self.log.info("Extracted instance '%s' to: %s", instance.name, representation) + self.log.info("Extracted instance '%s' to: %s", + instance.name, representation) diff --git a/pype/plugins/blender/publish/extract_fbx.py b/pype/plugins/blender/publish/extract_fbx.py index 95466c1d2b..231bfdde24 100644 --- a/pype/plugins/blender/publish/extract_fbx.py +++ b/pype/plugins/blender/publish/extract_fbx.py @@ -1,10 +1,10 @@ import os -import avalon.blender.workio import pype.api import bpy + class ExtractFBX(pype.api.Extractor): """Extract as FBX.""" @@ -20,29 +20,39 @@ class ExtractFBX(pype.api.Extractor): filename = f"{instance.name}.fbx" filepath = os.path.join(stagingdir, filename) + context = bpy.context + scene = context.scene + view_layer = context.view_layer + # Perform extraction self.log.info("Performing extraction..") - collections = [obj for obj in instance if type(obj) is bpy.types.Collection] + collections = [ + obj for obj in instance if type(obj) is bpy.types.Collection] - assert len(collections) == 1, "There should be one and only one collection collected for this asset" + assert len(collections) == 1, "There should be one and only one " \ + "collection collected for this asset" - old_active_layer_collection = bpy.context.view_layer.active_layer_collection + old_active_layer_collection = view_layer.active_layer_collection + + layers = view_layer.layer_collection.children # Get the layer collection from the collection we need to export. - # This is needed because in Blender you can only set the active + # This is needed because in Blender you can only set the active # collection with the layer collection, and there is no way to get - # the layer collection from the collection (but there is the vice versa). - layer_collections = [layer for layer in bpy.context.view_layer.layer_collection.children if layer.collection == collections[0]] + # the layer collection from the collection + # (but there is the vice versa). + layer_collections = [ + layer for layer in layers if layer.collection == collections[0]] assert len(layer_collections) == 1 - bpy.context.view_layer.active_layer_collection = layer_collections[0] + view_layer.active_layer_collection = layer_collections[0] - old_scale = bpy.context.scene.unit_settings.scale_length + old_scale = scene.unit_settings.scale_length # We set the scale of the scene for the export - bpy.context.scene.unit_settings.scale_length = 0.01 + scene.unit_settings.scale_length = 0.01 # We export the fbx bpy.ops.export_scene.fbx( @@ -52,9 +62,9 @@ class ExtractFBX(pype.api.Extractor): add_leaf_bones=False ) - bpy.context.view_layer.active_layer_collection = old_active_layer_collection + view_layer.active_layer_collection = old_active_layer_collection - bpy.context.scene.unit_settings.scale_length = old_scale + scene.unit_settings.scale_length = old_scale if "representations" not in instance.data: instance.data["representations"] = [] diff --git a/pype/plugins/blender/publish/extract_fbx_animation.py b/pype/plugins/blender/publish/extract_fbx_animation.py index 4b1fe98c2f..d51c641e9c 100644 --- a/pype/plugins/blender/publish/extract_fbx_animation.py +++ b/pype/plugins/blender/publish/extract_fbx_animation.py @@ -1,5 +1,4 @@ import os -import avalon.blender.workio import pype.api @@ -23,31 +22,42 @@ class ExtractAnimationFBX(pype.api.Extractor): filename = f"{instance.name}.fbx" filepath = os.path.join(stagingdir, filename) + context = bpy.context + scene = context.scene + view_layer = context.view_layer + # Perform extraction self.log.info("Performing extraction..") - collections = [obj for obj in instance if type(obj) is bpy.types.Collection] + collections = [ + obj for obj in instance if type(obj) is bpy.types.Collection] - assert len(collections) == 1, "There should be one and only one collection collected for this asset" + assert len(collections) == 1, "There should be one and only one " \ + "collection collected for this asset" - old_active_layer_collection = bpy.context.view_layer.active_layer_collection + old_active_layer_collection = view_layer.active_layer_collection + + layers = view_layer.layer_collection.children # Get the layer collection from the collection we need to export. - # This is needed because in Blender you can only set the active + # This is needed because in Blender you can only set the active # collection with the layer collection, and there is no way to get - # the layer collection from the collection (but there is the vice versa). - layer_collections = [layer for layer in bpy.context.view_layer.layer_collection.children if layer.collection == collections[0]] + # the layer collection from the collection + # (but there is the vice versa). + layer_collections = [ + layer for layer in layers if layer.collection == collections[0]] assert len(layer_collections) == 1 - bpy.context.view_layer.active_layer_collection = layer_collections[0] + view_layer.active_layer_collection = layer_collections[0] - old_scale = bpy.context.scene.unit_settings.scale_length + old_scale = scene.unit_settings.scale_length # We set the scale of the scene for the export - bpy.context.scene.unit_settings.scale_length = 0.01 + scene.unit_settings.scale_length = 0.01 - armatures = [obj for obj in collections[0].objects if obj.type == 'ARMATURE'] + armatures = [ + obj for obj in collections[0].objects if obj.type == 'ARMATURE'] object_action_pairs = [] original_actions = [] @@ -68,15 +78,15 @@ class ExtractAnimationFBX(pype.api.Extractor): curr_frame_range = curr_action.frame_range - starting_frames.append( curr_frame_range[0] ) - ending_frames.append( curr_frame_range[1] ) + starting_frames.append(curr_frame_range[0]) + ending_frames.append(curr_frame_range[1]) object_action_pairs.append((obj, copy_action)) original_actions.append(curr_action) # We compute the starting and ending frames - max_frame = min( starting_frames ) - min_frame = max( ending_frames ) + max_frame = min(starting_frames) + min_frame = max(ending_frames) # We bake the copy of the current action for each object bpy_extras.anim_utils.bake_action_objects( @@ -95,21 +105,24 @@ class ExtractAnimationFBX(pype.api.Extractor): add_leaf_bones=False ) - bpy.context.view_layer.active_layer_collection = old_active_layer_collection + view_layer.active_layer_collection = old_active_layer_collection - bpy.context.scene.unit_settings.scale_length = old_scale + scene.unit_settings.scale_length = old_scale # We delete the baked action and set the original one back for i in range(0, len(object_action_pairs)): - if original_actions[i]: + pair = object_action_pairs[i] + action = original_actions[i] - object_action_pairs[i][0].animation_data.action = original_actions[i] + if action: - if object_action_pairs[i][1]: + pair[0].animation_data.action = action - object_action_pairs[i][1].user_clear() - bpy.data.actions.remove(object_action_pairs[i][1]) + if pair[1]: + + pair[1].user_clear() + bpy.data.actions.remove(pair[1]) if "representations" not in instance.data: instance.data["representations"] = []