mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
Loaders and creators now use Blender main thread to operate
This commit is contained in:
parent
c8c70cc0a7
commit
6ee64d09ab
7 changed files with 87 additions and 39 deletions
|
|
@ -6,10 +6,11 @@ from typing import Dict, List, Optional
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from avalon import api, blender
|
from avalon import api, blender
|
||||||
|
from avalon.blender import ops
|
||||||
from avalon.blender.pipeline import AVALON_CONTAINERS
|
from avalon.blender.pipeline import AVALON_CONTAINERS
|
||||||
from openpype.api import PypeCreatorMixin
|
from openpype.api import PypeCreatorMixin
|
||||||
|
|
||||||
VALID_EXTENSIONS = [".blend", ".json", ".abc"]
|
VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx"]
|
||||||
|
|
||||||
|
|
||||||
def asset_name(
|
def asset_name(
|
||||||
|
|
@ -161,6 +162,15 @@ class AssetLoader(api.Loader):
|
||||||
raise NotImplementedError("Must be implemented by a sub-class")
|
raise NotImplementedError("Must be implemented by a sub-class")
|
||||||
|
|
||||||
def load(self,
|
def load(self,
|
||||||
|
context: dict,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
namespace: Optional[str] = None,
|
||||||
|
options: Optional[Dict] = None) -> Optional[bpy.types.Collection]:
|
||||||
|
""" Run the loader on Blender main thread"""
|
||||||
|
mti = ops.MainThreadItem(self._load, context, name, namespace, options)
|
||||||
|
ops.execute_in_main_thread(mti)
|
||||||
|
|
||||||
|
def _load(self,
|
||||||
context: dict,
|
context: dict,
|
||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
namespace: Optional[str] = None,
|
namespace: Optional[str] = None,
|
||||||
|
|
@ -216,10 +226,20 @@ class AssetLoader(api.Loader):
|
||||||
|
|
||||||
return self._get_instance_collection(instance_name, nodes)
|
return self._get_instance_collection(instance_name, nodes)
|
||||||
|
|
||||||
|
def exec_update(self, container: Dict, representation: Dict):
|
||||||
|
"""Must be implemented by a sub-class"""
|
||||||
|
raise NotImplementedError("Must be implemented by a sub-class")
|
||||||
|
|
||||||
def update(self, container: Dict, representation: Dict):
|
def update(self, container: Dict, representation: Dict):
|
||||||
|
""" Run the update on Blender main thread"""
|
||||||
|
mti = ops.MainThreadItem(self.exec_update, container, representation)
|
||||||
|
ops.execute_in_main_thread(mti)
|
||||||
|
|
||||||
|
def exec_remove(self, container: Dict) -> bool:
|
||||||
"""Must be implemented by a sub-class"""
|
"""Must be implemented by a sub-class"""
|
||||||
raise NotImplementedError("Must be implemented by a sub-class")
|
raise NotImplementedError("Must be implemented by a sub-class")
|
||||||
|
|
||||||
def remove(self, container: Dict) -> bool:
|
def remove(self, container: Dict) -> bool:
|
||||||
"""Must be implemented by a sub-class"""
|
""" Run the remove on Blender main thread"""
|
||||||
raise NotImplementedError("Must be implemented by a sub-class")
|
mti = ops.MainThreadItem(self.exec_remove, container)
|
||||||
|
ops.execute_in_main_thread(mti)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from avalon import api
|
from avalon import api
|
||||||
from avalon.blender import lib
|
from avalon.blender import lib, ops
|
||||||
from avalon.blender.pipeline import AVALON_INSTANCES
|
from avalon.blender.pipeline import AVALON_INSTANCES
|
||||||
from openpype.hosts.blender.api import plugin
|
from openpype.hosts.blender.api import plugin
|
||||||
|
|
||||||
|
|
@ -17,6 +17,11 @@ class CreateModel(plugin.Creator):
|
||||||
icon = "cube"
|
icon = "cube"
|
||||||
|
|
||||||
def process(self):
|
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 Containter or create it if it does not exist
|
# Get Instance Containter or create it if it does not exist
|
||||||
instances = bpy.data.collections.get(AVALON_INSTANCES)
|
instances = bpy.data.collections.get(AVALON_INSTANCES)
|
||||||
if not instances:
|
if not instances:
|
||||||
|
|
@ -40,8 +45,6 @@ class CreateModel(plugin.Creator):
|
||||||
for obj in selected:
|
for obj in selected:
|
||||||
obj.select_set(True)
|
obj.select_set(True)
|
||||||
selected.append(asset_group)
|
selected.append(asset_group)
|
||||||
context = plugin.create_blender_context(
|
bpy.ops.object.parent_set(keep_transform=True)
|
||||||
active=asset_group, selected=selected)
|
|
||||||
bpy.ops.object.parent_set(context, keep_transform=True)
|
|
||||||
|
|
||||||
return asset_group
|
return asset_group
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from avalon import api
|
from avalon import api
|
||||||
from avalon.blender import lib
|
from avalon.blender import lib, ops
|
||||||
from avalon.blender.pipeline import AVALON_INSTANCES
|
from avalon.blender.pipeline import AVALON_INSTANCES
|
||||||
from openpype.hosts.blender.api import plugin
|
from openpype.hosts.blender.api import plugin
|
||||||
|
|
||||||
|
|
@ -17,6 +17,11 @@ class CreateRig(plugin.Creator):
|
||||||
icon = "wheelchair"
|
icon = "wheelchair"
|
||||||
|
|
||||||
def process(self):
|
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 Containter or create it if it does not exist
|
# Get Instance Containter or create it if it does not exist
|
||||||
instances = bpy.data.collections.get(AVALON_INSTANCES)
|
instances = bpy.data.collections.get(AVALON_INSTANCES)
|
||||||
if not instances:
|
if not instances:
|
||||||
|
|
@ -40,8 +45,6 @@ class CreateRig(plugin.Creator):
|
||||||
for obj in selected:
|
for obj in selected:
|
||||||
obj.select_set(True)
|
obj.select_set(True)
|
||||||
selected.append(asset_group)
|
selected.append(asset_group)
|
||||||
context = plugin.create_blender_context(
|
bpy.ops.object.parent_set(keep_transform=True)
|
||||||
active=asset_group, selected=selected)
|
|
||||||
bpy.ops.object.parent_set(context, keep_transform=True)
|
|
||||||
|
|
||||||
return asset_group
|
return asset_group
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,7 @@ class CacheModelLoader(plugin.AssetLoader):
|
||||||
collection = bpy.context.view_layer.active_layer_collection.collection
|
collection = bpy.context.view_layer.active_layer_collection.collection
|
||||||
|
|
||||||
relative = bpy.context.preferences.filepaths.use_relative_paths
|
relative = bpy.context.preferences.filepaths.use_relative_paths
|
||||||
context = plugin.create_blender_context()
|
|
||||||
bpy.ops.wm.alembic_import(
|
bpy.ops.wm.alembic_import(
|
||||||
context,
|
|
||||||
filepath=libpath,
|
filepath=libpath,
|
||||||
relative_path=relative
|
relative_path=relative
|
||||||
)
|
)
|
||||||
|
|
@ -164,7 +162,7 @@ class CacheModelLoader(plugin.AssetLoader):
|
||||||
self[:] = objects
|
self[:] = objects
|
||||||
return objects
|
return objects
|
||||||
|
|
||||||
def update(self, container: Dict, representation: Dict):
|
def exec_update(self, container: Dict, representation: Dict):
|
||||||
"""Update the loaded asset.
|
"""Update the loaded asset.
|
||||||
|
|
||||||
This will remove all objects of the current collection, load the new
|
This will remove all objects of the current collection, load the new
|
||||||
|
|
@ -227,7 +225,7 @@ class CacheModelLoader(plugin.AssetLoader):
|
||||||
metadata["libpath"] = str(libpath)
|
metadata["libpath"] = str(libpath)
|
||||||
metadata["representation"] = str(representation["_id"])
|
metadata["representation"] = str(representation["_id"])
|
||||||
|
|
||||||
def remove(self, container: Dict) -> bool:
|
def exec_remove(self, container: Dict) -> bool:
|
||||||
"""Remove an existing container from a Blender scene.
|
"""Remove an existing container from a Blender scene.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from typing import Dict, List, Optional
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from avalon import api
|
from avalon import api
|
||||||
from avalon.blender import lib
|
from avalon.blender import lib, ops
|
||||||
from avalon.blender.pipeline import AVALON_CONTAINERS
|
from avalon.blender.pipeline import AVALON_CONTAINERS
|
||||||
from avalon.blender.pipeline import AVALON_CONTAINER_ID
|
from avalon.blender.pipeline import AVALON_CONTAINER_ID
|
||||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||||
|
|
@ -20,7 +20,7 @@ class FbxModelLoader(plugin.AssetLoader):
|
||||||
Stores the imported asset in an empty named after the asset.
|
Stores the imported asset in an empty named after the asset.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
families = ["model"]
|
families = ["model", "rig"]
|
||||||
representations = ["fbx"]
|
representations = ["fbx"]
|
||||||
|
|
||||||
label = "Load FBX"
|
label = "Load FBX"
|
||||||
|
|
@ -29,7 +29,6 @@ class FbxModelLoader(plugin.AssetLoader):
|
||||||
|
|
||||||
def _remove(self, asset_group):
|
def _remove(self, asset_group):
|
||||||
objects = list(asset_group.children)
|
objects = list(asset_group.children)
|
||||||
empties = []
|
|
||||||
|
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
if obj.type == 'MESH':
|
if obj.type == 'MESH':
|
||||||
|
|
@ -37,23 +36,21 @@ class FbxModelLoader(plugin.AssetLoader):
|
||||||
if material_slot.material:
|
if material_slot.material:
|
||||||
bpy.data.materials.remove(material_slot.material)
|
bpy.data.materials.remove(material_slot.material)
|
||||||
bpy.data.meshes.remove(obj.data)
|
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':
|
elif obj.type == 'EMPTY':
|
||||||
objects.extend(obj.children)
|
objects.extend(obj.children)
|
||||||
empties.append(obj)
|
bpy.data.objects.remove(obj)
|
||||||
|
|
||||||
for empty in empties:
|
def _process(self, libpath, asset_group, group_name, action):
|
||||||
bpy.data.objects.remove(empty)
|
|
||||||
|
|
||||||
def _process(self, libpath, asset_group, group_name):
|
|
||||||
bpy.ops.object.select_all(action='DESELECT')
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
|
||||||
collection = bpy.context.view_layer.active_layer_collection.collection
|
collection = bpy.context.view_layer.active_layer_collection.collection
|
||||||
|
|
||||||
context = plugin.create_blender_context()
|
bpy.ops.import_scene.fbx(filepath=libpath)
|
||||||
bpy.ops.import_scene.fbx(
|
|
||||||
context,
|
|
||||||
filepath=libpath
|
|
||||||
)
|
|
||||||
|
|
||||||
parent = bpy.context.scene.collection
|
parent = bpy.context.scene.collection
|
||||||
|
|
||||||
|
|
@ -97,9 +94,17 @@ class FbxModelLoader(plugin.AssetLoader):
|
||||||
name_data = obj.data.name
|
name_data = obj.data.name
|
||||||
obj.data.name = f"{group_name}:{name_data}"
|
obj.data.name = f"{group_name}:{name_data}"
|
||||||
|
|
||||||
|
if obj.type == 'MESH':
|
||||||
for material_slot in obj.material_slots:
|
for material_slot in obj.material_slots:
|
||||||
name_mat = material_slot.material.name
|
name_mat = material_slot.material.name
|
||||||
material_slot.material.name = f"{group_name}:{name_mat}"
|
material_slot.material.name = f"{group_name}:{name_mat}"
|
||||||
|
elif obj.type == 'ARMATURE':
|
||||||
|
anim_data = obj.animation_data
|
||||||
|
if action is not None:
|
||||||
|
anim_data.action = action
|
||||||
|
elif anim_data.action is not None:
|
||||||
|
name_action = anim_data.action.name
|
||||||
|
anim_data.action.name = f"{group_name}:{name_action}"
|
||||||
|
|
||||||
if not obj.get(AVALON_PROPERTY):
|
if not obj.get(AVALON_PROPERTY):
|
||||||
obj[AVALON_PROPERTY] = dict()
|
obj[AVALON_PROPERTY] = dict()
|
||||||
|
|
@ -122,7 +127,6 @@ class FbxModelLoader(plugin.AssetLoader):
|
||||||
context: Full parenthood of representation to load
|
context: Full parenthood of representation to load
|
||||||
options: Additional settings dictionary
|
options: Additional settings dictionary
|
||||||
"""
|
"""
|
||||||
|
|
||||||
libpath = self.fname
|
libpath = self.fname
|
||||||
asset = context["asset"]["name"]
|
asset = context["asset"]["name"]
|
||||||
subset = context["subset"]["name"]
|
subset = context["subset"]["name"]
|
||||||
|
|
@ -140,7 +144,14 @@ class FbxModelLoader(plugin.AssetLoader):
|
||||||
asset_group = bpy.data.objects.new(group_name, object_data=None)
|
asset_group = bpy.data.objects.new(group_name, object_data=None)
|
||||||
avalon_container.objects.link(asset_group)
|
avalon_container.objects.link(asset_group)
|
||||||
|
|
||||||
objects = self._process(libpath, asset_group, group_name)
|
objects = self._process(libpath, asset_group, group_name, None)
|
||||||
|
|
||||||
|
objects = []
|
||||||
|
nodes = list(asset_group.children)
|
||||||
|
|
||||||
|
for obj in nodes:
|
||||||
|
objects.append(obj)
|
||||||
|
nodes.extend(list(obj.children))
|
||||||
|
|
||||||
bpy.context.scene.collection.objects.link(asset_group)
|
bpy.context.scene.collection.objects.link(asset_group)
|
||||||
|
|
||||||
|
|
@ -160,7 +171,7 @@ class FbxModelLoader(plugin.AssetLoader):
|
||||||
self[:] = objects
|
self[:] = objects
|
||||||
return objects
|
return objects
|
||||||
|
|
||||||
def update(self, container: Dict, representation: Dict):
|
def exec_update(self, container: Dict, representation: Dict):
|
||||||
"""Update the loaded asset.
|
"""Update the loaded asset.
|
||||||
|
|
||||||
This will remove all objects of the current collection, load the new
|
This will remove all objects of the current collection, load the new
|
||||||
|
|
@ -214,16 +225,28 @@ class FbxModelLoader(plugin.AssetLoader):
|
||||||
self.log.info("Library already loaded, not updating...")
|
self.log.info("Library already loaded, not updating...")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Get the armature of the rig
|
||||||
|
objects = asset_group.children
|
||||||
|
armatures = [obj for obj in objects if obj.type == 'ARMATURE']
|
||||||
|
action = None
|
||||||
|
|
||||||
|
if armatures:
|
||||||
|
armature = armatures[0]
|
||||||
|
|
||||||
|
if armature.animation_data and armature.animation_data.action:
|
||||||
|
action = armature.animation_data.action
|
||||||
|
|
||||||
mat = asset_group.matrix_basis.copy()
|
mat = asset_group.matrix_basis.copy()
|
||||||
self._remove(asset_group)
|
self._remove(asset_group)
|
||||||
|
|
||||||
self._process(str(libpath), asset_group, object_name)
|
self._process(str(libpath), asset_group, object_name, action)
|
||||||
|
|
||||||
asset_group.matrix_basis = mat
|
asset_group.matrix_basis = mat
|
||||||
|
|
||||||
metadata["libpath"] = str(libpath)
|
metadata["libpath"] = str(libpath)
|
||||||
metadata["representation"] = str(representation["_id"])
|
metadata["representation"] = str(representation["_id"])
|
||||||
|
|
||||||
def remove(self, container: Dict) -> bool:
|
def exec_remove(self, container: Dict) -> bool:
|
||||||
"""Remove an existing container from a Blender scene.
|
"""Remove an existing container from a Blender scene.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
@ -145,7 +145,7 @@ class BlendModelLoader(plugin.AssetLoader):
|
||||||
self[:] = objects
|
self[:] = objects
|
||||||
return objects
|
return objects
|
||||||
|
|
||||||
def update(self, container: Dict, representation: Dict):
|
def exec_update(self, container: Dict, representation: Dict):
|
||||||
"""Update the loaded asset.
|
"""Update the loaded asset.
|
||||||
|
|
||||||
This will remove all objects of the current collection, load the new
|
This will remove all objects of the current collection, load the new
|
||||||
|
|
@ -218,7 +218,7 @@ class BlendModelLoader(plugin.AssetLoader):
|
||||||
metadata["libpath"] = str(libpath)
|
metadata["libpath"] = str(libpath)
|
||||||
metadata["representation"] = str(representation["_id"])
|
metadata["representation"] = str(representation["_id"])
|
||||||
|
|
||||||
def remove(self, container: Dict) -> bool:
|
def exec_remove(self, container: Dict) -> bool:
|
||||||
"""Remove an existing container from a Blender scene.
|
"""Remove an existing container from a Blender scene.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ class BlendRigLoader(plugin.AssetLoader):
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
if obj.type == 'MESH':
|
if obj.type == 'MESH':
|
||||||
for material_slot in list(obj.material_slots):
|
for material_slot in list(obj.material_slots):
|
||||||
bpy.data.materials.remove(material_slot.material)
|
if material_slot.material:
|
||||||
|
bpy.data.materials.remove(material_slot.material)
|
||||||
bpy.data.meshes.remove(obj.data)
|
bpy.data.meshes.remove(obj.data)
|
||||||
elif obj.type == 'ARMATURE':
|
elif obj.type == 'ARMATURE':
|
||||||
objects.extend(obj.children)
|
objects.extend(obj.children)
|
||||||
|
|
@ -178,7 +179,7 @@ class BlendRigLoader(plugin.AssetLoader):
|
||||||
self[:] = objects
|
self[:] = objects
|
||||||
return objects
|
return objects
|
||||||
|
|
||||||
def update(self, container: Dict, representation: Dict):
|
def exec_update(self, container: Dict, representation: Dict):
|
||||||
"""Update the loaded asset.
|
"""Update the loaded asset.
|
||||||
|
|
||||||
This will remove all children of the asset group, load the new ones
|
This will remove all children of the asset group, load the new ones
|
||||||
|
|
@ -232,7 +233,7 @@ class BlendRigLoader(plugin.AssetLoader):
|
||||||
if obj.get(AVALON_PROPERTY).get('libpath') == group_libpath:
|
if obj.get(AVALON_PROPERTY).get('libpath') == group_libpath:
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
# # Get the armature of the rig
|
# Get the armature of the rig
|
||||||
objects = asset_group.children
|
objects = asset_group.children
|
||||||
armature = [obj for obj in objects if obj.type == 'ARMATURE'][0]
|
armature = [obj for obj in objects if obj.type == 'ARMATURE'][0]
|
||||||
|
|
||||||
|
|
@ -256,7 +257,7 @@ class BlendRigLoader(plugin.AssetLoader):
|
||||||
metadata["libpath"] = str(libpath)
|
metadata["libpath"] = str(libpath)
|
||||||
metadata["representation"] = str(representation["_id"])
|
metadata["representation"] = str(representation["_id"])
|
||||||
|
|
||||||
def remove(self, container: Dict) -> bool:
|
def exec_remove(self, container: Dict) -> bool:
|
||||||
"""Remove an existing asset group from a Blender scene.
|
"""Remove an existing asset group from a Blender scene.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue