-
Notifications
You must be signed in to change notification settings - Fork 18
updated anim export and imp #274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
|
Could you rename this file |
|
Does anim export re-use an existing creator? |
Have renamed the files. |
i dont get it, which existing creator? |
the animation instance would be automatically created by default when you load the referenced rig with AYON. |
| staging_dir = self.staging_dir(instance) | ||
| filename = "{0}.anim".format(instance_data['variant']) | ||
| out_path = os.path.join(staging_dir, filename) | ||
| controls = [x for x in instance_data['setMembers'] if x.endswith(":rigMain_controls_SET")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| controls = [x for x in instance_data['setMembers'] if x.endswith(":rigMain_controls_SET")] | |
| controls = [x for x in instance_data['setMembers'] if x.endswith("controls_SET")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:rigMain_controls_SET is a bit hardcoded. The loaded rig product type can be with different variants, e.g. rigTest, rigTech_carA etc.
|
|
||
| def load(self, context, name, namespace, data): | ||
| project_name = context['project']['name'] | ||
| anim_file = context['representation']['attrib']['path'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| anim_file = context['representation']['attrib']['path'] | |
| anim_file = self.filepath_from_context(context) |
| _plugin = ReferenceLoader() | ||
| _plugin.load(context=context, name=context['product']['name'], | ||
| namespace=name_space, options=data) | ||
| ctrl_set = pm.ls(name_space + ":rigMain_controls_SET") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| ctrl_set = pm.ls(name_space + ":rigMain_controls_SET") | |
| ctrl_set = pm.ls(name_space + ":rigMain_controls_SET") |
Keep in mind there is the loaded instance with different variant, e.g. rigTest_controls_SET, rigTechCarA_controls_SET
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class AnimLoader(plugin.Loader): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EDIT: Please take references from how the setdress and layout loader implemented, super helpful for your case.
SetDress: https://github.com/ynput/ayon-maya/blob/develop/client/ayon_maya/plugins/load/load_assembly.py
Layout: https://github.com/ynput/ayon-maya/blob/develop/client/ayon_maya/plugins/load/load_layout.py
| _plugin = ReferenceLoader() | ||
| _plugin.load(context=context, name=context['product']['name'], | ||
| namespace=name_space, options=data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| asset_data = ayon_api.get_folder_by_name(project_name=project_name, folder_name=current_asset['asset_name']) | ||
| versions = ayon_api.get_last_version_by_product_name(project_name, "rigMain", asset_data['id']) | ||
| representations = ayon_api.get_representations( | ||
| project_name=project_name, | ||
| version_ids={versions['id']}, | ||
| fields={"id", "name", "files.path"} | ||
| ) | ||
| rep_id = None | ||
| for rep in representations: | ||
| if rep['name'] == 'ma': | ||
| rep_id = rep['id'] | ||
| break | ||
| context = get_representation_context(project_name, rep_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you refer to the current representation data from the loaded asset
| asset_data = ayon_api.get_folder_by_name(project_name=project_name, folder_name=current_asset['asset_name']) | |
| versions = ayon_api.get_last_version_by_product_name(project_name, "rigMain", asset_data['id']) | |
| representations = ayon_api.get_representations( | |
| project_name=project_name, | |
| version_ids={versions['id']}, | |
| fields={"id", "name", "files.path"} | |
| ) | |
| rep_id = None | |
| for rep in representations: | |
| if rep['name'] == 'ma': | |
| rep_id = rep['id'] | |
| break | |
| context = get_representation_context(project_name, rep_id) | |
| repre_entity = context["representation"] | |
| rep_id = repre_entity["id"] | |
| context = get_representation_context(project_name, rep_id) |
EDIT: if you inherited from ReferenceLoader, it shouldn't not be included in the code, as the context got from the get_representation_context should be the same with the context
| self.log.debug(f"ctrls: {ctrls}") | ||
| self.log.debug(f"namespace: {namespace}") | ||
| self.log.debug(f"anim_file: {anim_file}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| self.log.debug(f"ctrls: {ctrls}") | |
| self.log.debug(f"namespace: {namespace}") | |
| self.log.debug(f"anim_file: {anim_file}") |
| name_space = context['representation']['data']['context']['product']['name'].replace( | ||
| context['representation']['data']['context']['product']['type'], '') | ||
| if not cmds.namespace(exists=name_space): | ||
| assets = context['version']['data'].get("assets", []) | ||
| self.log.info(f"assets: {assets}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can take reference from other loaders in /load folder to get the namespace
| name_space = context['representation']['data']['context']['product']['name'].replace( | |
| context['representation']['data']['context']['product']['type'], '') | |
| if not cmds.namespace(exists=name_space): | |
| assets = context['version']['data'].get("assets", []) | |
| self.log.info(f"assets: {assets}") | |
| folder_name = context["folder"]["name"] | |
| namespace = namespace or lib.unique_namespace( | |
| folder_name + "_", | |
| prefix="_" if folder_name[0].isdigit() else "", | |
| suffix="_", | |
| ) | |
| return | ||
| current_asset = current_asset[0] | ||
| asset_data = ayon_api.get_folder_by_name(project_name=project_name, folder_name=current_asset['asset_name']) | ||
| versions = ayon_api.get_last_version_by_product_name(project_name, "rigMain", asset_data['id']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| versions = ayon_api.get_last_version_by_product_name(project_name, "rigMain", asset_data['id']) | |
| versions = ayon_api.get_last_version_by_product_name(project_name, "rigMain", asset_data['id']) |
We can have the product name with different variants, e.g. rigTest, rigTechCarA etc.
| representation = { | ||
| 'name': 'anim', 'ext': 'anim', | ||
| 'files': os.path.basename(out_path), | ||
| 'stagingDir': staging_dir.replace("\\", "/") | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| representation = { | |
| 'name': 'anim', 'ext': 'anim', | |
| 'files': os.path.basename(out_path), | |
| 'stagingDir': staging_dir.replace("\\", "/") | |
| } | |
| representation = { | |
| 'name': 'anim', | |
| 'ext': 'anim', | |
| 'files': os.path.basename(out_path), | |
| 'stagingDir': staging_dir.replace("\\", "/") | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was starting to give errors so i removed this block to add representation, but still the anim got added to the published version, any idea as to why this is happening
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was starting to give errors so i removed this block to add representation, but still the anim got added to the published version, any idea as to why this is happening
The suggested change only does the cosmetic adjustment.
Can you enclose with the publish report to show what the error you have encountered?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i was getting that the representation already exists, so i tried renaming to anim_curve and that seems to create two entries
so once i removed it , it was back to normal
Sorry I am a little bit confused what you mean. Do you mean you have published two sets of representation data (with the anim-related and anim_curves-related publish respectively) while you just implement only one representation data to be published in this extractor?
I am wondering where anim_curves comes from, is it from another set of representation data with the anim as extension? It is supposed that you can get the published data with the name anim and with the extension anim in the code you showed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i am guessing that the publish code automatically adds the representation based on the instance, without the need to define it in the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i am guessing that the publish code automatically adds the representation based on the instance, without the need to define it in the code.
The Extractor plugins you have inherited from MayaExtractorPlugin are also used for inheritance in other extractors(e.g. extract_obj.py etc). Please see the example here:
ayon-maya/client/ayon_maya/plugins/publish/extract_obj.py
Lines 159 to 168 in 9b026e7
| if "representation" not in instance.data: | |
| instance.data["representation"] = [] | |
| representation = { | |
| 'name': 'obj', | |
| 'ext': 'obj', | |
| 'files': filename, | |
| "stagingDir": staging_dir, | |
| } | |
| instance.data["representations"].append(representation) |
They are all with the similar implementation of the representation data
It would be great if you can enclose the publish report by exporting report in Publisher UI and we can take the deep look to see where the errors come from.

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
publish-report-250604-16-31.json
this is the error report i get and this is what i get

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bottom left in publisher UI, click the clipboard icon.
Export the report to a json file. Share it here. That way we can debug the full publish process.
This error really feels like you're adding the representation twice. Which in your case is true, as your screenshot shows... because you seem to have two extractor plug-ins:
It has this twice:
- Extract Animation curves
- Extract Animation curves
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have attached it already
publish-report-250604-16-31.json
But i found the issue, we had a separate package to do the anim export for now, and seems that addon was added to the bundle. (feeling dumb)
| if not len(static_name) > 1: | ||
| continue | ||
| static_name = static_name[1] | ||
| # static_name = static_chan.name().split(obj_shot_name + '.')[1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| # static_name = static_chan.name().split(obj_shot_name + '.')[1] |
| from pyblish.api import ExtractorOrder | ||
|
|
||
|
|
||
| class ExtractAnimCrv(plugin.MayaExtractorPlugin): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| class ExtractAnimCrv(plugin.MayaExtractorPlugin): | |
| class ExtractAnimCurve(plugin.MayaExtractorPlugin): |
|
@pavithiraraj Thank you for contribution, I double checked the code. I believe the best case to take reference for the loader is the setdress loader and the layout loader from the maya. Both of them read the data from json and applied it into the loaded asset. (Guess the anim data is similar to the json data as you used json.dumps) @BigRoy do we need to avoid pymel? I remember at some point we have discussed to unionize the implementation and use import from maya.cmds instead. |
BigRoy
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I don't think this will end up being merged if it remains close to what it is now. However, the feature itself is great - so let's work towards making it mergable.
The issues;
- Pymel dependendency. The code is now allowed to use
pymel. so please avoid using it. - Custom animation data file format. This introduces a completely custom animation data file format - which I think is a bad thing. There are tools and formats out there that I'd rather rely on or mimic, like
atomor matching e.g.studiolibraryexport/import or evenanimbot's anim export/import. Preferably it's a freely available one, and bonus points if it's by default included with Maya... but rolling our own I think is just plain overkill, plus not good for any interchangeable efforts down the line.
| hosts = ["maya"] | ||
|
|
||
| def process(self, instance): | ||
| instance_data = instance.data |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why? :)
| project_name = context['project']['name'] | ||
| anim_file = context['representation']['attrib']['path'] | ||
| self.log.info(f"anim_file: {anim_file}") | ||
| name_space = context['representation']['data']['context']['product']['name'].replace( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment or docstring to this loader as to what it's doing, how and why. Relying this heavily on a namespace feels very frowned upon.
I think you're much better applying the animation just to the target namespace, regardless of the source namespace. That way, if you'd load it twice... it would still work without much effort.
| import ayon_api | ||
| from ayon_maya.api import plugin | ||
| from ayon_core.pipeline.load.utils import get_representation_context | ||
| from ayon_maya.plugins.load.load_reference import ReferenceLoader |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do not explicitly import Loader plug-ins. These are dynamically loaded by the plugin system (similar to what pyblish does), and not cached in memory. By importing it we'll get into edge cases where you're referring to another ref loader than other code.
You're better of doing a get_loaders_by_name call (it's in ayon core) to get the loader that way. It at least finds it dynamically using the discover logic.
| read_anim(filepath=anim_file, objects=ctrls) | ||
|
|
||
|
|
||
| def read_anim(filepath='C:/temp/anim.anim', objects=pm.selected(), namespace=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default path should not be there ;)
Also, pm.selected() default value evaluates on function definition (when the code gets imported) and not on the function call itself.. .so it's not doing what you expect it to.
| logging.basicConfig(level=logging.DEBUG) | ||
| logger = logging.getLogger(__name__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to create a logger here - just pass the logger from the plug-in if you need one.
| representations = ayon_api.get_representations( | ||
| project_name=project_name, | ||
| version_ids={versions['id']}, | ||
| fields={"id", "name", "files.path"} | ||
| ) | ||
| rep_id = None | ||
| for rep in representations: | ||
| if rep['name'] == 'ma': | ||
| rep_id = rep['id'] | ||
| break |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You may just want to use ayon_api.get_representation_by_name
| } | ||
| version_data["assets"].append(asset_data) | ||
| instance_data["data"] = [name_space + ':' + reference_node.path.__str__()] | ||
| instance.data["assets"] = [name_space + ':' + reference_node.path.__str__()] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like arbitrary data?
Why not store the rig's representation id instead? So that on load, you're also loading the same matching rig production version (and representation). It'd greatly simplify loading too, because you can just directly load it from the representation id. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sound like a solid plan, how do i get the representation id from the instance?
is it the inputRepresentations in the instance.data ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sound like a solid plan, how do i get the representation id from the instance? is it the inputRepresentations in the instance.data ?
You can take reference from extract layout:
ayon-maya/client/ayon_maya/plugins/publish/extract_layout.py
Lines 97 to 115 in 9b026e7
| for container, container_root in container_dict.items(): | |
| representation_id = cmds.getAttr( | |
| "{}.representation".format(container)) | |
| # Ignore invalid UUID is the representation for whatever reason | |
| # is invalid | |
| if not is_valid_uuid(representation_id): | |
| self.log.warning( | |
| f"Skipping container with invalid UUID: {container}") | |
| continue | |
| # TODO: Once we support managed products from another project | |
| # we should be querying here using the project name from the | |
| # container instead. | |
| representation = get_representation_by_id( | |
| project_name, | |
| representation_id, | |
| fields={"versionId", "context", "name"} | |
| ) |
|
I have made most of the changes Can you have a look at it now, do you want me to change the way we write out the data, from a json to atom? |
@BigRoy and I suggest you refactor the extractor and the loader(to align what we did for the workflow in layout and setdress), besides the removal of pymel. But it requires some amount of time to do so. So take your time |
|
Ah ok , i will try and update this based on the layout loader, just curious, when they publish a next version, will the container know about the update? as we are loading the asset as a container and the anim data on top of that right? |
You can update your loaded asset through the scene inventory. You can find the hints from setdress/layout loader. |


Changelog Description
This exports the anim data of the characters so when the next department picks up the file they will have the option to load the anim and continue animation
Additional review information
Have added collect_anim_curve so it exports the anim data and adds to the representation. And to load back the anim you can right click on the anim representation in the loader and click "Load Anim", if the reference is present it will add the anim data else it will reference the asset and add the anim data.
the anim data that is exported is in a json format, as the atom export was not able to load the anim if the ctrls are selected in a different order.
Testing notes: