├── .gitignore ├── Assets └── Photogrammetry_App_Preview.png ├── LICENSE ├── Photogrammetry.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ └── Photogrammetry.xcscheme ├── Photogrammetry ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── PhotogrammetryIcon_1024.png │ │ ├── PhotogrammetryIcon_128.png │ │ ├── PhotogrammetryIcon_16.png │ │ ├── PhotogrammetryIcon_256 1.png │ │ ├── PhotogrammetryIcon_256.png │ │ ├── PhotogrammetryIcon_32 1.png │ │ ├── PhotogrammetryIcon_32.png │ │ ├── PhotogrammetryIcon_512 1.png │ │ ├── PhotogrammetryIcon_512.png │ │ └── PhotogrammetryIcon_64.png │ └── Contents.json ├── Delegate │ ├── ARContainerViewDelegate.swift │ ├── OnDropDelegate.swift │ └── PhotogrammetryDelegate.swift ├── Extensions │ ├── PhotogrammetrySession.swift │ └── URL.swift ├── Photogrammetry.entitlements ├── PhotogrammetryApp.swift ├── Resources │ └── en.lproj │ │ └── Localizable.strings ├── Types │ ├── ARContainerViewDelegateError.swift │ ├── ApplicationViewState.swift │ └── PhotogrammetryDelegateError.swift └── UI │ ├── ARContainerView.swift │ ├── ConfigurationView.swift │ ├── ContentView.swift │ ├── ExportView.swift │ ├── InputView.swift │ ├── ProcessingView.swift │ └── VibrancyView.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Xcode user data 5 | xcuserdata/ 6 | -------------------------------------------------------------------------------- /Assets/Photogrammetry_App_Preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unbinilium/Photogrammetry/572857e7bc224710e6c7555407d0d650149ce00a/Assets/Photogrammetry_App_Preview.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Unbinilium 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Photogrammetry.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 6289A613292F2E1D0090849E /* ARContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6289A612292F2E1D0090849E /* ARContainerView.swift */; }; 11 | 6289A615292F33E40090849E /* ARContainerViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6289A614292F33E40090849E /* ARContainerViewDelegate.swift */; }; 12 | 6289A617292F5C570090849E /* ARContainerViewDelegateError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6289A616292F5C570090849E /* ARContainerViewDelegateError.swift */; }; 13 | 628AF6F42934EE6B001BBEDE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 628AF6F62934EE6B001BBEDE /* Localizable.strings */; }; 14 | 628AF6F92934F108001BBEDE /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 628AF6F82934F108001BBEDE /* README.md */; }; 15 | 628AF6FB2934F119001BBEDE /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 628AF6FA2934F119001BBEDE /* LICENSE */; }; 16 | 62A9A122292FCDE6000776F2 /* VibrancyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A9A121292FCDE6000776F2 /* VibrancyView.swift */; }; 17 | 62B1C9B72928E095009F3EE3 /* PhotogrammetryApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B1C9B62928E095009F3EE3 /* PhotogrammetryApp.swift */; }; 18 | 62B1C9B92928E095009F3EE3 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B1C9B82928E095009F3EE3 /* ContentView.swift */; }; 19 | 62B1C9BB2928E096009F3EE3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 62B1C9BA2928E096009F3EE3 /* Assets.xcassets */; }; 20 | 62B1C9CC2928FF80009F3EE3 /* InputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B1C9CB2928FF80009F3EE3 /* InputView.swift */; }; 21 | 62B1C9D3292B8616009F3EE3 /* OnDropDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B1C9D2292B8616009F3EE3 /* OnDropDelegate.swift */; }; 22 | 62B1C9D6292B89F8009F3EE3 /* ApplicationViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B1C9D5292B89F8009F3EE3 /* ApplicationViewState.swift */; }; 23 | 62B1C9DA292CAED5009F3EE3 /* ConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B1C9D9292CAED5009F3EE3 /* ConfigurationView.swift */; }; 24 | 62B1C9DC292CB1C2009F3EE3 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B1C9DB292CB1C2009F3EE3 /* URL.swift */; }; 25 | 62B1C9DE292CC3F6009F3EE3 /* PhotogrammetryDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B1C9DD292CC3F6009F3EE3 /* PhotogrammetryDelegate.swift */; }; 26 | 62B1C9E0292CF924009F3EE3 /* PhotogrammetrySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B1C9DF292CF924009F3EE3 /* PhotogrammetrySession.swift */; }; 27 | 62B1C9E4292D2F2A009F3EE3 /* ProcessingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B1C9E3292D2F2A009F3EE3 /* ProcessingView.swift */; }; 28 | 62B1C9E6292D2F86009F3EE3 /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62B1C9E5292D2F86009F3EE3 /* ExportView.swift */; }; 29 | 62E30860292DD9BA00AFD0DD /* PhotogrammetryDelegateError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E3085F292DD9BA00AFD0DD /* PhotogrammetryDelegateError.swift */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 6289A612292F2E1D0090849E /* ARContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARContainerView.swift; sourceTree = ""; }; 34 | 6289A614292F33E40090849E /* ARContainerViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARContainerViewDelegate.swift; sourceTree = ""; }; 35 | 6289A616292F5C570090849E /* ARContainerViewDelegateError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARContainerViewDelegateError.swift; sourceTree = ""; }; 36 | 628AF6F52934EE6B001BBEDE /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 37 | 628AF6F82934F108001BBEDE /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 38 | 628AF6FA2934F119001BBEDE /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 39 | 62A9A121292FCDE6000776F2 /* VibrancyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VibrancyView.swift; sourceTree = ""; }; 40 | 62B1C9B32928E095009F3EE3 /* Photogrammetry.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Photogrammetry.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 62B1C9B62928E095009F3EE3 /* PhotogrammetryApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotogrammetryApp.swift; sourceTree = ""; }; 42 | 62B1C9B82928E095009F3EE3 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 43 | 62B1C9BA2928E096009F3EE3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 44 | 62B1C9BF2928E096009F3EE3 /* Photogrammetry.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Photogrammetry.entitlements; sourceTree = ""; }; 45 | 62B1C9CB2928FF80009F3EE3 /* InputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputView.swift; sourceTree = ""; }; 46 | 62B1C9D2292B8616009F3EE3 /* OnDropDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnDropDelegate.swift; sourceTree = ""; }; 47 | 62B1C9D5292B89F8009F3EE3 /* ApplicationViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationViewState.swift; sourceTree = ""; }; 48 | 62B1C9D9292CAED5009F3EE3 /* ConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationView.swift; sourceTree = ""; }; 49 | 62B1C9DB292CB1C2009F3EE3 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; 50 | 62B1C9DD292CC3F6009F3EE3 /* PhotogrammetryDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotogrammetryDelegate.swift; sourceTree = ""; }; 51 | 62B1C9DF292CF924009F3EE3 /* PhotogrammetrySession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotogrammetrySession.swift; sourceTree = ""; }; 52 | 62B1C9E3292D2F2A009F3EE3 /* ProcessingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessingView.swift; sourceTree = ""; }; 53 | 62B1C9E5292D2F86009F3EE3 /* ExportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportView.swift; sourceTree = ""; }; 54 | 62E3085F292DD9BA00AFD0DD /* PhotogrammetryDelegateError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotogrammetryDelegateError.swift; sourceTree = ""; }; 55 | /* End PBXFileReference section */ 56 | 57 | /* Begin PBXFrameworksBuildPhase section */ 58 | 62B1C9B02928E095009F3EE3 /* Frameworks */ = { 59 | isa = PBXFrameworksBuildPhase; 60 | buildActionMask = 2147483647; 61 | files = ( 62 | ); 63 | runOnlyForDeploymentPostprocessing = 0; 64 | }; 65 | /* End PBXFrameworksBuildPhase section */ 66 | 67 | /* Begin PBXGroup section */ 68 | 628AF6F72934F0CD001BBEDE /* Resources */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 628AF6F62934EE6B001BBEDE /* Localizable.strings */, 72 | ); 73 | path = Resources; 74 | sourceTree = ""; 75 | }; 76 | 62B1C9AA2928E095009F3EE3 = { 77 | isa = PBXGroup; 78 | children = ( 79 | 628AF6F82934F108001BBEDE /* README.md */, 80 | 628AF6FA2934F119001BBEDE /* LICENSE */, 81 | 62B1C9B52928E095009F3EE3 /* Photogrammetry */, 82 | 62B1C9B42928E095009F3EE3 /* Products */, 83 | ); 84 | sourceTree = ""; 85 | }; 86 | 62B1C9B42928E095009F3EE3 /* Products */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 62B1C9B32928E095009F3EE3 /* Photogrammetry.app */, 90 | ); 91 | name = Products; 92 | sourceTree = ""; 93 | }; 94 | 62B1C9B52928E095009F3EE3 /* Photogrammetry */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 62B1C9D4292B89DA009F3EE3 /* Types */, 98 | 62B1C9C52928E81D009F3EE3 /* Extensions */, 99 | 62B1C9C82928EA3A009F3EE3 /* UI */, 100 | 62B1C9CD292900CF009F3EE3 /* Delegate */, 101 | 628AF6F72934F0CD001BBEDE /* Resources */, 102 | 62B1C9B62928E095009F3EE3 /* PhotogrammetryApp.swift */, 103 | 62B1C9BA2928E096009F3EE3 /* Assets.xcassets */, 104 | 62B1C9BF2928E096009F3EE3 /* Photogrammetry.entitlements */, 105 | ); 106 | path = Photogrammetry; 107 | sourceTree = ""; 108 | }; 109 | 62B1C9C52928E81D009F3EE3 /* Extensions */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 62B1C9DB292CB1C2009F3EE3 /* URL.swift */, 113 | 62B1C9DF292CF924009F3EE3 /* PhotogrammetrySession.swift */, 114 | ); 115 | path = Extensions; 116 | sourceTree = ""; 117 | }; 118 | 62B1C9C82928EA3A009F3EE3 /* UI */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 62B1C9B82928E095009F3EE3 /* ContentView.swift */, 122 | 62B1C9CB2928FF80009F3EE3 /* InputView.swift */, 123 | 62B1C9D9292CAED5009F3EE3 /* ConfigurationView.swift */, 124 | 62B1C9E3292D2F2A009F3EE3 /* ProcessingView.swift */, 125 | 62B1C9E5292D2F86009F3EE3 /* ExportView.swift */, 126 | 6289A612292F2E1D0090849E /* ARContainerView.swift */, 127 | 62A9A121292FCDE6000776F2 /* VibrancyView.swift */, 128 | ); 129 | path = UI; 130 | sourceTree = ""; 131 | }; 132 | 62B1C9CD292900CF009F3EE3 /* Delegate */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 62B1C9D2292B8616009F3EE3 /* OnDropDelegate.swift */, 136 | 62B1C9DD292CC3F6009F3EE3 /* PhotogrammetryDelegate.swift */, 137 | 6289A614292F33E40090849E /* ARContainerViewDelegate.swift */, 138 | ); 139 | path = Delegate; 140 | sourceTree = ""; 141 | }; 142 | 62B1C9D4292B89DA009F3EE3 /* Types */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 62B1C9D5292B89F8009F3EE3 /* ApplicationViewState.swift */, 146 | 62E3085F292DD9BA00AFD0DD /* PhotogrammetryDelegateError.swift */, 147 | 6289A616292F5C570090849E /* ARContainerViewDelegateError.swift */, 148 | ); 149 | path = Types; 150 | sourceTree = ""; 151 | }; 152 | /* End PBXGroup section */ 153 | 154 | /* Begin PBXNativeTarget section */ 155 | 62B1C9B22928E095009F3EE3 /* Photogrammetry */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = 62B1C9C22928E096009F3EE3 /* Build configuration list for PBXNativeTarget "Photogrammetry" */; 158 | buildPhases = ( 159 | 62B1C9AF2928E095009F3EE3 /* Sources */, 160 | 62B1C9B02928E095009F3EE3 /* Frameworks */, 161 | 62B1C9B12928E095009F3EE3 /* Resources */, 162 | ); 163 | buildRules = ( 164 | ); 165 | dependencies = ( 166 | ); 167 | name = Photogrammetry; 168 | productName = Photogrammetry; 169 | productReference = 62B1C9B32928E095009F3EE3 /* Photogrammetry.app */; 170 | productType = "com.apple.product-type.application"; 171 | }; 172 | /* End PBXNativeTarget section */ 173 | 174 | /* Begin PBXProject section */ 175 | 62B1C9AB2928E095009F3EE3 /* Project object */ = { 176 | isa = PBXProject; 177 | attributes = { 178 | BuildIndependentTargetsInParallel = 1; 179 | LastSwiftUpdateCheck = 1410; 180 | LastUpgradeCheck = 1410; 181 | TargetAttributes = { 182 | 62B1C9B22928E095009F3EE3 = { 183 | CreatedOnToolsVersion = 14.1; 184 | }; 185 | }; 186 | }; 187 | buildConfigurationList = 62B1C9AE2928E095009F3EE3 /* Build configuration list for PBXProject "Photogrammetry" */; 188 | compatibilityVersion = "Xcode 14.0"; 189 | developmentRegion = en; 190 | hasScannedForEncodings = 0; 191 | knownRegions = ( 192 | en, 193 | ); 194 | mainGroup = 62B1C9AA2928E095009F3EE3; 195 | productRefGroup = 62B1C9B42928E095009F3EE3 /* Products */; 196 | projectDirPath = ""; 197 | projectRoot = ""; 198 | targets = ( 199 | 62B1C9B22928E095009F3EE3 /* Photogrammetry */, 200 | ); 201 | }; 202 | /* End PBXProject section */ 203 | 204 | /* Begin PBXResourcesBuildPhase section */ 205 | 62B1C9B12928E095009F3EE3 /* Resources */ = { 206 | isa = PBXResourcesBuildPhase; 207 | buildActionMask = 2147483647; 208 | files = ( 209 | 628AF6F92934F108001BBEDE /* README.md in Resources */, 210 | 62B1C9BB2928E096009F3EE3 /* Assets.xcassets in Resources */, 211 | 628AF6FB2934F119001BBEDE /* LICENSE in Resources */, 212 | 628AF6F42934EE6B001BBEDE /* Localizable.strings in Resources */, 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXResourcesBuildPhase section */ 217 | 218 | /* Begin PBXSourcesBuildPhase section */ 219 | 62B1C9AF2928E095009F3EE3 /* Sources */ = { 220 | isa = PBXSourcesBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | 62B1C9B92928E095009F3EE3 /* ContentView.swift in Sources */, 224 | 62B1C9DA292CAED5009F3EE3 /* ConfigurationView.swift in Sources */, 225 | 62B1C9CC2928FF80009F3EE3 /* InputView.swift in Sources */, 226 | 6289A617292F5C570090849E /* ARContainerViewDelegateError.swift in Sources */, 227 | 62B1C9DE292CC3F6009F3EE3 /* PhotogrammetryDelegate.swift in Sources */, 228 | 6289A615292F33E40090849E /* ARContainerViewDelegate.swift in Sources */, 229 | 6289A613292F2E1D0090849E /* ARContainerView.swift in Sources */, 230 | 62B1C9E0292CF924009F3EE3 /* PhotogrammetrySession.swift in Sources */, 231 | 62B1C9D3292B8616009F3EE3 /* OnDropDelegate.swift in Sources */, 232 | 62E30860292DD9BA00AFD0DD /* PhotogrammetryDelegateError.swift in Sources */, 233 | 62B1C9E4292D2F2A009F3EE3 /* ProcessingView.swift in Sources */, 234 | 62B1C9E6292D2F86009F3EE3 /* ExportView.swift in Sources */, 235 | 62B1C9D6292B89F8009F3EE3 /* ApplicationViewState.swift in Sources */, 236 | 62A9A122292FCDE6000776F2 /* VibrancyView.swift in Sources */, 237 | 62B1C9B72928E095009F3EE3 /* PhotogrammetryApp.swift in Sources */, 238 | 62B1C9DC292CB1C2009F3EE3 /* URL.swift in Sources */, 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | /* End PBXSourcesBuildPhase section */ 243 | 244 | /* Begin PBXVariantGroup section */ 245 | 628AF6F62934EE6B001BBEDE /* Localizable.strings */ = { 246 | isa = PBXVariantGroup; 247 | children = ( 248 | 628AF6F52934EE6B001BBEDE /* en */, 249 | ); 250 | name = Localizable.strings; 251 | sourceTree = ""; 252 | }; 253 | /* End PBXVariantGroup section */ 254 | 255 | /* Begin XCBuildConfiguration section */ 256 | 62B1C9C02928E096009F3EE3 /* Debug */ = { 257 | isa = XCBuildConfiguration; 258 | buildSettings = { 259 | ALWAYS_SEARCH_USER_PATHS = NO; 260 | CLANG_ANALYZER_NONNULL = YES; 261 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 262 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 263 | CLANG_ENABLE_MODULES = YES; 264 | CLANG_ENABLE_OBJC_ARC = YES; 265 | CLANG_ENABLE_OBJC_WEAK = YES; 266 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 267 | CLANG_WARN_BOOL_CONVERSION = YES; 268 | CLANG_WARN_COMMA = YES; 269 | CLANG_WARN_CONSTANT_CONVERSION = YES; 270 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 271 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 272 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 273 | CLANG_WARN_EMPTY_BODY = YES; 274 | CLANG_WARN_ENUM_CONVERSION = YES; 275 | CLANG_WARN_INFINITE_RECURSION = YES; 276 | CLANG_WARN_INT_CONVERSION = YES; 277 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 278 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 279 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 280 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 281 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 282 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 283 | CLANG_WARN_STRICT_PROTOTYPES = YES; 284 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 285 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 286 | CLANG_WARN_UNREACHABLE_CODE = YES; 287 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 288 | COPY_PHASE_STRIP = NO; 289 | DEBUG_INFORMATION_FORMAT = dwarf; 290 | ENABLE_STRICT_OBJC_MSGSEND = YES; 291 | ENABLE_TESTABILITY = YES; 292 | GCC_C_LANGUAGE_STANDARD = gnu11; 293 | GCC_DYNAMIC_NO_PIC = NO; 294 | GCC_NO_COMMON_BLOCKS = YES; 295 | GCC_OPTIMIZATION_LEVEL = 0; 296 | GCC_PREPROCESSOR_DEFINITIONS = ( 297 | "DEBUG=1", 298 | "$(inherited)", 299 | ); 300 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 301 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 302 | GCC_WARN_UNDECLARED_SELECTOR = YES; 303 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 304 | GCC_WARN_UNUSED_FUNCTION = YES; 305 | GCC_WARN_UNUSED_VARIABLE = YES; 306 | MACOSX_DEPLOYMENT_TARGET = 13.0; 307 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 308 | MTL_FAST_MATH = YES; 309 | ONLY_ACTIVE_ARCH = YES; 310 | SDKROOT = macosx; 311 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 312 | SWIFT_EMIT_LOC_STRINGS = YES; 313 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 314 | }; 315 | name = Debug; 316 | }; 317 | 62B1C9C12928E096009F3EE3 /* Release */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ALWAYS_SEARCH_USER_PATHS = NO; 321 | CLANG_ANALYZER_NONNULL = YES; 322 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 323 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 324 | CLANG_ENABLE_MODULES = YES; 325 | CLANG_ENABLE_OBJC_ARC = YES; 326 | CLANG_ENABLE_OBJC_WEAK = YES; 327 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 328 | CLANG_WARN_BOOL_CONVERSION = YES; 329 | CLANG_WARN_COMMA = YES; 330 | CLANG_WARN_CONSTANT_CONVERSION = YES; 331 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 333 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 334 | CLANG_WARN_EMPTY_BODY = YES; 335 | CLANG_WARN_ENUM_CONVERSION = YES; 336 | CLANG_WARN_INFINITE_RECURSION = YES; 337 | CLANG_WARN_INT_CONVERSION = YES; 338 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 339 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 340 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 341 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 342 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 344 | CLANG_WARN_STRICT_PROTOTYPES = YES; 345 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 346 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 347 | CLANG_WARN_UNREACHABLE_CODE = YES; 348 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 349 | COPY_PHASE_STRIP = NO; 350 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 351 | ENABLE_NS_ASSERTIONS = NO; 352 | ENABLE_STRICT_OBJC_MSGSEND = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu11; 354 | GCC_NO_COMMON_BLOCKS = YES; 355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 356 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 357 | GCC_WARN_UNDECLARED_SELECTOR = YES; 358 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 359 | GCC_WARN_UNUSED_FUNCTION = YES; 360 | GCC_WARN_UNUSED_VARIABLE = YES; 361 | MACOSX_DEPLOYMENT_TARGET = 13.0; 362 | MTL_ENABLE_DEBUG_INFO = NO; 363 | MTL_FAST_MATH = YES; 364 | SDKROOT = macosx; 365 | SWIFT_COMPILATION_MODE = wholemodule; 366 | SWIFT_EMIT_LOC_STRINGS = YES; 367 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 368 | }; 369 | name = Release; 370 | }; 371 | 62B1C9C32928E096009F3EE3 /* Debug */ = { 372 | isa = XCBuildConfiguration; 373 | buildSettings = { 374 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 375 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 376 | CODE_SIGN_ENTITLEMENTS = Photogrammetry/Photogrammetry.entitlements; 377 | CODE_SIGN_STYLE = Automatic; 378 | COMBINE_HIDPI_IMAGES = YES; 379 | CURRENT_PROJECT_VERSION = 1; 380 | DEVELOPMENT_ASSET_PATHS = ""; 381 | DEVELOPMENT_TEAM = TNBZ2XWR75; 382 | ENABLE_HARDENED_RUNTIME = YES; 383 | ENABLE_PREVIEWS = YES; 384 | GENERATE_INFOPLIST_FILE = YES; 385 | INFOPLIST_KEY_CFBundleDisplayName = Photogrammetry; 386 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 387 | INFOPLIST_KEY_NSHumanReadableCopyright = "Made with Love by Unbinilium, 2022."; 388 | LD_RUNPATH_SEARCH_PATHS = ( 389 | "$(inherited)", 390 | "@executable_path/../Frameworks", 391 | ); 392 | MARKETING_VERSION = 1.0; 393 | PRODUCT_BUNDLE_IDENTIFIER = com.unbinilium.photogrammetry; 394 | PRODUCT_NAME = "$(TARGET_NAME)"; 395 | SWIFT_EMIT_LOC_STRINGS = YES; 396 | SWIFT_VERSION = 5.0; 397 | }; 398 | name = Debug; 399 | }; 400 | 62B1C9C42928E096009F3EE3 /* Release */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 404 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 405 | CODE_SIGN_ENTITLEMENTS = Photogrammetry/Photogrammetry.entitlements; 406 | CODE_SIGN_STYLE = Automatic; 407 | COMBINE_HIDPI_IMAGES = YES; 408 | CURRENT_PROJECT_VERSION = 1; 409 | DEVELOPMENT_ASSET_PATHS = ""; 410 | DEVELOPMENT_TEAM = TNBZ2XWR75; 411 | ENABLE_HARDENED_RUNTIME = YES; 412 | ENABLE_PREVIEWS = YES; 413 | GENERATE_INFOPLIST_FILE = YES; 414 | INFOPLIST_KEY_CFBundleDisplayName = Photogrammetry; 415 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 416 | INFOPLIST_KEY_NSHumanReadableCopyright = "Made with Love by Unbinilium, 2022."; 417 | LD_RUNPATH_SEARCH_PATHS = ( 418 | "$(inherited)", 419 | "@executable_path/../Frameworks", 420 | ); 421 | MARKETING_VERSION = 1.0; 422 | PRODUCT_BUNDLE_IDENTIFIER = com.unbinilium.photogrammetry; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | SWIFT_EMIT_LOC_STRINGS = YES; 425 | SWIFT_VERSION = 5.0; 426 | }; 427 | name = Release; 428 | }; 429 | /* End XCBuildConfiguration section */ 430 | 431 | /* Begin XCConfigurationList section */ 432 | 62B1C9AE2928E095009F3EE3 /* Build configuration list for PBXProject "Photogrammetry" */ = { 433 | isa = XCConfigurationList; 434 | buildConfigurations = ( 435 | 62B1C9C02928E096009F3EE3 /* Debug */, 436 | 62B1C9C12928E096009F3EE3 /* Release */, 437 | ); 438 | defaultConfigurationIsVisible = 0; 439 | defaultConfigurationName = Release; 440 | }; 441 | 62B1C9C22928E096009F3EE3 /* Build configuration list for PBXNativeTarget "Photogrammetry" */ = { 442 | isa = XCConfigurationList; 443 | buildConfigurations = ( 444 | 62B1C9C32928E096009F3EE3 /* Debug */, 445 | 62B1C9C42928E096009F3EE3 /* Release */, 446 | ); 447 | defaultConfigurationIsVisible = 0; 448 | defaultConfigurationName = Release; 449 | }; 450 | /* End XCConfigurationList section */ 451 | }; 452 | rootObject = 62B1C9AB2928E095009F3EE3 /* Project object */; 453 | } 454 | -------------------------------------------------------------------------------- /Photogrammetry.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Photogrammetry.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Photogrammetry.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Photogrammetry.xcodeproj/xcshareddata/xcschemes/Photogrammetry.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Photogrammetry/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Photogrammetry/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "PhotogrammetryIcon_16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "PhotogrammetryIcon_32 1.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "PhotogrammetryIcon_32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "PhotogrammetryIcon_64.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "PhotogrammetryIcon_128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "PhotogrammetryIcon_256 1.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "PhotogrammetryIcon_256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "PhotogrammetryIcon_512 1.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "PhotogrammetryIcon_512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "PhotogrammetryIcon_1024.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unbinilium/Photogrammetry/572857e7bc224710e6c7555407d0d650149ce00a/Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_1024.png -------------------------------------------------------------------------------- /Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unbinilium/Photogrammetry/572857e7bc224710e6c7555407d0d650149ce00a/Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_128.png -------------------------------------------------------------------------------- /Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unbinilium/Photogrammetry/572857e7bc224710e6c7555407d0d650149ce00a/Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_16.png -------------------------------------------------------------------------------- /Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_256 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unbinilium/Photogrammetry/572857e7bc224710e6c7555407d0d650149ce00a/Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_256 1.png -------------------------------------------------------------------------------- /Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unbinilium/Photogrammetry/572857e7bc224710e6c7555407d0d650149ce00a/Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_256.png -------------------------------------------------------------------------------- /Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_32 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unbinilium/Photogrammetry/572857e7bc224710e6c7555407d0d650149ce00a/Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_32 1.png -------------------------------------------------------------------------------- /Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unbinilium/Photogrammetry/572857e7bc224710e6c7555407d0d650149ce00a/Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_32.png -------------------------------------------------------------------------------- /Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_512 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unbinilium/Photogrammetry/572857e7bc224710e6c7555407d0d650149ce00a/Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_512 1.png -------------------------------------------------------------------------------- /Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unbinilium/Photogrammetry/572857e7bc224710e6c7555407d0d650149ce00a/Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_512.png -------------------------------------------------------------------------------- /Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unbinilium/Photogrammetry/572857e7bc224710e6c7555407d0d650149ce00a/Photogrammetry/Assets.xcassets/AppIcon.appiconset/PhotogrammetryIcon_64.png -------------------------------------------------------------------------------- /Photogrammetry/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Photogrammetry/Delegate/ARContainerViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARContainerViewDelegate.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/24/22. 6 | // 7 | 8 | import Foundation 9 | import CoreGraphics 10 | import AppKit 11 | import RealityKit 12 | 13 | class ARContainerViewDelegate: ARView, ObservableObject { 14 | private var screen: NSScreen = NSScreen() 15 | private var cameraEntity: PerspectiveCamera = PerspectiveCamera() 16 | private var cameraAnchor: AnchorEntity = AnchorEntity(world: .zero) 17 | private var modelEntity: Entity = Entity() 18 | private var modelAnchor: AnchorEntity = AnchorEntity(world: .zero) 19 | private var modelRadius: Float = 0 { didSet { self.updateCamera() } } 20 | public var modelEntitySpin: Bool = false { didSet { self.spinModelEntity() } } 21 | public var modelEntityRotationAngle: Float = 0 { didSet { self.updateCamera() } } 22 | 23 | public required init(frame: NSRect) { 24 | super.init(frame: frame) 25 | self.environment.background = .color(.clear) 26 | self.cameraEntity.camera.fieldOfViewInDegrees = 80 27 | self.cameraAnchor.addChild(self.cameraEntity) 28 | self.modelAnchor.addChild(self.modelEntity) 29 | self.scene.anchors.append(self.cameraAnchor) 30 | self.scene.anchors.append(self.modelAnchor) 31 | self.updateCamera() 32 | } 33 | 34 | @MainActor required dynamic init?(coder decoder: NSCoder) { 35 | fatalError("init(coder:) has not been implemented") 36 | } 37 | 38 | // MARK: - Update Camera 39 | @MainActor private func updateCamera() { 40 | let translationTransform = Transform(scale: .one, rotation: simd_quatf(), translation: SIMD3(0, 0, modelRadius)) 41 | let rotationTransform = Transform(pitch: 0, yaw: modelEntityRotationAngle, roll: 0) 42 | let computedTransform = matrix_identity_float4x4 * rotationTransform.matrix * translationTransform.matrix 43 | cameraAnchor.transform = Transform(matrix: computedTransform) 44 | } 45 | 46 | // MARK: - Load Model Entity 47 | @MainActor public func loadModelEntity(modelEntityUrl: URL, completion: @escaping (_ result: Result) -> ()) { 48 | do { 49 | scene.anchors.remove(modelAnchor) 50 | modelAnchor.removeChild(modelEntity) 51 | modelEntity = try Entity.load(contentsOf: modelEntityUrl) 52 | let modelEntityBounds = modelEntity.visualBounds(relativeTo: nil) 53 | let modelEntityBoundSize = max(modelEntityBounds.max.x, modelEntityBounds.max.y, modelEntityBounds.max.z) 54 | modelRadius = modelEntityBoundSize * 1.8 55 | modelAnchor.addChild(modelEntity) 56 | scene.anchors.append(modelAnchor) 57 | completion(.success(modelEntity)) 58 | } catch { completion(.failure(ARContainerViewDelegateError(error: .failedLoadingEntity, comment: String(describing: error)))) } 59 | } 60 | 61 | // MARK: - Spin Model Entity 62 | private func spinModelEntity() { 63 | if !modelEntitySpin { return } 64 | DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + screen.minimumRefreshInterval) { 65 | self.modelEntityRotationAngle += Float(self.screen.minimumRefreshInterval) * 2.0 66 | self.spinModelEntity() 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Photogrammetry/Delegate/OnDropDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OnDropDelegate.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/21/22. 6 | // 7 | 8 | import UniformTypeIdentifiers 9 | import SwiftUI 10 | 11 | struct OnDropDelegate: DropDelegate { 12 | @Binding var applicationViewState: ApplicationViewState 13 | @ObservedObject var photogrammetryDelegate: PhotogrammetryDelegate 14 | 15 | func validateDrop(info: DropInfo) -> Bool { return info.hasItemsConforming(to: ["public.file-url"]) } 16 | 17 | func dropEntered(info: DropInfo) { NSSound(named: "Morse")?.play() } 18 | 19 | func performDrop(info: DropInfo) -> Bool { 20 | NSSound(named: "Submarine")?.play() 21 | guard let itemProvider = info.itemProviders(for: ["public.file-url"]).first else { return false } 22 | DispatchQueue.main.async { 23 | guard let identifier = itemProvider.registeredTypeIdentifiers.first else { return } 24 | itemProvider.loadItem(forTypeIdentifier: identifier) { (urlData, error) in 25 | if let urlData = urlData as? Data { 26 | let itemUrl = NSURL(absoluteURLWithDataRepresentation: urlData, relativeTo: nil) as URL 27 | if itemUrl.isDirectory == true { 28 | photogrammetryDelegate.inputFolderUrl = itemUrl 29 | applicationViewState = .onConfigurationView 30 | } 31 | } 32 | } 33 | } 34 | return true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Photogrammetry/Delegate/PhotogrammetryDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotogrammetryDelegate.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/22/22. 6 | // 7 | 8 | import os 9 | import Foundation 10 | import UniformTypeIdentifiers 11 | import AppKit 12 | import RealityKit 13 | 14 | class PhotogrammetryDelegate: ObservableObject { 15 | @Published var sessionRequestDetail: PhotogrammetrySession.Request.Detail = PhotogrammetrySession.Request.Detail() 16 | @Published var sessionConfiguration: PhotogrammetrySession.Configuration = PhotogrammetrySession.Configuration() 17 | @Published var sessionProgress: Double = 0 18 | @Published var sessionInfo: String = String() 19 | private var session: PhotogrammetrySession? 20 | private var logger: Logger = Logger(subsystem: "com.unbinilium.photogrammetry", category: "Photogrammetry") 21 | private var fileManager: FileManager = FileManager() 22 | public var outputModelUrl: URL? 23 | public var inputFolderUrl: URL? { 24 | didSet { 25 | guard let inputFolderUrl = inputFolderUrl else { return } 26 | let outputModelName = inputFolderUrl.lastPathComponent.appendingFormat(".usdz") 27 | let temporaryDirectoryUrl = fileManager.temporaryDirectory 28 | outputModelUrl = temporaryDirectoryUrl.appending(component: outputModelName) 29 | } 30 | } 31 | 32 | // MARK: - Helper Function 33 | public func checkAvailability() throws { 34 | if !PhotogrammetrySession.isSupported { throw PhotogrammetryDelegateError(error: .unsupportedHardware) } 35 | } 36 | 37 | public func cancelGeneratingModel() { 38 | if let session = session, session.isProcessing { self.session?.cancel() } 39 | } 40 | 41 | public func removeOutputModel() { 42 | guard let outputModelUrl = outputModelUrl else { return } 43 | DispatchQueue.global(qos: .background).async { 44 | do { 45 | self.logger.log("Removing output model: \(outputModelUrl.absoluteString)") 46 | try self.fileManager.removeItem(at: outputModelUrl) 47 | } catch { self.logger.error("\(String(describing: error))") } 48 | } 49 | } 50 | 51 | public func openInputFolderPanel(completion: @escaping (_ result: Result) -> ()) { 52 | let panel = NSOpenPanel() 53 | panel.allowsMultipleSelection = false 54 | panel.canChooseFiles = false 55 | panel.canChooseDirectories = true 56 | panel.allowedContentTypes = [UTType.folder] 57 | panel.begin { (result) in 58 | if result == .OK { 59 | guard let folderUrl = panel.urls.first else { return completion(.failure(PhotogrammetryDelegateError(error: .failedOpenInputFolder))) } 60 | completion(.success(folderUrl)) 61 | } 62 | } 63 | } 64 | 65 | public func openExportModelPanel(completion: @escaping (_ result: Result) -> ()) { 66 | guard let outputModelUrl = outputModelUrl else { 67 | logger.error("\(PhotogrammetryDelegateError(error: .missingOutputModelUrl).localizedDescription)") 68 | return 69 | } 70 | let panel = NSSavePanel() 71 | panel.canCreateDirectories = true 72 | panel.nameFieldStringValue = outputModelUrl.lastPathComponent 73 | panel.isExtensionHidden = true 74 | panel.showsTagField = true 75 | panel.allowedContentTypes = [.usdz] 76 | let response = panel.runModal() 77 | guard response == .OK, let exportFolderURL = panel.url else { return } 78 | DispatchQueue.global(qos: .background).async { 79 | do { 80 | try self.fileManager.copyItem(at: outputModelUrl, to: exportFolderURL) 81 | completion(.success(outputModelUrl)) 82 | } catch { 83 | self.logger.error("\(String(describing: error))") 84 | completion(.failure(error)) 85 | } 86 | } 87 | } 88 | 89 | // MARK: - Generate Model 90 | public func generateModel(completion: @escaping (_ result: Result) -> ()) { 91 | sessionProgress = 0 92 | sessionInfo = String(localized: "delegate.generating.3dmodel") 93 | do { 94 | try self.checkAvailability() 95 | self.session = try createSession() 96 | guard let session = session else { throw PhotogrammetryDelegateError(error: .failedAccessSession) } 97 | let waiter = Task { 98 | do { 99 | for try await output in session.outputs { 100 | switch output { 101 | case .processingComplete: 102 | self.logger.log("Processing is complete") 103 | DispatchQueue.main.async { self.sessionInfo = String(localized: "delegate.processing.complete") } 104 | 105 | case .requestError(let request, let error): 106 | self.logger.error("Request \(String(describing: request)) had an error: \(String(describing: error))") 107 | completion(.failure(PhotogrammetryDelegateError(error: .failedCompleteRequest))) 108 | 109 | case .requestComplete(let request, let result): 110 | self.logger.log("Request \(String(describing: request)) had a result: \(String(describing: result))") 111 | switch result { 112 | case .modelFile(let url): 113 | completion(.success(url)) 114 | default: 115 | completion(.failure(PhotogrammetryDelegateError(error: .unexpectedRequestResult, comment: String(describing: result)))) 116 | } 117 | 118 | case .requestProgress(let request, let fractionComplete): 119 | self.logger.log("Progress(request = \(String(describing: request)) = \(fractionComplete)") 120 | DispatchQueue.main.async { self.sessionProgress = fractionComplete } 121 | 122 | case .inputComplete: 123 | self.logger.log("Data ingestion is complete, beginning processing...") 124 | DispatchQueue.main.async { self.sessionInfo = String(localized: "delegate.processing.begin") } 125 | 126 | case .invalidSample(let id, let reason): 127 | self.logger.warning("Invalid Sample, id=\(id) reason=\"\(reason)\"") 128 | 129 | case .skippedSample(let id): 130 | self.logger.warning("Sample id=\(id) was skipped by processing") 131 | 132 | case .automaticDownsampling: 133 | self.logger.warning("Automatic downsampling was applied") 134 | DispatchQueue.main.async { self.sessionInfo = String(localized: "delegate.automatic.downsampling") } 135 | 136 | case .processingCancelled: 137 | self.logger.warning("Request of the session request was cancelled") 138 | DispatchQueue.main.async { self.sessionInfo = String(localized: "delegate.request.cancelled") } 139 | 140 | @unknown default: 141 | self.logger.warning("Unhandled output message: \(String(describing: output))") 142 | } 143 | } 144 | } catch { completion(.failure(error)) } 145 | } 146 | withExtendedLifetime((session, waiter)) { 147 | do { 148 | let request = try self.createRequest() 149 | try session.process(requests: [request]) 150 | } catch { completion(.failure(error)) } 151 | } 152 | } catch { completion(.failure(error)) } 153 | } 154 | 155 | // MARK: - Object Create Function 156 | private func createSession() throws -> PhotogrammetrySession { 157 | guard let inputFolderUrl = inputFolderUrl else { throw PhotogrammetryDelegateError(error: .missingInputFolderUrl) } 158 | do { return try PhotogrammetrySession(input: inputFolderUrl, configuration: sessionConfiguration) } 159 | catch { throw PhotogrammetryDelegateError(error: .failedCreateSession, comment: String(describing: error)) } 160 | } 161 | 162 | private func createRequest() throws -> PhotogrammetrySession.Request { 163 | guard let outputModelUrl = outputModelUrl else { throw PhotogrammetryDelegateError(error: .missingOutputModelUrl) } 164 | return PhotogrammetrySession.Request.modelFile(url: outputModelUrl, detail: sessionRequestDetail) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /Photogrammetry/Extensions/PhotogrammetrySession.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotogrammetrySession.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/22/22. 6 | // 7 | 8 | import RealityKit 9 | 10 | @available(macOS 12.0, *) 11 | extension PhotogrammetrySession.Request.Detail { 12 | public static var allCases = ["Preview", "Reduced", "Medium", "Full", "Raw"] 13 | 14 | init(_ detail: String = String()) { 15 | switch detail { 16 | case "Preview": self = .preview 17 | case "Reduced": self = .reduced 18 | case "Medium": self = .medium 19 | case "Full": self = .full 20 | case "Raw": self = .raw 21 | default: self = .preview 22 | } 23 | } 24 | } 25 | 26 | @available(macOS 12.0, *) 27 | extension PhotogrammetrySession.Configuration.FeatureSensitivity { 28 | public static var allCases = ["Normal", "High"] 29 | 30 | init(_ featureSensitivity: String = String()) { 31 | switch featureSensitivity { 32 | case "Normal": self = .normal 33 | case "High": self = .high 34 | default: self = .normal 35 | } 36 | } 37 | } 38 | 39 | @available(macOS 12.0, *) 40 | extension PhotogrammetrySession.Configuration.SampleOrdering { 41 | public static var allCases = ["Unordered", "Sequential"] 42 | 43 | init(_ sampleOrdering: String = String()) { 44 | switch sampleOrdering { 45 | case "Unordered": self = .unordered 46 | case "Sequential": self = .sequential 47 | default: self = .unordered 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Photogrammetry/Extensions/URL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/22/22. 6 | // 7 | 8 | import Foundation 9 | import AppKit 10 | 11 | extension URL { 12 | var isDirectory: Bool? { 13 | do { 14 | return try resourceValues(forKeys: [URLResourceKey.isDirectoryKey]).isDirectory 15 | } catch let error { 16 | print(error.localizedDescription) 17 | return nil 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Photogrammetry/Photogrammetry.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Photogrammetry/PhotogrammetryApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotogrammetryApp.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/19/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct PhotogrammetryApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | .background(VibrancyView().ignoresSafeArea()) 16 | .fixedSize() 17 | .onAppear { NSWindow.allowsAutomaticWindowTabbing = false } 18 | .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in 19 | NSApp.mainWindow?.standardWindowButton(.zoomButton)?.isHidden = true 20 | } 21 | } 22 | .windowStyle(.hiddenTitleBar) 23 | .windowResizability(.contentSize) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Photogrammetry/Resources/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | Photogrammetry 4 | 5 | Created by Unbinilium on 11/28/22. 6 | 7 | */ 8 | 9 | // MARK: - App 10 | "photogrammetry.button.about" = "About Photogrammetry"; 11 | 12 | // MARK: - Errors 13 | "error.hardware.notsupport" = "This Mac does not meet the minimal hardware requirements of Photogrammetry!"; 14 | "error.input.imagefolder.missing" = "Missing input image folder location!"; 15 | "error.output.model.missing" = "Missing output model location!"; 16 | "error.input.folder.open" = "Failed to open input folder!"; 17 | "error.create.session" = "Failed on creating session!"; 18 | "error.access.session" = "Failed on accessing an uninitialized session!"; 19 | "error.complete.request" = "Failed on processing 3D model request, please check the input images and photogrammetry configurations!"; 20 | "error.unexpected.result" = "Encounter an unexpected request result!"; 21 | "error.failed.load.model.entity" = "Failed on loading AR model entity!"; 22 | 23 | // MARK: - Delegates 24 | "delegate.generating.3dmodel" = "Generating 3D Model..."; 25 | "delegate.processing.complete" = "Processing is complete"; 26 | "delegate.processing.begin" = "Data ingestion is complete, beginning processing..."; 27 | "delegate.automatic.downsampling" = "Automatic downsampling was applied"; 28 | "delegate.request.cancelled" = "Request of the session request was cancelled"; 29 | 30 | // MARK: - Input View 31 | "input.text.drag&drop.hint" = "(A folder with 20~60 images is recommend)"; 32 | "input.text.drag&drop" = "Drag and drop Image Folder"; 33 | "input.text.or" = "or"; 34 | "input.button.open.imagefolder" = "Open Image Folder"; 35 | "input.button.ok" = "OK"; 36 | 37 | // MARK: - Configuration View 38 | "configuration.model.detail" = "Model Detail"; 39 | "configuration.model.detail.describe" = "Detail of output model in terms of mesh size and texture size"; 40 | "configuration.object.masking" = "Object Masking"; 41 | "configuration.object.masking.describe" = "Whether RealityKit uses the provided masks to separate the foreground object from the background"; 42 | "configuration.feature.sensitivity" = "Feature Sensitivity"; 43 | "configuration.feature.sensitivity.describe" = "Set to high if the scanned object does not contain a lot of discernible structures, edges or textures"; 44 | "configuration.sample.ordering" = "Sample Ordering"; 45 | "configuration.sample.ordering.describe" = "Setting to sequential may speed up computation if images are captured in a spatially sequential pattern"; 46 | "configuration.back" = "Back"; 47 | "configuration.generate.3dmodel" = "Generate 3D Model"; 48 | 49 | // MARK: - Processing View 50 | "processing.button.cancel.processing" = "Cancel Processing"; 51 | 52 | // MARK: - Export View 53 | "export.model.entity.missing" = "Model entity is missing for Preview"; 54 | "export.button.process.again" = "Process Again"; 55 | "export.button.export.usdzmodel" = "Export USDZ Model"; 56 | -------------------------------------------------------------------------------- /Photogrammetry/Types/ARContainerViewDelegateError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARContainerViewDelegateError.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/24/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct ARContainerViewDelegateError: Error { 11 | enum ErrorType { 12 | case failedLoadingEntity 13 | } 14 | 15 | var error: ErrorType 16 | var comment: String 17 | 18 | init(error: ErrorType, comment: String = String()) { 19 | self.error = error 20 | self.comment = comment 21 | } 22 | } 23 | 24 | extension ARContainerViewDelegateError: LocalizedError { 25 | public var errorDescription: String? { 26 | switch self.error { 27 | case .failedLoadingEntity: 28 | return NSLocalizedString("error.failed.load.model.entity", comment: self.comment) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Photogrammetry/Types/ApplicationViewState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationViewState.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/21/22. 6 | // 7 | 8 | enum ApplicationViewState { 9 | case onInputView 10 | case onConfigurationView 11 | case onProcessingView 12 | case onExportView 13 | } 14 | -------------------------------------------------------------------------------- /Photogrammetry/Types/PhotogrammetryDelegateError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotogrammetryDelegateError.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/23/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct PhotogrammetryDelegateError: Error { 11 | enum ErrorType { 12 | case unsupportedHardware 13 | case missingInputFolderUrl 14 | case missingOutputModelUrl 15 | case failedOpenInputFolder 16 | case failedCreateSession 17 | case failedAccessSession 18 | case failedCompleteRequest 19 | case unexpectedRequestResult 20 | } 21 | 22 | var error: ErrorType 23 | var comment: String 24 | 25 | init(error: ErrorType, comment: String = String()) { 26 | self.error = error 27 | self.comment = comment 28 | } 29 | } 30 | 31 | extension PhotogrammetryDelegateError: LocalizedError { 32 | public var errorDescription: String? { 33 | switch self.error { 34 | case .unsupportedHardware: 35 | return NSLocalizedString("error.hardware.notsupport", comment: self.comment) 36 | 37 | case .missingInputFolderUrl: 38 | return NSLocalizedString("error.input.imagefolder.missing", comment: self.comment) 39 | 40 | case .missingOutputModelUrl: 41 | return NSLocalizedString("error.output.model.missing", comment: self.comment) 42 | 43 | case .failedOpenInputFolder: 44 | return NSLocalizedString("error.input.folder.open", comment: self.comment) 45 | 46 | case .failedCreateSession: 47 | return NSLocalizedString("error.create.session", comment: self.comment) 48 | 49 | case .failedAccessSession: 50 | return NSLocalizedString("error.access.session", comment: self.comment) 51 | 52 | case .failedCompleteRequest: 53 | return NSLocalizedString("error.complete.request", comment: self.comment) 54 | 55 | case .unexpectedRequestResult: 56 | return NSLocalizedString("error.unexpected.result", comment: self.comment) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Photogrammetry/UI/ARContainerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARView.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/24/22. 6 | // 7 | 8 | import SwiftUI 9 | import RealityKit 10 | 11 | struct ARContainerView: NSViewRepresentable { 12 | public typealias NSViewType = ARView 13 | public var arContainerViewDelegate: ARContainerViewDelegate 14 | 15 | public func makeNSView(context: Context) -> ARView { return arContainerViewDelegate as ARView } 16 | 17 | public func updateNSView(_ nsView: ARView, context: Context) { } 18 | } 19 | -------------------------------------------------------------------------------- /Photogrammetry/UI/ConfigurationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConfigurationView.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/22/22. 6 | // 7 | 8 | import SwiftUI 9 | import RealityKit 10 | 11 | struct ConfigurationView: View { 12 | @Binding var applicationViewState: ApplicationViewState 13 | @ObservedObject var photogrammetryDelegate: PhotogrammetryDelegate 14 | 15 | var body: some View { 16 | VStack (alignment: .center, spacing: 15) { 17 | VStack (alignment: .leading, spacing: 5) { 18 | Spacer() 19 | Picker(LocalizedStringKey("configuration.model.detail"), selection: $photogrammetryDelegate.sessionRequestDetail) { 20 | ForEach(PhotogrammetrySession.Request.Detail.allCases, id: \.self) { 21 | Text($0).tag(PhotogrammetrySession.Request.Detail.init($0)) 22 | } 23 | } 24 | .pickerStyle(.menu) 25 | .padding([.leading, .trailing], 15) 26 | Text(LocalizedStringKey("configuration.model.detail.describe")) 27 | .font(.footnote) 28 | .padding([.leading, .trailing], 15) 29 | Spacer() 30 | } 31 | .frame(width: 320) 32 | .fixedSize() 33 | .background(Color.gray.opacity(0.1)) 34 | .cornerRadius(8) 35 | 36 | VStack (alignment: .leading, spacing: 5) { 37 | Spacer() 38 | HStack { 39 | Text(LocalizedStringKey("configuration.object.masking")) 40 | Spacer() 41 | Toggle(LocalizedStringKey("configuration.object.masking"), isOn: $photogrammetryDelegate.sessionConfiguration.isObjectMaskingEnabled) 42 | .toggleStyle(.switch) 43 | .labelsHidden() 44 | } 45 | .padding([.leading, .trailing], 15) 46 | Text(LocalizedStringKey("configuration.object.masking.describe")) 47 | .font(.footnote) 48 | .padding([.leading, .trailing], 15) 49 | 50 | Spacer() 51 | Picker(LocalizedStringKey("configuration.feature.sensitivity"), selection: $photogrammetryDelegate.sessionConfiguration.featureSensitivity) { 52 | ForEach(PhotogrammetrySession.Configuration.FeatureSensitivity.allCases, id: \.self) { 53 | Text($0).tag(PhotogrammetrySession.Configuration.FeatureSensitivity.init($0)) 54 | } 55 | } 56 | .padding([.leading, .trailing], 15) 57 | Text(LocalizedStringKey("configuration.feature.sensitivity.describe")) 58 | .font(.footnote) 59 | .padding([.leading, .trailing], 15) 60 | 61 | Spacer() 62 | Picker(LocalizedStringKey("configuration.sample.ordering"), selection: $photogrammetryDelegate.sessionConfiguration.sampleOrdering) { 63 | ForEach(PhotogrammetrySession.Configuration.SampleOrdering.allCases, id: \.self) { 64 | Text($0).tag(PhotogrammetrySession.Configuration.SampleOrdering.init($0)) 65 | } 66 | } 67 | .padding([.leading, .trailing], 15) 68 | Text(LocalizedStringKey("configuration.sample.ordering.describe")) 69 | .font(.footnote) 70 | .padding([.leading, .trailing], 15) 71 | Spacer() 72 | } 73 | .pickerStyle(.segmented) 74 | .frame(width: 320) 75 | .fixedSize() 76 | .background(Color.gray.opacity(0.1)) 77 | .cornerRadius(8) 78 | 79 | HStack { 80 | Button(LocalizedStringKey("configuration.back")) { applicationViewState = ApplicationViewState.onInputView } 81 | .keyboardShortcut(.leftArrow, modifiers: .command) 82 | 83 | Spacer() 84 | 85 | Button(LocalizedStringKey("configuration.generate.3dmodel")) { applicationViewState = ApplicationViewState.onProcessingView } 86 | .keyboardShortcut("g", modifiers: .command) 87 | } 88 | .frame(width: 320) 89 | .fixedSize() 90 | } 91 | .padding(.all, 20) 92 | } 93 | } 94 | 95 | // MARK: - ConfigurationView Preview 96 | struct ConfigurationView_Previews: PreviewProvider { 97 | static var previews: some View { 98 | ConfigurationView( 99 | applicationViewState: Binding.constant(ApplicationViewState.onConfigurationView), 100 | photogrammetryDelegate: PhotogrammetryDelegate()) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Photogrammetry/UI/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/19/22. 6 | // 7 | 8 | import SwiftUI 9 | import RealityKit 10 | 11 | struct ContentView: View { 12 | @State var applicationViewState = ApplicationViewState.onInputView 13 | @StateObject var photogrammetryDelegate = PhotogrammetryDelegate() 14 | 15 | var body: some View { 16 | ZStack { 17 | switch applicationViewState { 18 | case .onInputView: 19 | InputView(applicationViewState: $applicationViewState, photogrammetryDelegate: photogrammetryDelegate) 20 | 21 | case .onConfigurationView: 22 | ConfigurationView(applicationViewState: $applicationViewState, photogrammetryDelegate: photogrammetryDelegate) 23 | 24 | case .onProcessingView: 25 | ProcessingView(applicationViewState: $applicationViewState, photogrammetryDelegate: photogrammetryDelegate) 26 | 27 | case .onExportView: 28 | ExportView(applicationViewState: $applicationViewState, photogrammetryDelegate: photogrammetryDelegate) 29 | } 30 | } 31 | .animation(.spring(), value: applicationViewState) 32 | } 33 | } 34 | 35 | // MARK: - ContentView Preview 36 | struct ContentView_Previews: PreviewProvider { 37 | static var previews: some View { 38 | ContentView() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Photogrammetry/UI/ExportView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExportView.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/23/22. 6 | // 7 | 8 | import SwiftUI 9 | import RealityKit 10 | 11 | struct ExportView: View { 12 | @Binding var applicationViewState: ApplicationViewState 13 | @ObservedObject var photogrammetryDelegate: PhotogrammetryDelegate 14 | @StateObject private var arContainerViewDelegate: ARContainerViewDelegate = ARContainerViewDelegate(frame: .zero) 15 | @State private var arContainerViewLoaded: Bool = false 16 | @State private var arContainerViewInfo: String = String() 17 | 18 | var body: some View { 19 | VStack (spacing: 15) { 20 | ZStack { 21 | if arContainerViewLoaded { ARContainerView(arContainerViewDelegate: arContainerViewDelegate) } 22 | else { Text(arContainerViewInfo) } 23 | } 24 | .frame(width: 320, height: 320) 25 | .background(Color.gray.opacity(0.1)) 26 | .cornerRadius(8) 27 | .onAppear { 28 | guard let modelEntityUrl = photogrammetryDelegate.outputModelUrl else { 29 | arContainerViewInfo = String(localized: "export.model.entity.missing") 30 | return 31 | } 32 | arContainerViewDelegate.loadModelEntity(modelEntityUrl: modelEntityUrl) { (result) in 33 | if case .success(_) = result { 34 | arContainerViewLoaded = true 35 | arContainerViewDelegate.modelEntitySpin = true 36 | } else if case let .failure(error) = result { 37 | arContainerViewInfo = error.localizedDescription 38 | } 39 | } 40 | } 41 | .onDisappear { 42 | arContainerViewInfo.removeAll() 43 | arContainerViewLoaded = false 44 | arContainerViewDelegate.modelEntitySpin = false 45 | photogrammetryDelegate.removeOutputModel() 46 | } 47 | 48 | HStack { 49 | Button(LocalizedStringKey("export.button.process.again")) { applicationViewState = .onInputView } 50 | .keyboardShortcut("r", modifiers: .command) 51 | 52 | Spacer() 53 | 54 | Button(LocalizedStringKey("export.button.export.usdzmodel")) { photogrammetryDelegate.openExportModelPanel { _ in } } 55 | .keyboardShortcut("e", modifiers: .command) 56 | } 57 | .frame(width: 320) 58 | .fixedSize() 59 | } 60 | .padding(.all, 20) 61 | } 62 | } 63 | 64 | // MARK: - ExportView Preview 65 | struct ExportView_Previews: PreviewProvider { 66 | static var previews: some View { 67 | ExportView(applicationViewState: Binding.constant(ApplicationViewState.onExportView), 68 | photogrammetryDelegate: PhotogrammetryDelegate()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Photogrammetry/UI/InputView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InputView.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/19/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct InputView: View { 11 | @Binding var applicationViewState: ApplicationViewState 12 | @ObservedObject var photogrammetryDelegate: PhotogrammetryDelegate 13 | @State private var openFolderAlert: Bool = false 14 | @State private var openFolderAlertInfo: String = String() 15 | 16 | var body: some View { 17 | VStack() { 18 | VStack (spacing: 5) { 19 | Text(LocalizedStringKey("input.text.drag&drop")) 20 | Text(LocalizedStringKey("input.text.drag&drop.hint")).font(.footnote) 21 | } 22 | .frame(width: 320, height: 280) 23 | .background(Color.gray.opacity(0.1)) 24 | .cornerRadius(8) 25 | .onDrop(of: ["public.file-url"], delegate: OnDropDelegate( 26 | applicationViewState: $applicationViewState, 27 | photogrammetryDelegate: photogrammetryDelegate)) 28 | 29 | Text(LocalizedStringKey("input.text.or")) 30 | .italic() 31 | 32 | Button(LocalizedStringKey("input.button.open.imagefolder")) { 33 | photogrammetryDelegate.openInputFolderPanel { (result) in 34 | if case let .success(folderUrl) = result { 35 | photogrammetryDelegate.inputFolderUrl = folderUrl 36 | applicationViewState = .onConfigurationView 37 | } else if case let .failure(error) = result { 38 | openFolderAlertInfo = error.localizedDescription 39 | openFolderAlert = true 40 | } 41 | } 42 | } 43 | .keyboardShortcut("o", modifiers: .command) 44 | } 45 | .padding(.all, 20) 46 | .alert(openFolderAlertInfo, isPresented: $openFolderAlert) { 47 | Button(LocalizedStringKey("input.button.ok"), role: .cancel) { 48 | openFolderAlertInfo.removeAll() 49 | } 50 | } 51 | } 52 | } 53 | 54 | // MARK: - InputView Preview 55 | struct InputView_Previews: PreviewProvider { 56 | static var previews: some View { 57 | InputView( 58 | applicationViewState: Binding.constant(ApplicationViewState.onInputView), 59 | photogrammetryDelegate: PhotogrammetryDelegate()) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Photogrammetry/UI/ProcessingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProcessingView.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/23/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ProcessingView: View { 11 | @Binding var applicationViewState: ApplicationViewState 12 | @ObservedObject var photogrammetryDelegate: PhotogrammetryDelegate 13 | @State private var photogrammetryAlert: Bool = false 14 | @State private var photogrammetryAlertInfo: String = String() 15 | @State private var animation: Bool = false 16 | 17 | var body: some View { 18 | VStack (spacing: 15) { 19 | Spacer() 20 | ZStack { 21 | Image(systemName: "viewfinder") 22 | .font(.system(size: 100)) 23 | .foregroundColor(Color.secondary.opacity(0.6)) 24 | Image(systemName: "scale.3d") 25 | .font(.system(size: 60)) 26 | .foregroundColor(Color.secondary.opacity(0.8)) 27 | 28 | Group { 29 | Image(systemName: "gear.circle.fill") 30 | .font(.system(size: 36)) 31 | .foregroundColor(Color.secondary.opacity(0.6)) 32 | .shadow(radius: 3) 33 | .rotationEffect(Angle(degrees: animation ? 360 : 0)) 34 | .onAppear { 35 | withAnimation(.linear(duration: 3.0).repeatForever(autoreverses: false)) { 36 | animation.toggle() 37 | } 38 | } 39 | } 40 | .padding([.leading, .top], 80) 41 | } 42 | .frame(width: 320) 43 | .fixedSize() 44 | .padding([.trailing, .leading], 15) 45 | 46 | ProgressView(value: photogrammetryDelegate.sessionProgress, total: 1.0) 47 | .frame(width: 320) 48 | .fixedSize() 49 | .padding([.trailing, .leading], 15) 50 | 51 | Text(photogrammetryDelegate.sessionInfo) 52 | .lineLimit(2) 53 | .frame(width: 320) 54 | .fixedSize(horizontal: true, vertical: true) 55 | .padding([.trailing, .leading], 15) 56 | Spacer() 57 | } 58 | .onAppear { 59 | photogrammetryDelegate.generateModel { (result) in 60 | if case let .success(modelUrl) = result { 61 | photogrammetryDelegate.outputModelUrl = modelUrl 62 | applicationViewState = ApplicationViewState.onExportView 63 | } else if case let .failure(error) = result { 64 | photogrammetryAlertInfo = error.localizedDescription 65 | photogrammetryAlert = true 66 | } 67 | } 68 | } 69 | .onDisappear { photogrammetryAlertInfo.removeAll() } 70 | .alert(photogrammetryAlertInfo, isPresented: $photogrammetryAlert) { 71 | Button(LocalizedStringKey("processing.button.cancel.processing"), role: .cancel) { 72 | photogrammetryDelegate.cancelGeneratingModel() 73 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { self.applicationViewState = .onConfigurationView } 74 | } 75 | } 76 | } 77 | } 78 | 79 | // MARK: - ProcessingView Preview 80 | struct ProcessingView_Previews: PreviewProvider { 81 | static var previews: some View { 82 | ProcessingView(applicationViewState: Binding.constant(ApplicationViewState.onProcessingView), 83 | photogrammetryDelegate: PhotogrammetryDelegate()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Photogrammetry/UI/VibrancyView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VibrancyView.swift 3 | // Photogrammetry 4 | // 5 | // Created by Unbinilium on 11/25/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct VibrancyView: NSViewRepresentable { 11 | func makeNSView(context: Self.Context) -> NSView { 12 | let visualEffectView = NSVisualEffectView() 13 | visualEffectView.blendingMode = .behindWindow 14 | visualEffectView.material = .fullScreenUI 15 | visualEffectView.state = .active 16 | return visualEffectView 17 | } 18 | 19 | func updateNSView(_ nsView: NSView, context: Context) { } 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Photogrammetry App 2 | 3 | Photogrammetry is a macOS App that helps you creating 3D objects from photographs, with an interactive user interface you can easily obtain a 3D object in [USDZ format](https://en.wikipedia.org/wiki/Universal_Scene_Description) by passing some photos of a object which have been taken in different perspectives. 4 | 5 | ![Photogrammetry App](https://cdn.jsdelivr.net/gh/Unbinilium/Photogrammetry@latest/Assets/Photogrammetry_App_Preview.png) 6 | 7 | To compromise a photogrammetry, the App uses Apple's [RealityKit framwork](https://developer.apple.com/documentation/realitykit). There're also several custom options(masking, model size, feature sensitivity and image ordering) you can adjust to test to get better result. 8 | 9 | Additionally, it easy to get good reconstructing result even the photos does not contain a depth or gyroscope infomation. 10 | 11 | ## Requirement 12 | 13 | - macOS 12.0 or later 14 | - 4GB RAM or more 15 | 16 | ## License 17 | 18 | ``` 19 | MIT License 20 | 21 | Copyright (c) 2022 Unbinilium 22 | ``` 23 | --------------------------------------------------------------------------------