如何使用 ModelIO 以编程方式将 3D 网格导出为 USDZ?

How to programmatically export 3D mesh as USDZ using ModelIO?

是否可以使用 ModelIO 和 MetalKit 框架以编程方式将 3D 网格导出为 .usdz 文件格式?

这是一个代码:

import ARKit
import RealityKit
import MetalKit
import ModelIO

let asset = MDLAsset(bufferAllocator: allocator)
asset.add(mesh)

let filePath = FileManager.default.urls(for: .documentDirectory, 
                                         in: .userDomainMask).first!
    
let usdz: URL = filePath.appendingPathComponent("model.usdz")

do {
    try asset.export(to: usdz)               
    let controller = UIActivityViewController(activityItems: [usdz], 
                                      applicationActivities: nil)
    controller.popoverPresentationController?.sourceView = sender
    self.present(controller, animated: true, completion: nil)
} catch let error {
    fatalError(error.localizedDescription)
}

当我按下 保存 按钮时出现错误。

2021 年 6 月 24 日

目前 Apple 开发人员可以使用 canExportFileExtension(_:) 类型方法导出 .usd.usda.usdc 文件:

let usd = MDLAsset.canExportFileExtension("usd")
let usda = MDLAsset.canExportFileExtension("usda")
let usdc = MDLAsset.canExportFileExtension("usdc")
let usdz = MDLAsset.canExportFileExtension("usdz")
    
print(usd, usda, usdc, usdz)

它打印:

true true true false

但是,您可以使用名为 write(to:options:delegate:progressHandler:).

的实例方法轻松地将 SceneKit 的场景导出为 .usdz 文件
let path = FileManager.default.urls(for: .documentDirectory,
                                     in: .userDomainMask)[0]
                                         .appendingPathComponent("file.usdz")
    
sceneKitScene.write(to: path, 
               options: nil, 
              delegate: nil, 
       progressHandler: nil)

Andy Jazz 的答案是正确的,但需要修改才能在 SwiftUI 沙盒应用程序中工作:

首先,需要渲染 SCNScene 才能正确导出。您不能创建一堆节点,将它们塞入场景的根节点并调用 write() 并获得正确渲染的 usdz。它必须首先在 SwiftUI SceneView 中显示在屏幕上,这会导致加载所有资产等。我想你可以实例化一个 SCNRenderer 并在根节点上调用 prepare(),但是这有一些额外的并发症。

其次,沙盒阻止直接导出到 .fileExporter() 提供的 URL。这是因为 Scene.write() 分两步工作:它首先创建一个 .usdc 导出,然后将生成的文件压缩到一个 .usdz 中。中间文件没有 .fileExporter() 提供的 URL 的写入权限(假设您已将沙盒“用户选择的文件”权限设置为“Read/Write”),因此 Scene.write() 失败,即使目标 URL 是可写的,如果目标目录在沙箱之外。

我的解决方案是编写自定义 FileWrapper,如果 WriteConfiguration UTType 是 .usdz,我 return:

public class USDZExportFileWrapper: FileWrapper {
    var exportScene: SCNScene

    public init(scene: SCNScene) {
        exportScene = scene
        super.init(regularFileWithContents: Data())
    }

    required init?(coder inCoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override public func write(to url: URL,
                               options: FileWrapper.WritingOptions = [],
                               originalContentsURL: URL?) throws {
        let tempFilePath = NSTemporaryDirectory() + UUID().uuidString + ".usdz"
        let tempURL = URL(fileURLWithPath: tempFilePath)
        exportScene.write(to: tempURL, delegate: nil)
        try FileManager.default.moveItem(at: tempURL, to: url)
    }
}

ReferenceFileDocument中的用法:

public func fileWrapper(snapshot: Data, configuration: WriteConfiguration) throws -> FileWrapper {
    if configuration.contentType == .usdz {
        return USDZExportFileWrapper(scene: scene)
    }

    return .init(regularFileWithContents: snapshot)
}