Hello! I've been trying to create a custom USDZ viewer using the Vision Pro. Basically, I want to be able to load in a file and have a custom control system I can use to transform, playback animations, etc.
I'm getting stuck right at the starting line however. As far as I can tell, the only way to access the file system through SwiftUI is to use the DocumentGroup struct to bring up the view. This requires implementing a file type through the FileDocument protocol.
All of the resources I'm finding use text files as their example, so I'm unsure of how to implement USDZ files. Here is the FileDocument I've built so far:
import SwiftUI
import UniformTypeIdentifiers
import RealityKit
struct CoreUsdzFile: FileDocument {
// we only support .usdz files
static var readableContentTypes = [UTType.usdz]
// make empty by default
var content: ModelEntity = .init()
// initializer to create new, empty usdz files
init(initialContent: ModelEntity = .init()){
content = initialContent
}
// import or read file
init(configuration: ReadConfiguration) throws {
if let data = configuration.file.regularFileContents {
// convert file content to ModelEntity?
content = ModelEntity.init(mesh: data)
} else {
throw CocoaError(.fileReadCorruptFile)
}
}
// save file wrapper
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
let data = Data(content)
return FileWrapper(regularFileWithContents: data)
}
}
My errors are on conversion of the file data into a ModelEntity and the reverse. I'm not sure if ModelEntity is the correct typing here, but as far as I can tell .usdz files are imported as ModelEntities.
Any help is much appreciated!
Dylan
For example, this demonstrates how to load a USDz file from a file picker, as demonstrated here: https://vimeo.com/922469524
When you tap the button, it loads the system file picker and allows the user to load a USDz file from their downloads or locally saved files.
import RealityKit
import SwiftUI
@main
struct Application: App {
@State var entity: Entity? = nil
@State var showFilePicker: Bool = false
var body: some SwiftUI.Scene {
WindowGroup {
VStack {
// A view for displaying the loaded asset.
//
RealityView(
make: { content in
// Add a placeholder entity to parent the entity to.
//
let placeholderEntity = Entity()
placeholderEntity.name = "$__placeholder"
if let loadedEntity = self.entity {
placeholderEntity.addChild(loadedEntity)
}
content.add(placeholderEntity)
},
update: { content in
guard let placeholderEntity = content.entities.first(where: {
$0.name == "$__placeholder"
}) else {
preconditionFailure("Unable to find placeholder entity")
}
// If there is a loaded entity, remove the old child,
// and add the new one.
//
if let loadedEntity = self.entity {
placeholderEntity.children.removeAll()
placeholderEntity.addChild(loadedEntity)
}
}
)
// A button that displays a file picker for loading a USDZ.
//
Button(
action: {
showFilePicker.toggle()
},
label: {
Text("Load USDZ")
}
)
.padding()
}
.fileImporter(isPresented: $showFilePicker, allowedContentTypes: [.usdz]) { result in
// Get the URL of the USDZ picked by the user.
//
guard let url = try? result.get() else {
print("Unable to get URL")
return
}
Task {
// As the app is sandboxed, permission needs to be
// requested to access the file, as it's outside of
// the sandbox.
//
if url.startAccessingSecurityScopedResource() {
defer {
url.stopAccessingSecurityScopedResource()
}
// Load the USDZ asynchronously.
//
self.entity = try await Entity(contentsOf: url)
}
}
}
}
}
}