Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2023 Spatial Computing

WWDC23 · 23 min · Spatial Computing

Explore enhancements to RoomPlan

Join us for an exciting update to RoomPlan as we explore MultiRoom support and enhancements to room representations. Learn how you can scan areas with more detail, capture multiple rooms, and merge individual scans into one larger structure. We’ll also share workflows and best practices when working with RoomPlan results that you want to combine into your existing 3D model library.

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 10 snippets

RoomPlan with custom ARSession swift · at 3:00 ↗
// RoomCaptureSession

public class RoomCaptureSession {
    // Init: ARSession is an optional input for RoomCaptureSession
    public init(arSession: ARSession? = nil) {
       ...
  }

    // Stop: pauseARSession is used for whether to continue ARSession experience
    public func stop(pauseARSession: Bool = true) {
       ...
  }
}
MultiRoom support with Continuous ARSession swift · at 5:50 ↗
// Continuous ARSession

// start 1st scan
roomCaptureSession.run(configuration: captureSessionConfig)

// stop 1st scan with continuing ARSession
roomCaptureSession.stop(pauseARSession: false)

// start 2nd scan
roomCaptureSession.run(configuration: captureSessionConfig)

// stop 2nd scan (pauseARSession = true by default)
roomCaptureSession.stop()
MultiRoom capture with loading ARWorldMap swift · at 7:30 ↗
// Capture with loading ARWorldMap

// load ARWorldMap
let arWorldMap = try NSKeyedUnarchiver.unarchivedObject(ofClass: ARWorldMap.self, from: data)

// run ARKit relocalization
let arWorldTrackingConfig = ARWorldTrackingConfiguration()
arWorldTrackingConfig.initialWorldMap = arWorldMap
roomCaptureSession.init()
roomCaptureSession.arSession.run(arWorldTrackingConfig, options: [])

// Wait for relocalization to complete
// start 2nd scan
roomCaptureSession.run(configuration: captureSessionConfig)

// stop 2nd scan
roomCaptureSession.stop()
StructureBuilder swift · at 9:40 ↗
// StructureBuilder

// create structureBuilder instance
let structureBuilder = StructureBuilder(option: [.beautifyObjects])

// load multiple capturedRoom results to capturedRoomArray
var capturedRoomArray: [CapturedRoom] = []

// run structureBuilder API to get capturedStructure
let capturedStructure = try await structureBuilder.capturedStructure(from: capturedRoomArray)

// export capturedStructure to usdz
try capturedStructure.export(to: destinationURL)
CapturedStructure swift · at 10:11 ↗
// CapturedStructure

public struct CapturedStructure: Codable, Sendable {
    public var rooms: [CapturedRoom]

    public var walls: [Surface]
    public var doors: [Surface]
    public var windows: [Surface]
    public var openings: [Surface]
    public var objects: [Object]
    public var floors: [Surface]
    public var sections: [Section]

    public func export(to url: URL, metadataURL: URL? = nil, modelProvider: ModelProvider? = nil, exportOptions: USDExportOptions = .mesh) throws
}
Parse attributes and categories to create folder hierarchy swift · at 19:20 ↗
// Parse attributes and categories to create folder hierarchy

for category in CapturedRoom.Object.Category.allCases {

    let url = generateFolderURL(category: category, attributes: [])
    FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)

    for attributes in category.supportedCombinations {
        let url = generateFolderURL(category: category, attributes: attributes)
        FileManager.default.createDirectory(at: url, withIntermediateDirectories: true)
    }
}
Create a Catalog index swift · at 20:00 ↗
// Create a Catalog index

struct RoomPlanCatalog: Codable {
    let categoryAttributes: [RoomPlanCatalogCategoryAttribute]
}

struct RoomPlanCatalogCategoryAttribute: Codable {
    enum CodingKeys: String, CodingKey {
        case folderRelativePath
        case category
        case attributes
        case modelFilename
    }
    let category: CapturedRoom.Object.Category
    let attributes: [any CapturedRoomAttribute]
    let folderRelativePath: String
    private(set) var modelFilename: String? = nil
    
    func encode(to encoder: Encoder) throws {
       
    }
}
Create a Catalog bundle swift · at 20:15 ↗
//  Create a Catalog bundle

let catalog = RoomPlanCatalog(categoryAttributes: categoryAttributes)
let plistEncoder = PropertyListEncoder()
let data = try plistEncoder.encode(catalog)
let catalogURL = inputURL.appending(path: "catalog.plist")
try data.write(to: catalogURL)
let fileWrapper = try FileWrapper(url: inputURL)
try fileWrapper.write(to: outputURL, options: [.atomic, .withNameUpdating],
                      originalContentsURL: nil)
Instantiate a Model Provider from a Catalog swift · at 20:22 ↗
// Instantiate a Model Provider from a Catalog

for categoryAttribute in catalog.categoryAttributes {
    guard let modelFilename = categoryAttribute.modelFilename else {
        continue 
    }
    let folderRelativePath = categoryAttribute.folderRelativePath
    let modelURL = url.appending(path: folderRelativePath).appending(path: modelFilename)
    if categoryAttribute.attributes.isEmpty {
       try modelProvider.setModelFileURL(modelURL, for: categoryAttribute.category)
    } else {
       try modelProvider.setModelFileURL(modelURL, for: categoryAttribute.attributes)
    }
}
Exporting a captured room to usdz with models swift · at 20:47 ↗
// Exporting a captured room to usdz with models

try capturedRoom.export(to: outputURL, modelProvider: modelProvider, 
                        exportOptions: .model)