我正在尝试将在Cheetah 3D和Blender 3D中创建的动画加载到Scene Kit中,但我得到的是一堆"无标题动画"每一个都是同一个动画。
有没有人知道如何从Blender或Cheetah 3D中正确导出这些,以便Scene Kit可以使用它们?
答案 0 :(得分:29)
我挖了这个因为它也让我烦恼。所有“无标题动画”都是每个骨骼的单独动画。您可以从xcode右侧面板中的属性inspecter获取id。像这样使用swift,你可以得到你的动画。
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
必须对所有骨架中的骨骼进行此操作。 **恼人!!! *
Apple使用3dmax进行所有示例项目,这些项目仅显示每个collada文件的一个动画。这是因为3dmax在一个动画下输出所有骨骼,而搅拌机将每个骨骼分开。
临时工作 使用TextEdit或在.dae文件的末尾添加.xml扩展名,并使用xml编辑器打开它(很多都是免费的在线)。 xml编辑器更容易使用。向下滚动到动画起始块。它看起来就像......
<library_animations>
<animation id= "first_bone">
<source id= "first_bone-input">
将其更改为...
<library_animations>
<animation> ---this is the only modified line
<source id="first_bone-input">
每个动画结束时都会像这样结束......
</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">
当然,在你最后一个骨头的末尾离开动画结束块就像这样......
</animation>
</library_animations>
这将在.dae文件中为您提供一个动画,该动画与您的文件同名,并在末尾添加-1!
编辑 - 以下是Automator服务的链接,该服务将为您转换上述代码!
Automater collada converter download
将文件解压缩并放入〜/ Library / services文件夹中。从那里你可以右键单击你的collada文件并向下滚动到ConvertToXcodeCollada和presto!完成后会弹出一个窗口(大约半秒钟)。
答案 1 :(得分:4)
这是因为.dae文件中的每个骨骼都有自己的<animation>
标记。
FlippinFun正确地说明删除除了之外的所有打开和关闭<animation>
标记的第一个和最后一个将动画组合在一起,使其可以在Xcode中通过标识符{{1}访问}。
我碰巧使用的是MayaLT&gt; .FBX&gt; .DAE工作流程,发现他链接的服务对我不起作用。这是因为我的.dae文件格式很差,在同一行中有一些FileName-1
标记与双嵌套<source>
标记。结果整个行被删除,破坏了.dae文件。
对于使用此工作流程的任何其他人,这是我正在运行的清理的sed命令,希望它对某人有帮助!
<animation>
答案 2 :(得分:1)
我正在处理同一个问题,但如果您下载SceneKit slides for WWDC 2014
我正在使用Maya文件AAPLSlideAnimationEvents.m有一些导入DAE文件的例子,如你所描述的那样有多个“无标题动画”,希望有所帮助
答案 3 :(得分:1)
如果其他人发现它有用,我已经编写了一个python脚本,它就是这样做的。提供一个文件路径数组,脚本将动画组合成一个动画,删除几何体,并删除材料。
这个新的,纤细的dae可以用作你的场景包动画,只要你应用动画的模型的骨骼被命名并完全匹配(就像它们应该的那样)。
#!/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)
使用方法:
python cleanupForXcodeColladaAnimation.py dancing_anim.dae
https://gist.github.com/joncardasis/e815ec69f81ed767389aa7a878f3deb6
答案 4 :(得分:0)
删除动画标签(请参阅FlippinFun的解决方案)并阅读以下链接,了解如何使用SceneKit准备Blender动画以进行回放(说明不适用于SceneKit,但它们的效果非常好)。
答案 5 :(得分:0)
以下是删除不必要的xml节点的方法:
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)
}
答案 6 :(得分:0)
这是我修改n33kos的脚本以使用Mixamo资源。
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
答案 7 :(得分:0)
以下观察可能对某人有用: 我直接将未修改的mixamo .dae(带有动画)导入到xcode 10.2.1中 以我为例,角色“ Big Vegas”(带有桑巴舞)。 以下代码给出了动画的列表ID:
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)
})
}
输出:
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 ...
您可能会注意到,“ animation / 1”似乎是一个动画组,可以通过以下方式进行访问:
let sambaAnimation = sceneSource.entryWithIdentifier("animation/1", withClass: CAAnimation.self)
“ sambaAnimation”可以应用于“ Big Vegas”的父节点:
self.addAnimation(sambaAnimation, forKey: "Dance")
如果您下载了带有其他动画的相同角色,则可以按照以下说明拉出动画:
let animation = sceneSource.entryWithIdentifier("animation/1", withClass: CAAnimation.self)
并将其应用于您的角色。
答案 8 :(得分:0)
这是一个完整的脚本,无需正则表达式即可用于执行相同的操作。将以下代码复制粘贴到prep_dae_for_scenekit.py
之类的文件中。
通过执行./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()
答案 9 :(得分:0)
对我来说,自动脚本无法使用。.我写了一些小的python脚本,将所有动画组合为一个。
import sys
import re
fileNameIn = sys.argv[1]
fileNameOut = fileNameIn + '-e' #Output file will contain suffix '-e'
fileIn = open(fileNameIn, 'r')
data = fileIn.read()
fileIn.close()
splitted = re.split(r'<animation id=[^>]+>', data)
result = splitted[0] + '<animation>' + "".join(splitted[1:])
splitted = result.split('</animation>')
result = "".join(splitted[:-1]) + '</animation>' + splitted[-1]
fileOut = open(fileNameOut, 'wt')
fileOut.write(result)
fileOut.close()
您可以在这里找到它:link
用法:python fix_dae_script.py <file.dae>
答案 10 :(得分:0)
问题:您的 DAE 文件中有太多 <animation></animation>
标签
当您将 DAE 文件作为 XML 或文本文件打开时,您应该会看到许多 <animation id="..." name="..."></animation>
对,但您只需要一对就可以在 Xcode 中使用它。
解决方案:从 DAE 文件中删除那些标签,除了一对
所以你需要删除那些标签,除了一个。虽然您可以手动执行此操作,但使用 Python 脚本执行此操作要容易得多。这是写的解决方案。
import re
input_file = open("/file/path/to/input/.dae", "r")
output_file = open("/file/path/to/output/.dae", "w")
# Scan the file to count the number of <animation>
count = 0
lines = []
for line in input_file.readlines():
if re.search("^\s+<animation.*?>", line):
count += 1
lines.append(line)
# Delete all <animation> tags except the first one,
# and delete all </animation> tags except the last one.
count_start = 0
count_end = 0
for line in lines:
result = re.findall("^\s+<animation.*?>", line)
if len(result) > 0:
count_start += 1
# Check if the <animation> tag is the first one
if count_start == 1:
line = re.sub("^\s+<animation.*?>", "<animation>", line)
output_file.write(line)
continue
line = re.sub("^\s+<animation.*?>", "", line)
result = re.findall("</animation>", line)
if len(result) > 0:
count_end += 1
# Check if the </animation> tag is the last one
if count_end == count:
output_file.write(line)
continue
line = re.sub("</animation>", "", line)
output_file.write(line)
您可以通过键入以下命令在终端中运行代码。运行脚本时,确保更改脚本中的输入和输出文件路径。
python3 script.py