问题
I am trying to load animations created in Cheetah 3D and Blender 3D into Scene Kit, but all I get is a bunch of "untitled-animations" with each one being the same animation.
Does anyone know how to properly export these from Blender or Cheetah 3D so that Scene Kit can use them?
回答1:
I dug into this because it was annoying me too. All of the "untitled animations" are individual animations for each bone. You can get the id from the attributes inspecter in the panel on the right side of xcode. Using swift like so, you can get your animation.
let urlOfScene = Bundle.main.url(forResources: "your url", withExtension: "dae")
let source = SCNSceneSource(url: urlOfScene, options: nil)
let armature = source.entryWithIdentifier("Armature", withClass: SCNNode.self) as SCNNode
let animation = armature.entryWithIdentifier("your bone id", withClass: CAAnimation.self) as CAAnimation
This must be done for All the bones in your armature. **Annoying!!!*
Apple used 3dmax for all their sample projects which only show one animation for each collada file. This is because 3dmax exports all the bones under one animation while blender seperates each bone.
Temp Work Around Use TextEdit or append a .xml extension on the end of your .dae file and open it with an xml editor (plenty are free online). xml editors are a little easier to work with. Scroll down to the animation start block. It will look like so...
<library_animations>
<animation id= "first_bone">
<source id= "first_bone-input">
Change it to...
<library_animations>
<animation> ---this is the only modified line
<source id="first_bone-input">
At the end of each animation will be an end block like so...
</animtion> ---delete this line as long as its not your last bone
<animation id="second_bone"> ---delete this line too
<source id="second_bone-input">
Of course at the end of your last bone leave the animation end block like so...
</animation>
</library_animations>
This will give you a single animation within your .dae file which has the same name as your file with a -1 appended to the end!
EDIT - Here is a link to an Automator service that will convert the above code for you!
Automater collada converter download
Unzip and drop the file in your ~/Library/services folder. From there you can just right click on your collada file and scroll down to ConvertToXcodeCollada and presto! A window will pop up when complete (about half a second).
回答2:
This is because each bone in your .dae file has its own <animation>
tag.
FlippinFun correctly states that removing all opening and closing <animation>
tags except for the very first and last will group the animation together, making it accessible within Xcode by the identifier FileName-1
.
I happen to be using a MayaLT > .FBX > .DAE workflow and found that the service he linked does not work for me. This is because my .dae file was formatted poorly with some <source>
tags on the same line as double nested <animation>
tags. As a result the entire line was removed, corrupting the .dae file.
For anyone else using this workflow, here is the sed command I am running to clean up, hope it's helpful to someone!
sed -i .bak -e 's/\(.*\)<animation id.*><animation>\(.*\)/\1\2/g; s/\(.*\)<\/animation><\/animation>\(.*\)/\1\2/g; s/\(.*\)<library_animations>\(.*\)/\1<library_animations><animation>\2/g; s/\(.*\)<\/library_animations>\(.*\)/\1<\/animation><\/library_animations>\2/g' Walk.dae
回答3:
I'm working on the same issue but I'm using Maya, if you download SceneKit slides for WWDC 2014
The file AAPLSlideAnimationEvents.m has some examples of importing DAE files with multiple “untitled-animations” as you describe, hope it helps
回答4:
In case anyone else finds it useful I've written a python script which does just this. Providing an array of file paths the script will combine the animations into a single animation, remove geometry, and remove materials.
This new, slimmer dae can be used as your scenekit animation as long as the bones of your model you are applying the animation to are named and match exactly (as they should).
#!/usr/local/bin/python
# Jonathan Cardasis, 2018
#
# Cleans up a collada `dae` file removing all unnessasary data
# only leaving animations and bone structures behind.
# Combines multiple animation sequences into a single animation
# sequence for Xcode to use.
import sys
import os
import re
import subprocess
def print_usage(app_name):
print 'Usage:'
print ' {} [path(s) to collada file(s)...]'.format(app_name)
print ''
def xml_is_collada(xml_string):
return bool(re.search('(<COLLADA).*(>)', xml_string))
################
## MAIN ##
################
DAE_TAGS_TO_STRIP = ['library_geometries', 'library_materials', 'library_images']
if len(sys.argv) < 2:
app_name = os.path.basename(sys.argv[0])
print_usage(app_name)
sys.exit(1)
print 'Stripping collada files of non-animation essential features...'
failed_file_conversions = 0
for file_path in sys.argv[1:]:
try:
print 'Stripping {} ...'.format(file_path)
dae_filename = os.path.basename(file_path)
renamed_dae_path = file_path + '.old'
dae = open(file_path, 'r')
xml_string = dae.read().strip()
dae.close()
# Ensure is a collada file
if not xml_is_collada(xml_string):
raise Exception('Not a proper Collada file.')
# Strip tags
for tag in DAE_TAGS_TO_STRIP:
xml_string = re.sub('(?:<{tag}>)([\s\S]+?)(?:</{tag}>)'.format(tag=tag), '', xml_string)
# Combine animation keys into single key:
# 1. Remove all <animation> tags.
# 2. Add leading and trailing <library_animation> tags with single <animation> tag between.
xml_string = re.sub(r'\s*(<animation[^>]*>)\s*', '\n', xml_string)
xml_string = re.sub(r'\s*(<\/animation\s*>.*)\s*', '', xml_string)
xml_string = re.sub(r'\s*(<library_animations>)\s*', '<library_animations>\n<animation>\n', xml_string)
xml_string = re.sub(r'\s*(<\/library_animations>)\s*', '\n</animation>\n</library_animations>', xml_string)
# Rename original and dump xml to previous file location
os.rename(file_path, renamed_dae_path)
with open(file_path, 'w') as new_dae:
new_dae.write(xml_string)
print 'Finished processing {}. Old file can be found at {}.\n'.format(file_path, renamed_dae_path)
except Exception as e:
print '[!] Failed to correctly parse {}: {}'.format(file_path, e)
failed_file_conversions += 1
if failed_file_conversions > 0:
print '\nFailed {} conversion(s).'.format(failed_file_conversions)
sys.exit(1)
Usage:
python cleanupForXcodeColladaAnimation.py dancing_anim.dae
https://gist.github.com/joncardasis/e815ec69f81ed767389aa7a878f3deb6
回答5:
Delete the animations tags (see FlippinFun's solution) and read the link below on how to prepare Blender animations for play back with SceneKit (instructions are not for SceneKit but they work pretty well).
http://trac.wildfiregames.com/wiki/AnimationExportTutorial
回答6:
Here is how you can delete unnecessary xml nodes:
let currentDirectory = NSFileManager.defaultManager().currentDirectoryPath
let files = (try! NSFileManager.defaultManager().contentsOfDirectoryAtPath(currentDirectory)).filter { (fname:String) -> Bool in
return NSString(string: fname).pathExtension.lowercaseString == "dae"
}.map { (fname: String) -> String in
return "\(currentDirectory)/\(fname)"
}
//print(files)
for file in files {
print(file)
var fileContent = try! NSString(contentsOfFile: file, encoding: NSUTF8StringEncoding)
// remove all but not last </animation>
let closing_animation = "</animation>"
var closing_animation_last_range = fileContent.rangeOfString(closing_animation, options: NSStringCompareOptions.CaseInsensitiveSearch.union(NSStringCompareOptions.BackwardsSearch))
var closing_animation_current_range = fileContent.rangeOfString(closing_animation, options: NSStringCompareOptions.CaseInsensitiveSearch)
while closing_animation_current_range.location != closing_animation_last_range.location {
fileContent = fileContent.stringByReplacingCharactersInRange(closing_animation_current_range, withString: "")
closing_animation_current_range = fileContent.rangeOfString(closing_animation, options: NSStringCompareOptions.CaseInsensitiveSearch)
closing_animation_last_range = fileContent.rangeOfString(closing_animation, options: NSStringCompareOptions.CaseInsensitiveSearch.union(NSStringCompareOptions.BackwardsSearch))
}
// remove all but not first <animation .. >
let openning_animation_begin = "<animation "
let openning_animation_end = ">"
let openning_animation_begin_range = fileContent.rangeOfString(openning_animation_begin, options:NSStringCompareOptions.CaseInsensitiveSearch)
var openning_animation_end_range = fileContent.rangeOfString(openning_animation_begin, options: NSStringCompareOptions.CaseInsensitiveSearch.union(NSStringCompareOptions.BackwardsSearch))
while openning_animation_begin_range.location != openning_animation_end_range.location {
let openning_animation_end_location = fileContent.rangeOfString(openning_animation_end, options: .CaseInsensitiveSearch, range: NSRange.init(location: openning_animation_end_range.location, length: openning_animation_end.characters.count))
let lengthToRemove = NSString(string: fileContent.substringFromIndex(openning_animation_end_range.location)).rangeOfString(openning_animation_end, options:NSStringCompareOptions.CaseInsensitiveSearch).location + openning_animation_end.characters.count
let range = NSRange.init(location: openning_animation_end_range.location, length: lengthToRemove)
fileContent = fileContent.stringByReplacingCharactersInRange(range, withString: "")
openning_animation_end_range = fileContent.rangeOfString(openning_animation_begin, options: NSStringCompareOptions.CaseInsensitiveSearch.union(NSStringCompareOptions.BackwardsSearch))
}
// save
try! fileContent.writeToFile(file, atomically: true, encoding: NSUTF8StringEncoding)
}
回答7:
Here is my modification of n33kos's script to work with Mixamo resources.
sed -i .bak -e 's/\(.*\)<animation id.*<source\(.*\)/\1<source\2/g; s/\(.*\)<\/animation>\(.*\)/\1\2/g; s/\(.*\)<library_animations>\(.*\)/\1<library_animations><animation>\2/g; s/\(.*\)<\/library_animations>\(.*\)/\1<\/animation><\/library_animations>\2/g' Standing_Idle.dae
回答8:
Maybe the following observation is useful for someone: i directly imported untouched mixamo .dae (with animations) into xcode 10.2.1 In my case the Character "Big Vegas" (with a samba dance). the following code gives a list id's of animations:
var sceneUrl = Bundle.main.url(forResource: "Art.scnassets/Elvis/SambaDancingFixed", withExtension: "dae")!
if let sceneSource = SCNSceneSource(url: sceneUrl, options: nil){
let caAnimationIDs = sceneSource.identifiersOfEntries(withClass: CAAnimation.self)
caAnimationIDs.forEach({id in
let anAnimation = sceneSource.entryWithIdentifier(id, withClass: CAAnimation.self)
print(id,anAnimation)
})
}
OUTPUT:
animation/1 Optional(<CAAnimationGroup:0x283c05fe0; animations = (
"SCN_CAKeyframeAnimation 0x28324f5a0 (duration=23.833332, keyPath:/newVegas_Hips.transform)",
"SCN_CAKeyframeAnimation 0x28324f600 (duration=23.833332, keyPath:/newVegas_Pelvis.transform)",
"SCN_CAKeyframeAnimation 0x28324f690 (duration=23.833332, keyPath:/newVegas_LeftUpLeg.transform)",
"SCN_CAKeyframeAnimation 0x28324f750 (duration=23.833332, keyPath:/newVegas_LeftLeg.transform)",
"SCN_CAKeyframeAnimation 0x28324f810 (duration=23.833332, keyPath:/newVegas_LeftFoot.transform)",
"SCN_CAKeyframeAnimation 0x28324f8d0 (duration=23.833332, keyPath:/newVegas_RightUpLeg.transform)",
... and so on ...
As you may notice, "animation/1" appears to be an animation group which can be accessed with:
let sambaAnimation = sceneSource.entryWithIdentifier("animation/1", withClass: CAAnimation.self)
"sambaAnimation" can be applied to the parent-node of "Big Vegas":
self.addAnimation(sambaAnimation, forKey: "Dance")
in the case you download the same character with other animations on it you can pull out the animation as described:
let animation = sceneSource.entryWithIdentifier("animation/1", withClass: CAAnimation.self)
and apply it to your character.
回答9:
Here's a full script that you can use for doing the same thing without regular expressions. Copy paste the below code into a file like prep_dae_for_scenekit.py
.
Convert your file by doing ./prep_dae_for_scenekit.py input.dae -o output.dae
.
#!/usr/bin/env python3
import xml.etree.ElementTree as ET
import argparse
def main():
"""Read the existing filename, retrieve all animation elements and combine all the sub elements into a single animation element."""
input_filename, output_filename = parse_arguments()
# Register default namespace. We want to set this so our new file isn't prepended with ns0.
# We have to set this before reading the file so thats why we're parsing twice.
tree = ET.parse(input_filename)
root = tree.getroot()
namespace_url = root.tag.split("{")[1].split("}")[0]
namespace = f"{{{namespace_url}}}"
ET.register_namespace("", namespace_url)
# Parse the file
print(f"Parsing filename '{input_filename}'")
tree = ET.parse(input_filename)
root = tree.getroot()
library_animations_tag = f"{namespace}library_animations"
# Create a new compressed element with only a single animation tag
compressed_library = ET.Element(library_animations_tag)
compressed_animation = ET.SubElement(compressed_library, "animation")
for animation_item in root.find(library_animations_tag):
for item in animation_item:
compressed_animation.append(item)
# Overwrite existing library animations element with new one.
for idx, item in enumerate(root):
if item.tag == library_animations_tag:
break
root[idx] = compressed_library
# Write to file
print(f"Writing compressed file to '{output_filename}'")
tree.write(output_filename, xml_declaration=True, encoding="utf-8", method="xml")
def parse_arguments():
"""Parse command line arguments.
:return: (input_filename, output_filename)
"""
parser = argparse.ArgumentParser(
description="Script to collapse multiple animation elements into a single animation element. Useful for cleaning up .dae files before importing into iOS SceneKit."
)
parser.add_argument("filename", help="The input .dae filename")
parser.add_argument(
"-o",
"--output-filename",
help="The input .dae filename. defaults to new-<your filename>",
default=None,
)
args = parser.parse_args()
if args.output_filename is None:
output_filename = f"new-{args.filename}"
else:
output_filename = args.output_filename
return args.filename, output_filename
if __name__ == "__main__":
main()
来源:https://stackoverflow.com/questions/24539607/how-can-i-export-dae-files-for-use-in-scene-kit-without-seeing-untitled-animati