-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
runner
authored and
runner
committed
Jan 26, 2024
1 parent
0c1e228
commit 31615d7
Showing
33 changed files
with
1,229 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
Sources/HVCaptureSDK/HVCaptureSDK.docc/CaptureSDK.docc/CaptureSDK.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# ``_HVCameraExterior`` | ||
|
||
The HOVER capture experience | ||
|
||
@Metadata { | ||
@PageImage( | ||
purpose: icon, | ||
source: "hvcamera-icon", | ||
alt: "An icon representing the HVCamera framework.") | ||
@PageColor(blue) | ||
} | ||
|
||
## Overview | ||
|
||
The HOVER Capture SDK provides on-device functionality to facilitate HOVER 3D model creation. Specifically, it provides: | ||
|
||
- Sensor interaction for data acquisition | ||
- On-device data processing and transformation | ||
- Visual elements and user guidance to guide data acquisition and improve captured data | ||
- On-device local persistence | ||
- Robust data uploads to the HOVER backend | ||
|
||
Once the data has been captured and uploaded from the SDK, the HOVER backend will begin the 3D model processing. | ||
|
||
## Essentials | ||
|
||
@Links(visualStyle: compactGrid) { | ||
- <doc:GettingStarted> | ||
- <doc:BasicSDK> | ||
} |
160 changes: 160 additions & 0 deletions
160
Sources/HVCaptureSDK/HVCaptureSDK.docc/CaptureSDK.docc/GettingStarted.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
# GettingStarted | ||
|
||
Initialize the SDK, create a capture session and begin the capture flow UI. | ||
|
||
@Metadata { | ||
@PageImage( | ||
purpose: card, | ||
source: "hvcamera-icon", | ||
alt: "A stylized house.") | ||
@PageImage( | ||
purpose: icon, | ||
source: "hover-logo", | ||
alt: "An icon representing the HOVER logo framework.") | ||
@PageColor(blue) | ||
} | ||
|
||
## Overview | ||
|
||
The SDK is akin to an embedded app within a host app. It will run and attach its own `View`s to the `ViewController`, and run as a self-contained black box until the capture session is complete. | ||
This article will walk through the steps required to use the SDK within a host app. | ||
|
||
### Initializing the SDK | ||
|
||
The SDK should be initialized as early as possible in the app lifecycle. This is because the SDK does some background processing to set up required structures and upload any remaining captured data from past jobs that have yet to complete. | ||
As such, the SDK should (ideally) be initialized in host application’s `applicationDidFinishLaunching` method, so that the SDK can continue uploading any files that remain to be uploaded. This helps expedite 3D model generation, as we need all the captured images and metadata to begin the 3D reconstruction process. | ||
|
||
```swift | ||
import HVCamera | ||
class AppDelegate: UIResponder, UIApplicationDelegate { | ||
// ... | ||
|
||
@MainActor | ||
private func applicationDidFinishLaunching(_ notification: Notification) { | ||
HVCameraExterior.sharedInstance.initialize() | ||
} | ||
} | ||
``` | ||
|
||
While running this in `applicationDidFinishLaunching` would be ideal, at a minimum it should be run at some point prior to starting a capture session. | ||
|
||
### Creating a Capture Session | ||
|
||
The host app can launch the SDK in any way it sees fit, as long as there is an active `ViewController` running somewhere in the app. Here is a minimal example of launching the SDK capture flow on a button click using `SwiftUI`: | ||
|
||
```swift | ||
import HVCamera | ||
import SwiftUI | ||
|
||
struct FooView: View { | ||
let jobInfo: CaptureJobInformation | ||
let sessionSettings: HVCameraSettings | ||
|
||
// ... populate settings, etc. | ||
|
||
var body: some View { | ||
Button("Start Capture") { | ||
let captureTask = Task { | ||
do { | ||
try await HVCameraExterior.sharedInstance.startCaptureSession(settings: sessionSettings, info: jobInfo) | ||
try await HVCameraExterior.sharedInstance.startCaptureFlow() | ||
} catch let error as HVSessionError { | ||
// TODO: handle the known errors here | ||
print("Known capture flow error: \(error.localizedDescription)") | ||
} catch { | ||
// TODO: handle unknown errors, who knows what to do | ||
print("Unknown Capture Flow Error: \(error.localizedDescription)") | ||
} | ||
captureSessionCompleted() | ||
} | ||
} | ||
} | ||
|
||
func captureSessionCompleted() { | ||
// perform any next steps for post-capture session work | ||
} | ||
} | ||
``` | ||
|
||
Note that the SDK executes asynchronously, and the task that calls ``startCaptureSession`` will suspend until the capture flow completes. As such, there's a very linear flow to interacting with the SDK, and once the call's `await` returns, the host app knows the SDK's capture session is complete (as seen with the `captureSessionCompleted` function above). If the capture session encountered a fatal error, it will raise the error as an exception. | ||
|
||
#### Cancelling a Capture Session | ||
|
||
Since we execute asynchronously within a Swift ``Task``, we also honor its cancellation functionality and stop the capture session and capture flow UI if the task is cancelled. | ||
|
||
```swift | ||
let captureTask = Task { | ||
do { | ||
try await HVCameraExterior.sharedInstance.startCaptureSession(settings: sessionSettings, info: jobInfo) | ||
try await HVCameraExterior.sharedInstance.startCaptureFlow() | ||
} catch let error as HVSessionError { | ||
switch error.kind { | ||
case .UserCancelled: | ||
print("We've been cancelled!") | ||
} | ||
} | ||
} | ||
|
||
// You can cancel the task (and thereby, the capture flow + capture session) like this: | ||
DispatchQueue.main.asyncAfter(deadline: .now() + 10, execute: { | ||
captureTask.cancel() | ||
}) | ||
``` | ||
|
||
#### Monitoring Job Status | ||
|
||
Since the capture flow proceeds asynchronously, the host app may want to monitor the local job status as the capture proceeds. There are a few methods for obtaining Job status: | ||
|
||
1. on-demand: The ``HVCameraExterior`` class exposes a public method ``_HVCameraExterior.getClientJobStatus(for:)``. This is an `async` method that will return what the requested `Job`'s current status as a ``JobStatus``. If the requested ``Job`` doesn't exist locally, then it will raise a ``HVJobError`` exception. | ||
2. streaming: The ``HVCameraExterior`` class also exposes a public method ``getJobStateObservable`` that returns a `Combine` publisher for the requested `Job`. The publisher will emit ``JobStatus`` instances whenever there's a change in the `Job`'s status. Additionally, ``startCaptureSession`` will return what the current `Job`'s status is when called, so together with `getJobStateObservable` you can track the whole status history for the `Job` (n.b. the initial state won't be published for a `Job`, so to get the complete status history you need to use the initial state returned from ``_HVCameraExterior.startCaptureSession`` in conjunction with the publisher from `getJobStateObservable`). The initial Job state will generally be ``JobStatus.Created`` if newly created, or ``JobStatus.Draft`` if resuming an existing Job. | ||
|
||
For example, adapting the previous example to monitor the `Job` status and build a complete `JobStatus` history for the capture session, you can do: | ||
|
||
```swift | ||
import HVCamera | ||
import SwiftUI | ||
|
||
struct FooView: View { | ||
let jobInfo: CaptureJobInformation | ||
let sessionSettings: HVCameraSettings | ||
|
||
// if you you want monitor multiple job's statuses | ||
@State private var jobCancellables = [JobIdentifier: AnyCancellable]() | ||
@State private var jobStatusHistory = [JobIdentifier: [JobStatus]]() | ||
|
||
// ... populate settings, etc. | ||
|
||
var body: some View { | ||
Button("Start Capture") { | ||
let captureTask = Task { | ||
do { | ||
let jobState = try await HVCameraExterior.sharedInstance.startCaptureSession(settings: sessionSettings, info: jobInfo) | ||
jobStatusHistory[jobInfo.identifier]?.append(jobStatus) | ||
// check if we have a listener for the job already, so we don't make duplicate listeners each time the view is created | ||
if jobCancellables[jobInfo.identifier] == nil { | ||
let cancellable = HVCameraExterior.sharedInstance.getJobStateObservable(for: jobInfo.identifier).sink(receiveValue: { (jobState: JobStatus) in | ||
// NOTE: you can t ake various actions here based on the status change | ||
if case let .UploadProgress(_, uploadStatus) = jobState { | ||
print("Job@State: \(jobState) --> File@State: \(String(describing: uploadStatus))") | ||
} else if case let .Error(_, error) = jobState { | ||
print("Job@State: \(jobState) --> Error: \(error)") | ||
} else { | ||
print("Job@State: \(jobState)") | ||
} | ||
jobStatusHistory[jobInfo.identifier]?.append(jobStatus) | ||
}) | ||
jobCancellables[jobInfo.identifier] = cancellable | ||
} | ||
try await HVCameraExterior.sharedInstance.startCaptureFlow() | ||
} catch let error as HVSessionError { | ||
// TODO: handle the known errors here | ||
print("Known capture flow error: \(error.localizedDescription)") | ||
} catch { | ||
// TODO: handle unknown errors, who knows what to do | ||
print("Unknown Capture Flow Error: \(error.localizedDescription)") | ||
} | ||
captureSessionCompleted() | ||
} | ||
} | ||
} | ||
``` |
58 changes: 58 additions & 0 deletions
58
...SDK/HVCaptureSDK.docc/CaptureSDK.docc/Resources/code-files/tutorial-hvtheme-code-01.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import SwiftUI | ||
|
||
struct HVTheme { | ||
// ... | ||
public init( | ||
primary: Color? = nil, | ||
onPrimary: Color? = nil, | ||
primaryContainer: Color? = nil, | ||
onPrimaryContainer: Color? = nil, | ||
secondary: Color? = nil, | ||
onSecondary: Color? = nil, | ||
subtleElements: Color? = nil, | ||
error: Color? = nil, | ||
onError: Color? = nil, | ||
surface: Color? = nil, | ||
onSurface: Color? = nil, | ||
textPrimary: Color? = nil, | ||
textSecondary: Color? = nil, | ||
textPrimaryDark: Color? = nil, | ||
footerCTABackground: Color? = nil, | ||
toolbarBack: Image? = nil, | ||
toolbarClose: Image? = nil, | ||
permissionCameraHeader: String? = nil, | ||
permissionCameraBody: String? = nil, | ||
permissionCameraConfirm: String? = nil, | ||
permissionCameraDeniedButtonText: String? = nil, | ||
permissionLocationHeader: String? = nil, | ||
permissionLocationBody: String? = nil, | ||
permissionLocationConfirm: String? = nil, | ||
galleryDeleteConfirmHeader: String? = nil, | ||
galleryDeleteConfirmBody: String? = nil, | ||
successIcon: Image? = nil, | ||
successMessage: String? = nil, | ||
successNextSteps: String? = nil, | ||
cameraCaptureBackground: Color? = nil, | ||
toolbarBackground: Color? = nil, | ||
toolbarHeader: Color? = nil, | ||
toolbarIcon: Color? = nil, | ||
textLink: Color? = nil, | ||
illustrationMainStroke: Color? = nil, | ||
illustrationSubStroke: Color? = nil, | ||
gridItemBorder: Color? = nil, | ||
gridItemBackground: Color? = nil, | ||
gridItemBorderRadius: CGFloat? = nil, | ||
primaryButtonBackground: HVColorStateList? = nil, | ||
primaryButtonText: HVColorStateList? = nil, | ||
primaryButtonShadowBackground: HVColorStateList? = nil, | ||
buttonCornerRadius: CGFloat? = nil, | ||
secondaryButtonBackground: HVColorStateList? = nil, | ||
secondaryButtonText: HVColorStateList? = nil, | ||
secondaryButtonShadowBackground: HVColorStateList? = nil, | ||
secondaryButtonStrokeColor: HVColorStateList? = nil, | ||
secondaryButtonStrokeWidth: CGFloat? = nil, | ||
toolbarLogo: (() -> AnyView)? = nil | ||
) { | ||
// ... | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
...SDK/HVCaptureSDK.docc/CaptureSDK.docc/Resources/code-files/tutorial-hvtheme-code-02.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import SwiftUI | ||
|
||
public struct HVColorStateList { | ||
// ... | ||
public init(active: Color? = nil, pressed: Color? = nil, inactive: Color? = nil) { | ||
// ... | ||
} | ||
} | ||
|
||
struct HVTheme { | ||
// ... | ||
public init( | ||
primary: Color? = nil, | ||
onPrimary: Color? = nil, | ||
primaryContainer: Color? = nil, | ||
onPrimaryContainer: Color? = nil, | ||
secondary: Color? = nil, | ||
onSecondary: Color? = nil, | ||
subtleElements: Color? = nil, | ||
error: Color? = nil, | ||
onError: Color? = nil, | ||
surface: Color? = nil, | ||
onSurface: Color? = nil, | ||
textPrimary: Color? = nil, | ||
textSecondary: Color? = nil, | ||
textPrimaryDark: Color? = nil, | ||
footerCTABackground: Color? = nil, | ||
toolbarBack: Image? = nil, | ||
toolbarClose: Image? = nil, | ||
permissionCameraHeader: String? = nil, | ||
permissionCameraBody: String? = nil, | ||
permissionCameraConfirm: String? = nil, | ||
permissionCameraDeniedButtonText: String? = nil, | ||
permissionLocationHeader: String? = nil, | ||
permissionLocationBody: String? = nil, | ||
permissionLocationConfirm: String? = nil, | ||
galleryDeleteConfirmHeader: String? = nil, | ||
galleryDeleteConfirmBody: String? = nil, | ||
successIcon: Image? = nil, | ||
successMessage: String? = nil, | ||
successNextSteps: String? = nil, | ||
cameraCaptureBackground: Color? = nil, | ||
toolbarBackground: Color? = nil, | ||
toolbarHeader: Color? = nil, | ||
toolbarIcon: Color? = nil, | ||
textLink: Color? = nil, | ||
illustrationMainStroke: Color? = nil, | ||
illustrationSubStroke: Color? = nil, | ||
gridItemBorder: Color? = nil, | ||
gridItemBackground: Color? = nil, | ||
gridItemBorderRadius: CGFloat? = nil, | ||
primaryButtonBackground: HVColorStateList? = nil, | ||
primaryButtonText: HVColorStateList? = nil, | ||
primaryButtonShadowBackground: HVColorStateList? = nil, | ||
buttonCornerRadius: CGFloat? = nil, | ||
secondaryButtonBackground: HVColorStateList? = nil, | ||
secondaryButtonText: HVColorStateList? = nil, | ||
secondaryButtonShadowBackground: HVColorStateList? = nil, | ||
secondaryButtonStrokeColor: HVColorStateList? = nil, | ||
secondaryButtonStrokeWidth: CGFloat? = nil, | ||
toolbarLogo: (() -> AnyView)? = nil | ||
) { | ||
// ... | ||
} | ||
} |
Oops, something went wrong.