diff --git a/.gitignore b/.gitignore index 330d167..a997c6b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,12 @@ # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore +## macOS Finder +**/.DS_Store + ## User settings xcuserdata/ +*.xcworkspace ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) *.xcscmblueprint diff --git a/Capturinator/Capturinator.xcodeproj/project.pbxproj b/Capturinator/Capturinator.xcodeproj/project.pbxproj new file mode 100644 index 0000000..eb6bddb --- /dev/null +++ b/Capturinator/Capturinator.xcodeproj/project.pbxproj @@ -0,0 +1,529 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 5613ADD526CE778D00397E41 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5613ADD326CE778D00397E41 /* Localizable.strings */; }; + 561DEB03278D5D53007A918D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 561DEB01278D5D53007A918D /* InfoPlist.strings */; }; + 5659FC96278469E50048326C /* Capturinator-InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5659FC94278469E50048326C /* Capturinator-InfoPlist.strings */; }; + 565E3EB326CE668000E5FE4D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 565E3EB226CE668000E5FE4D /* Assets.xcassets */; }; + 565E3EB626CE668000E5FE4D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 565E3EB526CE668000E5FE4D /* Preview Assets.xcassets */; }; + 565E3EC426CE671600E5FE4D /* CloseAlertWindowDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3EC126CE671600E5FE4D /* CloseAlertWindowDelegate.swift */; }; + 565E3EC526CE671600E5FE4D /* Object_Capture_CreateApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3EC226CE671600E5FE4D /* Object_Capture_CreateApp.swift */; }; + 565E3EC626CE671600E5FE4D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3EC326CE671600E5FE4D /* AppDelegate.swift */; }; + 565E3EC926CE672500E5FE4D /* SharedData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3EC726CE672500E5FE4D /* SharedData.swift */; }; + 565E3ECA26CE672500E5FE4D /* ModelFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3EC826CE672500E5FE4D /* ModelFileManager.swift */; }; + 565E3ECE26CE673000E5FE4D /* Sidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3ECB26CE673000E5FE4D /* Sidebar.swift */; }; + 565E3ECF26CE673000E5FE4D /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3ECC26CE673000E5FE4D /* OnboardingView.swift */; }; + 565E3ED026CE673000E5FE4D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3ECD26CE673000E5FE4D /* ContentView.swift */; }; + 565E3EDB26CE675B00E5FE4D /* VisualEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3ED126CE675A00E5FE4D /* VisualEffectView.swift */; }; + 565E3EDC26CE675B00E5FE4D /* TouchBarFocusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3ED226CE675B00E5FE4D /* TouchBarFocusView.swift */; }; + 565E3EDD26CE675B00E5FE4D /* InclusiveGreeting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3ED326CE675B00E5FE4D /* InclusiveGreeting.swift */; }; + 565E3EDE26CE675B00E5FE4D /* HelpPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3ED426CE675B00E5FE4D /* HelpPopover.swift */; }; + 565E3EDF26CE675B00E5FE4D /* ModelViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3ED526CE675B00E5FE4D /* ModelViewer.swift */; }; + 565E3EE026CE675B00E5FE4D /* TitleLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3ED626CE675B00E5FE4D /* TitleLabel.swift */; }; + 565E3EE126CE675B00E5FE4D /* ToggleWithSpacing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3ED726CE675B00E5FE4D /* ToggleWithSpacing.swift */; }; + 565E3EE226CE675B00E5FE4D /* ModelProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3ED826CE675B00E5FE4D /* ModelProgressView.swift */; }; + 565E3EE326CE675B00E5FE4D /* CheckableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3ED926CE675B00E5FE4D /* CheckableLabel.swift */; }; + 565E3EE426CE675B00E5FE4D /* OnboardingLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3EDA26CE675B00E5FE4D /* OnboardingLabel.swift */; }; + 565E3EE826CE676100E5FE4D /* GradientBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3EE526CE676000E5FE4D /* GradientBackground.swift */; }; + 565E3EE926CE676100E5FE4D /* GradientExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3EE626CE676000E5FE4D /* GradientExtension.swift */; }; + 565E3EEA26CE676100E5FE4D /* RoundedFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 565E3EE726CE676000E5FE4D /* RoundedFont.swift */; }; + 56D7A622278CD63200925B5C /* CompatibilityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D7A621278CD63200925B5C /* CompatibilityChecker.swift */; }; + 56D7A624278CDBC300925B5C /* NotSupportedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56D7A623278CDBC300925B5C /* NotSupportedView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 5613ADD426CE778D00397E41 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 561DEB02278D5D53007A918D /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = ""; }; + 562BCF4D26D2A4D200309670 /* Capturinator.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Capturinator.entitlements; sourceTree = ""; }; + 5659FC95278469E50048326C /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = "tr.lproj/Capturinator-InfoPlist.strings"; sourceTree = ""; }; + 565E3EAB26CE667F00E5FE4D /* Capturinator.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Capturinator.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 565E3EB226CE668000E5FE4D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 565E3EB526CE668000E5FE4D /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 565E3EC126CE671600E5FE4D /* CloseAlertWindowDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloseAlertWindowDelegate.swift; sourceTree = ""; }; + 565E3EC226CE671600E5FE4D /* Object_Capture_CreateApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Object_Capture_CreateApp.swift; sourceTree = ""; }; + 565E3EC326CE671600E5FE4D /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 565E3EC726CE672500E5FE4D /* SharedData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharedData.swift; sourceTree = ""; }; + 565E3EC826CE672500E5FE4D /* ModelFileManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelFileManager.swift; sourceTree = ""; }; + 565E3ECB26CE673000E5FE4D /* Sidebar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sidebar.swift; sourceTree = ""; }; + 565E3ECC26CE673000E5FE4D /* OnboardingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; + 565E3ECD26CE673000E5FE4D /* ContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 565E3ED126CE675A00E5FE4D /* VisualEffectView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VisualEffectView.swift; sourceTree = ""; }; + 565E3ED226CE675B00E5FE4D /* TouchBarFocusView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchBarFocusView.swift; sourceTree = ""; }; + 565E3ED326CE675B00E5FE4D /* InclusiveGreeting.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InclusiveGreeting.swift; sourceTree = ""; }; + 565E3ED426CE675B00E5FE4D /* HelpPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HelpPopover.swift; sourceTree = ""; }; + 565E3ED526CE675B00E5FE4D /* ModelViewer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelViewer.swift; sourceTree = ""; }; + 565E3ED626CE675B00E5FE4D /* TitleLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleLabel.swift; sourceTree = ""; }; + 565E3ED726CE675B00E5FE4D /* ToggleWithSpacing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToggleWithSpacing.swift; sourceTree = ""; }; + 565E3ED826CE675B00E5FE4D /* ModelProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelProgressView.swift; sourceTree = ""; }; + 565E3ED926CE675B00E5FE4D /* CheckableLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckableLabel.swift; sourceTree = ""; }; + 565E3EDA26CE675B00E5FE4D /* OnboardingLabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnboardingLabel.swift; sourceTree = ""; }; + 565E3EE526CE676000E5FE4D /* GradientBackground.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradientBackground.swift; sourceTree = ""; }; + 565E3EE626CE676000E5FE4D /* GradientExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GradientExtension.swift; sourceTree = ""; }; + 565E3EE726CE676000E5FE4D /* RoundedFont.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RoundedFont.swift; sourceTree = ""; }; + 56D7A621278CD63200925B5C /* CompatibilityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompatibilityChecker.swift; sourceTree = ""; }; + 56D7A623278CDBC300925B5C /* NotSupportedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotSupportedView.swift; sourceTree = ""; }; + 56DE62BC2788374C009FE059 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 56E9B3CD26D0EE46001653DF /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 565E3EA826CE667F00E5FE4D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 565E3EA226CE667F00E5FE4D = { + isa = PBXGroup; + children = ( + 5659FC94278469E50048326C /* Capturinator-InfoPlist.strings */, + 5613ADD326CE778D00397E41 /* Localizable.strings */, + 565E3EAD26CE667F00E5FE4D /* Capturinator */, + 565E3EAC26CE667F00E5FE4D /* Products */, + ); + sourceTree = ""; + }; + 565E3EAC26CE667F00E5FE4D /* Products */ = { + isa = PBXGroup; + children = ( + 565E3EAB26CE667F00E5FE4D /* Capturinator.app */, + ); + name = Products; + sourceTree = ""; + }; + 565E3EAD26CE667F00E5FE4D /* Capturinator */ = { + isa = PBXGroup; + children = ( + 56DE62BC2788374C009FE059 /* Info.plist */, + 561DEB01278D5D53007A918D /* InfoPlist.strings */, + 562BCF4D26D2A4D200309670 /* Capturinator.entitlements */, + 565E3EBC26CE66A800E5FE4D /* App */, + 565E3EBD26CE66B800E5FE4D /* Data */, + 565E3EBE26CE66BD00E5FE4D /* Navigation */, + 565E3EBF26CE66C500E5FE4D /* Views */, + 565E3EB226CE668000E5FE4D /* Assets.xcassets */, + 565E3EB426CE668000E5FE4D /* Preview Content */, + ); + path = Capturinator; + sourceTree = ""; + }; + 565E3EB426CE668000E5FE4D /* Preview Content */ = { + isa = PBXGroup; + children = ( + 565E3EB526CE668000E5FE4D /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 565E3EBC26CE66A800E5FE4D /* App */ = { + isa = PBXGroup; + children = ( + 565E3EC226CE671600E5FE4D /* Object_Capture_CreateApp.swift */, + 565E3EC326CE671600E5FE4D /* AppDelegate.swift */, + 565E3EC126CE671600E5FE4D /* CloseAlertWindowDelegate.swift */, + ); + path = App; + sourceTree = ""; + }; + 565E3EBD26CE66B800E5FE4D /* Data */ = { + isa = PBXGroup; + children = ( + 565E3EC726CE672500E5FE4D /* SharedData.swift */, + 56D7A621278CD63200925B5C /* CompatibilityChecker.swift */, + 565E3EC826CE672500E5FE4D /* ModelFileManager.swift */, + ); + path = Data; + sourceTree = ""; + }; + 565E3EBE26CE66BD00E5FE4D /* Navigation */ = { + isa = PBXGroup; + children = ( + 565E3ECC26CE673000E5FE4D /* OnboardingView.swift */, + 565E3ECB26CE673000E5FE4D /* Sidebar.swift */, + 56D7A623278CDBC300925B5C /* NotSupportedView.swift */, + 565E3ECD26CE673000E5FE4D /* ContentView.swift */, + ); + path = Navigation; + sourceTree = ""; + }; + 565E3EBF26CE66C500E5FE4D /* Views */ = { + isa = PBXGroup; + children = ( + 565E3ED926CE675B00E5FE4D /* CheckableLabel.swift */, + 565E3ED426CE675B00E5FE4D /* HelpPopover.swift */, + 565E3ED326CE675B00E5FE4D /* InclusiveGreeting.swift */, + 565E3ED826CE675B00E5FE4D /* ModelProgressView.swift */, + 565E3ED526CE675B00E5FE4D /* ModelViewer.swift */, + 565E3EDA26CE675B00E5FE4D /* OnboardingLabel.swift */, + 565E3ED626CE675B00E5FE4D /* TitleLabel.swift */, + 565E3ED726CE675B00E5FE4D /* ToggleWithSpacing.swift */, + 565E3ED226CE675B00E5FE4D /* TouchBarFocusView.swift */, + 565E3ED126CE675A00E5FE4D /* VisualEffectView.swift */, + 565E3EC026CE66CB00E5FE4D /* UI Helpers */, + ); + path = Views; + sourceTree = ""; + }; + 565E3EC026CE66CB00E5FE4D /* UI Helpers */ = { + isa = PBXGroup; + children = ( + 565E3EE526CE676000E5FE4D /* GradientBackground.swift */, + 565E3EE626CE676000E5FE4D /* GradientExtension.swift */, + 565E3EE726CE676000E5FE4D /* RoundedFont.swift */, + ); + path = "UI Helpers"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 565E3EAA26CE667F00E5FE4D /* Capturinator */ = { + isa = PBXNativeTarget; + buildConfigurationList = 565E3EB926CE668000E5FE4D /* Build configuration list for PBXNativeTarget "Capturinator" */; + buildPhases = ( + 565E3EA726CE667F00E5FE4D /* Sources */, + 565E3EA826CE667F00E5FE4D /* Frameworks */, + 565E3EA926CE667F00E5FE4D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Capturinator; + productName = Capturinator; + productReference = 565E3EAB26CE667F00E5FE4D /* Capturinator.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 565E3EA326CE667F00E5FE4D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1300; + LastUpgradeCheck = 1310; + TargetAttributes = { + 565E3EAA26CE667F00E5FE4D = { + CreatedOnToolsVersion = 13.0; + LastSwiftMigration = 1300; + }; + }; + }; + buildConfigurationList = 565E3EA626CE667F00E5FE4D /* Build configuration list for PBXProject "Capturinator" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + tr, + ); + mainGroup = 565E3EA226CE667F00E5FE4D; + productRefGroup = 565E3EAC26CE667F00E5FE4D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 565E3EAA26CE667F00E5FE4D /* Capturinator */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 565E3EA926CE667F00E5FE4D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 565E3EB626CE668000E5FE4D /* Preview Assets.xcassets in Resources */, + 5613ADD526CE778D00397E41 /* Localizable.strings in Resources */, + 565E3EB326CE668000E5FE4D /* Assets.xcassets in Resources */, + 5659FC96278469E50048326C /* Capturinator-InfoPlist.strings in Resources */, + 561DEB03278D5D53007A918D /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 565E3EA726CE667F00E5FE4D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 565E3EDE26CE675B00E5FE4D /* HelpPopover.swift in Sources */, + 565E3EE926CE676100E5FE4D /* GradientExtension.swift in Sources */, + 565E3EC926CE672500E5FE4D /* SharedData.swift in Sources */, + 565E3EC626CE671600E5FE4D /* AppDelegate.swift in Sources */, + 565E3EE426CE675B00E5FE4D /* OnboardingLabel.swift in Sources */, + 565E3EE226CE675B00E5FE4D /* ModelProgressView.swift in Sources */, + 565E3EE326CE675B00E5FE4D /* CheckableLabel.swift in Sources */, + 565E3EDB26CE675B00E5FE4D /* VisualEffectView.swift in Sources */, + 56D7A622278CD63200925B5C /* CompatibilityChecker.swift in Sources */, + 565E3EEA26CE676100E5FE4D /* RoundedFont.swift in Sources */, + 565E3ED026CE673000E5FE4D /* ContentView.swift in Sources */, + 56D7A624278CDBC300925B5C /* NotSupportedView.swift in Sources */, + 565E3EC426CE671600E5FE4D /* CloseAlertWindowDelegate.swift in Sources */, + 565E3ECA26CE672500E5FE4D /* ModelFileManager.swift in Sources */, + 565E3ECE26CE673000E5FE4D /* Sidebar.swift in Sources */, + 565E3EE126CE675B00E5FE4D /* ToggleWithSpacing.swift in Sources */, + 565E3EDF26CE675B00E5FE4D /* ModelViewer.swift in Sources */, + 565E3EC526CE671600E5FE4D /* Object_Capture_CreateApp.swift in Sources */, + 565E3EDD26CE675B00E5FE4D /* InclusiveGreeting.swift in Sources */, + 565E3EE026CE675B00E5FE4D /* TitleLabel.swift in Sources */, + 565E3ECF26CE673000E5FE4D /* OnboardingView.swift in Sources */, + 565E3EE826CE676100E5FE4D /* GradientBackground.swift in Sources */, + 565E3EDC26CE675B00E5FE4D /* TouchBarFocusView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 5613ADD326CE778D00397E41 /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 5613ADD426CE778D00397E41 /* en */, + 56E9B3CD26D0EE46001653DF /* tr */, + ); + name = Localizable.strings; + sourceTree = ""; + }; + 561DEB01278D5D53007A918D /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 561DEB02278D5D53007A918D /* tr */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 5659FC94278469E50048326C /* Capturinator-InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 5659FC95278469E50048326C /* tr */, + ); + name = "Capturinator-InfoPlist.strings"; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 565E3EB726CE668000E5FE4D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 565E3EB826CE668000E5FE4D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 565E3EBA26CE668000E5FE4D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Capturinator/Capturinator.entitlements; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Capturinator/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Capturinator/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = "© 2021 Mehmet Bertan Tarakçıoğlu"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 565E3EBB26CE668000E5FE4D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Capturinator/Capturinator.entitlements; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = "\"Capturinator/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = Capturinator/Info.plist; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + INFOPLIST_KEY_NSHumanReadableCopyright = "© 2021 Mehmet Bertan Tarakçıoğlu"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 12.0; + MARKETING_VERSION = 1.0; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 565E3EA626CE667F00E5FE4D /* Build configuration list for PBXProject "Capturinator" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 565E3EB726CE668000E5FE4D /* Debug */, + 565E3EB826CE668000E5FE4D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 565E3EB926CE668000E5FE4D /* Build configuration list for PBXNativeTarget "Capturinator" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 565E3EBA26CE668000E5FE4D /* Debug */, + 565E3EBB26CE668000E5FE4D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 565E3EA326CE667F00E5FE4D /* Project object */; +} diff --git a/Capturinator/Capturinator.xcodeproj/xcshareddata/xcschemes/Capturinator.xcscheme b/Capturinator/Capturinator.xcodeproj/xcshareddata/xcschemes/Capturinator.xcscheme new file mode 100644 index 0000000..b586fc7 --- /dev/null +++ b/Capturinator/Capturinator.xcodeproj/xcshareddata/xcschemes/Capturinator.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Capturinator/Capturinator/App/AppDelegate.swift b/Capturinator/Capturinator/App/AppDelegate.swift new file mode 100644 index 0000000..2cca4d9 --- /dev/null +++ b/Capturinator/Capturinator/App/AppDelegate.swift @@ -0,0 +1,56 @@ +// +// AppDelegate.swift +// Object Capture Create +// +// Created by Bertan on 26.06.2021. +// + +import SwiftUI +import AppKit +import UserNotifications + +class AppDelegate: NSObject, NSApplicationDelegate { + @AppStorage("userSuppressedQuitAlert") private var userSuppressedQuitAlert = false + private var shouldAskBeforeQuitting = true + + func applicationDidFinishLaunching(_ notification: Notification) { + UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge]) { success, error in + if success { + print("User granted notifications access :)") + }else if let e = error { + print(e.localizedDescription) + } + } + } + + + func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + shouldAskBeforeQuitting = false + return true + } + + func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { + if shouldAskBeforeQuitting, !userSuppressedQuitAlert { + let alert = NSAlert() + alert.alertStyle = .warning + alert.messageText = String(localized: "QuitAppAlertTitle", comment: "Title of the app termination confirmation dialog") + alert.informativeText = String(localized: "QuitAppAlertBody", comment: "Body of the app termination confirmation dialog") + alert.addButton(withTitle: String(localized: "Quit", comment: "Button: Quits app")) + alert.addButton(withTitle: String(localized: "Cancel", comment: "General purpose cancel button")) + alert.showsSuppressionButton = true + + let response = alert.runModal() + + if let suppressionResponse = alert.suppressionButton?.state { + if suppressionResponse == NSControl.StateValue.on { + userSuppressedQuitAlert = true + } + } + + if response == .alertSecondButtonReturn { + return .terminateCancel + } + } + return .terminateNow + } +} diff --git a/Capturinator/Capturinator/App/CloseAlertWindowDelegate.swift b/Capturinator/Capturinator/App/CloseAlertWindowDelegate.swift new file mode 100644 index 0000000..466c7a4 --- /dev/null +++ b/Capturinator/Capturinator/App/CloseAlertWindowDelegate.swift @@ -0,0 +1,38 @@ +// +// CloseAlertWindowDelegate.swift +// Object Capture Create +// +// Created by Bertan on 28.06.2021. +// + +import AppKit +import SwiftUI + +class CloseAlertWindowDelegate: NSObject, NSWindowDelegate { + @AppStorage("userSuppressedWindowCloseAlert") private var userSuppressedWindowCloseAlert = false + + func windowShouldClose(_ sender: NSWindow) -> Bool { + if !userSuppressedWindowCloseAlert { + let alert = NSAlert() + alert.alertStyle = .warning + alert.messageText = String(localized: "CloseWindowAlertTitle", comment: "Title of the window closing confirmation dialog") + alert.informativeText = String(localized: "CloseWindowAlertBody", comment: "Body of the window closing confirmation dialog") + alert.addButton(withTitle: NSLocalizedString("Close", comment: "Button: Closes window")) + alert.addButton(withTitle: String(localized: "Cancel")) + alert.showsSuppressionButton = true + + let response = alert.runModal() + + if let suppressionResponse = alert.suppressionButton?.state { + if suppressionResponse == NSControl.StateValue.on { + userSuppressedWindowCloseAlert = true + } + } + + if response == .alertSecondButtonReturn { + return false + } + } + return true + } +} diff --git a/Capturinator/Capturinator/App/Object_Capture_CreateApp.swift b/Capturinator/Capturinator/App/Object_Capture_CreateApp.swift new file mode 100644 index 0000000..c38f271 --- /dev/null +++ b/Capturinator/Capturinator/App/Object_Capture_CreateApp.swift @@ -0,0 +1,66 @@ +// +// Object_Capture_CreateApp.swift +// Object Capture Create +// +// Created by Bertan on 22.06.2021. +// + +import SwiftUI + +@main +struct Object_Capture_CreateApp: App { + @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + @StateObject private var sharedData = SharedData() + @AppStorage("onboardingShown") private var onboardingShown = false + @State private var showingOnboarding = false + var supportsObjectCapture = CompatibilityChecker().checkObjectCaptureSupport() + + var body: some Scene { + WindowGroup { + if supportsObjectCapture { + ContentView() + .frame(minWidth: 1280, maxWidth: .infinity, minHeight: 720, maxHeight: .infinity) + .environmentObject(sharedData) + .onAppear { + if !onboardingShown { + showingOnboarding = true + onboardingShown = true + } + } + .sheet(isPresented: $showingOnboarding) { + OnboardingView() + + } + }else { + NotSupportedView() + .frame(minWidth: 1280, maxWidth: .infinity, minHeight: 720, maxHeight: .infinity) + } + } + .commands { + CommandGroup(replacing: .appInfo) { + Button(String(localized: "AboutMenuCommandTtitle", comment: "Menu Command: Shows the about this app window")){ + NSApplication.shared.orderFrontStandardAboutPanel( + options: [ + NSApplication.AboutPanelOptionKey.credits: NSAttributedString(string: String(localized: "AboutWindowText", comment: "Short description of the app") + "\n" + String(localized: "MadeWithLove"), attributes: [ NSAttributedString.Key.font: NSFont.labelFont(ofSize: 12)]), + NSApplication.AboutPanelOptionKey(rawValue: "Copyright"): Bundle.main.object(forInfoDictionaryKey: "NSHumanReadableCopyright") ?? "" + ]) + + } + } + CommandGroup(replacing: .help) { + Button(String(localized: "GettingStartedMenuCommandTittle", comment: "Menu Command: Shows the onboarding screen")) { + showingOnboarding.toggle() + } + .keyboardShortcut("?") + } + CommandGroup(after: .help) { + Button(String(localized: "SupportMenuCommandTitle", comment: "Menu Command: Opens the Capturinator Support Website")) { + if let url = URL(string: "http://www.capturinator.bertan.codes/support") { + NSWorkspace.shared.open(url) + } + } + } + SidebarCommands() + } + } +} diff --git a/Capturinator/Capturinator/Assets.xcassets/AccentColor.colorset/Contents.json b/Capturinator/Capturinator/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..67fecba --- /dev/null +++ b/Capturinator/Capturinator/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,28 @@ +{ + "colors" : [ + { + "color" : { + "platform" : "universal", + "reference" : "systemMintColor" + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "platform" : "universal", + "reference" : "systemMintColor" + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/Contents.json b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..b8bd462 --- /dev/null +++ b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "filename" : "icon_16x16.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "filename" : "icon_16x16@2x@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "filename" : "icon_32x32.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "filename" : "icon_32x32@2x@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "filename" : "icon_128x128.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "filename" : "icon_128x128@2x@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "filename" : "icon_256x256.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "filename" : "icon_256x256@2x@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "filename" : "icon_512x512.png", + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "filename" : "icon_512x512@2x@2x.png", + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_128x128.png new file mode 100644 index 0000000..9c3da33 Binary files /dev/null and b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_128x128.png differ diff --git a/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x@2x.png b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x@2x.png new file mode 100644 index 0000000..d5cccc9 Binary files /dev/null and b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x@2x.png differ diff --git a/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_16x16.png b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_16x16.png new file mode 100644 index 0000000..962544d Binary files /dev/null and b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_16x16.png differ diff --git a/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x@2x.png b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x@2x.png new file mode 100644 index 0000000..3038e8d Binary files /dev/null and b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x@2x.png differ diff --git a/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_256x256.png b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_256x256.png new file mode 100644 index 0000000..b23d3d9 Binary files /dev/null and b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_256x256.png differ diff --git a/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x@2x.png b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x@2x.png new file mode 100644 index 0000000..ff7d63e Binary files /dev/null and b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x@2x.png differ diff --git a/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_32x32.png b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_32x32.png new file mode 100644 index 0000000..3038e8d Binary files /dev/null and b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_32x32.png differ diff --git a/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x@2x.png b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x@2x.png new file mode 100644 index 0000000..ca166cf Binary files /dev/null and b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x@2x.png differ diff --git a/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_512x512.png b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_512x512.png new file mode 100644 index 0000000..d948123 Binary files /dev/null and b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_512x512.png differ diff --git a/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x@2x.png b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x@2x.png new file mode 100644 index 0000000..63289ef Binary files /dev/null and b/Capturinator/Capturinator/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x@2x.png differ diff --git a/Capturinator/Capturinator/Assets.xcassets/Contents.json b/Capturinator/Capturinator/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Capturinator/Capturinator/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Capturinator/Capturinator/Capturinator.entitlements b/Capturinator/Capturinator/Capturinator.entitlements new file mode 100644 index 0000000..19afff1 --- /dev/null +++ b/Capturinator/Capturinator/Capturinator.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + + + diff --git a/Capturinator/Capturinator/Data/CompatibilityChecker.swift b/Capturinator/Capturinator/Data/CompatibilityChecker.swift new file mode 100644 index 0000000..a212f6f --- /dev/null +++ b/Capturinator/Capturinator/Data/CompatibilityChecker.swift @@ -0,0 +1,31 @@ +// +// CompatibilityChecker.swift +// Capturinator +// +// Created by Bertan on 11.01.2022. +// + +import Metal + +class CompatibilityChecker { + private func supportsObjectReconstruction() -> Bool { + for device in MTLCopyAllDevices() where + !device.isLowPower && + device.areBarycentricCoordsSupported && + device.recommendedMaxWorkingSetSize >= UInt64(4e9) { + return true + } + return false + } + + private func supportsRayTracing() -> Bool { + for device in MTLCopyAllDevices() where device.supportsRaytracing { + return true + } + return false + } + + func checkObjectCaptureSupport() -> Bool { + return supportsObjectReconstruction() && supportsRayTracing() + } +} diff --git a/Capturinator/Capturinator/Data/ModelFileManager.swift b/Capturinator/Capturinator/Data/ModelFileManager.swift new file mode 100644 index 0000000..81843b5 --- /dev/null +++ b/Capturinator/Capturinator/Data/ModelFileManager.swift @@ -0,0 +1,33 @@ +// +// TemporaryModels.swift +// Object Capture Create +// +// Created by Bertan on 10.07.2021. +// + +import Foundation + +class ModelFileManager { + private func tempFolderURL(apropiateFor: URL?) -> URL { + let tempFolderURL = try? FileManager.default.url(for: .itemReplacementDirectory, in: .userDomainMask, appropriateFor: apropiateFor, create: true) + return tempFolderURL ?? URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) + } + + func generateTempModelURL(apropiateFor: URL? = nil) -> URL { + let directoryURL = tempFolderURL(apropiateFor: apropiateFor) + let tempFileURL = directoryURL.appendingPathComponent(UUID().uuidString).appendingPathExtension("usdz") + return tempFileURL + } + + func copyTempModel(tempModelURL: URL, permanentURL: URL) throws { + let fileManager = FileManager.default + if fileManager.fileExists(atPath: permanentURL.path) { + try fileManager.removeItem(at: permanentURL) + } + try fileManager.copyItem(at: tempModelURL, to: permanentURL) + } + + func removeTempModel(modelURL: URL) throws{ + try FileManager.default.removeItem(at: modelURL) + } +} diff --git a/Capturinator/Capturinator/Data/SharedData.swift b/Capturinator/Capturinator/Data/SharedData.swift new file mode 100644 index 0000000..922e4eb --- /dev/null +++ b/Capturinator/Capturinator/Data/SharedData.swift @@ -0,0 +1,17 @@ +// +// SharedData.swift +// Object Capture Create +// +// Created by Bertan on 27.06.2021. +// + +import Foundation +import Metal +import Combine + +final class SharedData: ObservableObject { + @Published var modelViewerModelURL: URL? + @Published var modelProgressViewState: ModelProgressView.PresentationState = .hidden + @Published var modelProcessingProgress: CGFloat = 0.0 + @Published var showInFinderURL: URL? +} diff --git a/Capturinator/Capturinator/Info.plist b/Capturinator/Capturinator/Info.plist new file mode 100644 index 0000000..bc11256 --- /dev/null +++ b/Capturinator/Capturinator/Info.plist @@ -0,0 +1,8 @@ + + + + + ITSAppUsesNonExemptEncryption + + + diff --git a/Capturinator/Capturinator/Navigation/ContentView.swift b/Capturinator/Capturinator/Navigation/ContentView.swift new file mode 100644 index 0000000..3d97991 --- /dev/null +++ b/Capturinator/Capturinator/Navigation/ContentView.swift @@ -0,0 +1,91 @@ +// +// ContentView.swift +// Object Capture Create +// +// Created by Bertan on 22.06.2021. +// + +import SwiftUI +import SpriteKit +import SceneKit +import UserNotifications +import RealityKit + +struct ContentView: View { + @EnvironmentObject private var sharedData: SharedData + @State private var photogrammetrySession: PhotogrammetrySession? + @State private var showingCancelAlert = false + + var body: some View { + + NavigationView { + Sidebar(photogrammetrySession: $photogrammetrySession) + .frame(minWidth: 300, maxWidth: .infinity, maxHeight: .infinity) + .environmentObject(sharedData) + .background( + VisualEffectView(material: .windowBackground, blendingMode: .behindWindow) + .edgesIgnoringSafeArea(.all) + ) + ZStack(alignment: .bottom) { + VisualEffectView(material: .sidebar, blendingMode: .behindWindow) + Group { + if let modelURL = sharedData.modelViewerModelURL { + ModelViewer(modelURL: modelURL) + }else { + Label(String(localized: "NoModelToViewMessage", comment: "Label: Viewed as a placeholder when there is no model in the model viewer"), systemImage: "arkit") + .font(.title2) + } + } + .frame(maxHeight: .infinity) + ModelProgressView(cancelAction: { + showingCancelAlert.toggle() + }) + .environmentObject(sharedData) + .padding() + } + } + .onAppear { + + } + .onExitCommand { + if photogrammetrySession?.isProcessing ?? false { + showingCancelAlert.toggle() + } + } + .onDisappear { + photogrammetrySession?.cancel() + if let modelViewerModelURL = sharedData.modelViewerModelURL { + try? ModelFileManager().removeTempModel(modelURL: modelViewerModelURL) + } + } + .alert(isPresented: $showingCancelAlert) { + Alert( + title: + Text("StopCreatingModelAlertTitle", comment: "Title for the stop model creation confirmation dialog"), + message: + Text("StopCreatingModelAlertBody", comment: "Body for the stop model creation confirmation dialog"), + primaryButton: + .cancel(), + secondaryButton: + .destructive(Text("Stop", comment: "Stop model creation"), action: { photogrammetrySession?.cancel() }) + ) + } + .toolbar { + ToolbarItem(placement: .navigation) { + Button(action: toogleSidebar) { + Label(String(localized: "ToggleSidebar", comment: "Button: Toggles Sidebar"), systemImage: "sidebar.left") + } + } + } + } + + func toogleSidebar() { + NSApp.keyWindow?.firstResponder?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil) + } +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/Capturinator/Capturinator/Navigation/NotSupportedView.swift b/Capturinator/Capturinator/Navigation/NotSupportedView.swift new file mode 100644 index 0000000..da7bb95 --- /dev/null +++ b/Capturinator/Capturinator/Navigation/NotSupportedView.swift @@ -0,0 +1,34 @@ +// +// NotSupportedView.swift +// Capturinator +// +// Created by Bertan on 11.01.2022. +// + +import SwiftUI + +struct NotSupportedView: View { + var body: some View { + ZStack(alignment: .center) { + VisualEffectView(material: .sidebar, blendingMode: .behindWindow) + VStack { + Label(title: { + Text((try? AttributedString(markdown: String(localized: "ObjectCaptureNotSupportedTitle", comment: "Label: Shown in the main window if the user's devie does not support Object Capture."))) ?? "MD Formatting Error!") + }, icon: { + Image(systemName: "laptopcomputer.trianglebadge.exclamationmark") + }) + .font(.title) + .foregroundColor(.red) + .padding(2) + Text((try? AttributedString(markdown: String(localized: "ObjectCaptureNotSupportedDescription", comment: "Label: Shown in the main window if the user's devie does not support Object Capture, more info about compatibility issue."))) ?? "MD Formatting Error!") + .font(.title3) + } + } + } +} + +struct NotSupportedView_Previews: PreviewProvider { + static var previews: some View { + NotSupportedView() + } +} diff --git a/Capturinator/Capturinator/Navigation/OnboardingView.swift b/Capturinator/Capturinator/Navigation/OnboardingView.swift new file mode 100644 index 0000000..2c70e71 --- /dev/null +++ b/Capturinator/Capturinator/Navigation/OnboardingView.swift @@ -0,0 +1,54 @@ +// +// Onboarding.swift +// Onboarding +// +// Created by Bertan on 24.07.2021. +// + +import SwiftUI + +struct OnboardingView: View { + @Environment(\.presentationMode) var presentationMode + var body: some View { + VStack { + Image(nsImage: NSImage(named: "AppIcon") ?? NSImage()) + .resizable() + .scaledToFit() + .frame(maxWidth: 90) + .padding(.top) + InclusiveGreeting(greetingMessage: String(localized: "OnboardingWelcome", comment: "Text: Greets the user for the first time on the onboarding screen"), gradient: .sixColorGradient()) + VStack(alignment: .leading, spacing: 20) { + OnboardingLabel(title: String(localized: "TakePhotos", comment: "Onboarding screen subtitle"), description: String(localized: "TakePhotosBody", comment: "Onboarding screen text under take photos"), systemImage: "camera.fill") + OnboardingLabel(title: String(localized: "ImportPhotos", comment: "Onboarding screen subtitle"), description: String(localized: "ImportPhotosBody", comment: "Onboarding screen text under import photos"), systemImage: "square.and.arrow.down.fill") + OnboardingLabel(title: String(localized: "Create", comment: "Onboarding screen subtitle"), description: String(localized: "CreateBody", comment: "Onboarding screen text under create"), systemImage: "wand.and.stars") + OnboardingLabel(title: String(localized: "OpenSource", comment: "Onboarding screen subtitle"), description: String(localized: "OpenSourceBody", comment: "Onboarding screen text under open source"), systemImage: "chevron.left.forwardslash.chevron.right") + + } + .fixedSize(horizontal: false, vertical: true) + .padding(.top) + Spacer() + Text("MadeWithLove", comment: "Message to be displayed at about this app window and onboarding screen") + .roundedFont(.body) + Spacer() + Spacer() + Button(action: { + presentationMode.wrappedValue.dismiss() + }) { + Text("StartCreating", comment: "Button: Dismisses the onboarding screen") + .padding(.horizontal) + } + .controlSize(.large) + .buttonStyle(.borderedProminent) + .roundedFont(.title3) + .keyboardShortcut(.defaultAction) + } + .frame(width: 550, height: 600) + .padding() + } +} + +struct OnboardingView_Previews: PreviewProvider { + static var previews: some View { + OnboardingView() + } +} diff --git a/Capturinator/Capturinator/Navigation/Sidebar.swift b/Capturinator/Capturinator/Navigation/Sidebar.swift new file mode 100644 index 0000000..8b1e856 --- /dev/null +++ b/Capturinator/Capturinator/Navigation/Sidebar.swift @@ -0,0 +1,409 @@ +// +// Sidebar.swift +// Object Capture Create +// +// Created by Bertan on 23.06.2021. +// + +import SwiftUI +import Combine +import RealityKit +import UserNotifications + +struct Sidebar: View { + typealias PSConfig = PhotogrammetrySession.Configuration + typealias PSRequestDetail = PhotogrammetrySession.Request.Detail + + @State private var processingErrorOccured = false + + @EnvironmentObject private var sharedData: SharedData + @Binding var photogrammetrySession: PhotogrammetrySession? + + @State private var showingAlert = false + + @State private var usingSequentialSamples = false + @State private var objectMasking = true + @State private var highFeatureSensivity = false + + @State private var alertText = "" { + didSet { showingAlert.toggle() } + } + + @State private var inputFolderURL: URL? + @State var psConfig = PhotogrammetrySession.Configuration() + @State private var requestDetail: PSRequestDetail = .medium + @State private var psProcessTask: Task<(), Never>? + + @FocusState private var isFocused: Bool + + enum TouchBarState { case main, settings, quality } + @State private var touchBarState: TouchBarState = .main + + var body: some View { + VStack { + // Tocuh Bar! This view is not visible + TouchBarFocusView() + .frame(width: 0, height: 0) + .touchBar( + TouchBar(id: UUID().uuidString) { + Group { + switch touchBarState { + case .main: + Button(action: openFolder) { + Label(String(localized: "OpenFolder", comment: "Button[TouchBar]: Opens image folder"), systemImage: "folder.fill") + } + Button(action: { + touchBarState = .settings + }) { + Label(String(localized: "Settings", comment: "Button[TouchBar]: Shows Model Settings Strip"), systemImage: "slider.horizontal.3") + } + Button(action: { + touchBarState = .quality + }) { + Label(NSLocalizedString("Quality", comment: "Button[TouchBar]: Shows Model Quality Picker Strip"), systemImage: "dial.min.fill") + } + Spacer() + Group { + Button(action: createPreview) { + Label(String(localized: "Preview", comment: "Button[TouchBar]: Creates a preview model"), systemImage: "paintbrush.fill") + } + Button(action: { exportModel() }) { + Label(String(localized: "ExportModel", comment: "Button: Creates and exports model"), systemImage: "arkit") + } + .buttonStyle(.borderedProminent) + } + .disabled(inputFolderURL == nil || photogrammetrySession?.isProcessing ?? false) + case .settings: + Button(action: { + withAnimation { + touchBarState = .main + } + }) { + Image(systemName: "xmark.circle.fill") + .font(.title) + } + .buttonStyle(.borderless) + ToggleWithSpacing(String(localized: "SequentialSamples", comment: "Toggle: Sets Sequential samples option"), isOn: $usingSequentialSamples) + ToggleWithSpacing(String(localized: "ObjectMasking", comment: "Toggle: Sets Object masking option"), isOn: $objectMasking) + ToggleWithSpacing(String(localized: "HighFeatureSensitivityShortened", comment: "Toggle[TouchBar]: Sets high feature sensitivity option"), isOn: $highFeatureSensivity) + case .quality: + Button(action: { + withAnimation { + touchBarState = .main + } + }) { + Image(systemName: "xmark.circle.fill") + .font(.title) + } + .buttonStyle(.borderless) + + + Button(action: { + requestDetail = .reduced + }) { + CheckableLabel(text: String(localized: "Reduced", comment: "The lowest model quality"), systemImage: "square.grid.2x2", checked: requestDetail == .reduced) + } + + Button(action: { + requestDetail = .medium + }) { + CheckableLabel(text: String(localized: "Medium", comment: "Medium model quality"), systemImage: "square.grid.2x2.fill", checked: requestDetail == .medium) + } + + Button(action: { + requestDetail = .full + }) { + CheckableLabel(text: String(localized: "Full", comment: "Full model quality"), systemImage: "square.grid.3x2", checked: requestDetail == .full) + } + + Button(action: { + requestDetail = .raw + }) { + CheckableLabel(text: String(localized: "RAW", comment: "RAW model quality"), systemImage: "square.grid.3x2.fill", checked: requestDetail == .raw) + } + } + } + .disabled(photogrammetrySession?.isProcessing ?? false) + } + ) + Text("Welcome", comment: "Welcome text at the top of the sidebar") + .bold() + .roundedFont(.largeTitle) + .gradientBackground(gradient: .indigoGradient) + Text("AppInfo", comment: "Short app info under the welcome text in the sidebar") + .roundedFont(.body) + .multilineTextAlignment(.center) + .padding([.horizontal, .bottom]) + .offset(y: 10) + + VStack(alignment: .leading) { + Divider() + .padding(.bottom, 5) + Group { + TitleLabel(title: String(localized: "ImageFolder", comment: "Image folder title in the sidebar"), systemImage: "folder.fill", gradient: .purpleGradient) + HStack { + Group { + if let url = inputFolderURL { + Text(url.lastPathComponent) + Spacer() + }else { + Text("NotSelected", comment: "Image folder name placeholder when not selected") + } + } + .roundedFont(.headline) + Spacer() + Button(inputFolderURL == nil ? String(localized: "Open", comment: "Button: Opens image folder") : String(localized: "Change", comment: "Chnages the selected image folder")) { + openFolder() + } + .roundedFont(.body) + } + .helpPopover(title: String(localized: "ImageFolderHelpTitle", comment: "Help popover title for image folder"), description: String(localized: "ImageFolderHelpBody", comment: "Help popover body for image folder")) + .padding(0) + } + .disabled(photogrammetrySession?.isProcessing ?? false) + + Divider() + .padding(.vertical, 5) + + Group { + TitleLabel(title: String(localized: "ModelSettings", comment: "Model Settings title in the sidebar"), systemImage: "slider.horizontal.3", gradient: .yellowGradient) + + ToggleWithSpacing(String(localized: "SequentialSamples"), isOn: $usingSequentialSamples) + .onChange(of: usingSequentialSamples) { newValue in + psConfig.sampleOrdering = newValue ? .sequential : .unordered + } + .helpPopover(title: String(localized: "SequentialSamplesHelpTitle", comment: "Help popover title for sequential samples"), description: String(localized: "SequentialSamplesHelpBody", comment: "Help popover body for sequential samples")) + + ToggleWithSpacing(String(localized: "ObjectMasking"), isOn: $objectMasking) + .onChange(of: objectMasking) { newValue in + psConfig.isObjectMaskingEnabled = newValue + } + .helpPopover(title: String(localized: "ObjectMaskingHelpTitle", comment: "Help popover title for object masking"), description: String(localized: "ObjectMaskingHelpBody", comment: "Help popover body for object masking")) + + ToggleWithSpacing(String(localized: "HighFeatureSensitivity"), isOn: $highFeatureSensivity) + .onChange(of: highFeatureSensivity) { newValue in + psConfig.featureSensitivity = newValue ? .high : .normal + } + .helpPopover(title: String(localized: "HighFeatureSensitivityHelpTitle", comment: "Help popover title for high feature sensitivity"), description: String(localized: "HighFeatureSensitivityHelpBody", comment: "Help popover body for high feature sensitivity")) + + Picker(String(localized: "ModelQuality", comment: "Picker: Sets the model quality"), selection: $requestDetail) { + Text("RAW").tag(PSRequestDetail.raw) + Text("Full").tag(PSRequestDetail.full) + Text("Medium").tag(PSRequestDetail.medium) + Text("Reduced").tag(PSRequestDetail.reduced) + + } + .roundedFont(.headline) + .helpPopover(title: String(localized: "ModelQualityHelpTitle", comment: "Help popover title for model quality"), description: String(localized: "ModelQualityHelpBody", comment: "Help popover title for model quality")) + } + .disabled(photogrammetrySession?.isProcessing ?? false) + + Spacer() + + Divider() + .padding(.vertical, 5) + Group { + TitleLabel(title: String(localized: "Rendering", comment: "Rendering title in the sidebar"), systemImage: "paintbrush.fill", gradient: .greenGradient) + HStack { + Spacer() + Button(sharedData.modelViewerModelURL == nil ? String(localized: "CreatePreview", comment: "Creates a preview model") : String(localized: "RefinePreview", comment: "Refines a preview model")) { + createPreview() + } + .controlSize(.large) + Spacer() + Button("ExportModel") { + exportModel() + } + .controlSize(.large) + .buttonStyle(.borderedProminent) + Spacer() + } + .roundedFont(.body) + } + .disabled(inputFolderURL == nil || photogrammetrySession?.isProcessing ?? false) + } + .padding([.horizontal, .bottom]) + } + .alert(isPresented: $showingAlert) { + return Alert(title: Text(String(localized: "ErrorAlertTitle", comment: "Title for generic error alert")), message: Text(alertText), dismissButton: nil) + } + } + + private func openFolder() { + let openPanel = NSOpenPanel() + openPanel.canChooseDirectories = true + openPanel.canChooseFiles = false + if openPanel.runModal() == .OK { + self.inputFolderURL = openPanel.url + print("Source folder selected: \(openPanel.url!)") + } + } + + private func chooseSaveDestination() -> URL? { + // Clear out the old url + let savePanel = NSSavePanel() + savePanel.title = String(localized: "SavePanelTitle", comment: "Title for the save panel shown before exporting model") + savePanel.allowsOtherFileTypes = false + savePanel.allowedContentTypes = [.usdz] + savePanel.nameFieldStringValue = String(localized: "DefaultExportFilename", comment: "Default filename while saving the model") + if savePanel .runModal() == .OK { + return savePanel.url + } + return nil + } + + private func createModel(permenantSaveURL: URL? = nil) { + processingErrorOccured = false + + guard let inputURL = inputFolderURL else { + print("inputFolderURL is nil! Aborting...") + processingErrorOccured = true + alertText = String(localized: "NoSourceFolderErrorAlertBody", comment: "Alert body for no source folder found error") + return + } + + do { + photogrammetrySession = try PhotogrammetrySession(input: inputURL, configuration: psConfig) + }catch { + print("Could not create photogrammetry session, aborting...") + processingErrorOccured = true + alertText = String(localized: "PSCreationErrorAlertBody", comment: "Alert body for photogrammetry session creation error") + return + } + + let temporarySaveURL = ModelFileManager().generateTempModelURL(apropiateFor: permenantSaveURL) + let request = PhotogrammetrySession.Request.modelFile(url: temporarySaveURL, detail: permenantSaveURL == nil ? .preview : requestDetail) + + + Task(priority: .userInitiated) { + do { + for try await output in photogrammetrySession!.outputs { + switch output { + case .inputComplete: + print("Successfully initiallized images, beginning processing...") + case .requestError(_, _): + print("Request error!") + processingErrorOccured = true + alertText = String(localized: "ModelCreationErrorAlertBody", comment: "Alert body for model creation error") + case .requestComplete(_, _): + print("Completed request!") + case .requestProgress(_, fractionComplete: let fractionComplete): + print("Current request is \(Int(fractionComplete*100))% complete") + sharedData.modelProgressViewState = .bar + sharedData.modelProcessingProgress = fractionComplete + case .processingComplete: + print("Done processing!") + handleCreationCompletion(temporaryLocation: temporarySaveURL, permenantSaveURL: permenantSaveURL) + case .processingCancelled: + print("Processing is cancelled") + withAnimation { + sharedData.modelProgressViewState = .hidden + } + case .invalidSample(id: let id, reason: let reason): + print("Sample with the id \(id) is invalid. Reason: \(reason)") + case .skippedSample(id: let id): + print("Skipped a sample image with id: \(id)") + case .automaticDownsampling: + print("Enabled auto downsampling because of limited system resources!") + @unknown default: + print("Received unknown session output: \(output)") + } + } + }catch { + print("Unexpected fatal session error. Aborting... ERROR=\(error)") + processingErrorOccured = true + alertText = String(localized: "UnexpectedFatalSessionErrorAlertBody", comment: "Alert body for unexpected fatal session error") + } + } + + do { + withAnimation { + sharedData.modelProgressViewState = .initializing + } + + try photogrammetrySession!.process(requests: [request]) + }catch { + print("Cannot process requests. ERROR=\(error)") + processingErrorOccured = true + alertText = String(localized: "UnexpectedModelCreationErrorAlertBody", comment: "Alert body for unexpected model creation error") + withAnimation { + sharedData.modelProgressViewState = .hidden + } + } + + } + + private func createPreview() { + createModel() + } + + private func exportModel() { + if let destinationURL = chooseSaveDestination() { + createModel(permenantSaveURL: destinationURL) + } + } + + private func handleCreationCompletion(temporaryLocation: URL, permenantSaveURL: URL? = nil) { + if processingErrorOccured { + withAnimation { + sharedData.modelProgressViewState = .hidden + } + sendCreationConclusionNotification(success: false, exportedModelFilename: permenantSaveURL?.lastPathComponent) + return + } + + let modelFileManager = ModelFileManager() + + let oldModelURL = sharedData.modelViewerModelURL + + withAnimation { + sharedData.modelViewerModelURL = temporaryLocation + } + + if let oldURL = oldModelURL { + try? modelFileManager.removeTempModel(modelURL: oldURL) + } + + if let saveURL = permenantSaveURL { + sharedData.modelProgressViewState = .copying + do { + try modelFileManager.copyTempModel(tempModelURL: temporaryLocation, permanentURL: saveURL) + sharedData.showInFinderURL = saveURL + sharedData.modelProgressViewState = .finished + }catch { + print("Cannot save model to destination URL") + alertText = String(localized: "ModelSaveErrorAlertBody", comment: "Alert body for model save") + } + }else { + withAnimation { + sharedData.modelProgressViewState = .hidden + } + } + sendCreationConclusionNotification(success: true, exportedModelFilename: permenantSaveURL?.lastPathComponent) + } + + private func sendCreationConclusionNotification(success: Bool, exportedModelFilename: String?) { + let content = UNMutableNotificationContent() + + if let filename = exportedModelFilename { + if success { + content.title = String(localized: "ModelExportSucessNotificationTitle", comment: "Notification title for model export success") + content.subtitle = String(format: String(localized: "ModelExportSucessNotificationBody %@", comment: "Notification body for model export success"), filename) + }else { + content.title = String(localized: "ModelExportFailureNotificationTitle", comment: "Notification title for model export failure") + content.subtitle = String(format: String(localized: "ModelExportFailureNotificationBody %@", comment: "Notification body for model export failure"), filename) + } + }else if let inputFolderName = inputFolderURL?.lastPathComponent { + if success { + content.title = String(localized: "PreviewCreationSuccessNotificationTitle", comment: "Notification title for preview creation success") + content.subtitle = String(format: String(localized: "PreviewCreationSuccessNotificationBody %@", comment: "Notification body for preview creation success"), inputFolderName) + }else { + content.title = String(localized: "PreviewCreationFailureNotificationTitle", comment: "Notification title for preview creation failure") + content.subtitle = String(format: String(localized: "PreviewCreationFailureNotificationBody %@", comment: "Notification body for preview creation failure"), inputFolderName) + } + } + + let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil) + UNUserNotificationCenter.current().add(request) + } +} diff --git a/Capturinator/Capturinator/Preview Content/Preview Assets.xcassets/Contents.json b/Capturinator/Capturinator/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Capturinator/Capturinator/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Capturinator/Capturinator/Views/CheckableLabel.swift b/Capturinator/Capturinator/Views/CheckableLabel.swift new file mode 100644 index 0000000..2dc8771 --- /dev/null +++ b/Capturinator/Capturinator/Views/CheckableLabel.swift @@ -0,0 +1,32 @@ +// +// CheckableLabel.swift +// CheckableLabel +// +// Created by Bertan on 22.07.2021. +// + +import SwiftUI + +struct CheckableLabel: View { + let text: String + let systemImage: String + let checked: Bool + var body: some View { + HStack { + Label(text, systemImage: systemImage) + if checked { + Image(systemName: "checkmark") + }else { + Image(systemName: "checkmark") + .hidden() + } + } + } +} + +struct CheckableLabel_Preiews: PreviewProvider { + static var previews: some View { + CheckableLabel(text: "Hello World", systemImage: "face.smiling", checked: true) + .font(.largeTitle) + } +} diff --git a/Capturinator/Capturinator/Views/HelpPopover.swift b/Capturinator/Capturinator/Views/HelpPopover.swift new file mode 100644 index 0000000..42efbf5 --- /dev/null +++ b/Capturinator/Capturinator/Views/HelpPopover.swift @@ -0,0 +1,48 @@ +// +// HelpPopover.swift +// Object Capture Create +// +// Created by Bertan on 25.06.2021. +// + +import SwiftUI + +struct HelpPopover: View { + @State private var presented = false + let title: String + let description: String + var body: some View { + Button(action: { presented.toggle() }) { + Image(systemName: "questionmark.circle") + .accessibilityLabel(title) + .font(.title3) + } + .buttonStyle(BorderlessButtonStyle()) + .popover(isPresented: $presented) { + VStack(alignment: .leading) { + Text(title) + .roundedFont(.headline) + Text(description) + } + .frame(width: 300) + .frame(maxHeight: .infinity) + .padding(10) + } + } +} + +extension View { + func helpPopover(title: String, description: String) -> some View { + HStack { + HelpPopover(title: title, description: description) + .padding(.leading, 3) + self + } + } +} + +struct HelpPopover_Previews: PreviewProvider { + static var previews: some View { + HelpPopover(title: "Hello, world!", description: "This is the SwiftUI preview for the help popover veiw I've created") + } +} diff --git a/Capturinator/Capturinator/Views/InclusiveGreeting.swift b/Capturinator/Capturinator/Views/InclusiveGreeting.swift new file mode 100644 index 0000000..b08f28e --- /dev/null +++ b/Capturinator/Capturinator/Views/InclusiveGreeting.swift @@ -0,0 +1,58 @@ +// +// InclusiveGreeting.swift +// Object Capture Create +// +// Created by Bertan on 23.06.2021. +// + +import SwiftUI +import Combine + +// Pretty self explanatory :)) +// A weaving hand emoji emoji that cycles between skin colors. #BlackLivesMatter +public struct InclusiveGreeting: View { + @State private var currentHand = "" + private var greetingMessage: String + private let gradient: Gradient + private let cycleDelay: Double + private let hands = ["\u{1F44B}", "\u{1F44B}\u{1F3FB}", "\u{1F44B}\u{1F3FC}", "\u{1F44B}\u{1F3FD}", "\u{1F44B}\u{1F3FE}", "\u{1F44B}\u{1F3FF}"] + // This timer will make sure the hands will switch periodically + private let timer: Publishers.Autoconnect + + public init(greetingMessage: String, gradient: Gradient, cycleDelay: Double = 3) { + self.greetingMessage = greetingMessage + self.gradient = gradient + self.cycleDelay = cycleDelay + timer = Timer.publish(every: cycleDelay, on: .main, in: .common).autoconnect() + } + + public var body: some View { + HStack { + Text(currentHand) + .roundedFont(.largeTitle) + .transition(.opacity) + .id(currentHand) + Text(greetingMessage) + .bold() + .roundedFont(.largeTitle) + .gradientBackground(gradient: gradient) + } + // Choose a random hand as soon as the view appears + .onAppear { + currentHand = hands.randomElement()! + } + // Switch to another weaving hand emoji when the timer fires, making sure it's a different one + // Animation duration is equal to cycleDelay so that it can looks like a continuous cycle + .onReceive(timer) { _ in + withAnimation(.easeIn(duration: cycleDelay)) { + currentHand = hands.filter { $0 != currentHand }.randomElement()! + } + } + } +} + +struct InclusiveGreeting_Previews: PreviewProvider { + static var previews: some View { + InclusiveGreeting(greetingMessage: "Hey!", gradient: .indigoGradient) + } +} diff --git a/Capturinator/Capturinator/Views/ModelProgressView.swift b/Capturinator/Capturinator/Views/ModelProgressView.swift new file mode 100644 index 0000000..da29976 --- /dev/null +++ b/Capturinator/Capturinator/Views/ModelProgressView.swift @@ -0,0 +1,101 @@ +// +// ModelProgressView.swift +// Object Capture Create +// +// Created by Bertan on 26.06.2021. +// + +import SwiftUI + +struct ModelProgressView: View { + @EnvironmentObject private var sharedData: SharedData + var cancelAction: () -> Void + + enum PresentationState { case hidden, initializing, bar, copying, finished } + + var body: some View { + Group { + switch sharedData.modelProgressViewState { + case .hidden: + EmptyView() + case .initializing: + HStack { + ProgressView() + .scaleEffect(0.6) + Text("Initializing", comment: "Starting model creation") + .roundedFont(.title3) + Spacer() + } + + case .bar: + HStack(alignment: .bottom) { + VStack(alignment: .leading, spacing: 5.0) { + Text("CreatingModel \(Int(sharedData.modelProcessingProgress * 100))", comment: "Text: Shows model creation progress in progress bar") + .bold() + .roundedFont(.body) + + ProgressView(value: sharedData.modelProcessingProgress, total: 1) + } + Button(action: cancelAction) { + Image(systemName: "xmark.circle.fill") + } + .help(String(localized: "StopCreating", comment: "Button[Progress Bar]: Stops model creation")) + .buttonStyle(BorderlessButtonStyle()) + } + case .copying: + HStack { + ProgressView() + .scaleEffect(0.6) + Text("SavingModel", comment: "Text: Shown in progress bar while saving a finished model") + .roundedFont(.title3) + Spacer() + } + case .finished: + HStack { + Label { + Text("ExportComplete \(sharedData.showInFinderURL?.lastPathComponent ?? "")", comment: "Text: Shown in progress bar after a model has been succesfully exported") + }icon: { + Image(systemName: "checkmark.circle") + .symbolRenderingMode(.hierarchical) + .foregroundStyle(.green) + .imageScale(.large) + .font(.title2) + } + .roundedFont(.title3) + Spacer() + Button(String(localized: "ShowInFinder", comment: "Button: Shows the exported model in Finder")) { + NSWorkspace.shared.selectFile(sharedData.showInFinderURL?.path, inFileViewerRootedAtPath: "") + } + Button(action: { + withAnimation { + sharedData.modelProgressViewState = .hidden + } + }) { + Image(systemName: "xmark.circle.fill") + } + .help(String(localized: "Dismiss", comment: "Button: Hides the progress bar before it automatically hides after successful export")) + .buttonStyle(BorderlessButtonStyle()) + } + + } + } + .zIndex(1) + .transition(.move(edge: .bottom)) + .frame(maxWidth: .infinity, maxHeight: 25) + .padding() + .background( + RoundedRectangle(cornerRadius: 20, style: .continuous) + .shadow(radius: 15) + .foregroundStyle(.regularMaterial) + ) + .onChange(of: sharedData.modelProgressViewState) { newValue in + if newValue == .finished { + DispatchQueue.main.asyncAfter(deadline: .now() + 15) { + withAnimation { + sharedData.modelProgressViewState = .hidden + } + } + } + } + } +} diff --git a/Capturinator/Capturinator/Views/ModelViewer.swift b/Capturinator/Capturinator/Views/ModelViewer.swift new file mode 100644 index 0000000..5e9f8a8 --- /dev/null +++ b/Capturinator/Capturinator/Views/ModelViewer.swift @@ -0,0 +1,42 @@ +// +// ModelViewer.swift +// Object Capture Create +// +// Created by Bertan on 24.06.2021. +// + +import SwiftUI +import SceneKit + +class NotFocusableSCNView: SCNView { + override var acceptsFirstResponder: Bool { return false } +} + +struct ModelViewer: NSViewRepresentable { + private var modelURL: URL + + init(modelURL: URL) { + self.modelURL = modelURL + } + + func makeNSView(context: Context) -> SCNView { + let scnView = NotFocusableSCNView() + return scnView + } + + func updateNSView(_ scnView: SCNView, context: Context) { + let scene = try? SCNScene(url: modelURL) + scene?.background.contents = NSColor.clear + scnView.scene = scene + scnView.allowsCameraControl = true + scnView.autoenablesDefaultLighting = true + scnView.backgroundColor = NSColor.clear + } + +} + +struct ModelViewer_Previews: PreviewProvider { + static var previews: some View { + ModelViewer(modelURL: Bundle.main.url(forResource: "plane", withExtension: "usdz")!) + } +} diff --git a/Capturinator/Capturinator/Views/OnboardingLabel.swift b/Capturinator/Capturinator/Views/OnboardingLabel.swift new file mode 100644 index 0000000..1881367 --- /dev/null +++ b/Capturinator/Capturinator/Views/OnboardingLabel.swift @@ -0,0 +1,37 @@ +// +// OnboardingLabel.swift +// OnboardingLabel +// +// Created by Bertan on 14.08.2021. +// + +import SwiftUI + +struct OnboardingLabel: View { + let title: String + let description: String + let systemImage: String + + var body: some View { + HStack { + Image(systemName: systemImage) + .font(.largeTitle) + .foregroundColor(.accentColor) + .frame(width: 40) + VStack(alignment: .leading) { + Text((try? AttributedString(markdown: title)) ?? "MD Formatting Error!") + .roundedFont(.headline) + Text((try? AttributedString(markdown: description)) ?? "MD Formatting Error!") + .roundedFont(.body) + } + } + } +} + +struct OnboardingLabel_Previews: PreviewProvider { + static var previews: some View { + VStack(alignment: .leading) { + OnboardingLabel(title: "Hello, World!", description: "Lorem ipsum dolor sit amet.", systemImage: "camera.fill") + } + } +} diff --git a/Capturinator/Capturinator/Views/TitleLabel.swift b/Capturinator/Capturinator/Views/TitleLabel.swift new file mode 100644 index 0000000..0c46896 --- /dev/null +++ b/Capturinator/Capturinator/Views/TitleLabel.swift @@ -0,0 +1,31 @@ +// +// TitleLabel.swift +// Object Capture Create +// +// Created by Bertan on 25.06.2021. +// + +import SwiftUI + +struct TitleLabel: View { + let title: String + let systemImage: String + let gradient: Gradient + + var body: some View { + HStack { + Image(systemName: systemImage) + .accessibilityLabel(title) + Text(title) + .bold() + } + .roundedFont(.title2) + .gradientBackground(gradient: gradient) + } +} + +struct TitleLabel_Previews: PreviewProvider { + static var previews: some View { + TitleLabel(title: "Hello, world!", systemImage: "face.smiling", gradient: .purpleGradient) + } +} diff --git a/Capturinator/Capturinator/Views/ToggleWithSpacing.swift b/Capturinator/Capturinator/Views/ToggleWithSpacing.swift new file mode 100644 index 0000000..a995cf6 --- /dev/null +++ b/Capturinator/Capturinator/Views/ToggleWithSpacing.swift @@ -0,0 +1,29 @@ +// +// ToggleWithSpacing.swift +// Object Capture Create +// +// Created by Bertan on 2.07.2021. +// + +import SwiftUI + +struct ToggleWithSpacing: View { + let title: String + @Binding var isOn: Bool + + init(_ title: String, isOn: Binding) { + self.title = title + self._isOn = isOn + } + + var body: some View { + HStack { + Text(title) + Spacer() + Toggle(title, isOn: $isOn) + .labelsHidden() + .toggleStyle(.switch) + } + .roundedFont(.headline) + } +} diff --git a/Capturinator/Capturinator/Views/TouchBarFocusView.swift b/Capturinator/Capturinator/Views/TouchBarFocusView.swift new file mode 100644 index 0000000..9ee5f9b --- /dev/null +++ b/Capturinator/Capturinator/Views/TouchBarFocusView.swift @@ -0,0 +1,31 @@ +// +// TouchBarFocusView.swift +// Object Capture Create +// +// Created by Bertan on 2.07.2021. +// + +import SwiftUI + +private class NSFocusView: NSView { + override var acceptsFirstResponder: Bool { return true } +} + +struct TouchBarFocusView: NSViewRepresentable { + private let closeAlertWindowDelegate = CloseAlertWindowDelegate() + func makeNSView(context: Context) -> some NSView { + return NSFocusView() + } + + func updateNSView(_ nsView: NSViewType, context: Context) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + if let window = nsView.window { + // While we're at it, why not implement the window close alert :) + window.delegate = closeAlertWindowDelegate + if let _ = window.firstResponder as? NSWindow { + window.makeFirstResponder(nsView) + } + } + } + } +} diff --git a/Capturinator/Capturinator/Views/UI Helpers/GradientBackground.swift b/Capturinator/Capturinator/Views/UI Helpers/GradientBackground.swift new file mode 100644 index 0000000..3cee8b6 --- /dev/null +++ b/Capturinator/Capturinator/Views/UI Helpers/GradientBackground.swift @@ -0,0 +1,16 @@ +// +// GradientBackground.swift +// Object Capture Create +// +// Created by Bertan on 25.06.2021. +// + +import SwiftUI + +extension View { + func gradientBackground(gradient: Gradient, startPoint: UnitPoint = .topLeading, endPoint: UnitPoint = .bottomTrailing) -> some View { + self + .overlay(LinearGradient(gradient: gradient, startPoint: startPoint, endPoint: endPoint)) + .mask(self) + } +} diff --git a/Capturinator/Capturinator/Views/UI Helpers/GradientExtension.swift b/Capturinator/Capturinator/Views/UI Helpers/GradientExtension.swift new file mode 100644 index 0000000..1187a7b --- /dev/null +++ b/Capturinator/Capturinator/Views/UI Helpers/GradientExtension.swift @@ -0,0 +1,34 @@ +// +// GradientExtension.swift +// Object Capture Create +// +// Created by Bertan on 24.06.2021. +// + +import SwiftUI + +public extension Gradient { + // Some hand dandy predefined gradients + static var greenGradient = Gradient(colors: [NSColor.systemTeal, NSColor.systemGreen].map { Color($0) }) + static var indigoGradient = Gradient(colors: [NSColor.systemIndigo, NSColor.systemMint].map { Color($0) }) + static var yellowGradient = Gradient(colors: [NSColor.systemOrange, NSColor.systemPink].map { Color($0) }) + static var purpleGradient = Gradient(colors: [Color(NSColor.systemPurple), Color(NSColor.systemPink)]) + static var redGradient = Gradient(colors: [NSColor.systemRed, NSColor.systemPink].map { Color($0) }) + + // Some fancy, + // Computed property, + // Some Fruit Company, + // Gredient out here. + + // Those colors... Seem familiar somehow + static func sixColorGradient(colorRange: ClosedRange? = nil) -> Gradient { + let sixColors = [NSColor](arrayLiteral: .systemGreen, .systemYellow, .systemOrange, .systemRed, .systemPurple, .systemBlue).map { Color($0) } + + if let range = colorRange, sixColors.indices.contains(range.lowerBound), sixColors.indices.contains(range.upperBound) { + return Gradient(colors: Array(sixColors[range])) + + }else { + return Gradient(colors: sixColors) + } + } +} diff --git a/Capturinator/Capturinator/Views/UI Helpers/RoundedFont.swift b/Capturinator/Capturinator/Views/UI Helpers/RoundedFont.swift new file mode 100644 index 0000000..445e181 --- /dev/null +++ b/Capturinator/Capturinator/Views/UI Helpers/RoundedFont.swift @@ -0,0 +1,15 @@ +// +// RoundedFont.swift +// Object Capture Create +// +// Created by Bertan on 24.06.2021. +// + +import SwiftUI + +extension View { + func roundedFont(_ style: Font.TextStyle) -> some View { + self + .font(.system(style, design: .rounded)) + } +} diff --git a/Capturinator/Capturinator/Views/VisualEffectView.swift b/Capturinator/Capturinator/Views/VisualEffectView.swift new file mode 100644 index 0000000..5bc9631 --- /dev/null +++ b/Capturinator/Capturinator/Views/VisualEffectView.swift @@ -0,0 +1,28 @@ +// +// VisualEffectView.swift +// Object Capture Create +// +// Created by Bertan on 23.06.2021. +// + +import SwiftUI + +struct VisualEffectView: NSViewRepresentable { + let material: NSVisualEffectView.Material + let blendingMode: NSVisualEffectView.BlendingMode + + func makeNSView(context: Context) -> NSVisualEffectView { + let vfxView = NSVisualEffectView() + + vfxView.material = material + vfxView.blendingMode = blendingMode + vfxView.state = NSVisualEffectView.State.active + + return vfxView + } + + func updateNSView(_ vfxView: NSVisualEffectView, context: Context) { + vfxView.material = material + vfxView.blendingMode = blendingMode + } +} diff --git a/Capturinator/Capturinator/tr.lproj/InfoPlist.strings b/Capturinator/Capturinator/tr.lproj/InfoPlist.strings new file mode 100644 index 0000000..1df299d --- /dev/null +++ b/Capturinator/Capturinator/tr.lproj/InfoPlist.strings @@ -0,0 +1,6 @@ +/* Bundle name */ +"CFBundleName" = "Capturinator"; + +/* Copyright (human-readable) */ +"NSHumanReadableCopyright" = "© 2021 Mehmet Bertan Tarakçıoğlu"; + diff --git a/Capturinator/en.lproj/Localizable.strings b/Capturinator/en.lproj/Localizable.strings new file mode 100644 index 0000000..46fea57 --- /dev/null +++ b/Capturinator/en.lproj/Localizable.strings @@ -0,0 +1,219 @@ +/* Button: Shows the about this app window */ +"AboutMenuCommandTtitle" = "About Capturinator"; + +/* Short description of the app */ +"AboutWindowText" = "Create 3D Models With Photos!"; + +/* Short app info under the welcome text in the sidebar */ +"AppInfo" = "Instantly turn photos of objects into 3D USDZ models for your AR projects and more! Select an image folder to get started."; + +/* Body of the window closing confirmation dialog */ +"CloseWindowAlertBody" = "Any currently running operations will stop, and all changes made in this window will be lost."; + +/* Title of the window closing confirmation dialog */ +"CloseWindowAlertTitle" = "Close This Window?"; + +/* Onboarding screen subtitle */ +"Create" = "Create!"; + +/* Onboarding screen text under create */ +"CreateBody" = "Tweak the settings, preview, and create your model! If you need help with any of the functions, click the question mark symbol next to them to get help."; + +/* Creates a preview model */ +"CreatePreview" = "Create Preview"; + +/* Text: Shows model creation progress in progress bar */ +"CreatingModel %lld" = "Creating Model: %lld%%"; + +/* Default filename while saving the model */ +"DefaultExportFilename" = "My Model"; + +/* Title for generic error alert */ +"ErrorAlertTitle" = "Something Went Wrong"; + +/* Text: Shown in progress bar after a model has been succesfully exported */ +"ExportComplete %@" = "Sucessfully exported model: %@"; + +/* Button: Creates and exports model */ +"ExportModel" = "Export Model"; + +/* Button: Shows the onboarding screen */ +"GettingStartedMenuCommandTittle" = "Getting Started Guide"; + +/* Toggle: Sets high feature sensitivity option */ +"HighFeatureSensitivity" = "High Feature Sensitivity"; + +/* Help popover body for high feature sensitivity */ +"HighFeatureSensitivityHelpBody" = "You should turn high feature sensitivity on if the sample images are out of focus or have low contrast and if the object is single-color or doesn’t have much texture. Though it may take longer to process, it’ll produce a better result."; + +/* Help popover title for high feature sensitivity */ +"HighFeatureSensitivityHelpTitle" = "About High Feature Sensitivity"; + +/* Toggle[TouchBar]: Sets high feature sensitivity option */ +"HighFeatureSensitivityShortened" = "High Feature Sensitivity"; + +/* Image folder title in the sidebar */ +"ImageFolder" = "Image Folder"; + +/* Help popover body for image folder */ +"ImageFolderHelpBody" = "This folder should contain the sample images from which you want to create the 3D model."; + +/* Help popover title for image folder */ +"ImageFolderHelpTitle" = "About Image Folder"; + +/* Onboarding screen subtitle */ +"ImportPhotos" = "Import Photos"; + +/* Onboarding screen text under import photos */ +"ImportPhotosBody" = "Put all the photos you took in a folder and import the folder into Capturinator. The folder should only contain your images and nothing else."; + +/* Starting model creation */ +"Initializing" = "Initializing..."; + +/* Message to be displayed at about this app window and onboarding screen */ +"MadeWithLove" = "Made with ❤️ and passion by Bertan."; + +/* Alert body for model creation error */ +"ModelCreationErrorAlertBody" = "An error occurred while creating the model. Make sure the source images are valid."; + +/* Notification body for model export failure */ +"ModelExportFailureNotificationBody %@" = "Could not export the model: \"%@\"."; + +/* Notification title for model export failure */ +"ModelExportFailureNotificationTitle" = "❌ Model Export Failed"; + +/* Notification body for model export success */ +"ModelExportSucessNotificationBody %@" = "Successfully exported the model: \"%@\"."; + +/* Notification title for model export success */ +"ModelExportSucessNotificationTitle" = "😄 Model Export Complete!"; + +/* Picker: Sets the model quality */ +"ModelQuality" = "Model Quality"; + +/* Help popover title for model quality */ +"ModelQualityHelpBody" = "Choose the output model detail. Keep in mind that the higher the quality, the more disk space and time it'll take to create!"; + +/* Help popover title for model quality */ +"ModelQualityHelpTitle" = "About Model Quality"; + +/* Alert body for model save */ +"ModelSaveErrorAlertBody" = "An error occurred while saving the model."; + +/* Model Settings title in the sidebar */ +"ModelSettings" = "Model Settings"; + +/* Label: Viewed as a placeholder when there is no model in the model viewer */ +"NoModelToViewMessage" = "Preview or export a model to see it here!"; + +/* Alert body for no source folder found error */ +"NoSourceFolderErrorAlertBody" = "Source folder not found—this may be a bug."; + +/* Image folder name placeholder when not selected */ +"NotSelected" = "Not Selected"; + +/* Label: Shown in the main window if the user's devie does not support Object Capture, more info about compatibility issue. */ +"ObjectCaptureNotSupportedDescription" = "To use this app, you need either a Mac with an Apple Silicon processor or an Intel processor and a “high-power” discrete GPU. [Learn more...](http://www.capturinator.bertan.codes/support/system-requirements)"; + +/* Label: Shown in the main window if the user's devie does not support Object Capture. */ +"ObjectCaptureNotSupportedTitle" = "Sorry, your device doesn't seem to support Object Capture"; + +/* Toggle: Sets Object masking option */ +"ObjectMasking" = "Object Masking"; + +/* Help popover body for object masking */ +"ObjectMaskingHelpBody" = "Object masking tries to filter out unrelated surroundings (plants, buildings, etc.) from the samples to increase performance and create a more accurate model. Turn this off if you don’t want the background filtered out."; + +/* Help popover title for object masking */ +"ObjectMaskingHelpTitle" = "About Object Masking"; + +/* Text: Greets the user for the first time on the onboarding screen */ +"OnboardingWelcome" = "Welcome to Capturinator!"; + +/* Button[TouchBar]: Opens image folder */ +"OpenFolder" = "Open Folder"; + +/* Onboarding screen subtitle */ +"OpenSource" = "Open Source"; + +/* Onboarding screen text under open source */ +"OpenSourceBody" = "This app is open-source under the MIT license. I'm a 16-year-old developer and a lover of all things tech. You can check out and contribute to my projects on [my GitHub profile](http://github.com/bertant). Thanks for using the app!"; + +/* Notification body for preview creation failure */ +"PreviewCreationFailureNotificationBody %@" = "Could not create the preview for the source folder: \"%@\"."; + +/* Notification title for preview creation failure */ +"PreviewCreationFailureNotificationTitle" = "❌ Preview Creation Failed"; + +/* Notification body for preview creation success */ +"PreviewCreationSuccessNotificationBody %@" = "Successfully created the preview for the source folder: \"%@\"."; + +/* Notification title for preview creation success */ +"PreviewCreationSuccessNotificationTitle" = "😄 Preview Creation Complete!"; + +/* Alert body for photogrammetry session creation error */ +"PSCreationErrorAlertBody" = "An error occurred while creating photogrammetry session. Make sure the source images are valid!"; + +/* Body of the app termination confirmation dialog */ +"QuitAppAlertBody" = "Any currently running operations will stop, and all changes will be lost."; + +/* Title of the app termination confirmation dialog */ +"QuitAppAlertTitle" = "Quit Now?"; + +/* RAW model quality */ +"RAW" = "RAW!"; + +/* Refines a preview model */ +"RefinePreview" = "Refine Preview"; + +/* Title for the save panel shown before exporting model */ +"SavePanelTitle" = "Choose Where to Save Your Model"; + +/* Text: Shown in progress bar while saving a finished model */ +"SavingModel" = "Saving Model..."; + +/* Toggle: Sets Sequential samples option */ +"SequentialSamples" = "Sequential Samples"; + +/* Help popover body for sequential samples */ +"SequentialSamplesHelpBody" = "If adjacent sample images are next to each other, turn this on to increase performance. If not provided in a particular order, keep this off."; + +/* Help popover title for sequential samples */ +"SequentialSamplesHelpTitle" = "About Sequential Samples"; + +/* Button: Shows the exported model in Finder */ +"ShowInFinder" = "Show In Finder"; + +/* Button: Dismisses the onboarding screen */ +"StartCreating" = "Start Creating!"; + +/* Button[Progress Bar]: Stops model creation */ +"StopCreating" = "Stop Creating"; + +/* Body for the stop model creation confirmation dialog */ +"StopCreatingModelAlertBody" = "Are you sure you want to stop creating the model? All progress will be lost."; + +/* Title for the stop model creation confirmation dialog */ +"StopCreatingModelAlertTitle" = "Stop Creating Model?"; + +/* Menu Command: Opens the Capturinator Support Website */ +"SupportMenuCommandTitle" = "Capturinator Support"; + +/* Onboarding screen subtitle */ +"TakePhotos" = "Take Photos"; + +/* Onboarding screen text under take photos */ +"TakePhotosBody" = "Capture 30-200 overlapping photographs of the object you want to scan from every angle - including the top and the bottom. Preferably use an iPhone with a depth-sensing camera or a high-quality DSLR camera."; + +/* Button: Toggles Sidebar */ +"ToggleSidebar" = "Toggle Sidebar"; + +/* Alert body for unexpected fatal session error */ +"UnexpectedFatalSessionErrorAlertBody" = "An unexpected fatal error occurred with the photogrammetry session :{"; + +/* Alert body for unexpected model creation error */ +"UnexpectedModelCreationErrorAlertBody" = "An unexpected error occurred while creating the model."; + +/* Welcome text at the top of the sidebar */ +"Welcome" = "Welcome!"; + diff --git a/Capturinator/tr.lproj/Capturinator-InfoPlist.strings b/Capturinator/tr.lproj/Capturinator-InfoPlist.strings new file mode 100644 index 0000000..1df299d --- /dev/null +++ b/Capturinator/tr.lproj/Capturinator-InfoPlist.strings @@ -0,0 +1,6 @@ +/* Bundle name */ +"CFBundleName" = "Capturinator"; + +/* Copyright (human-readable) */ +"NSHumanReadableCopyright" = "© 2021 Mehmet Bertan Tarakçıoğlu"; + diff --git a/Capturinator/tr.lproj/Localizable.strings b/Capturinator/tr.lproj/Localizable.strings new file mode 100644 index 0000000..3293cf1 --- /dev/null +++ b/Capturinator/tr.lproj/Localizable.strings @@ -0,0 +1,261 @@ +/* Button: Shows the about this app window */ +"AboutMenuCommandTtitle" = "Capturinator Hakkında"; + +/* Short description of the app */ +"AboutWindowText" = "Fotoğraflar ile 3D Modeller Oluşturun!"; + +/* Short app info under the welcome text in the sidebar */ +"AppInfo" = "Objelerin fotoğraflarını anında AR projeleri ve fazlası için kullanabileceğiniz 3D USDZ modellere dönüştürün! Başlamak için bir fotoğraf klasörü seçin."; + +/* General purpose cancel button */ +"Cancel" = "Vazgeç"; + +/* Chnages the selected image folder */ +"Change" = "Değiştir"; + +/* Button: Closes window */ +"Close" = "Kapat"; + +/* Body of the window closing confirmation dialog */ +"CloseWindowAlertBody" = "Bu pencere içinde devam eden bütün işlemler durdurulacak ve bütün değişiklikler kaybolacaktır."; + +/* Title of the window closing confirmation dialog */ +"CloseWindowAlertTitle" = "Bu pencere kapatılsın mı?"; + +/* Onboarding screen subtitle */ +"Create" = "Model Oluşturun!"; + +/* Onboarding screen text under create */ +"CreateBody" = "Ayarları özelleştirin, modeli önizleyin ve dışarı aktarın! Seçeneklerden herhangi birisi ile ilgili yardıma ihtiyacınız olursa yanında bulunan soru işareti sembolüne tıklayarak yadım alabilirsiniz."; + +/* Creates a preview model */ +"CreatePreview" = "Önizleme Oluştur"; + +/* Text: Shows model creation progress in progress bar */ +"CreatingModel %lld" = "Model Oluşturuluyor: %%%lld"; + +/* Default filename while saving the model */ +"DefaultExportFilename" = "Modelim"; + +/* Button: Hides the progress bar before it automatically hides after successful export */ +"Dismiss" = "Gizle"; + +/* Title for generic error alert */ +"ErrorAlertTitle" = "Bir Şeyler Ters Gitti"; + +/* Text: Shown in progress bar after a model has been succesfully exported */ +"ExportComplete %@" = "%@ başarılı bir şekilde dışarı aktarıldı!"; + +/* Button: Creates and exports model */ +"ExportModel" = "Dışarı Aktar"; + +/* Full model quality */ +"Full" = "Yüksek"; + +/* Button: Shows the onboarding screen */ +"GettingStartedMenuCommandTittle" = "Başlangıç Klavuzu"; + +/* Toggle: Sets high feature sensitivity option */ +"HighFeatureSensitivity" = "Yüksek Detay Hassasiyeti"; + +/* Help popover body for high feature sensitivity */ +"HighFeatureSensitivityHelpBody" = "Eğer kaynak fotoğraflarınız odak dışı vaya düşük kontrasta sahipse, ya da taramak istediğiniz obje tek renkli veya az yüzey detayına sahipse bu özelliği etkinleştirmelisiniz. İşlem süresi artacaktır, ancak bu özellik daha iyi sonuçlar elde etmenizi sağlar."; + +/* Help popover title for high feature sensitivity */ +"HighFeatureSensitivityHelpTitle" = "Yüksek Detay Hassasiyeti Hakkında"; + +/* Toggle[TouchBar]: Sets high feature sensitivity option */ +"HighFeatureSensitivityShortened" = "Yük. Detay Hassasiyeti"; + +/* Image folder title in the sidebar */ +"ImageFolder" = "Fotoğraf Klasörü"; + +/* Help popover body for image folder */ +"ImageFolderHelpBody" = "Bu klasör 3D modele çevirmek istediğiniz fotoğrafları içermelidir."; + +/* Help popover title for image folder */ +"ImageFolderHelpTitle" = "Fotoğraf Klasörü Hakkında"; + +/* Onboarding screen subtitle */ +"ImportPhotos" = "Fotoğrafları İçe Aktarın"; + +/* Onboarding screen text under import photos */ +"ImportPhotosBody" = "Çektiğiniz bütün fotoğrafları bir klasöre taşıyın ve Capturinator'a aktarın. Bu klasör fotoğraflarınızdan başka hiçbir dosya içermemelidir."; + +/* Starting model creation */ +"Initializing" = "Başlatılıyor..."; + +/* Message to be displayed at about this app window and onboarding screen */ +"MadeWithLove" = "Bertan tarafından ❤️ ve tutku ile yapıldı."; + +/* Medium model quality */ +"Medium" = "Orta"; + +/* Alert body for model creation error */ +"ModelCreationErrorAlertBody" = "Model oluşturulurken bir sorun meydana geldi. Lütfen kaynak görsellerinizin geçerli olduğundan emin olun!"; + +/* Notification body for model export failure */ +"ModelExportFailureNotificationBody %@" = "\"%@\" Dışarı aktarılırken bir sorun meydana geldi."; + +/* Notification title for model export failure */ +"ModelExportFailureNotificationTitle" = "❌ Model Dışarı Aktarılamadı"; + +/* Notification body for model export success */ +"ModelExportSucessNotificationBody %@" = "\"%@\" Başarılı bir şekilde dışarı aktarıldı."; + +/* Notification title for model export success */ +"ModelExportSucessNotificationTitle" = "😄 Model Dışarı Aktarıldı!"; + +/* Picker: Sets the model quality */ +"ModelQuality" = "Model Kalitesi"; + +/* Help popover title for model quality */ +"ModelQualityHelpBody" = "Modelinizin kalitesini ve detaylılığını seçin. Unutmayın ki kalite yükseldikçe modelinizin kapladığı disk alanı ve işlem süresi de artar."; + +/* Help popover title for model quality */ +"ModelQualityHelpTitle" = "Model Kalitesi Hakkında"; + +/* Alert body for model save */ +"ModelSaveErrorAlertBody" = "Model kaydedilirken bir sorun oluştu."; + +/* Model Settings title in the sidebar */ +"ModelSettings" = "Model Ayarları"; + +/* Label: Viewed as a placeholder when there is no model in the model viewer */ +"NoModelToViewMessage" = "Burada görüntülemek için bir önizleme oluşturun ya da dışarı model aktarın!"; + +/* Alert body for no source folder found error */ +"NoSourceFolderErrorAlertBody" = "Kaynak klasörü bulunamadı - bu bir yazılım hatası olabilir."; + +/* Image folder name placeholder when not selected */ +"NotSelected" = "Seçilmedi"; + +/* Label: Shown in the main window if the user's devie does not support Object Capture, more info about compatibility issue. */ +"ObjectCaptureNotSupportedDescription" = "Bu uygulamayı kullanabilmek için Apple Silicon işlemciye ya da Intel işlemciye ve \"yüksek güçlü\" bir grafik işlemcisine sahip bir Mac bilgisayarına ihtiyacınız var. [Daha fazla bilgi...](http://www.capturinator.bertan.codes/support/system-requirements)"; + +/* Label: Shown in the main window if the user's devie does not support Object Capture. */ +"ObjectCaptureNotSupportedTitle" = "Maalesef cihazınız Object Capture teknolojisini desteklemiyor"; + +/* Toggle: Sets Object masking option */ +"ObjectMasking" = "Obje Maskeleme"; + +/* Help popover body for object masking */ +"ObjectMaskingHelpBody" = "Obje maskeleme, daha kaliteli bir model oluşturmak ve performansı artırmak için görsellerden taramak istediğiniz obje dışındaki alakasız nesneleri (bitkiler, binalar vb.) filtrelemeye çalışır. Arka planın filtrelenmesini istemiyorsanız bu özelliği kapatın."; + +/* Help popover title for object masking */ +"ObjectMaskingHelpTitle" = "Obje Maskeleme Hakkında"; + +/* Text: Greets the user for the first time on the onboarding screen */ +"OnboardingWelcome" = "Capturinator'a Hoşgeldiniz!"; + +/* Button: Opens image folder */ +"Open" = "Aç"; + +/* Button[TouchBar]: Opens image folder */ +"OpenFolder" = "Klasör Aç"; + +/* Onboarding screen subtitle */ +"OpenSource" = "Açık Kaynaklı"; + +/* Onboarding screen text under open source */ +"OpenSourceBody" = "Bu uygulama MIT lisansı kapsamında açık kaynaklıdır. Ben derin bir teknoloji tutkusuna sahip 16 yaşındaki bir geliştiriciyim. [GitHub profilim](http://github.com/bertant) üzerinden projelerime göz atabilir, ve katkıda bulanabilirsiniz. Uygulamayı kullandığınız için teşekkürler!"; + +/* Button[TouchBar]: Creates a preview model */ +"Preview" = "Önizleme"; + +/* Notification body for preview creation failure */ +"PreviewCreationFailureNotificationBody %@" = "\"%@\" kaynak klasörü için önizleme oluşturulurken bir sorun meydana geldi."; + +/* Notification title for preview creation failure */ +"PreviewCreationFailureNotificationTitle" = "❌ Önizleme Oluşturulamadı"; + +/* Notification body for preview creation success */ +"PreviewCreationSuccessNotificationBody %@" = "\"%@\" kaynak klasörü için başarılı bir şekilde önizleme oluşturuldu."; + +/* Notification title for preview creation success */ +"PreviewCreationSuccessNotificationTitle" = "😄 Önizleme Hazır!"; + +/* Alert body for photogrammetry session creation error */ +"PSCreationErrorAlertBody" = "Fotogrametri başlatılırken bir hata oluştu. Lütfen kaynak fotoğraflarınızın geçerli olduğundan emin olun!"; + +/* Button[TouchBar]: Shows Model Quality Picker Strip */ +"Quality" = "Kalite"; + +/* Button: Quits app */ +"Quit" = "Çık"; + +/* Body of the app termination confirmation dialog */ +"QuitAppAlertBody" = "Devam eden bütün işlemler durdurulacak ve bütün ilerleme kaybolacaktır."; + +/* Title of the app termination confirmation dialog */ +"QuitAppAlertTitle" = "Şimdi çıkmak istiyor musunuz?"; + +/* RAW model quality */ +"RAW" = "RAW!"; + +/* The lowest model quality */ +"Reduced" = "Azaltılmış"; + +/* Refines a preview model */ +"RefinePreview" = "Önizlemeyi Güncelle"; + +/* Rendering title in the sidebar */ +"Rendering" = "Model Oluşturma"; + +/* Title for the save panel shown before exporting model */ +"SavePanelTitle" = "Modelinizi Nereye Kaydedeceğinizi Seçin"; + +/* Text: Shown in progress bar while saving a finished model */ +"SavingModel" = "Model Kaydediliyor..."; + +/* Toggle: Sets Sequential samples option */ +"SequentialSamples" = "Ardışık Görseller"; + +/* Help popover body for sequential samples */ +"SequentialSamplesHelpBody" = "Eğer görseller fotoğraflandıkları sırayla sağlanıyorsa performansı artırmak için bu özelliği etkinleştirin. Fotoğraflar belirli bir sırada sağlanmıyorsa bu özelliği kapatın."; + +/* Help popover title for sequential samples */ +"SequentialSamplesHelpTitle" = "Ardışık Görseller Hakkında"; + +/* Button[TouchBar]: Shows Model Settings Strip */ +"Settings" = "Ayarlar"; + +/* Button: Shows the exported model in Finder */ +"ShowInFinder" = "Finder'da Göster"; + +/* Button: Dismisses the onboarding screen */ +"StartCreating" = "Başla!"; + +/* Stop model creation */ +"Stop" = "Durdur"; + +/* Button[Progress Bar]: Stops model creation */ +"StopCreating" = "Modellemeyi Durdur"; + +/* Body for the stop model creation confirmation dialog */ +"StopCreatingModelAlertBody" = "Modellemeyi durdurmak istediğinize emin misiniz? Bütün ilerleme kaybolacaktır."; + +/* Title for the stop model creation confirmation dialog */ +"StopCreatingModelAlertTitle" = "Modelleme durdurulsun mu?"; + +/* Menu Command: Opens the Capturinator Support Website */ +"SupportMenuCommandTitle" = "Capturinator Destek"; + +/* Onboarding screen subtitle */ +"TakePhotos" = "Fotoğraf Çekin"; + +/* Onboarding screen text under take photos */ +"TakePhotosBody" = "Taramak istediğiniz objenin bütün açılardan birbiriyle kesişen 30-200 adet fotoğrafını çekin - altı ve üstü de dahil. Tercihen derinlik algılayabilen kamerası olan bir iPhone ya da yüksek kaliteli bir DSLR fotoğraf makinesi kullanın."; + +/* Button: Toggles Sidebar */ +"ToggleSidebar" = "Kenar Çubuğunu Gizle/Göster"; + +/* Alert body for unexpected fatal session error */ +"UnexpectedFatalSessionErrorAlertBody" = "Fotogrametri ile ilgili beklenmeyen bir kritik sorun oluştu. :{"; + +/* Alert body for unexpected model creation error */ +"UnexpectedModelCreationErrorAlertBody" = "Model yaratılırken beklenmeyen bir sorun oluştu."; + +/* Welcome text at the top of the sidebar */ +"Welcome" = "Merhaba!"; +