Workflows
Workflows Guide
Section titled “Workflows Guide”Learn common workflows and best practices for using the Roboscope 2 platform.
Workflow 1: Basic Inspection
Section titled “Workflow 1: Basic Inspection”Complete workflow for conducting an inspection session.
1. Create a Space
Section titled “1. Create a Space”curl -X POST http://localhost:8080/api/v1/spaces \ -H "Content-Type: application/json" \ -d '{ "key": "warehouse-a1", "name": "Warehouse A1", "description": "Main storage warehouse", "calibration_vector": [0, 0, 0] }'Response: Space object with id
2. Start Work Session
Section titled “2. Start Work Session”curl -X POST http://localhost:8080/api/v1/work-sessions \ -H "Content-Type: application/json" \ -d '{ "space_id": "SPACE_UUID", "session_type": "inspection", "status": "active", "started_at": "2025-01-16T09:00:00Z" }'Response: Work session object with id
3. Join Session (iOS)
Section titled “3. Join Session (iOS)”let sessionId = UUID(uuidString: "SESSION_UUID")!
// Join presencetry await PresenceService.shared.joinSession(sessionId)
// Start heartbeatstartHeartbeat(sessionId: sessionId)4. Place Markers in AR
Section titled “4. Place Markers in AR”// In AR view, capture 4 corner pointslet corners: [SIMD3<Float>] = [point1, point2, point3, point4]
let marker = CreateMarker( workSessionId: sessionId, label: "Crack in wall", p1: corners[0].toDoubleArray(), p2: corners[1].toDoubleArray(), p3: corners[2].toDoubleArray(), p4: corners[3].toDoubleArray(), color: "#FF0000", customProps: [ "severity": AnyCodable("medium"), "type": AnyCodable("crack"), "inspector": AnyCodable("John Doe") ])
let created = try await MarkerService.shared.createMarker(marker)5. Calculate Marker Details
Section titled “5. Calculate Marker Details”# Auto-calculate all marker details for the sessioncurl -X POST http://localhost:8080/api/v1/work-sessions/SESSION_UUID/markers/calculate-detailsResponse: Count of calculated details
6. Complete Session
Section titled “6. Complete Session”let update = UpdateWorkSession( status: .done, completedAt: Date(), version: currentSession.version)
let completed = try await WorkSessionService.shared.updateWorkSession( id: sessionId, update: update)7. Leave Session
Section titled “7. Leave Session”try await PresenceService.shared.leaveSession(sessionId)stopHeartbeat()8. Export Session
Section titled “8. Export Session”# Export for backup or transfercurl http://localhost:8080/api/v1/work-sessions/SESSION_UUID/export \ -o inspection_session.jsonWorkflow 2: Model Alignment with ICP
Section titled “Workflow 2: Model Alignment with ICP”Align a 3D model with a real-world LiDAR scan.
Prerequisites
Section titled “Prerequisites”- 3D model file (USDC/USDZ)
- LiDAR-capable iOS device
- Alignment server running
1. Upload Model to Space
Section titled “1. Upload Model to Space”curl -X POST http://localhost:8080/api/v1/spaces \ -H "Content-Type: application/json" \ -d '{ "key": "room-101", "name": "Conference Room 101", "model_usdc_url": "https://storage.example.com/models/room101.usdc", "calibration_vector": [0, 0, 0] }'2. Load Model in AR (iOS)
Section titled “2. Load Model in AR (iOS)”import RealityKit
// Load USDC modellet modelEntity = try await ModelEntity(named: "room.usdc")
// Place model 1.5m in front of cameralet transform = simd_float4x4( translation: SIMD3<Float>(0, 0, -1.5))
let anchor = AnchorEntity(world: transform)anchor.addChild(modelEntity)arView.scene.addAnchor(anchor)3. Start LiDAR Scanning
Section titled “3. Start LiDAR Scanning”// Start mesh reconstructioncaptureSession.startScanning()
// Scan for 10-15 seconds to capture environment// UI shows scan progress and mesh coverage4. Export Scan to OBJ
Section titled “4. Export Scan to OBJ”let progress: (Double) -> Void = { percent in print("Export progress: \(percent)%")}
let objData = try await captureSession.exportMeshData( progress: progress, completion: { data in return data })5. Send to Alignment Server
Section titled “5. Send to Alignment Server”let serverURL = "http://localhost:6000/align"
// POST OBJ datalet response = try await URLSession.shared.upload( for: URLRequest(url: URL(string: serverURL)!), from: objData)
// Parse transformation matrixlet result = try JSONDecoder().decode(AlignmentResult.self, from: response)let transformMatrix = result.matrix // [[Float; 4]; 4]6. Apply Transformation
Section titled “6. Apply Transformation”// Convert to simd_float4x4let transform = simd_float4x4(transformMatrix)
// Update model anchoranchor.transform.matrix = transform7. Verify Alignment
Section titled “7. Verify Alignment”// Visual inspection// Model should align with real-world geometry
// Optional: Create markers to verify alignment accuracylet verificationMarkers = createAlignmentVerificationMarkers()Workflow 3: Space Calibration
Section titled “Workflow 3: Space Calibration”Apply calibration to transform marker coordinates.
1. Determine Calibration Vector
Section titled “1. Determine Calibration Vector”// Measure known reference point in ARlet referencePointAR = SIMD3<Float>(1.2, 0.5, 3.4)
// Known position in real-world coordinate systemlet referencePointReal = SIMD3<Float>(0.0, 0.5, 0.0)
// Calculate calibration vectorlet calibrationVector = referencePointReal - referencePointAR// Result: [-1.2, 0.0, -3.4]2. Update Space Calibration
Section titled “2. Update Space Calibration”curl -X PATCH http://localhost:8080/api/v1/spaces/SPACE_UUID \ -H "Content-Type: application/json" \ -d '{ "calibration_vector": [-1.2, 0.0, -3.4] }'3. Calibrate All Markers in Session
Section titled “3. Calibrate All Markers in Session”curl -X POST http://localhost:8080/api/v1/work-sessions/SESSION_UUID/calibrate-markersResponse: Array of markers with populated calibrated_data
4. Verify Calibration
Section titled “4. Verify Calibration”func verifyCalibration(marker: Marker, calibrationVector: [Double]) { guard let calibrated = marker.calibratedData else { print("No calibrated data") return }
// Check that calibrated = raw + calibration vector let expected = [ marker.p1[0] + calibrationVector[0], marker.p1[1] + calibrationVector[1], marker.p1[2] + calibrationVector[2] ]
let diff = abs(calibrated.p1[0] - expected[0]) print("Calibration difference: \(diff)m") // Should be very small (<1mm)}Workflow 4: Collaborative Editing
Section titled “Workflow 4: Collaborative Editing”Multiple users working on the same session.
1. User A Joins Session
Section titled “1. User A Joins Session”// User Atry await PresenceService.shared.joinSession( sessionId, userId: "user-a", userName: "Alice")
// Start heartbeatstartHeartbeat(sessionId: sessionId, userId: "user-a")2. User B Joins Session
Section titled “2. User B Joins Session”// User Btry await PresenceService.shared.joinSession( sessionId, userId: "user-b", userName: "Bob")
// Start heartbeatstartHeartbeat(sessionId: sessionId, userId: "user-b")3. View Active Users
Section titled “3. View Active Users”let activeUsers = try await PresenceService.shared.listActiveUsers(sessionId)// Returns: ["Alice", "Bob"]4. User A Acquires Edit Lock
Section titled “4. User A Acquires Edit Lock”let acquired = try await LockService.shared.acquireLock( sessionId: sessionId, userId: "user-a", ttl: 60 // 60 second lock)
if acquired { // User A can now edit print("Lock acquired")} else { print("Session is locked by another user")}5. User A Makes Changes
Section titled “5. User A Makes Changes”// Create markers, update session, etc.let marker = try await MarkerService.shared.createMarker(newMarker)
// Update session statuslet updated = try await WorkSessionService.shared.updateWorkSession( id: sessionId, update: UpdateWorkSession(status: .active, version: session.version))6. User A Releases Lock
Section titled “6. User A Releases Lock”try await LockService.shared.releaseLock( sessionId: sessionId, userId: "user-a")7. User B Acquires Lock
Section titled “7. User B Acquires Lock”// Now User B can acquire the locklet acquired = try await LockService.shared.acquireLock( sessionId: sessionId, userId: "user-b", ttl: 60)8. Handle Version Conflicts
Section titled “8. Handle Version Conflicts”do { try await WorkSessionService.shared.updateWorkSession( id: sessionId, update: update )} catch APIError.conflict(let message) { // Version mismatch - another user modified the session
// Refetch latest version let latest = try await WorkSessionService.shared.getWorkSession(id: sessionId)
// Merge changes or prompt user showConflictResolution(current: session, latest: latest)
// Retry with latest version update.version = latest.version try await WorkSessionService.shared.updateWorkSession( id: sessionId, update: update )}Workflow 5: Export and Import
Section titled “Workflow 5: Export and Import”Transfer sessions between spaces or environments.
Export Session
Section titled “Export Session”1. Export from Source
Section titled “1. Export from Source”curl http://localhost:8080/api/v1/work-sessions/SOURCE_SESSION_UUID/export \ -o session_export.jsonExported Data:
{ "session_type": "inspection", "status": "done", "started_at": "2025-01-16T09:00:00Z", "completed_at": "2025-01-16T12:00:00Z", "meta": {}, "markers": [ { "label": "Issue 1", "p1": [0, 0, 0], "p2": [0.1, 0, 0], "p3": [0.1, 0.1, 0], "p4": [0, 0.1, 0], "color": "#FF0000", "custom_props": { "severity": "high" }, "details": { "center_location_long": 0.05, "center_location_cross": 0.05, ... } } ]}Import to Target
Section titled “Import to Target”2. Create Target Space (if needed)
Section titled “2. Create Target Space (if needed)”curl -X POST http://localhost:8080/api/v1/spaces \ -H "Content-Type: application/json" \ -d '{ "key": "target-space", "name": "Target Space", "calibration_vector": [0, 0, 0] }'3. Import Session
Section titled “3. Import Session”curl -X POST http://localhost:8080/api/v1/work-sessions/import \ -H "Content-Type: application/json" \ -d '{ "space_id": "TARGET_SPACE_UUID", "session": '$(cat session_export.json)' }'Result: New work session created with all markers and details
iOS Implementation
Section titled “iOS Implementation”func exportAndImport( sourceSessionId: UUID, targetSpaceId: UUID) async throws { // 1. Export let exported = try await WorkSessionService.shared.exportSession( id: sourceSessionId )
// 2. Import to target space let imported = try await WorkSessionService.shared.importSession( spaceId: targetSpaceId, session: exported )
print("Imported session: \(imported.id)")}Workflow 6: Batch Marker Creation
Section titled “Workflow 6: Batch Marker Creation”Create multiple markers efficiently.
1. Collect Marker Data
Section titled “1. Collect Marker Data”var markerBatch: [CreateMarker] = []
// Scan area and collect multiple marker positionsfor defect in detectedDefects { let marker = CreateMarker( workSessionId: sessionId, label: defect.label, p1: defect.corners[0].toDoubleArray(), p2: defect.corners[1].toDoubleArray(), p3: defect.corners[2].toDoubleArray(), p4: defect.corners[3].toDoubleArray(), color: defect.severityColor, customProps: [ "severity": AnyCodable(defect.severity), "type": AnyCodable(defect.type), "detected_at": AnyCodable(Date()) ] )
markerBatch.append(marker)}2. Bulk Create
Section titled “2. Bulk Create”// Create all markers in one requestlet created = try await MarkerService.shared.bulkCreateMarkers(markerBatch)
print("Created \(created.count) markers")3. Calculate All Details
Section titled “3. Calculate All Details”# Auto-calculate details for all markerscurl -X POST http://localhost:8080/api/v1/work-sessions/SESSION_UUID/markers/calculate-detailsBenefits:
- Single network request
- Atomic operation
- Better performance
- Reduced overhead
Workflow 7: Quality Control
Section titled “Workflow 7: Quality Control”Validate and verify marker data.
1. Validation Rules
Section titled “1. Validation Rules”struct MarkerValidator { static func validate(_ marker: CreateMarker) -> [String] { var errors: [String] = []
// Check edge lengths let edges = [ (marker.p1, marker.p2), (marker.p2, marker.p3), (marker.p3, marker.p4), (marker.p4, marker.p1) ]
for (i, (a, b)) in edges.enumerated() { let dist = distance(a, b) if dist < 0.005 { errors.append("Edge \(i+1) too short: \(dist)m") } }
// Check coordinate bounds let allCoords = [marker.p1, marker.p2, marker.p3, marker.p4].flatMap { $0 } for coord in allCoords { if !coord.isFinite { errors.append("Invalid coordinate: \(coord)") } }
return errors }
static func distance(_ a: [Double], _ b: [Double]) -> Double { let dx = a[0] - b[0] let dy = a[1] - b[1] let dz = a[2] - b[2] return sqrt(dx*dx + dy*dy + dz*dz) }}2. Pre-Creation Validation
Section titled “2. Pre-Creation Validation”let errors = MarkerValidator.validate(newMarker)
if errors.isEmpty { try await MarkerService.shared.createMarker(newMarker)} else { showValidationErrors(errors)}3. Post-Creation Verification
Section titled “3. Post-Creation Verification”func verifyMarkerDetails(_ marker: Marker) async throws { // Calculate details try await MarkerDetailsService.shared.calculateDetails(markerId: marker.id)
// Fetch details let details = try await MarkerDetailsService.shared.getDetails(markerId: marker.id)
// Verify size is reasonable if details.longSize < 0.01 || details.longSize > 5.0 { print("⚠️ Unusual length: \(details.longSize)m") }
if details.crossSize < 0.01 || details.crossSize > 2.0 { print("⚠️ Unusual width: \(details.crossSize)m") }}Best Practices
Section titled “Best Practices”1. Always Use Versioning
Section titled “1. Always Use Versioning”// Include version in updateslet update = UpdateWorkSession( status: .done, completedAt: Date(), version: session.version // ✅ Include version)
// Handle conflictsdo { try await service.updateWorkSession(id: id, update: update)} catch APIError.conflict { // Refetch and retry}2. Clean Up Presence
Section titled “2. Clean Up Presence”class SessionManager { private var sessionId: UUID?
func startSession(_ id: UUID) async { self.sessionId = id try? await PresenceService.shared.joinSession(id) }
func endSession() async { if let id = sessionId { try? await PresenceService.shared.leaveSession(id) } }
deinit { // Ensure cleanup Task { await endSession() } }}3. Use Bulk Operations
Section titled “3. Use Bulk Operations”// ❌ Slow - multiple network requestsfor marker in markers { try await service.createMarker(marker)}
// ✅ Fast - single requesttry await service.bulkCreateMarkers(markers)4. Calculate Details in Batch
Section titled “4. Calculate Details in Batch”# ✅ Efficient - calculate all at oncecurl -X POST /api/v1/work-sessions/{id}/markers/calculate-details
# ❌ Inefficient - calculate individuallycurl -X POST /api/v1/markers/{id1}/details/calculatecurl -X POST /api/v1/markers/{id2}/details/calculate5. Handle Network Errors Gracefully
Section titled “5. Handle Network Errors Gracefully”func loadSessionWithRetry(id: UUID, maxRetries: Int = 3) async throws -> WorkSession { var lastError: Error?
for attempt in 1...maxRetries { do { return try await WorkSessionService.shared.getWorkSession(id: id) } catch { lastError = error if attempt < maxRetries { try await Task.sleep(nanoseconds: UInt64(attempt) * 1_000_000_000) } } }
throw lastError ?? APIError.networkError(NSError())}