├── .swiftlint.yml ├── BioViewer.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ └── xcuserdata │ │ └── raul.xcuserdatad │ │ ├── UserInterfaceState.xcuserstate │ │ └── WorkspaceSettings.xcsettings ├── xcshareddata │ ├── xcbaselines │ │ └── C44FC07E2641AF1E00459E01.xcbaseline │ │ │ ├── F9975422-2017-4B21-ACC6-016EB4D927C6.plist │ │ │ └── Info.plist │ └── xcschemes │ │ ├── BioViewer.xcscheme │ │ └── BioViewerTests.xcscheme └── xcuserdata │ ├── andro.xcuserdatad │ └── xcschemes │ │ └── xcschememanagement.plist │ └── raul.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── BioViewer ├── AppState.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x-1.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x-1.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x-1.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x-1.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ ├── Icon-App-83.5x83.5@2x.png │ │ ├── Icon-MacOS-128x128@1x.png │ │ ├── Icon-MacOS-128x128@2x.png │ │ ├── Icon-MacOS-16x16@1x.png │ │ ├── Icon-MacOS-256x256@1x.png │ │ ├── Icon-MacOS-256x256@2x.png │ │ ├── Icon-MacOS-32x32@2x.png │ │ ├── Icon-MacOS-512x512@1x.png │ │ └── Icon-MacOS-512x512@2x-1.png │ ├── Contents.json │ ├── DefaultViewfinderImage.imageset │ │ ├── Contents.json │ │ └── DefaultViewfinderImage.png │ ├── PlaceholderCoffee.imageset │ │ ├── Contents.json │ │ ├── Screenshot 2021-05-24 at 00.28.39-1.png │ │ └── Screenshot 2021-05-24 at 00.28.39.png │ └── Shutter.imageset │ │ ├── Contents.json │ │ └── Lights.png ├── BioBench │ ├── BenchmarkedProtein.swift │ ├── BioBenchConfig.swift │ ├── BioBenchResultsView.swift │ ├── BioBenchView.swift │ └── BioBenchViewModel.swift ├── BioViewer-Bridging-Header.h ├── BioViewer.entitlements ├── BioViewerApp.swift ├── BioViewerCommands.swift ├── BioViewerLogger.swift ├── Components │ ├── BioViewerPicker.swift │ ├── EmptyDataRow.swift │ ├── PersistentDisclosureGroup.swift │ └── Rows │ │ ├── ButtonRow.swift │ │ ├── ColorPaletteRow.swift │ │ ├── ColorPickerRow.swift │ │ ├── ComputedPropertyRow.swift │ │ ├── FocalLengthRow.swift │ │ ├── InfoLongTextRow.swift │ │ ├── InfoPopoverRow.swift │ │ ├── InfoTextRow.swift │ │ ├── InputWithButtonRow.swift │ │ ├── LegacyPickerRow.swift │ │ ├── PickerRow.swift │ │ ├── RangeRow.swift │ │ ├── SliderRow.swift │ │ ├── SunDirectionRow.swift │ │ └── SwitchRow.swift ├── Data structures │ ├── AtomElement.swift │ ├── BoundingVolumes.swift │ ├── ColorPalettes.swift │ ├── ProteinDataSource │ │ ├── ProteinDataSource.swift │ │ └── ProteinDataSourceError.swift │ ├── RCSB │ │ ├── RCSBError.swift │ │ └── RCSBRequest.swift │ ├── Residue.swift │ └── SecondaryStructure.swift ├── Extensions │ └── UserDefaults+Extension.swift ├── Files │ ├── BioViewerWorkspace.swift │ ├── ExportError.swift │ ├── Import │ │ ├── FileImporter.swift │ │ ├── FileParser.swift │ │ ├── ImageExporter.swift │ │ ├── ImportDroppedFilesDelegate.swift │ │ ├── ImportError.swift │ │ └── ImportedUTTypes.swift │ └── WorkspaceExporter.swift ├── Info.plist ├── JSONs │ └── RCSBSuggestionData.json ├── Media Assets │ ├── 3JBT.pdb │ ├── ShutterClosed.aiff │ └── ShutterOpen.aiff ├── Metal │ ├── AtomRadii+Extensions.swift │ ├── Compute │ │ ├── ComputeAmbientOcclusion.metal │ │ ├── ComputeMolecularSurfaceUtility.swift │ │ ├── ComputeSDFGrid.metal │ │ ├── FillColorBuffer.metal │ │ ├── FillColorInput.h │ │ ├── FillColorInputUtility.swift │ │ └── SDFGrid.h │ ├── Functions │ │ ├── ComputeAmbientOcclusion.swift │ │ ├── ComputeSDFGrid.swift │ │ ├── CreateBondsGeometry.swift │ │ ├── CreateImpostorSpheres.swift │ │ ├── CreateSphereModel.swift │ │ ├── MakeBufferFromArray.swift │ │ ├── MetalScheduler.swift │ │ └── PipelineStateBundle.swift │ ├── Meshes │ │ ├── AtomProperties.h │ │ ├── BillboardVertexBuffers.swift │ │ ├── CreateAtomMesh.metal │ │ ├── CreateImpostorBonds.metal │ │ ├── CreateImpostorSpheres.metal │ │ └── GeneratedVertex.h │ ├── Renderer │ │ ├── Camera.swift │ │ ├── ComputePasses │ │ │ ├── ComputePipelines.swift │ │ │ └── FillColorPass.swift │ │ ├── ConfigurationSelector.swift │ │ ├── ConnectivityGenerator.swift │ │ ├── Data Structures │ │ │ └── VisualizationConfiguration.swift │ │ ├── Draw │ │ │ ├── ProteinDraw.swift │ │ │ └── ProteinHighQualityRender.swift │ │ ├── MutableState │ │ │ ├── MutableState.swift │ │ │ └── PopulateVisualizationBuffers.swift │ │ ├── ProteinRenderTarget.swift │ │ ├── ProteinRenderer.swift │ │ ├── Ray.swift │ │ ├── RenderPasses │ │ │ ├── DebugPass │ │ │ │ └── PointsPass.swift │ │ │ ├── ImpostorPass │ │ │ │ ├── DepthPrePassStage.swift │ │ │ │ └── ImpostorPass.swift │ │ │ ├── Postprocessing │ │ │ │ ├── CopyToDrawable.swift │ │ │ │ ├── ShadowBlurPass.swift │ │ │ │ └── Upscaling │ │ │ │ │ ├── MetalFXScalers.swift │ │ │ │ │ └── MetalFXUpscaling.swift │ │ │ ├── RendererPipelines.swift │ │ │ └── ShadowPass │ │ │ │ ├── ShadowDepthPrePass.swift │ │ │ │ └── ShadowPass.swift │ │ ├── Scene │ │ │ ├── MetalScene.swift │ │ │ └── SceneAnimator.swift │ │ ├── Textures │ │ │ ├── AmbientOcclusion3DTexture.swift │ │ │ ├── BenchmarkTextures.swift │ │ │ ├── DepthPrePassTextures.swift │ │ │ ├── HQTextures.swift │ │ │ ├── MetalFXUpscaledTexture.swift │ │ │ ├── ProteinRenderedTextures.swift │ │ │ ├── ShadowTextures.swift │ │ │ └── TextureToImage.swift │ │ └── VisualizationBufferLoader.swift │ ├── Shaders │ │ ├── BasicShader.metal │ │ ├── DebugShader │ │ │ └── DebugPointShader.metal │ │ ├── DepthPrePassShader.metal │ │ ├── FrameData.h │ │ ├── ImpostorBondShader.metal │ │ ├── ImpostorSphereShader.metal │ │ ├── Postprocessing │ │ │ ├── MotionTexture.metal │ │ │ ├── ReprojectionData.h │ │ │ └── ShadowBlur.metal │ │ ├── ShaderCommon.h │ │ ├── ShadowDepthPrePassShader.metal │ │ └── ShadowShader.metal │ ├── SharedDataStructs.h │ └── Utilities │ │ ├── MatrixTransform.swift │ │ └── SIMDExtensions.swift ├── MetalLegacySupport.swift ├── Models │ └── Protein │ │ └── ProteinFileInfo.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── ProteinMath.swift ├── Views │ ├── MainView.swift │ ├── Onboarding │ │ ├── ChangeLog.plist │ │ ├── NewsRow.swift │ │ ├── WhatsNewView.swift │ │ └── WhatsNewViewModel.swift │ ├── PresentedViews │ │ ├── PeriodicTableContentView.swift │ │ └── PeriodicTableView.swift │ ├── ProteinViews │ │ ├── Data │ │ │ ├── BallAndStickRadiusOptions.swift │ │ │ ├── ProteinColorModel.swift │ │ │ ├── ProteinColorViewModel.swift │ │ │ ├── ProteinGraphicsSettings.swift │ │ │ ├── ProteinShadowsViewModel.swift │ │ │ ├── ProteinViewModel.swift │ │ │ ├── ProteinVisualizationOption.swift │ │ │ ├── ProteinVisualizationViewModel.swift │ │ │ ├── SelectionModel.swift │ │ │ └── SolidSpheresRadiusOptions.swift │ │ ├── DynamicStructureControlView.swift │ │ ├── ImportProteins │ │ │ ├── ImportRowView.swift │ │ │ ├── ProteinImportView.swift │ │ │ ├── ProteinImportViewModel.swift │ │ │ └── ProteinRCSBViews │ │ │ │ ├── RCSBDetailView.swift │ │ │ │ ├── RCSBEmptyImportView.swift │ │ │ │ ├── RCSBImportView.swift │ │ │ │ ├── RCSBImportViewModel.swift │ │ │ │ ├── RCSBRowView.swift │ │ │ │ ├── RCSBSuggestionsView.swift │ │ │ │ └── RCSBSuggestionsViewModel.swift │ │ ├── PhotoMode │ │ │ ├── PhotoModeConfig.swift │ │ │ ├── PhotoModeContent.swift │ │ │ ├── PhotoModeContentHeaderView.swift │ │ │ ├── PhotoModeFooter.swift │ │ │ ├── PhotoModeUnsupportedView.swift │ │ │ ├── PhotoModeView.swift │ │ │ ├── PhotoModeViewModel.swift │ │ │ ├── PhotoModeViewfinder.swift │ │ │ └── ShutterAnimator.swift │ │ ├── ProeteinSequenceView.swift │ │ ├── ProteinMetal │ │ │ ├── ProteinMetalView.swift │ │ │ ├── ProteinMetalViewController.swift │ │ │ └── ProteinRenderedView.swift │ │ ├── ProteinSequenceView.swift │ │ ├── ProteinView.swift │ │ ├── SelectTool │ │ │ ├── SelectedAtom.swift │ │ │ ├── SelectedAtomContentView.swift │ │ │ └── SelectedDebugView.swift │ │ ├── Sidebar │ │ │ ├── ProteinSidebar.swift │ │ │ └── Segments │ │ │ │ ├── AppearanceSegmentProtein │ │ │ │ ├── AppearanceSegmentProtein.swift │ │ │ │ └── Subsections │ │ │ │ │ ├── ColorSection.swift │ │ │ │ │ ├── ShadowsSection.swift │ │ │ │ │ └── VisualizationSection.swift │ │ │ │ ├── FileSegmentProtein │ │ │ │ ├── FileAtomElementPopover │ │ │ │ │ ├── CompositionItem.swift │ │ │ │ │ ├── FileCompositionChartView.swift │ │ │ │ │ └── FileCompositionView.swift │ │ │ │ ├── FileRow.swift │ │ │ │ ├── FileSegmentProtein.swift │ │ │ │ ├── FileSource │ │ │ │ │ ├── FileSourceRow.swift │ │ │ │ │ ├── FileSourceView.swift │ │ │ │ │ └── FileSourceViewModel.swift │ │ │ │ ├── InfoAtomsRow.swift │ │ │ │ ├── InfoSegmentedCapsule.swift │ │ │ │ ├── Sections │ │ │ │ │ └── FileDetailsComponent.swift │ │ │ │ ├── WorkspaceHelp.swift │ │ │ │ └── WorkspaceRow.swift │ │ │ │ ├── FunctionsSegmentProtein.swift │ │ │ │ ├── GraphicsSettingsSegment.swift │ │ │ │ ├── SettingsSegmentProtein.swift │ │ │ │ └── TrajectorySegmentProtein.swift │ │ └── Top toolbar │ │ │ ├── CameraControlToolbar.swift │ │ │ ├── ToolbarConfig.swift │ │ │ └── TopToolbar.swift │ ├── SequenceViews │ │ ├── Rows │ │ │ └── SequenceRow.swift │ │ └── SequenceView.swift │ ├── SettingsView.swift │ ├── StatusView │ │ ├── StatusAction.swift │ │ ├── StatusOverlayView.swift │ │ └── StatusViewModel.swift │ └── UI Elements │ │ ├── BioViewerProgress │ │ ├── BVDismissActionButton.swift │ │ ├── BVProgressComponent.swift │ │ ├── BVProgressFailedView.swift │ │ └── BVProgressView.swift │ │ ├── CoffeeViews │ │ ├── BuyCoffeeView.swift │ │ ├── CoffeeRow.swift │ │ └── CoffeeTipRow.swift │ │ ├── ColorPalettePopover.swift │ │ ├── ColorPalettePopoverRow.swift │ │ ├── ColorPaletteView.swift │ │ ├── CoordinatesView.swift │ │ ├── CustomLinearProgressView.swift │ │ ├── Debug Views │ │ ├── FPSCounterView.swift │ │ └── ResolutionView.swift │ │ ├── LineGraphView.swift │ │ └── UnitTextView.swift ├── en.lproj │ └── Localizable.strings └── es.lproj │ └── Localizable.strings ├── BioViewerPackages ├── BioViewerFoundation │ ├── .swiftpm │ │ └── xcode │ │ │ ├── package.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ └── xcuserdata │ │ │ ├── andro.xcuserdatad │ │ │ └── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ │ └── raul.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── Package.swift │ ├── Sources │ │ └── BioViewerFoundation │ │ │ ├── Models │ │ │ ├── BoundingVolumes.swift │ │ │ ├── Protein.swift │ │ │ ├── ProteinComposition │ │ │ │ ├── ProteinChainComposition.swift │ │ │ │ ├── ProteinElementComposition.swift │ │ │ │ └── ProteinResidueComposition.swift │ │ │ ├── ProteinFile.swift │ │ │ └── ProteinFileInfo.swift │ │ │ ├── ProteinMath.swift │ │ │ └── Types │ │ │ ├── AtomElement.swift │ │ │ ├── BondStruct.swift │ │ │ ├── ChainID.swift │ │ │ ├── Residue.swift │ │ │ └── SecondaryStructure.swift │ └── Tests │ │ └── BioViewerFoundationTests │ │ └── BioViewerFoundationTests.swift ├── CIFParser │ ├── .gitignore │ ├── .swiftpm │ │ └── xcode │ │ │ └── package.xcworkspace │ │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Package.swift │ ├── Sources │ │ └── CIFParser │ │ │ ├── CIFConstants.swift │ │ │ └── CIFParser.swift │ └── Tests │ │ └── CIFParserTests │ │ └── CIFParserTests.swift ├── PDBParser │ ├── .swiftpm │ │ └── xcode │ │ │ ├── package.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ └── xcuserdata │ │ │ ├── andro.xcuserdatad │ │ │ └── xcschemes │ │ │ │ └── xcschememanagement.plist │ │ │ └── raul.xcuserdatad │ │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ ├── Package.swift │ ├── Sources │ │ └── PDBParser │ │ │ ├── PDBConstants.swift │ │ │ ├── PDBParseError.swift │ │ │ └── PDBParser.swift │ └── Tests │ │ └── PDBParserTests │ │ └── PDBParserTests.swift └── XYZParser │ ├── .swiftpm │ └── xcode │ │ ├── package.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ ├── andro.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ │ └── raul.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist │ ├── Package.swift │ ├── Sources │ └── XYZParser │ │ ├── XYZConstants.swift │ │ ├── XYZParsedConfiguration.swift │ │ ├── XYZParser.swift │ │ └── XYZParserError.swift │ └── Tests │ └── XYZParserTests │ └── XYZParserTests.swift ├── BioViewerTests ├── BioViewerTests.swift └── Info.plist ├── BioViewerUITests ├── BioViewerUITests.swift └── Info.plist ├── LICENSE.md ├── Privacy policy └── en │ └── PrivacyPolicy.md ├── PromoAssets ├── MetalFeatures.png └── Mockup.png ├── ProteinThumbnail ├── Assets.xcassets │ ├── Contents.json │ └── OverlayPDB.imageset │ │ ├── Contents.json │ │ └── OverlayPDB.png ├── Info.plist ├── ProteinThumbnail.entitlements └── ThumbnailProvider.swift ├── README.md ├── docs ├── ProteinVisualization │ ├── Figures │ │ ├── HighResH2O.png │ │ ├── PercentageCloseFiltering.png │ │ ├── ShadowAcne.png │ │ ├── ShadowedDrawableTexture.png │ │ └── SunDepthTexture.png │ ├── HardShadows.md │ └── MolecularSurface.md ├── _config.yml └── index.md └── scripts ├── Plot3DPoints.py ├── UnitaryIcosahedron.playground ├── Contents.swift ├── contents.xcplayground └── playground.xcworkspace │ └── contents.xcworkspacedata └── UnitaryIcosahedron.py /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | disabled_rules: 2 | - trailing_whitespace 3 | - large_tuple 4 | - empty_count 5 | opt_in_rules: 6 | - empty_count 7 | - empty_string 8 | identifier_name: 9 | validates_start_with_lowercase: false 10 | allowed_symbols: "_" 11 | excluded: 12 | - id 13 | - i 14 | - j 15 | - k 16 | - a 17 | - b 18 | - x 19 | - y 20 | - z 21 | - t 22 | - u 23 | - v 24 | - v0 25 | - v1 26 | - v2 27 | - v3 28 | - v4 29 | - v5 30 | line_length: 31 | warning: 150 32 | error: 1000 33 | ignores_function_declarations: true 34 | ignores_comments: true 35 | ignores_urls: true 36 | function_body_length: 37 | warning: 300 38 | error: 500 39 | function_parameter_count: 40 | warning: 10 41 | error: 20 42 | type_body_length: 43 | warning: 700 44 | error: 800 45 | file_length: 46 | warning: 1000 47 | error: 1500 48 | ignore_comment_only_lines: true 49 | cyclomatic_complexity: 50 | warning: 15 51 | error: 250 52 | reporter: "xcode" 53 | type_name: 54 | allowed_symbols: "_" -------------------------------------------------------------------------------- /BioViewer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BioViewer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BioViewer.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BioViewer.xcodeproj/project.xcworkspace/xcuserdata/raul.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer.xcodeproj/project.xcworkspace/xcuserdata/raul.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /BioViewer.xcodeproj/project.xcworkspace/xcuserdata/raul.xcuserdatad/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildLocationStyle 6 | UseAppPreferences 7 | CustomBuildLocationType 8 | RelativeToDerivedData 9 | DerivedDataLocationStyle 10 | Default 11 | IssueFilterStyle 12 | ShowActiveSchemeOnly 13 | LiveSourceIssuesEnabled 14 | 15 | ShowSharedSchemesAutomaticallyEnabled 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /BioViewer.xcodeproj/xcshareddata/xcbaselines/C44FC07E2641AF1E00459E01.xcbaseline/F9975422-2017-4B21-ACC6-016EB4D927C6.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classNames 6 | 7 | BioViewerTests 8 | 9 | testPerformanceExample() 10 | 11 | com.apple.dt.XCTMetric_Clock.time.monotonic 12 | 13 | baselineAverage 14 | 0.348 15 | baselineIntegrationDisplayName 16 | Local Baseline 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /BioViewer.xcodeproj/xcshareddata/xcbaselines/C44FC07E2641AF1E00459E01.xcbaseline/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | runDestinationsByUUID 6 | 7 | F9975422-2017-4B21-ACC6-016EB4D927C6 8 | 9 | localComputer 10 | 11 | busSpeedInMHz 12 | 400 13 | cpuCount 14 | 1 15 | cpuKind 16 | 8-Core Intel Core i9 17 | cpuSpeedInMHz 18 | 2300 19 | logicalCPUCoresPerPackage 20 | 16 21 | modelCode 22 | MacBookPro16,1 23 | physicalCPUCoresPerPackage 24 | 8 25 | platformIdentifier 26 | com.apple.platform.macosx 27 | 28 | targetArchitecture 29 | x86_64 30 | targetDevice 31 | 32 | modelCode 33 | iPad13,10 34 | platformIdentifier 35 | com.apple.platform.iphonesimulator 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /BioViewer.xcodeproj/xcshareddata/xcschemes/BioViewerTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 37 | 38 | 44 | 45 | 47 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /BioViewer.xcodeproj/xcuserdata/andro.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | BioViewer.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | BioViewerTests.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | ProteinThumbnail.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 2 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /BioViewer.xcodeproj/xcuserdata/raul.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | BioViewer.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | BioViewerTests.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | C44FC06D2641AF1B00459E01 21 | 22 | primary 23 | 24 | 25 | C44FC07E2641AF1E00459E01 26 | 27 | primary 28 | 29 | 30 | C44FC0892641AF1E00459E01 31 | 32 | primary 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "display-p3", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0.843", 9 | "green" : "0.303", 10 | "red" : "0.468" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "display-p3", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "1.000", 27 | "green" : "0.410", 28 | "red" : "0.607" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x-1.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x-1.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@1x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-128x128@2x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-16x16@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-16x16@1x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@1x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-256x256@2x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-32x32@2x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@1x.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/AppIcon.appiconset/Icon-MacOS-512x512@2x-1.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/DefaultViewfinderImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "DefaultViewfinderImage.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/DefaultViewfinderImage.imageset/DefaultViewfinderImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/DefaultViewfinderImage.imageset/DefaultViewfinderImage.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/PlaceholderCoffee.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "Screenshot 2021-05-24 at 00.28.39.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "Screenshot 2021-05-24 at 00.28.39-1.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/PlaceholderCoffee.imageset/Screenshot 2021-05-24 at 00.28.39-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/PlaceholderCoffee.imageset/Screenshot 2021-05-24 at 00.28.39-1.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/PlaceholderCoffee.imageset/Screenshot 2021-05-24 at 00.28.39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/PlaceholderCoffee.imageset/Screenshot 2021-05-24 at 00.28.39.png -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/Shutter.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "Lights.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /BioViewer/Assets.xcassets/Shutter.imageset/Lights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Assets.xcassets/Shutter.imageset/Lights.png -------------------------------------------------------------------------------- /BioViewer/BioBench/BenchmarkedProtein.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BenchmarkedProtein.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 12/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | struct BenchmarkedProtein: Equatable, Identifiable, Sendable { 11 | let id = UUID() 12 | let name: String 13 | let atoms: Int 14 | let time: Double 15 | let std: (Double, Double) 16 | 17 | static func == (lhs: BenchmarkedProtein, rhs: BenchmarkedProtein) -> Bool { 18 | return lhs.id == rhs.id 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /BioViewer/BioBench/BioBenchConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BioBenchConfig.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 25/1/23. 6 | // 7 | 8 | import Foundation 9 | 10 | class BioBenchConfig { 11 | static let numberOfFrames: Int = 1000 12 | } 13 | -------------------------------------------------------------------------------- /BioViewer/BioViewer-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "FrameData.h" 6 | #import "GeneratedVertex.h" 7 | #import "SharedDataStructs.h" 8 | #import "FillColorInput.h" 9 | #import "SDFGrid.h" 10 | #import "ReprojectionData.h" 11 | -------------------------------------------------------------------------------- /BioViewer/BioViewer.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /BioViewer/BioViewerApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PDB_ViewerApp.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 4/5/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct BioViewerApp: App { 12 | 13 | var body: some Scene { 14 | 15 | WindowGroup { 16 | MainView() 17 | } 18 | .commands { 19 | BioViewerCommands() 20 | } 21 | 22 | WindowGroup(id: "BioBench") { 23 | BioBenchView() 24 | .navigationTitle("BioBench") 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BioViewer/BioViewerLogger.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BioViewerLogger.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 11/5/22. 6 | // 7 | 8 | import Foundation 9 | import os 10 | 11 | class BioViewerLogger { 12 | 13 | static let shared = BioViewerLogger() 14 | 15 | private var rendererLogger = Logger( 16 | subsystem: "BioViewer", 17 | category: "ProteinRenderer" 18 | ) 19 | private var computeSurfaceUtilityLogger = Logger( 20 | subsystem: "BioViewer", 21 | category: "ComputeSurfaceUtility" 22 | ) 23 | 24 | private init() { 25 | 26 | } 27 | 28 | // MARK: - Functions 29 | func log(type: LogType, category: LogCategory, message: String) { 30 | 31 | #if DEBUG 32 | var logger: Logger 33 | switch category { 34 | case .computeSurfaceUtility: 35 | logger = computeSurfaceUtilityLogger 36 | case .proteinRenderer: 37 | logger = rendererLogger 38 | } 39 | 40 | switch type { 41 | case .info: 42 | logger.info("\(message)") 43 | case .warning: 44 | logger.warning("\(message)") 45 | case .error: 46 | logger.critical("\(message)") 47 | } 48 | #endif 49 | } 50 | 51 | } 52 | 53 | enum LogType { 54 | case info 55 | case warning 56 | case error 57 | } 58 | 59 | enum LogCategory { 60 | case proteinRenderer 61 | case computeSurfaceUtility 62 | } 63 | -------------------------------------------------------------------------------- /BioViewer/Components/BioViewerPicker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BioViewerPicker.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 21/1/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct BioViewerPicker: View { 11 | 12 | @Binding var selection: T 13 | 14 | var body: some View { 15 | #if targetEnvironment(macCatalyst) 16 | Picker("", selection: $selection.animation()) { 17 | ForEach(Array(T.allCases), id: \.self) { 18 | Text($0.displayName) 19 | .tag($0) 20 | } 21 | } 22 | .pickerStyle(MenuPickerStyle()) 23 | #else 24 | Menu { 25 | Picker("", selection: $selection.animation()) { 26 | ForEach(Array(T.allCases), id: \.self) { 27 | Text($0.displayName) 28 | .tag($0) 29 | } 30 | } 31 | } label: { 32 | HStack(spacing: 4) { 33 | Text(selection.displayName) 34 | .padding(.vertical, 4) 35 | .padding(.horizontal, 8) 36 | .background(Color.accentColor) 37 | .foregroundColor(.white) 38 | .cornerRadius(4) 39 | .fixedSize(horizontal: true, vertical: false) 40 | } 41 | } 42 | .menuOrder(.fixed) 43 | #endif 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /BioViewer/Components/EmptyDataRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyDataRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 12/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct EmptyDataRow: View { 11 | 12 | let text: String 13 | 14 | var body: some View { 15 | Text(text) 16 | .foregroundColor(.secondary) 17 | .italic() 18 | } 19 | } 20 | 21 | struct EmptyDataRow_Previews: PreviewProvider { 22 | static var previews: some View { 23 | List { 24 | EmptyDataRow(text: NSLocalizedString("No data", comment: "")) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BioViewer/Components/PersistentDisclosureGroup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PersistentDisclosureGroup.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 15/12/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | enum PersistentGroupName: String { 11 | case colorGroup 12 | case visualizationGroup 13 | case solidSpheresRadiusGroup 14 | case ballAndStickRadiusGroup 15 | case shadowGroup 16 | case depthCueingGroup 17 | case residueColoringAminoAcid 18 | case residueColoringDNANucleobase 19 | case residueColoringRNANucleobase 20 | case metalFXUpscalingSettings 21 | /// This will still create a DisclosureGroup, but not persistent. 22 | case error 23 | } 24 | 25 | struct PersistentDisclosureGroup: View { 26 | 27 | @State var isExpanded: Bool 28 | 29 | let group: PersistentGroupName 30 | let content: () -> Content 31 | let label: () -> LabelContent 32 | 33 | init(for group: PersistentGroupName, defaultOpen: Bool, @ViewBuilder content: @escaping () -> Content, @ViewBuilder label: @escaping () -> LabelContent) { 34 | self.group = group 35 | self.content = content 36 | self.label = label 37 | guard group != .error else { 38 | _isExpanded = State(initialValue: false) 39 | return 40 | } 41 | if UserDefaults.standard.object(forKey: group.rawValue + "Expanded") != nil { 42 | _isExpanded = State(initialValue: UserDefaults.standard.bool(forKey: group.rawValue + "Expanded")) 43 | } else { 44 | _isExpanded = State(initialValue: defaultOpen) 45 | } 46 | } 47 | 48 | var body: some View { 49 | DisclosureGroup( 50 | isExpanded: $isExpanded, 51 | content: content, 52 | label: label 53 | ) 54 | .onChange(of: isExpanded) { 55 | guard group != .error else { return } 56 | UserDefaults.standard.setValue(isExpanded, forKey: group.rawValue + "Expanded") 57 | } 58 | } 59 | } 60 | 61 | struct PersistentDisclosureGroup_Previews: PreviewProvider { 62 | static var previews: some View { 63 | PersistentDisclosureGroup( 64 | for: .colorGroup, 65 | defaultOpen: true, 66 | content: { 67 | Rectangle() 68 | }, 69 | label: { 70 | Circle() 71 | } 72 | ) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /BioViewer/Components/Rows/ButtonRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 29/11/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ButtonRow: View { 11 | 12 | let action: () -> Void 13 | let text: String 14 | 15 | var body: some View { 16 | Button( 17 | action: { 18 | action() 19 | }, 20 | label: { 21 | Text(text) 22 | } 23 | ) 24 | } 25 | } 26 | 27 | struct ButtonRow_Previews: PreviewProvider { 28 | static var previews: some View { 29 | ButtonRow(action: {}, text: "Test button") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BioViewer/Components/Rows/ColorPaletteRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorPaletteRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 6/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ColorPaletteRow: View { 11 | 12 | @State var showPalettePicker: Bool = false 13 | let colorPalette: ColorPalette 14 | 15 | var body: some View { 16 | HStack { 17 | Text(NSLocalizedString("Color palette", comment: "")) 18 | Spacer() 19 | Button(action: { 20 | showPalettePicker.toggle() 21 | }, label: { 22 | ColorPaletteView(colorPalette: colorPalette) 23 | .popover(isPresented: $showPalettePicker) { 24 | ColorPalettePopover() 25 | } 26 | }) 27 | .buttonStyle(PlainButtonStyle()) 28 | } 29 | } 30 | } 31 | 32 | struct ColorPaletteRow_Previews: PreviewProvider { 33 | static var previews: some View { 34 | List { 35 | ColorPaletteRow(colorPalette: ColorPalette(.default)) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BioViewer/Components/Rows/ColorPickerRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorPickerRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 10/5/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ColorPickerRow: View { 11 | 12 | var title: String 13 | @Binding var selectedColor: Color 14 | 15 | var body: some View { 16 | HStack { 17 | #if targetEnvironment(macCatalyst) 18 | Text(title) 19 | Spacer() 20 | ColorPicker("", 21 | selection: $selectedColor, 22 | supportsOpacity: false) 23 | .frame(width: 96) 24 | #else 25 | ColorPicker(title, 26 | selection: $selectedColor, 27 | supportsOpacity: false) 28 | #endif 29 | } 30 | } 31 | } 32 | 33 | struct ColorPickerRow_Previews: PreviewProvider { 34 | static var previews: some View { 35 | ColorPickerRow(title: NSLocalizedString("Selected color", comment: ""), 36 | selectedColor: .constant(Color.black)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BioViewer/Components/Rows/FocalLengthRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FocalLengthRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 30/10/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FocalLengthRow: View { 11 | 12 | @Binding var focalLength: Float 13 | 14 | var body: some View { 15 | VStack(spacing: 0) { 16 | 17 | Slider(value: $focalLength, in: 24...300) 18 | 19 | HStack { 20 | Text("24") 21 | .font(.system(size: 12)) 22 | 23 | Spacer() 24 | 25 | Text("\(focalLength, specifier: "%.0f") mm") 26 | .font(.system(size: 14)) 27 | .padding(.all, 4) 28 | .foregroundColor(.secondary) 29 | .overlay( 30 | RoundedRectangle(cornerRadius: 8) 31 | .stroke(Color.secondary, lineWidth: 1) 32 | ) 33 | #if targetEnvironment(macCatalyst) 34 | .scaleEffect(0.75) 35 | #endif 36 | Spacer() 37 | 38 | Text("300") 39 | .font(.system(size: 12)) 40 | } 41 | } 42 | } 43 | } 44 | 45 | struct FocalLengthRow_Previews: PreviewProvider { 46 | static var previews: some View { 47 | FocalLengthRow(focalLength: .constant(200)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /BioViewer/Components/Rows/InfoLongTextRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoLongTextRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 13/11/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct InfoLongTextRow: View { 11 | 12 | @State var title: String 13 | var longText: String? 14 | 15 | var body: some View { 16 | if let longText = longText { 17 | VStack(alignment: .leading, spacing: 2) { 18 | Text(title) 19 | .multilineTextAlignment(.leading) 20 | .frame(maxWidth: .infinity, alignment: .leading) 21 | Text(longText) 22 | .foregroundColor(Color(uiColor: UIColor.secondaryLabel)) 23 | } 24 | .frame(maxWidth: .infinity) 25 | } else { 26 | HStack(spacing: 4) { 27 | Text(title) 28 | .multilineTextAlignment(.leading) 29 | .frame(alignment: .leading) 30 | Text("-") 31 | .foregroundColor(Color(uiColor: UIColor.secondaryLabel)) 32 | Spacer() 33 | } 34 | .frame(maxWidth: .infinity) 35 | } 36 | } 37 | } 38 | 39 | struct LongTextRow_Previews: PreviewProvider { 40 | static var previews: some View { 41 | InfoLongTextRow(title: "Description", longText: "Long text example") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BioViewer/Components/Rows/InfoPopoverRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoPopoverRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 11/5/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct InfoPopoverRow: View { 11 | 12 | let label: String 13 | let value: String 14 | let isDisabled: Bool 15 | let popoverView: Content 16 | @State var buttonToggle: Bool = false 17 | 18 | init(label: String, value: String, isDisabled: Bool, @ViewBuilder content: () -> Content) { 19 | self.label = label 20 | self.value = value 21 | self.isDisabled = isDisabled 22 | self.popoverView = content() 23 | } 24 | 25 | var body: some View { 26 | HStack(spacing: 4) { 27 | Text(label) 28 | Text(value) 29 | .foregroundColor(Color(uiColor: .secondaryLabel)) 30 | Spacer() 31 | Button(action: { 32 | buttonToggle.toggle() 33 | }, 34 | label: { 35 | Image(systemName: "info.circle") 36 | }) 37 | .foregroundColor(Color.accentColor) 38 | .buttonStyle(PlainButtonStyle()) 39 | .disabled(isDisabled) 40 | .popover(isPresented: $buttonToggle) { 41 | popoverView 42 | } 43 | } 44 | } 45 | } 46 | 47 | struct InformationRow_Previews: PreviewProvider { 48 | static var previews: some View { 49 | List { 50 | InfoPopoverRow(label: "Número de átomos", 51 | value: "58336", 52 | isDisabled: false, 53 | content: { FileCompositionView() }) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /BioViewer/Components/Rows/InfoTextRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoTextRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 24/11/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct InfoTextRow: View { 11 | @State var text: String 12 | var value: String? 13 | 14 | var body: some View { 15 | HStack(spacing: 4) { 16 | Text(text) 17 | Text(value ?? "-") 18 | .foregroundColor(Color(uiColor: .secondaryLabel)) 19 | } 20 | } 21 | } 22 | 23 | struct InfoTextRow_Previews: PreviewProvider { 24 | static var previews: some View { 25 | InfoTextRow(text: "Number of something:", value: "3340") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BioViewer/Components/Rows/InputWithButtonRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InputWithButtonRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 18/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct InputWithButtonRow: View { 11 | 12 | let title: String 13 | @Binding var value: Float 14 | let buttonTitle: String 15 | let action: () -> Void 16 | let formatter: NumberFormatter 17 | 18 | var body: some View { 19 | HStack { 20 | Text(title) 21 | .frame(maxWidth: .infinity, alignment: .leading) 22 | .multilineTextAlignment(.leading) 23 | TextField(title, value: $value, formatter: formatter) 24 | .textFieldStyle(RoundedBorderTextFieldStyle()) 25 | .frame(maxWidth: 48) 26 | .multilineTextAlignment(.trailing) 27 | Button(action: { 28 | action() 29 | }, label: { 30 | Text(buttonTitle) 31 | }) 32 | .buttonStyle(BorderedProminentButtonStyle()) 33 | } 34 | } 35 | } 36 | 37 | struct InputWithButtonRow_Previews: PreviewProvider { 38 | static var previews: some View { 39 | List { 40 | InputWithButtonRow(title: "Go to frame", value: .constant(256), buttonTitle: "Go", action: {}, formatter: NumberFormatter()) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /BioViewer/Components/Rows/PickerRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PickerRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 19/1/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | protocol PickableEnum: CaseIterable, Hashable { 11 | var displayName: String { get } 12 | } 13 | 14 | struct PickerRow: View { 15 | 16 | let optionName: String 17 | @Binding var selection: T 18 | 19 | @Environment(\.horizontalSizeClass) var horizontalSizeClass 20 | 21 | init(optionName: String, selection: Binding) { 22 | self.optionName = optionName 23 | self._selection = selection 24 | } 25 | 26 | var body: some View { 27 | #if targetEnvironment(macCatalyst) 28 | HStack(spacing: .zero) { 29 | Text(optionName) 30 | BioViewerPicker(selection: $selection) 31 | } 32 | #else 33 | HStack { 34 | Text(optionName + ":") 35 | Spacer() 36 | BioViewerPicker(selection: $selection) 37 | } 38 | #endif 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /BioViewer/Components/Rows/RangeRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RangeRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 10/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RangeRow: View { 11 | 12 | let title: String 13 | 14 | struct RangeDelimiter: View { 15 | 16 | let isLeftRange: Bool 17 | 18 | private enum RangeDelimiterConstants { 19 | #if targetEnvironment(macCatalyst) 20 | static let size: CGFloat = 16 21 | #else 22 | static let size: CGFloat = 24 23 | #endif 24 | } 25 | 26 | var body: some View { 27 | Image(systemName: "triangle.fill") 28 | .resizable() 29 | .aspectRatio(1.0, contentMode: .fit) 30 | .frame(width: RangeDelimiterConstants.size) 31 | .foregroundColor(Color(uiColor: .white)) 32 | .rotationEffect(Angle(degrees: isLeftRange ? 90 : -90)) 33 | .shadow(color: .black.opacity(0.2), 34 | radius: 4, 35 | x: 0, 36 | y: 2) 37 | } 38 | } 39 | 40 | var body: some View { 41 | VStack(alignment: .leading) { 42 | HStack { 43 | Text(title) 44 | Text("0 to 300 Å") 45 | .foregroundColor(.secondary) 46 | } 47 | ZStack { 48 | Capsule() 49 | .frame(height: 4) 50 | .padding(.horizontal, 10) 51 | .foregroundColor(.accentColor) 52 | HStack { 53 | RangeDelimiter(isLeftRange: true) 54 | Spacer() 55 | RangeDelimiter(isLeftRange: false) 56 | } 57 | } 58 | } 59 | .padding(.vertical, 4) 60 | } 61 | } 62 | 63 | struct RangeRow_Previews: PreviewProvider { 64 | static var previews: some View { 65 | List { 66 | RangeRow(title: "Range:") 67 | } 68 | .preferredColorScheme(.dark) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /BioViewer/Components/Rows/SliderRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SliderRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 10/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SliderRow: View { 11 | 12 | let title: String 13 | @Binding var value: Float 14 | let minValue: Float 15 | let maxValue: Float 16 | let stepSize: Float 17 | 18 | func getNumberFormatter(min: Float, max: Float) -> NumberFormatter { 19 | let numberFormatter = NumberFormatter() 20 | numberFormatter.numberStyle = .decimal 21 | numberFormatter.maximumFractionDigits = 1 22 | numberFormatter.minimum = min as NSNumber 23 | numberFormatter.maximum = max as NSNumber 24 | return numberFormatter 25 | } 26 | 27 | init(title: String, value: Binding, minValue: Float, maxValue: Float, stepSize: Float = 0.1) { 28 | self.title = title 29 | self._value = value 30 | self.minValue = minValue 31 | self.maxValue = maxValue 32 | self.stepSize = stepSize 33 | } 34 | 35 | var body: some View { 36 | HStack { 37 | Text(title) 38 | Slider(value: $value, in: minValue...maxValue) 39 | .frame(maxWidth: .infinity) 40 | #if targetEnvironment(macCatalyst) 41 | Stepper(value: $value, in: minValue...maxValue, step: stepSize) { 42 | TextField("Value", 43 | value: $value, 44 | formatter: getNumberFormatter(min: minValue, max: maxValue)) 45 | .textFieldStyle(RoundedBorderTextFieldStyle()) 46 | } 47 | .frame(maxWidth: 64) 48 | #endif 49 | } 50 | } 51 | } 52 | 53 | struct SliderRow_Previews: PreviewProvider { 54 | static var previews: some View { 55 | List { 56 | SliderRow(title: "Strength", 57 | value: .constant(0.3), 58 | minValue: 0.0, 59 | maxValue: 1.0) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /BioViewer/Components/Rows/SwitchRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwitchRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 27/5/21. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct SwitchRow: View { 12 | 13 | var title: String 14 | @Binding var toggledVariable: Bool 15 | 16 | var body: some View { 17 | #if targetEnvironment(macCatalyst) 18 | Toggle(title, isOn: $toggledVariable) 19 | .tint(.accentColor) 20 | #else 21 | Toggle(title, isOn: $toggledVariable.animation()) 22 | .tint(.accentColor) 23 | #endif 24 | } 25 | 26 | } 27 | 28 | struct SwitchRow_Previews: PreviewProvider { 29 | static var previews: some View { 30 | SwitchRow(title: "Sample toggle", toggledVariable: .constant(true)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BioViewer/Data structures/BoundingVolumes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BoundingVolumes.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 7/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | struct BoundingSphere: Sendable { 11 | let center: simd_float3 12 | let radius: Float 13 | } 14 | 15 | struct BoundingBox: Sendable { 16 | let minX: Float 17 | let maxX: Float 18 | let minY: Float 19 | let maxY: Float 20 | let minZ: Float 21 | let maxZ: Float 22 | } 23 | 24 | struct BoundingVolume: Sendable { 25 | let sphere: BoundingSphere 26 | let box: BoundingBox 27 | 28 | static var zero: Self { 29 | return BoundingVolume( 30 | sphere: BoundingSphere(center: .zero, radius: .zero), 31 | box: BoundingBox(minX: .zero, maxX: .zero, minY: .zero, maxY: .zero, minZ: .zero, maxZ: .zero) 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /BioViewer/Data structures/ColorPalettes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorPalettes.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 6/12/21. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | enum ColorPaletteType { 12 | case `default` 13 | case bioViewer 14 | case custom 15 | } 16 | 17 | struct ColorPalette { 18 | let color0: Color 19 | let color1: Color 20 | let color2: Color 21 | let color3: Color 22 | let color4: Color 23 | let color5: Color 24 | 25 | init(_ colorPaletteType: ColorPaletteType) { 26 | // TO-DO: 27 | switch colorPaletteType { 28 | case .bioViewer: 29 | color0 = .purple 30 | color1 = .purple 31 | color2 = .purple 32 | color3 = .purple 33 | color4 = .purple 34 | color5 = .purple 35 | default: 36 | color0 = .green 37 | color1 = .gray 38 | color2 = .blue 39 | color3 = .red 40 | color4 = .orange 41 | color5 = .gray 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /BioViewer/Data structures/ProteinDataSource/ProteinDataSourceError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProteinDataSourceError.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 27/5/23. 6 | // 7 | 8 | import Foundation 9 | 10 | enum ProteinDataSourceError: Error { 11 | case unableToUpdateProteinConnectivity 12 | } 13 | -------------------------------------------------------------------------------- /BioViewer/Data structures/RCSB/RCSBError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RCSBError.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 15/11/21. 6 | // 7 | 8 | import Foundation 9 | 10 | enum RCSBError: Error { 11 | case invalidID 12 | case malformedURL 13 | case notFound 14 | case internalServerError 15 | case badImageData 16 | case malformedInput 17 | case unknown 18 | } 19 | 20 | extension RCSBError: LocalizedError { 21 | public var errorDescription: String? { 22 | switch self { 23 | case .invalidID, .malformedURL: 24 | // Malformed URLs are likely due to an invalid PDB ID. If there's another 25 | // cause, it's unlikely that the user has any other way to fix it. 26 | return NSLocalizedString("Invalid RCSB ID", comment: "") 27 | case .notFound: 28 | return NSLocalizedString("No structure found with the given RCSB ID", comment: "") 29 | case .internalServerError: 30 | return NSLocalizedString("There seems to be a problem with the server", comment: "") 31 | case .badImageData: 32 | return NSLocalizedString("Invalid image data", comment: "") 33 | case .malformedInput: 34 | return NSLocalizedString("Invalid input", comment: "") 35 | case .unknown: 36 | return NSLocalizedString("Unknown error", comment: "") 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /BioViewer/Data structures/SecondaryStructure.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecondaryStructure.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 5/3/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | enum SecondaryStructure: UInt8, CaseIterable { 12 | case helix 13 | case sheet 14 | case loop 15 | case nonChain 16 | 17 | var name: String { 18 | switch self { 19 | case .helix: 20 | return NSLocalizedString("Helix", comment: "") 21 | case .sheet: 22 | return NSLocalizedString("Sheet", comment: "") 23 | case .loop: 24 | return NSLocalizedString("Loop", comment: "") 25 | case .nonChain: 26 | return NSLocalizedString("Non-chain", comment: "") 27 | } 28 | } 29 | 30 | var defaultColor: Color { 31 | switch self { 32 | case .helix: 33 | return Color(.displayP3, red: 0.423, green: 0.733, blue: 0.235, opacity: 1) 34 | case .sheet: 35 | return Color(.displayP3, red: 0.000, green: 0.590, blue: 1.000, opacity: 1) 36 | case .loop: 37 | return Color(.displayP3, red: 0.500, green: 0.500, blue: 0.500, opacity: 1) 38 | case .nonChain: 39 | return Color(.displayP3, red: 0.750, green: 0.750, blue: 0.750, opacity: 1) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /BioViewer/Extensions/UserDefaults+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserDefaults+Extension.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 29/11/22. 6 | // 7 | 8 | import Foundation 9 | 10 | @propertyWrapper 11 | struct UserDefault { 12 | let key: String 13 | let defaultValue: Value 14 | var container: UserDefaults = .standard 15 | 16 | var wrappedValue: Value { 17 | get { 18 | return container.object(forKey: key) as? Value ?? defaultValue 19 | } 20 | set { 21 | container.set(newValue, forKey: key) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BioViewer/Files/BioViewerWorkspace.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BioViewerWorkspace.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 6/12/21. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | class BioViewerWorkspace: UIDocument { 12 | 13 | // TO-DO: Saved contents 14 | var testContent = "Test" 15 | 16 | // MARK: - Inner files 17 | var infoFileName = "Info.txt" 18 | 19 | override func load(fromContents contents: Any, ofType typeName: String?) throws { 20 | guard let topFileWrapper = contents as? FileWrapper, 21 | let textData = topFileWrapper.fileWrappers?[infoFileName]?.regularFileContents else { 22 | return 23 | } 24 | testContent = String(data: textData, encoding: .utf8)! 25 | } 26 | 27 | override func contents(forType typeName: String) throws -> Any { 28 | guard let workspaceData = testContent.data(using: .utf8) else { 29 | throw ExportError.unknownError 30 | } 31 | 32 | return FileWrapper(regularFileWithContents: workspaceData) 33 | } 34 | } 35 | 36 | /* 37 | class BioViewerWorkspaceDelegate: DocumentPickerDelegate { 38 | 39 | } 40 | */ 41 | -------------------------------------------------------------------------------- /BioViewer/Files/ExportError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExportError.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 6/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | enum ExportError: Error { 11 | case unknownError 12 | } 13 | -------------------------------------------------------------------------------- /BioViewer/Files/Import/ImportError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImportErrors.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 23/11/21. 6 | // 7 | 8 | import Foundation 9 | 10 | enum ImportError: Error { 11 | case unknownFileType 12 | case emptyAtomCount 13 | case notFound 14 | case downloadError 15 | case unknownFileExtension 16 | case unknownError 17 | } 18 | 19 | extension ImportError: LocalizedError { 20 | public var errorDescription: String? { 21 | switch self { 22 | case .unknownFileType: 23 | return NSLocalizedString("Error: Unknown file type", comment: "") 24 | case .emptyAtomCount: 25 | return NSLocalizedString("Error: File does not contain any atom positions", comment: "") 26 | case .notFound: 27 | return NSLocalizedString("Error: File not found", comment: "") 28 | case .downloadError: 29 | return NSLocalizedString("Error downloading file", comment: "") 30 | case .unknownFileExtension: 31 | return NSLocalizedString("Unknown file extension", comment: "") 32 | case .unknownError: 33 | return NSLocalizedString("Error importing file", comment: "") 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BioViewer/Files/Import/ImportedUTTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImportedUTTypes.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 12/11/23. 6 | // 7 | 8 | import Foundation 9 | import UniformTypeIdentifiers 10 | 11 | extension UTType { 12 | static var pdbFiles: UTType { 13 | UTType(importedAs: "com.raulmonton.bioviewer.pdb") 14 | } 15 | static var xyzFiles: UTType { 16 | UTType(importedAs: "com.raulmonton.bioviewer.xyz") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BioViewer/Files/WorkspaceExporter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkspaceExporter.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 6/12/21. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | class WorkspaceExporter { 12 | 13 | static func createWorkspace(proteinViewModel: ProteinViewModel) { 14 | // Write to cache 15 | let cachesDir = FileManager.default.urls(for: FileManager.SearchPathDirectory.cachesDirectory, in: .allDomainsMask).first! 16 | let dataDir = cachesDir.appendingPathComponent("export", isDirectory: true) 17 | try? FileManager.default.createDirectory(at: dataDir, withIntermediateDirectories: true, attributes: nil) 18 | 19 | let fileURL = dataDir.appendingPathComponent("Workspace").appendingPathExtension("bioviewer") 20 | 21 | Task { 22 | /* 23 | let archive = await BioViewerWorkspace(fileURL: fileURL) 24 | // FIXME: archive.testContent = await proteinViewModel.dataSource?.files.first?.fileInfo.pdbID ?? "Unknown PDB ID" 25 | 26 | let success = await archive.save(to: archive.fileURL, for: .forCreating) 27 | 28 | await MainActor.run { 29 | guard success else { 30 | return 31 | } 32 | 33 | let documentPicker = UIDocumentPickerViewController(forExporting: [fileURL]) 34 | let delegate = BioViewerWorkspaceDelegate() 35 | documentPicker.delegate = delegate 36 | 37 | // TO-DO: Improve how the current window is located. This is a hacky workaround. 38 | for scene in UIApplication.shared.connectedScenes where scene.activationState == .foregroundActive { 39 | guard let windowSceneDelegate = ((scene as? UIWindowScene)?.delegate as? UIWindowSceneDelegate) else { 40 | return 41 | } 42 | guard let window = windowSceneDelegate.window else { 43 | return 44 | } 45 | guard let rootViewController = window?.rootViewController else { 46 | return 47 | } 48 | 49 | rootViewController.present(documentPicker, animated: true) 50 | } 51 | } 52 | */ 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /BioViewer/JSONs/RCSBSuggestionData.json: -------------------------------------------------------------------------------- 1 | { 2 | "sections": [ 3 | { 4 | "sectionTitle": "Chaperone", 5 | "sectionDescription": "chaperone_description", 6 | "rowData": [ 7 | { 8 | "rcsbid": "6MRC", 9 | "description": "ADP-bound human mitochondrial Hsp60-Hsp10 football complex", 10 | "authors": "Gomez-Llorente, Y., Jebara, F., Patra, M., Malik, R., Nissemblat, S., Azem, A., Hirsch, J.A., Ubarretxena-Belandia, I." 11 | } 12 | ] 13 | }, 14 | { 15 | "sectionTitle": "MHC", 16 | "sectionDescription": "mhc_description", 17 | "rowData": [ 18 | { 19 | "rcsbid": "2XPG", 20 | "description": "Crystal structure of a MHC class I-peptide complex", 21 | "authors": "McMahon, R.M., Friis, L., Siebold, C., Friese, M.A., Fugger, L., Jones, E.Y." 22 | } 23 | ] 24 | }, 25 | { 26 | "sectionTitle": "Apoptosome", 27 | "sectionDescription": "apoptosome_description", 28 | "rowData": [ 29 | { 30 | "rcsbid": "3JBT", 31 | "description": "Atomic structure of the Apaf-1 apoptosome", 32 | "authors": "Zhou, M., Li, Y., Hu, Q., Bai, X., Huang, W., Yan, C., Scheres, S.H.W., Shi, Y." 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /BioViewer/Media Assets/ShutterClosed.aiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Media Assets/ShutterClosed.aiff -------------------------------------------------------------------------------- /BioViewer/Media Assets/ShutterOpen.aiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/BioViewer/Media Assets/ShutterOpen.aiff -------------------------------------------------------------------------------- /BioViewer/Metal/Compute/FillColorInput.h: -------------------------------------------------------------------------------- 1 | // 2 | // FillColorInput.h 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 23/1/22. 6 | // 7 | 8 | #ifndef FillColorInput_h 9 | #define FillColorInput_h 10 | 11 | /// Maximum number of colours that can be passed down to the GPU. 12 | #define MAX_CHAIN_COLORS 512 13 | #define MAX_ELEMENT_COLORS 128 14 | #define MAX_RESIDUE_COLORS 35 15 | #define MAX_SECONDARY_STRUCTURE_COLORS 4 16 | 17 | typedef struct { 18 | 19 | /// Color by element, used as a percentage (is always 0 or 1 outside animations). 20 | float colorByElement; 21 | /// Color by residue type, used as a percentage (is always 0 or 1 outside animations). 22 | float colorByResidue; 23 | /// Color by chain, used as a percentage (is always 0 or 1 outside animations). 24 | float colorByChain; 25 | /// Color by secondary structure, used as a percentage (is always 0 or 1 outside animations). 26 | float colorBySecondaryStructure; 27 | 28 | /// Color used when coloring by element. 29 | simd_float4 element_color [MAX_ELEMENT_COLORS]; 30 | /// Color used when coloring by subunit. 31 | simd_float4 subunit_color [MAX_CHAIN_COLORS]; 32 | /// Color used when coloring by amino acid. 33 | simd_float4 residue_color [MAX_RESIDUE_COLORS]; 34 | /// Color used when coloring by secondary structure. 35 | simd_float4 secondary_structure_color [MAX_SECONDARY_STRUCTURE_COLORS]; 36 | 37 | } FillColorInput; 38 | 39 | #endif /* FillColorInput_h */ 40 | -------------------------------------------------------------------------------- /BioViewer/Metal/Compute/SDFGrid.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDFGrid.h 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 18/3/22. 6 | // 7 | 8 | #ifndef SDFGrid_h 9 | #define SDFGrid_h 10 | 11 | typedef struct { 12 | 13 | /// Number of points per grid side. 14 | int32_t grid_resolution; 15 | /// Grid side size (in Armstrongs). 16 | float grid_size; 17 | /// Number of atoms contained inside the grid. 18 | int32_t number_of_atoms; 19 | 20 | } SDFGrid; 21 | 22 | #endif /* SDFGrid_h */ 23 | -------------------------------------------------------------------------------- /BioViewer/Metal/Functions/MakeBufferFromArray.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MakeBufferFromArray.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 10/5/22. 6 | // 7 | 8 | import Foundation 9 | import Metal 10 | 11 | extension MetalScheduler { 12 | public func makeBufferFromArray(array: [T]) -> MTLBuffer? { 13 | let arrayCopy = array 14 | let buffer = device.makeBuffer(bytes: arrayCopy, 15 | length: MemoryLayout.stride * arrayCopy.count) 16 | return buffer 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /BioViewer/Metal/Functions/MetalScheduler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetalScheduler.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 22/5/21. 6 | // 7 | 8 | import Foundation 9 | import Metal 10 | import simd 11 | 12 | class MetalScheduler { 13 | 14 | // MARK: - Properties 15 | 16 | static let shared = MetalScheduler() 17 | 18 | public enum Task { 19 | case createSASPoints 20 | case none 21 | } 22 | 23 | // MARK: - Private properties 24 | 25 | let device: MTLDevice! 26 | let queue: MTLCommandQueue? 27 | let library: MTLLibrary? 28 | 29 | // PipelineStateBundle bundles 30 | var createSphereModelBundle = PipelineStateBundle() 31 | var createBondsBundle = PipelineStateBundle() 32 | var createSASPointsBundle = PipelineStateBundle() 33 | var removeSASPointsInsideSolidBundle = PipelineStateBundle() 34 | 35 | /// DispatchQueue for synchronization 36 | private(set) var metalDispatchQueue: DispatchQueue 37 | 38 | // MARK: - Initialization 39 | 40 | private init() { 41 | // Initialize device 42 | self.device = MTLCreateSystemDefaultDevice() 43 | 44 | // Initialize command queue 45 | self.queue = device.makeCommandQueue() 46 | 47 | // Initialize default Metal library 48 | self.library = device.makeDefaultLibrary() 49 | 50 | // Create a queue to dispatch metal work (FIFO) to synchronize work 51 | metalDispatchQueue = DispatchQueue.init(label: "Metal Scheduler", qos: .default) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /BioViewer/Metal/Meshes/AtomProperties.h: -------------------------------------------------------------------------------- 1 | // 2 | // AtomProperties.h 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 1/6/21. 6 | // 7 | 8 | #ifndef AtomProperties_h 9 | #define AtomProperties_h 10 | 11 | #include 12 | 13 | // Carbon, Hydrogen, Nitrogen, Oxygen, Sulfur, Others 14 | constant float atomSolidSphereRadius [] = {1.70, 1.10, 1.55, 1.52, 1.80, 1.50}; 15 | constant float atomAtomicRadius [] = {0.70, 0.25, 0.65, 0.60, 1.00, 0.50}; 16 | 17 | #endif /* AtomProperties_h */ 18 | -------------------------------------------------------------------------------- /BioViewer/Metal/Meshes/GeneratedVertex.h: -------------------------------------------------------------------------------- 1 | // 2 | // GeneratedVertex.h 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 18/10/21. 6 | // 7 | 8 | #ifndef GeneratedVertex_h 9 | #define GeneratedVertex_h 10 | 11 | #include 12 | #include 13 | 14 | typedef struct { 15 | 16 | /// Position of the vertex in world space 17 | simd_float3 position; 18 | 19 | /// Normal of the surface tangent to the vertex in world space 20 | simd_float3 normal; // TO-DO: Use simd_half3 when supported... 21 | 22 | } GeneratedVertex; 23 | 24 | typedef struct { 25 | 26 | /// Position of the vertex in world space 27 | simd_float3 position; 28 | 29 | /// Position of the atom center in world space 30 | simd_float3 billboard_world_center; 31 | 32 | /// Position of the atomic center in world space 33 | simd_float2 billboardMapping; 34 | 35 | /// Atom radii 36 | float atom_radius; 37 | 38 | } BillboardVertex; 39 | 40 | typedef struct { 41 | 42 | /// Position of the first atom in world space. 43 | simd_float3 atom_A; 44 | 45 | /// Position of the first atom in world space. 46 | simd_float3 atom_B; 47 | 48 | /// Cylinder center in world space. 49 | simd_float3 cylinder_center; 50 | 51 | /// Bond radius. 52 | float bond_radius; 53 | 54 | } RawBondStruct; 55 | 56 | typedef struct { 57 | 58 | /// Position of the vertex in world space 59 | simd_float3 position; 60 | 61 | } DebugPoint; 62 | 63 | #endif /* GeneratedVertex_h */ 64 | -------------------------------------------------------------------------------- /BioViewer/Metal/Renderer/ComputePasses/ComputePipelines.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComputePipelines.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 21/2/22. 6 | // 7 | 8 | import Foundation 9 | import Metal 10 | 11 | extension MutableState { 12 | 13 | // MARK: - Fill color pass 14 | 15 | func makeFillColorComputePipelineState(device: MTLDevice) { 16 | // Setup pipeline 17 | guard let defaultLibrary = try? device.makeDefaultLibrary(bundle: Bundle(for: ProteinRenderer.self)) else { 18 | fatalError() 19 | } 20 | 21 | guard let fillColorKernel = defaultLibrary.makeFunction(name: "fill_color_buffer") else { 22 | NSLog("Failed to make fill color kernel") 23 | return 24 | } 25 | 26 | fillColorComputePipelineState = try? device.makeComputePipelineState(function: fillColorKernel) 27 | } 28 | 29 | // MARK: - Shadow blurring pass 30 | 31 | func makeShadowBlurringComputePipelineState(device: MTLDevice) { 32 | // Setup pipeline 33 | guard let defaultLibrary = try? device.makeDefaultLibrary(bundle: Bundle(for: ProteinRenderer.self)) else { 34 | NSLog("Failed to retrieve the default library.") 35 | return 36 | } 37 | 38 | guard let shadowBlurKernel = defaultLibrary.makeFunction(name: "shadow_blur") else { 39 | NSLog("Failed to make shadow blur kernel") 40 | return 41 | } 42 | 43 | shadowBlurPipelineState = try? device.makeComputePipelineState(function: shadowBlurKernel) 44 | } 45 | 46 | // MARK: - Motion texture pass 47 | 48 | func makeMotionComputePipelineState(device: MTLDevice) { 49 | // Setup pipeline 50 | guard let defaultLibrary = try? device.makeDefaultLibrary(bundle: Bundle(for: ProteinRenderer.self)) else { 51 | NSLog("Failed to retrieve the default library.") 52 | return 53 | } 54 | 55 | guard let motionKernel = defaultLibrary.makeFunction(name: "motion_texture") else { 56 | NSLog("Failed to make shadow blur kernel") 57 | return 58 | } 59 | 60 | let descriptor = MTLComputePipelineDescriptor() 61 | descriptor.computeFunction = motionKernel 62 | descriptor.label = "Motion Texture Pass" 63 | motionPipelineState = try? device.makeComputePipelineState( 64 | descriptor: descriptor, 65 | options: MTLPipelineOption() 66 | ).0 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /BioViewer/Metal/Renderer/Data Structures/VisualizationConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VisualizationConfiguration.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 30/4/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct VisualizationConfiguration { 12 | let atomRadii: AtomRadii 13 | let colorBy: ProteinColorByOption 14 | } 15 | -------------------------------------------------------------------------------- /BioViewer/Metal/Renderer/Ray.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Ray.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 24/2/24. 6 | // 7 | 8 | import Foundation 9 | import simd 10 | 11 | struct Ray { 12 | let origin: simd_float3 13 | let direction: simd_float3 14 | 15 | static func *(transform: float4x4, ray: Ray) -> Ray { 16 | let originT = (transform * simd_float4(ray.origin, 1)).xyz 17 | let directionT = (transform * simd_float4(ray.direction, 0)).xyz 18 | return Ray(origin: originT, direction: directionT) 19 | } 20 | 21 | /// Determine the point along this ray at the given parameter 22 | func extrapolate(_ parameter: Float) -> simd_float4 { 23 | return simd_float4(origin + parameter * direction, 1) 24 | } 25 | 26 | /// Determine the parameter corresponding to the point, 27 | /// assuming it lies on this ray 28 | func interpolate(_ point: simd_float4) -> Float { 29 | return length(point.xyz - origin) / length(direction) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BioViewer/Metal/Renderer/RenderPasses/ImpostorPass/DepthPrePassStage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DepthBoundPass.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 25/5/22. 6 | // 7 | 8 | import Foundation 9 | import Metal 10 | import QuartzCore 11 | import MetalKit 12 | 13 | extension MutableState { 14 | 15 | func encodeDepthBoundStage(renderer: ProteinRenderer, renderCommandEncoder: MTLRenderCommandEncoder, uniformBuffer: inout MTLBuffer) { 16 | 17 | // Ensure transparent buffers are loaded 18 | guard let billboardVertexBuffers = self.billboardVertexBuffers else { return } 19 | guard let impostorIndexBuffer = self.impostorIndexBuffer else { return } 20 | 21 | // MARK: - Impostor sphere rendering 22 | 23 | guard let pipelineState = depthPrePassRenderPipelineState else { 24 | return 25 | } 26 | renderCommandEncoder.setRenderPipelineState(pipelineState) 27 | 28 | // Add buffers to pipeline 29 | renderCommandEncoder.setVertexBuffer( 30 | billboardVertexBuffers.positionBuffer, 31 | offset: 0, 32 | index: 0 33 | ) 34 | renderCommandEncoder.setVertexBuffer( 35 | billboardVertexBuffers.atomWorldCenterBuffer, 36 | offset: 0, 37 | index: 1 38 | ) 39 | renderCommandEncoder.setVertexBuffer( 40 | uniformBuffer, 41 | offset: 0, 42 | index: 5 43 | ) 44 | 45 | // Don't render back-facing triangles (cull them) 46 | renderCommandEncoder.setCullMode(.back) 47 | 48 | // Draw primitives 49 | guard let configurationSelector = scene.configurationSelector else { 50 | return 51 | } 52 | let indexBufferRegion = configurationSelector.getImpostorIndexBufferRegion() 53 | 54 | renderCommandEncoder.drawIndexedPrimitives( 55 | type: .triangle, 56 | indexCount: indexBufferRegion.length, 57 | indexType: .uint32, 58 | indexBuffer: impostorIndexBuffer, 59 | indexBufferOffset: indexBufferRegion.offset * MemoryLayout.stride 60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /BioViewer/Metal/Renderer/RenderPasses/Postprocessing/CopyToDrawable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CopyToDrawable.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 9/4/23. 6 | // 7 | 8 | import Foundation 9 | import MetalKit 10 | 11 | extension MutableState { 12 | 13 | /// Blits the final, upscaled from renderer texture to the drawable size. 14 | func copyToDrawable( 15 | commandBuffer: MTLCommandBuffer, 16 | finalRenderedTexture: MTLTexture, 17 | drawableTexture: MTLTexture 18 | ) { 19 | guard finalRenderedTexture.width == drawableTexture.width else { 20 | return 21 | } 22 | guard finalRenderedTexture.height == drawableTexture.height else { 23 | return 24 | } 25 | let blitCommandEncoder = commandBuffer.makeBlitCommandEncoder() 26 | blitCommandEncoder?.copy(from: finalRenderedTexture, to: drawableTexture) 27 | blitCommandEncoder?.endEncoding() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /BioViewer/Metal/Renderer/RenderPasses/Postprocessing/ShadowBlurPass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ShadowBlurStage.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 9/4/23. 6 | // 7 | 8 | import Foundation 9 | import MetalKit 10 | 11 | extension MutableState { 12 | 13 | func shadowBlurPass( 14 | renderer: ProteinRenderer, 15 | commandBuffer: MTLCommandBuffer, 16 | texture: MTLTexture 17 | ) { 18 | // Set Metal compute encoder 19 | guard let computeEncoder = commandBuffer.makeComputeCommandEncoder() else { 20 | return 21 | } 22 | 23 | guard let shadowBlurPipelineState = shadowBlurPipelineState else { 24 | return 25 | } 26 | 27 | // Set compute pipeline state for current arguments 28 | computeEncoder.setComputePipelineState(shadowBlurPipelineState) 29 | 30 | computeEncoder.setTexture(texture, index: 0) 31 | 32 | // Schedule the threads 33 | if device.supportsFamily(.common3) { 34 | // Create threadgroup sizes 35 | let width = shadowBlurPipelineState.threadExecutionWidth 36 | let height = shadowBlurPipelineState.maxTotalThreadsPerThreadgroup / width 37 | let threadsPerThreadgroup = MTLSizeMake(width, height, 1) 38 | // Create threadgroup grid 39 | let threadsPerGrid = MTLSize( 40 | width: texture.width, 41 | height: texture.height, 42 | depth: 1 43 | ) 44 | // Dispatch threads 45 | computeEncoder.dispatchThreads(threadsPerGrid, threadsPerThreadgroup: threadsPerThreadgroup) 46 | } 47 | 48 | // End encoding 49 | computeEncoder.endEncoding() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /BioViewer/Metal/Renderer/RenderPasses/Postprocessing/Upscaling/MetalFXScalers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetalFXScalers.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 9/4/23. 6 | // 7 | 8 | import Foundation 9 | #if canImport(MetalFX) 10 | import MetalFX 11 | #endif 12 | import MetalKit 13 | 14 | extension MutableState { 15 | 16 | /// Creates the MetalFX spatial scaler. 17 | func makeSpatialScaler(inputSize: MTLSize, outputSize: MTLSize) { 18 | #if canImport(MetalFX) 19 | let descriptor = MTLFXSpatialScalerDescriptor() 20 | descriptor.inputWidth = inputSize.width 21 | descriptor.inputHeight = inputSize.height 22 | descriptor.outputWidth = outputSize.width 23 | descriptor.outputHeight = outputSize.height 24 | descriptor.colorTextureFormat = .bgra8Unorm 25 | descriptor.outputTextureFormat = .bgra8Unorm 26 | descriptor.colorProcessingMode = .linear 27 | 28 | guard let spatialScaler = descriptor.makeSpatialScaler(device: device) else { 29 | print("The spatial scaler effect is not usable!") 30 | return 31 | } 32 | metalFXSpatialScaler = spatialScaler 33 | #else 34 | print("Can't import MetalFX!") 35 | #endif 36 | } 37 | 38 | /// Creates the MetalFX spatial scaler. 39 | func makeTemporalScaler(inputSize: MTLSize, outputSize: MTLSize) { 40 | #if canImport(MetalFX) 41 | let descriptor = MTLFXTemporalScalerDescriptor() 42 | descriptor.inputWidth = inputSize.width 43 | descriptor.inputHeight = inputSize.height 44 | descriptor.outputWidth = outputSize.width 45 | descriptor.outputHeight = outputSize.height 46 | descriptor.colorTextureFormat = .bgra8Unorm 47 | descriptor.depthTextureFormat = .depth32Float 48 | descriptor.motionTextureFormat = ProteinRenderedTextures.motionPixelFormat 49 | descriptor.outputTextureFormat = .bgra8Unorm 50 | descriptor.isAutoExposureEnabled = false 51 | 52 | guard let temporalScaler = descriptor.makeTemporalScaler(device: device) else { 53 | print("The temporal scaler effect is not usable!") 54 | return 55 | } 56 | temporalScaler.motionVectorScaleX = Float(inputSize.width) 57 | temporalScaler.motionVectorScaleY = Float(inputSize.height) 58 | metalFXTemporalScaler = temporalScaler 59 | #else 60 | print("Can't import MetalFX!") 61 | #endif 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /BioViewer/Metal/Renderer/RenderPasses/ShadowPass/ShadowDepthPrePass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DepthBoundShadowPass.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 4/6/22. 6 | // 7 | 8 | import Foundation 9 | import Metal 10 | import QuartzCore 11 | import MetalKit 12 | 13 | extension MutableState { 14 | 15 | func encodeShadowDepthPrePassStage(renderer: ProteinRenderer, renderCommandEncoder: MTLRenderCommandEncoder, uniformBuffer: inout MTLBuffer) { 16 | 17 | // Ensure transparent buffers are loaded 18 | guard let billboardVertexBuffers = self.billboardVertexBuffers else { return } 19 | guard let impostorIndexBuffer = self.impostorIndexBuffer else { return } 20 | 21 | // MARK: - Depth pre-pass rendering 22 | 23 | guard let pipelineState = shadowDepthPrePassRenderPipelineState else { 24 | return 25 | } 26 | renderCommandEncoder.setRenderPipelineState(pipelineState) 27 | 28 | // Add buffers to pipeline 29 | renderCommandEncoder.setVertexBuffer( 30 | billboardVertexBuffers.positionBuffer, 31 | offset: 0, 32 | index: 0 33 | ) 34 | renderCommandEncoder.setVertexBuffer( 35 | billboardVertexBuffers.atomWorldCenterBuffer, 36 | offset: 0, 37 | index: 1 38 | ) 39 | renderCommandEncoder.setVertexBuffer( 40 | uniformBuffer, 41 | offset: 0, 42 | index: 4 43 | ) 44 | 45 | // Don't render back-facing triangles (cull them) 46 | renderCommandEncoder.setCullMode(.back) 47 | 48 | // Draw primitives 49 | guard let configurationSelector = scene.configurationSelector else { 50 | return 51 | } 52 | let indexBufferRegion = configurationSelector.getImpostorIndexBufferRegion() 53 | 54 | renderCommandEncoder.drawIndexedPrimitives( 55 | type: .triangle, 56 | indexCount: indexBufferRegion.length, 57 | indexType: .uint32, 58 | indexBuffer: impostorIndexBuffer, 59 | indexBufferOffset: indexBufferRegion.offset * MemoryLayout.stride 60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /BioViewer/Metal/Renderer/Textures/AmbientOcclusion3DTexture.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AmbientOcclusion3DTexture.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 29/4/23. 6 | // 7 | 8 | import Foundation 9 | import Metal 10 | 11 | struct AmbientOcclusion3DTexture { 12 | 13 | static let defaultSize: Int = 64 14 | static let pixelFormat: MTLPixelFormat = .r32Float 15 | 16 | var texture: MTLTexture? 17 | var textureSize: Int = AmbientOcclusion3DTexture.defaultSize 18 | 19 | mutating func makeTexture(device: MTLDevice) { 20 | 21 | // MARK: - Texture size 22 | self.textureSize = AmbientOcclusion3DTexture.defaultSize 23 | 24 | // MARK: - Texture descriptor 25 | let textureDescriptor = MTLTextureDescriptor() 26 | textureDescriptor.width = textureSize 27 | textureDescriptor.height = textureSize 28 | textureDescriptor.depth = textureSize 29 | textureDescriptor.textureType = .type3D 30 | textureDescriptor.storageMode = .private 31 | 32 | texture = device.makeTexture(descriptor: textureDescriptor) 33 | texture?.label = "Ambient Occlusion Texture" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /BioViewer/Metal/Renderer/Textures/BenchmarkTextures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BenchmarkTextures.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 25/1/23. 6 | // 7 | 8 | import Foundation 9 | import Metal 10 | 11 | struct BenchmarkTextures { 12 | 13 | static let benchmarkResolution: Int = 1440 14 | 15 | var colorTexture: MTLTexture! 16 | var depthTexture: MTLTexture! 17 | 18 | static let colorPixelFormat = MTLPixelFormat.bgra8Unorm 19 | static let depthPixelFormat = MTLPixelFormat.depth32Float 20 | 21 | mutating func makeTextures(device: MTLDevice) { 22 | 23 | // Color texture descriptor 24 | let colorTextureDescriptor = MTLTextureDescriptor 25 | .texture2DDescriptor(pixelFormat: BenchmarkTextures.colorPixelFormat, 26 | width: BenchmarkTextures.benchmarkResolution, 27 | height: BenchmarkTextures.benchmarkResolution, 28 | mipmapped: false) 29 | colorTextureDescriptor.textureType = .type2D 30 | colorTextureDescriptor.usage = [.renderTarget] 31 | colorTextureDescriptor.storageMode = .shared 32 | colorTexture = device.makeTexture(descriptor: colorTextureDescriptor) 33 | colorTexture.label = "Benchmark color Texture" 34 | 35 | // Depth texture 36 | let depthTextureDescriptor = MTLTextureDescriptor 37 | .texture2DDescriptor(pixelFormat: BenchmarkTextures.depthPixelFormat, 38 | width: BenchmarkTextures.benchmarkResolution, 39 | height: BenchmarkTextures.benchmarkResolution, 40 | mipmapped: false) 41 | depthTextureDescriptor.textureType = .type2D 42 | depthTextureDescriptor.usage = [.shaderRead, .shaderWrite, .renderTarget] 43 | depthTextureDescriptor.storageMode = .shared 44 | depthTexture = device.makeTexture(descriptor: depthTextureDescriptor) 45 | depthTexture.label = "Benchmark depth Texture" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /BioViewer/Metal/Renderer/Textures/HQTextures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HQTextures.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 20/12/21. 6 | // 7 | 8 | import Foundation 9 | import Metal 10 | 11 | struct HQTextures { 12 | var hqTexture: MTLTexture! 13 | var hqDepthTexture: MTLTexture! 14 | var hqSampler: MTLSamplerState? 15 | var textureWidth: Int! 16 | var textureHeight: Int! 17 | 18 | // Since the texture should be just enough to fit the bounding sphere on an 19 | // orthographic projection, the hq texture should be square. High resolution 20 | // hqs are *very* expensive due to the need to call the fragment shader. 21 | 22 | static let hqTexturePixelFormat = MTLPixelFormat.bgra8Unorm 23 | static let hqDepthTexturePixelFormat = MTLPixelFormat.depth32Float 24 | 25 | mutating func makeTextures(device: MTLDevice, photoConfig: PhotoModeConfig) { 26 | 27 | // MARK: - Common texture descriptor 28 | let hqTextureDescriptor = MTLTextureDescriptor 29 | .texture2DDescriptor(pixelFormat: HQTextures.hqTexturePixelFormat, 30 | width: photoConfig.finalTextureSize, 31 | height: photoConfig.finalTextureSize, 32 | mipmapped: false) 33 | 34 | hqTextureDescriptor.textureType = .type2D 35 | 36 | // MARK: - HQ color texture 37 | 38 | hqTextureDescriptor.pixelFormat = HQTextures.hqTexturePixelFormat 39 | hqTextureDescriptor.usage = [.renderTarget] 40 | 41 | hqTexture = device.makeTexture(descriptor: hqTextureDescriptor) 42 | hqTexture.label = "HQ Texture" 43 | 44 | // MARK: - HQ depth texture 45 | 46 | hqTextureDescriptor.pixelFormat = .depth32Float 47 | hqTextureDescriptor.usage = [.shaderRead, .shaderWrite, .renderTarget] 48 | hqTextureDescriptor.storageMode = .shared 49 | hqDepthTexture = device.makeTexture(descriptor: hqTextureDescriptor) 50 | hqDepthTexture.label = "HQ Depth Texture" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /BioViewer/Metal/Renderer/Textures/MetalFXUpscaledTexture.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetalFXUpscaledTexture.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 9/4/23. 6 | // 7 | 8 | import Foundation 9 | import MetalKit 10 | 11 | struct MetalFXUpscaledTexture { 12 | 13 | var upscaledColor: MTLTexture! 14 | 15 | mutating func makeTexture(device: MTLDevice, width: Int, height: Int) { 16 | 17 | // Color texture descriptor 18 | let colorTextureDescriptor = MTLTextureDescriptor 19 | .texture2DDescriptor( 20 | pixelFormat: BenchmarkTextures.colorPixelFormat, 21 | width: width, 22 | height: height, 23 | mipmapped: false 24 | ) 25 | colorTextureDescriptor.textureType = .type2D 26 | colorTextureDescriptor.usage = [.renderTarget, .shaderWrite] 27 | colorTextureDescriptor.storageMode = .private 28 | upscaledColor = device.makeTexture(descriptor: colorTextureDescriptor) 29 | upscaledColor.label = "MetalFX Upscaled Texture" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /BioViewer/Metal/Shaders/FrameData.h: -------------------------------------------------------------------------------- 1 | // 2 | // FrameData.h 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 17/10/21. 6 | // 7 | 8 | #ifndef FrameData_h 9 | #define FrameData_h 10 | 11 | #include 12 | #include "../SharedDataStructs.h" 13 | 14 | typedef struct { 15 | 16 | /// Number of atoms in a single configuration. 17 | int atoms_per_configuration; 18 | 19 | /// The depth bias, in Normalized Device Coordinates, that is applied in the 20 | /// depth and shadow depth pre-passes, to avoid artifacts due to the regular 21 | /// and shadow passes excluding fragments whose depth is close to the 22 | /// pre-computed depth of the pre-pass. Should be the equivalent of about 23 | /// 2 Armstrongs (translated to NDCs). 24 | float depth_bias; 25 | 26 | // MARK: - Matrices 27 | 28 | /// Model to view matrix 29 | simd_float4x4 model_view_matrix; 30 | 31 | /// Projection matrix 32 | simd_float4x4 projectionMatrix; 33 | 34 | /// Rotation matrix 35 | simd_float4x4 rotation_matrix; 36 | 37 | /// Inverse rotation matrix 38 | simd_float4x4 inverse_rotation_matrix; 39 | 40 | /// Shadow projection matrix 41 | simd_float4x4 shadowProjectionMatrix; 42 | 43 | /// Rotation matrix to view the model from the sun's point of view. 44 | simd_float4x4 sun_rotation_matrix; 45 | 46 | /// Transform from camera coordinates to sun's perspective coordinates. 47 | simd_float4x4 camera_to_shadow_projection_matrix; 48 | 49 | // MARK: - Bonds 50 | 51 | simd_float3 bond_color; 52 | 53 | // MARK: - Shadows 54 | 55 | /// Sun direction. 56 | simd_float3 sun_direction; 57 | /// Whether it should cast shadows. 58 | int8_t has_shadows; 59 | /// The strength of the shadow color subtraction, from 0 to 1. 60 | float shadow_strength; 61 | 62 | /// Whether it should use depth cueing. 63 | int8_t has_depth_cueing; 64 | /// The strength of the depth cueing, from 0 to 1. 65 | float depth_cueing_strength; 66 | 67 | } FrameData; 68 | 69 | #endif /* FrameData_h */ 70 | -------------------------------------------------------------------------------- /BioViewer/Metal/Shaders/Postprocessing/ReprojectionData.h: -------------------------------------------------------------------------------- 1 | // 2 | // ReprojectionData.h 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 16/4/23. 6 | // 7 | 8 | #ifndef ReprojectionData_h 9 | #define ReprojectionData_h 10 | 11 | #include 12 | #include "../../SharedDataStructs.h" 13 | 14 | typedef struct { 15 | 16 | /// Reprojects from the current frame's NDC to the previous frame's NDC. 17 | simd_float4x4 reprojection_matrix; 18 | /// Width of the render target. 19 | int32_t renderWidth; 20 | /// Height of the render target. 21 | int32_t renderHeight; 22 | /// Pixel jitter in NDC coordinates (-0.5 to 0.5). 23 | simd_float2 pixel_jitter; 24 | /// Jitter performed on the projection, in texture coordinates. 25 | simd_float2 texel_jitter; 26 | /// Jitter performed on the projection, in texture coordinates. 27 | simd_float2 previous_texel_jitter; 28 | 29 | } ReprojectionData; 30 | 31 | #endif /* ReprojectionData_h */ 32 | -------------------------------------------------------------------------------- /BioViewer/Metal/Shaders/Postprocessing/ShadowBlur.metal: -------------------------------------------------------------------------------- 1 | // 2 | // ShadowBlur.metal 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 9/4/23. 6 | // 7 | 8 | #include 9 | using namespace metal; 10 | 11 | kernel void shadow_blur(const texture2d renderer_output [[ texture(0) ]], 12 | uint2 texture_position [[ thread_position_in_grid ]]) { 13 | float4 source_color = float4(0.0, 0.0, 0.0, 0.0); 14 | for (int i = 0; i < 7; i++) { 15 | for (int j = 0; j < 7; j++) { 16 | uint2 sample_position = texture_position; 17 | sample_position.x += i; 18 | sample_position.y += j; 19 | source_color += renderer_output.read(sample_position); 20 | } 21 | } 22 | renderer_output.write(source_color / 49, texture_position); 23 | } 24 | -------------------------------------------------------------------------------- /BioViewer/Metal/Shaders/ShaderCommon.h: -------------------------------------------------------------------------------- 1 | // 2 | // ShaderCommon.h 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 11/6/22. 6 | // 7 | 8 | #ifndef ShaderCommon_h 9 | #define ShaderCommon_h 10 | 11 | struct DepthPrePassFragmentOut{ 12 | half4 throwaway_color [[ color(0) ]]; 13 | float bounded_depth [[ color(1) ]]; 14 | }; 15 | 16 | struct ShadowDepthPrePassFragmentOut{ 17 | float throwaway_color [[ color(0) ]]; 18 | float bounded_depth [[ color(1) ]]; 19 | }; 20 | 21 | #endif /* ShaderCommon_h */ 22 | -------------------------------------------------------------------------------- /BioViewer/Metal/SharedDataStructs.h: -------------------------------------------------------------------------------- 1 | // 2 | // SharedDataStructs.h 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 8/1/22. 6 | // 7 | 8 | #ifndef SharedDataStructs_h 9 | #define SharedDataStructs_h 10 | 11 | /// Supported number of (different) atom types. 12 | #define ATOM_TYPE_COUNT 64 13 | 14 | struct Sphere { 15 | vector_float3 origin; 16 | float radius; 17 | }; 18 | 19 | typedef struct { 20 | /// Radius of each atom type (C, H, N, O, S, Others). 21 | float atomRadius [ATOM_TYPE_COUNT]; 22 | } AtomRadii; 23 | 24 | #endif /* SharedDataStructs_h */ 25 | -------------------------------------------------------------------------------- /BioViewer/Metal/Utilities/SIMDExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SIMDExtensions.swift 3 | // BioViewer 4 | // 5 | // Imported by Raúl Montón Pinillos on 1/6/21. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - SIMD4 11 | extension SIMD4 { 12 | // Convenience getter for the first 3 components of a SIMD4 vector. 13 | var xyz: SIMD3 { 14 | self[SIMD3(0, 1, 2)] 15 | } 16 | } 17 | 18 | extension simd_quatd { 19 | static var identity: simd_quatd { 20 | return simd_quatd(ix: 0, iy: 0, iz: 0, r: 1) 21 | } 22 | } 23 | 24 | extension Float { 25 | static var randomSign: Float { 26 | if Bool.random() { 27 | return 1 28 | } else { 29 | return -1 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BioViewer/MetalLegacySupport.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetalLegacySupport.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 30/4/22. 6 | // 7 | 8 | import Foundation 9 | import Metal 10 | 11 | class MetalLegacySupport { 12 | 13 | static func legacyDispatchThreadsForArray(commandEncoder: MTLComputeCommandEncoder, length: Int, pipelineState: MTLComputePipelineState) { 14 | 15 | let threadsPerThreadgroup = MTLSizeMake(pipelineState.maxTotalThreadsPerThreadgroup, 1, 1) 16 | let numberOfFullThreadgroups = Int(ceilf(Float(length) / Float(pipelineState.maxTotalThreadsPerThreadgroup))) 17 | let threadgroupSize = MTLSizeMake(numberOfFullThreadgroups, 1, 1) 18 | 19 | // Dispatch the threadgroups 20 | commandEncoder.dispatchThreadgroups(threadgroupSize, threadsPerThreadgroup: threadsPerThreadgroup) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BioViewer/Models/Protein/ProteinFileInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProteinFileInfo.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 14/11/21. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Class containing the data related to the imported protein file itself. 11 | struct ProteinFileInfo { 12 | 13 | /// PDB ID as in RCSB database. 14 | var pdbID: String? 15 | /// Human-readable description of the protein. 16 | var description: String? 17 | /// Authors of the file. 18 | var authors: String? 19 | /// Full source file text 20 | var sourceLines: [String]? 21 | 22 | /// List of all lines with warnings 23 | var warningIndices: [Int] = [] 24 | 25 | // MARK: - Initialization 26 | 27 | init() {} 28 | 29 | init(pdbID: String?, description: String?, authors: String?, sourceLines: [String]?) { 30 | self.pdbID = pdbID 31 | self.description = description 32 | self.authors = authors 33 | self.sourceLines = sourceLines 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /BioViewer/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /BioViewer/Views/Onboarding/NewsRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NewsRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 11/3/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct NewsRow: View { 11 | 12 | let rowType: NewsRowType 13 | let title: String 14 | let subtitle: String 15 | 16 | init(whatsNewItem: WhatsNew) { 17 | self.rowType = whatsNewItem.type 18 | self.title = whatsNewItem.title 19 | self.subtitle = whatsNewItem.subtitle 20 | } 21 | 22 | var body: some View { 23 | HStack(alignment: .top, spacing: 18) { 24 | switch rowType { 25 | case .feature: 26 | VStack(spacing: 4) { 27 | Image(systemName: "newspaper.fill") 28 | .foregroundColor(.accentColor) 29 | .font(.title) 30 | Text("New".uppercased()) 31 | .bold() 32 | .font(.caption) 33 | .foregroundColor(.accentColor) 34 | } 35 | .padding(.top, 4) 36 | case .fix: 37 | VStack(spacing: 4) { 38 | Image(systemName: "wrench.fill") 39 | .foregroundColor(.accentColor) 40 | .font(.title) 41 | Text("Fix".uppercased()) 42 | .bold() 43 | .font(.caption) 44 | .foregroundColor(.accentColor) 45 | } 46 | .padding(.top, 4) 47 | } 48 | 49 | VStack(alignment: .leading, spacing: 4) { 50 | Text(title) 51 | .bold() 52 | Text(subtitle) 53 | .foregroundColor(.gray) 54 | } 55 | } 56 | .padding(.vertical, 6) 57 | } 58 | } 59 | 60 | struct NewsRow_Previews: PreviewProvider { 61 | static var previews: some View { 62 | List { 63 | NewsRow(whatsNewItem: WhatsNew(type: .feature, 64 | title: "Selectable atom radii", 65 | subtitle: "Atom radii can now be selected for both the solid spheres and the ball and stick visualization modes.")) 66 | } 67 | .listStyle(.plain) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /BioViewer/Views/Onboarding/WhatsNewView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhatsNewView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 11/3/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct WhatsNewView: View { 11 | 12 | @Environment(\.dismiss) var dismiss 13 | @StateObject var viewModel = WhatsNewViewModel() 14 | 15 | var body: some View { 16 | NavigationView { 17 | ZStack(alignment: .bottom) { 18 | List(viewModel.newItems) { newItem in 19 | NewsRow(whatsNewItem: newItem) 20 | .listRowSeparator(.hidden) 21 | } 22 | .listStyle(.plain) 23 | .navigationTitle(NSLocalizedString("What's new (\(viewModel.version))", comment: "")) 24 | 25 | // Bottom buttons 26 | VStack { 27 | Divider() 28 | 29 | Button(action: { 30 | dismiss() 31 | }, label: { 32 | Text(NSLocalizedString("Continue", comment: "")) 33 | .padding(4) 34 | .frame(maxWidth: .infinity) 35 | }) 36 | .padding(.horizontal) 37 | .padding(.top, 4) 38 | .frame(maxWidth: .infinity) 39 | .buttonStyle(.borderedProminent) 40 | 41 | Button(action: { 42 | dismiss() 43 | AppState.shared.userDoesNotWantUpdates() 44 | }, label: { 45 | Text(NSLocalizedString("Don't notify new features", comment: "")) 46 | .padding(4) 47 | .frame(maxWidth: .infinity) 48 | }) 49 | .padding(.bottom, 4) 50 | } 51 | .background(.regularMaterial) 52 | } 53 | } 54 | .navigationBarTitleDisplayMode(.large) 55 | } 56 | } 57 | 58 | struct WhatsNewView_Previews: PreviewProvider { 59 | static var previews: some View { 60 | WhatsNewView() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /BioViewer/Views/Onboarding/WhatsNewViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WhatsNewViewModel.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 12/3/22. 6 | // 7 | 8 | import Foundation 9 | 10 | enum NewsRowType { 11 | case feature 12 | case fix 13 | } 14 | 15 | struct WhatsNew: Identifiable { 16 | let id = UUID() 17 | let type: NewsRowType 18 | let title: String 19 | let subtitle: String 20 | } 21 | 22 | class WhatsNewViewModel: ObservableObject { 23 | 24 | @Published var newItems = [WhatsNew]() 25 | let version = AppState.shared.version() 26 | 27 | init() { 28 | var changeLog: NSDictionary? 29 | if let path = Bundle.main.path(forResource: "ChangeLog", ofType: "plist") { 30 | changeLog = NSDictionary(contentsOfFile: path) 31 | } 32 | guard let changeLog = changeLog else { 33 | return 34 | } 35 | guard let currentNews = changeLog[version] as? [NSDictionary] else { 36 | return 37 | } 38 | for newItem in currentNews { 39 | var type: NewsRowType? 40 | if newItem["Type"] as? String == "FEAT" { 41 | type = .feature 42 | } else if newItem["Type"] as? String == "FIX" { 43 | type = .fix 44 | } 45 | let title = newItem["Title"] as? String 46 | let subtitle = newItem["Subtitle"] as? String 47 | 48 | if let type = type, let title = title, let subtitle = subtitle { 49 | newItems.append(WhatsNew(type: type, title: title, subtitle: subtitle)) 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /BioViewer/Views/PresentedViews/PeriodicTableContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeriodicTableContentView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 29/11/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PeriodicTableContentView: View { 11 | var body: some View { 12 | GeometryReader { geometryProxy in 13 | ScrollView(.horizontal) { 14 | VStack(spacing: 2) { 15 | ForEach(0..<6) { _ in 16 | HStack(spacing: 2) { 17 | ForEach(0..<17, id: \.self) { _ in 18 | ZStack { 19 | Rectangle() 20 | .aspectRatio(1.0, contentMode: .fit) 21 | Text("He") 22 | .foregroundColor(.white) 23 | } 24 | .frame(minWidth: 24) 25 | } 26 | } 27 | } 28 | } 29 | .padding() 30 | .frame(idealWidth: geometryProxy.size.width) 31 | } 32 | } 33 | } 34 | } 35 | 36 | struct PeriodicTableContentView_Previews: PreviewProvider { 37 | static var previews: some View { 38 | PeriodicTableContentView() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /BioViewer/Views/PresentedViews/PeriodicTableView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PeriodicTableView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 29/11/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PeriodicTableView: View { 11 | 12 | @Environment(\.dismiss) var dismiss 13 | 14 | var body: some View { 15 | if #available(iOS 16, *) { 16 | NavigationStack { 17 | VStack { 18 | Divider() 19 | PeriodicTableContentView() 20 | Spacer() 21 | } 22 | .navigationTitle("Elements") 23 | .navigationBarTitleDisplayMode(.inline) 24 | .navigationBarItems( 25 | leading: Button( 26 | action: { 27 | dismiss() 28 | }, 29 | label: { 30 | Text(NSLocalizedString("Close", comment: "")) 31 | } 32 | ) 33 | ) 34 | } 35 | } else { 36 | EmptyView() 37 | } 38 | } 39 | } 40 | 41 | struct PeriodicTableView_Previews: PreviewProvider { 42 | static var previews: some View { 43 | PeriodicTableView() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Data/BallAndStickRadiusOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SolidSpheresRadiusOptions.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 11/3/22. 6 | // 7 | 8 | import Foundation 9 | 10 | enum BallAndStickRadiusOptions: PickableEnum { 11 | case fixed 12 | case scaledVDW 13 | 14 | var displayName: String { 15 | switch self { 16 | case .fixed: 17 | return NSLocalizedString("Fixed", comment: "") 18 | case .scaledVDW: 19 | return NSLocalizedString("Van Der Waals", comment: "") 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Data/ProteinGraphicsSettings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProteinGraphicsSettings.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 27/4/23. 6 | // 7 | 8 | import Foundation 9 | 10 | @MainActor @Observable class ProteinGraphicsSettings { 11 | 12 | weak var proteinViewModel: ProteinViewModel? 13 | 14 | var metalFXUpscalingMode: MetalFXUpscalingMode = .none { 15 | didSet { 16 | guard let renderer = proteinViewModel?.renderer else { 17 | return 18 | } 19 | switch metalFXUpscalingMode { 20 | case .temporal, .spatial: 21 | self.ssaaFactor = 1.5 22 | self.metalFXFactor = 1.5 23 | case .none: 24 | self.ssaaFactor = 1.0 25 | self.metalFXFactor = 1.0 26 | } 27 | Task { 28 | await renderer.mutableState.updateMetalFXUpscalingMode( 29 | to: metalFXUpscalingMode, 30 | renderer: renderer 31 | ) 32 | } 33 | } 34 | } 35 | 36 | var ssaaFactor: Float = 1.0 { 37 | didSet { 38 | Task { 39 | await updateFactors() 40 | } 41 | } 42 | } 43 | var metalFXFactor: Float = 1.5 { 44 | didSet { 45 | Task { 46 | await updateFactors() 47 | } 48 | } 49 | } 50 | 51 | private func updateFactors() async { 52 | guard let renderer = proteinViewModel?.renderer else { 53 | return 54 | } 55 | await renderer.mutableState.updateProteinRenderFactors( 56 | ssaa: ssaaFactor, 57 | metalFXUpscaling: metalFXFactor, 58 | renderer: renderer 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Data/ProteinViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProteinViewModel.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 13/5/21. 6 | // 7 | 8 | import Combine 9 | import Foundation 10 | import SwiftUI 11 | 12 | @MainActor class ProteinViewModel: ObservableObject { 13 | 14 | // MARK: - Properties 15 | 16 | /// Metal rendering engine. 17 | let renderer: ProteinRenderer 18 | /// Datasource to hold actual protein data. 19 | var dataSource: ProteinDataSource? 20 | /// Linked ProteinColorViewModel 21 | var colorViewModel: ProteinColorViewModel? 22 | /// Linked ProteinVisualizationViewModel 23 | var visualizationViewModel: ProteinVisualizationViewModel? 24 | /// Toolbar view model. 25 | var toolbarConfig: ToolbarConfig? 26 | /// Reference to the status view model for updates. 27 | var statusViewModel: StatusViewModel? 28 | 29 | /// Delegate to handle dropped files in view. 30 | var dropHandler: ImportDroppedFilesDelegate 31 | 32 | // MARK: - Initialization 33 | 34 | init(isBenchmark: Bool = false) { 35 | // Setup Metal renderer 36 | guard let device = MTLCreateSystemDefaultDevice() else { 37 | fatalError("Unable to create default Metal Device") 38 | } 39 | self.renderer = ProteinRenderer(device: device, isBenchmark: isBenchmark) 40 | 41 | // Setup drop delegate 42 | self.dropHandler = ImportDroppedFilesDelegate() 43 | 44 | // Pass reference to ProteinViewModel to delegates and datasources 45 | self.dropHandler.proteinViewModel = self 46 | } 47 | 48 | // MARK: - Public functions 49 | 50 | func removeAllFiles() async { 51 | await self.dataSource?.removeAllFilesFromDatasource() 52 | // FIXME: Status changes, self.statusViewModel?.removeAllWarnings() 53 | // FIXME: Status changes, self.statusViewModel?.removeAllErrors() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Data/ProteinVisualizationOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProteinVisualizationOption.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 31/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | enum ProteinVisualizationOption: PickableEnum { 11 | case solidSpheres 12 | case ballAndStick 13 | 14 | var displayName: String { 15 | switch self { 16 | case .solidSpheres: 17 | return NSLocalizedString("Space-filling spheres", comment: "") 18 | case .ballAndStick: 19 | return NSLocalizedString("Ball and stick (beta)", comment: "") 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Data/SolidSpheresRadiusOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SolidSpheresRadiusOptions.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 11/3/22. 6 | // 7 | 8 | import Foundation 9 | 10 | enum SolidSpheresRadiusOptions: PickableEnum { 11 | case vanDerWaals 12 | case fixed 13 | 14 | var displayName: String { 15 | switch self { 16 | case .vanDerWaals: 17 | return NSLocalizedString("Van Der Waals", comment: "") 18 | case .fixed: 19 | return NSLocalizedString("Fixed", comment: "") 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/ImportProteins/ImportRowView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImportRowView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 23/1/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ImportRowView: View { 11 | var title: String 12 | var imageName: String 13 | var action: ProteinImportView.ImportAction 14 | var parent: ProteinImportView 15 | 16 | var body: some View { 17 | Button( 18 | action: { 19 | parent.launchImportAction(action: action) 20 | }, 21 | label: { 22 | HStack(spacing: 10) { 23 | Image(systemName: imageName) 24 | .frame(width: 32, height: 32, alignment: .center) 25 | Text(title) 26 | .frame(width: 200, alignment: .leading) 27 | } 28 | .font(.headline) 29 | .foregroundColor(.white) 30 | } 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/ImportProteins/ProteinImportViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProteinImportViewModel.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 22/11/21. 6 | // 7 | 8 | import Foundation 9 | 10 | class ProteinImportViewModel { 11 | // TO-DO 12 | } 13 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/ImportProteins/ProteinRCSBViews/RCSBDetailView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RCSBDetail.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 13/6/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RCSBDetailView: View { 11 | var body: some View { 12 | ZStack(alignment: .bottom) { 13 | 14 | VStack(alignment: .leading) { 15 | Text("2OGM") 16 | .font(.largeTitle) 17 | .bold() 18 | ZStack { 19 | RoundedRectangle(cornerRadius: 24) 20 | .stroke(Color(uiColor: .separator), 21 | style: StrokeStyle(lineWidth: 2)) 22 | .opacity(0.2) 23 | } 24 | .aspectRatio(1.0, contentMode: .fit) 25 | Spacer() 26 | } 27 | .frame(maxWidth: .infinity, alignment: .leading) 28 | .padding() 29 | 30 | VStack(spacing: .zero) { 31 | Divider() 32 | Button(action: { 33 | // TO-DO 34 | }, label: { 35 | HStack { 36 | if #available(iOS 16.0, *) { 37 | Image(systemName: "arrow.down.doc") 38 | // FIXME: .bold() 39 | } 40 | Text(NSLocalizedString("Download", comment: "")) 41 | .bold() 42 | } 43 | .padding(8) 44 | .frame(maxWidth: .infinity) 45 | }) 46 | .buttonStyle(.borderedProminent) 47 | .padding() 48 | } 49 | .background(.regularMaterial) 50 | } 51 | } 52 | } 53 | 54 | struct RCSBDetail_Previews: PreviewProvider { 55 | static var previews: some View { 56 | RCSBDetailView() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/ImportProteins/ProteinRCSBViews/RCSBEmptyImportView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RCSBEmptyView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 1/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RCSBEmptyImportView: View { 11 | 12 | @Binding var rcsbShowSheet: Bool 13 | 14 | var body: some View { 15 | VStack(spacing: 4) { 16 | Image(systemName: "text.magnifyingglass") 17 | .resizable() 18 | .frame(width: 128, height: 128) 19 | .aspectRatio(contentMode: .fit) 20 | .foregroundColor(Color(uiColor: .tertiaryLabel)) 21 | Spacer() 22 | .frame(height: 16) 23 | Text("Search protein structures by entering their RCSB ID.") 24 | .multilineTextAlignment(.center) 25 | .foregroundColor(Color(uiColor: .tertiaryLabel)) 26 | Spacer() 27 | .frame(height: 16) 28 | Text("Don't know what to search for?") 29 | .multilineTextAlignment(.center) 30 | .foregroundColor(Color(uiColor: .tertiaryLabel)) 31 | NavigationLink(destination: RCSBSuggestionsView(rcsbShowSheet: $rcsbShowSheet)) { 32 | Text("Here are some suggestions...") 33 | .multilineTextAlignment(.center) 34 | } 35 | } 36 | .frame(maxWidth: .infinity, maxHeight: .infinity) 37 | } 38 | } 39 | 40 | struct RCSBEmptyImportView_Previews: PreviewProvider { 41 | static var previews: some View { 42 | RCSBEmptyImportView(rcsbShowSheet: .constant(true)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/ImportProteins/ProteinRCSBViews/RCSBSuggestionsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RCSBSuggestionsView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 1/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RCSBSuggestionHeaderView: View { 11 | 12 | let title: String? 13 | let description: String? 14 | var body: some View { 15 | VStack(alignment: .leading, spacing: 2) { 16 | Text(title ?? "") 17 | .bold() 18 | .frame(alignment: .leading) 19 | Text(description ?? "") 20 | .textCase(nil) 21 | Spacer() 22 | .frame(height: 4) 23 | } 24 | } 25 | } 26 | 27 | // swiftlint:disable all 28 | struct RCSBSuggestionsView: View { 29 | 30 | @Binding var rcsbShowSheet: Bool 31 | @StateObject var suggestionViewModel = RCSBSuggestionViewModel() 32 | 33 | var body: some View { 34 | List { 35 | if let sections = suggestionViewModel.suggestionData?.sections { 36 | ForEach(sections, id: \.self) { section in 37 | Section(content: { 38 | ForEach(section.rowData, id: \.self) { rcsbRow in 39 | let pdbInfo = PDBInfo(suggestion: rcsbRow) 40 | RCSBRowView( 41 | pdbInfo: pdbInfo, 42 | searchTerm: nil, 43 | rcsbShowSheet: $rcsbShowSheet 44 | ) 45 | } 46 | }, header: { 47 | RCSBSuggestionHeaderView( 48 | title: NSLocalizedString(section.sectionTitle, comment: ""), 49 | description: NSLocalizedString(section.sectionDescription, comment: "") 50 | ) 51 | }) 52 | } 53 | } 54 | } 55 | .navigationTitle(NSLocalizedString("Suggestions", comment: "")) 56 | .navigationBarTitleDisplayMode(.inline) 57 | } 58 | } 59 | 60 | struct RCSBSuggestions_Previews: PreviewProvider { 61 | static var previews: some View { 62 | RCSBSuggestionsView(rcsbShowSheet: .constant(true)) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/PhotoMode/PhotoModeConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoModeConfig.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 20/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | struct PhotoModeConfig: Sendable { 11 | var finalTextureSize: Int = 2048 12 | var shadowTextureSize: Int = 4096 13 | var clearBackground: Bool = true 14 | } 15 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/PhotoMode/PhotoModeFooter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoModeFooter.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 19/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PhotoModeFooter: View { 11 | 12 | @EnvironmentObject var proteinViewModel: ProteinViewModel 13 | @Environment(ProteinRenderer.self) var renderer: ProteinRenderer 14 | @Environment(PhotoModeViewModel.self) var photoModeViewModel: PhotoModeViewModel 15 | @Environment(ShutterAnimator.self) var shutterAnimator: ShutterAnimator 16 | 17 | var body: some View { 18 | VStack(spacing: 0) { 19 | Divider() 20 | Button(action: { 21 | Task { 22 | await photoModeViewModel.shutterAnimator.openShutter() 23 | try? await renderer.mutableState.drawHighQualityFrame( 24 | renderer: renderer, 25 | size: CGSize(width: 2048, height: 2048), 26 | photoConfig: photoModeViewModel.photoConfig, 27 | photoModeViewModel: photoModeViewModel 28 | ) 29 | } 30 | }, label: { 31 | HStack { 32 | Image(systemName: "camera") 33 | .resizable() 34 | .aspectRatio(contentMode: .fit) 35 | .frame(width: 24, height: 24) 36 | Text(NSLocalizedString("Take photo", comment: "")) 37 | .bold() 38 | } 39 | .padding(4) 40 | .frame(maxWidth: .infinity) 41 | }) 42 | .disabled(photoModeViewModel.shutterAnimator.shutterAnimationRunning) 43 | .buttonStyle(BorderedProminentButtonStyle()) 44 | .padding() 45 | } 46 | .background(.regularMaterial) 47 | .edgesIgnoringSafeArea(.bottom) 48 | } 49 | } 50 | 51 | struct PhotoModeFooter_Previews: PreviewProvider { 52 | static var previews: some View { 53 | PhotoModeFooter() 54 | .environment(ShutterAnimator()) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/PhotoMode/PhotoModeUnsupportedView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoModeUnsupportedView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 13/3/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PhotoModeUnsupportedView: View { 11 | 12 | @Environment(\.dismiss) var dismiss 13 | 14 | var body: some View { 15 | VStack(alignment: .leading, spacing: 2) { 16 | Text(NSLocalizedString(":(", comment: "")) 17 | .font(.largeTitle) 18 | .bold() 19 | .multilineTextAlignment(.center) 20 | Spacer() 21 | .frame(height: 24) 22 | Text(NSLocalizedString("Photo Mode is not supported on this device.", comment: "")) 23 | .font(.headline) 24 | .bold() 25 | .multilineTextAlignment(.center) 26 | Text(NSLocalizedString("Supported devices include iOS devices with A11 or later and Apple Silicon Macs.", comment: "")) 27 | .font(.body) 28 | .foregroundColor(.gray) 29 | Spacer() 30 | Button(action: { 31 | dismiss() 32 | }, label: { 33 | Text(NSLocalizedString("Close", comment: "")) 34 | .bold() 35 | .frame(maxWidth: .infinity) 36 | }) 37 | .buttonStyle(.borderedProminent) 38 | } 39 | .padding() 40 | } 41 | } 42 | 43 | struct PhotoModeUnsupportedView_Previews: PreviewProvider { 44 | static var previews: some View { 45 | PhotoModeUnsupportedView() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/PhotoMode/PhotoModeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoModeView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 19/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PhotoModeView: View { 11 | 12 | @Environment(\.dismiss) var dismiss 13 | @State var photoModeViewModel = PhotoModeViewModel() 14 | 15 | var body: some View { 16 | NavigationView { 17 | ZStack { 18 | PhotoModeContent() 19 | .edgesIgnoringSafeArea(.bottom) 20 | .environment(photoModeViewModel.shutterAnimator) 21 | .environment(photoModeViewModel) 22 | 23 | VStack { 24 | Spacer() 25 | PhotoModeFooter() 26 | .environment(photoModeViewModel.shutterAnimator) 27 | .environment(photoModeViewModel) 28 | } 29 | } 30 | .navigationTitle(NSLocalizedString("Photo Mode", comment: "")) 31 | .navigationBarItems(leading: 32 | Button( 33 | action: { 34 | dismiss() 35 | }, 36 | label: { 37 | Text(NSLocalizedString("Cancel", comment: "")) 38 | } 39 | ) 40 | .disabled(photoModeViewModel.shutterAnimator.shutterAnimationRunning) 41 | ) 42 | } 43 | } 44 | } 45 | 46 | struct PhotoModeView_Previews: PreviewProvider { 47 | static var previews: some View { 48 | PhotoModeView() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/PhotoMode/PhotoModeViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PhotoModeViewModel.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 20/12/21. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | @Observable class PhotoModeViewModel { 12 | 13 | // MARK: - Config 14 | var photoConfig = PhotoModeConfig() 15 | var shutterAnimator = ShutterAnimator() 16 | 17 | // MARK: - Pickers 18 | var finalTextureSizeOption: Int = PhotoModeTextureOptions.high { 19 | didSet { 20 | switch finalTextureSizeOption { 21 | case PhotoModeTextureOptions.normal: 22 | photoConfig.finalTextureSize = 1024 23 | case PhotoModeTextureOptions.high: 24 | photoConfig.finalTextureSize = 2048 25 | case PhotoModeTextureOptions.highest: 26 | photoConfig.finalTextureSize = 4096 27 | default: 28 | photoConfig.finalTextureSize = 2048 29 | } 30 | } 31 | } 32 | 33 | var shadowResolution: Int = PhotoModeShadowOptions.high { 34 | didSet { 35 | switch finalTextureSizeOption { 36 | case PhotoModeTextureOptions.normal: 37 | photoConfig.shadowTextureSize = 2048 38 | case PhotoModeTextureOptions.high: 39 | photoConfig.shadowTextureSize = 4096 40 | case PhotoModeTextureOptions.highest: 41 | photoConfig.shadowTextureSize = 8192 42 | default: 43 | photoConfig.shadowTextureSize = 4096 44 | } 45 | } 46 | } 47 | } 48 | 49 | struct PhotoModeTextureOptions { 50 | static let normal = 0 51 | static let high = 1 52 | static let highest = 2 53 | } 54 | 55 | struct PhotoModeShadowOptions { 56 | static let normal = 0 57 | static let high = 1 58 | static let highest = 2 59 | } 60 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/ProeteinSequenceView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProeteinSequenceView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 9/5/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ProteinSequenceView: View { 11 | var body: some View { 12 | ZStack { 13 | Color.red 14 | HStack { 15 | 16 | } 17 | } 18 | .frame(height: 36) 19 | .cornerRadius(8) 20 | .overlay( 21 | RoundedRectangle(cornerRadius: 8) 22 | .stroke(Color(UIColor.opaqueSeparator), lineWidth: 3) 23 | ) 24 | } 25 | } 26 | 27 | struct ProeteinSequenceView_Previews: PreviewProvider { 28 | static var previews: some View { 29 | ProteinSequenceView() 30 | .previewDevice("iPhone SE (2nd generation)") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/ProteinMetal/ProteinMetalView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProteinMetalView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 30/5/21. 6 | // 7 | 8 | import SwiftUI 9 | import Metal 10 | import MetalKit 11 | 12 | struct ProteinMetalView: UIViewControllerRepresentable { 13 | 14 | typealias UIViewControllerType = ProteinMetalViewController 15 | 16 | let proteinViewModel: ProteinViewModel 17 | let selectionModel: SelectionModel 18 | 19 | func makeUIViewController(context: Context) -> ProteinMetalViewController { 20 | return ProteinMetalViewController( 21 | proteinViewModel: self.proteinViewModel, 22 | selectionModel: self.selectionModel 23 | ) 24 | } 25 | 26 | func updateUIViewController(_ uiViewController: ProteinMetalViewController, context: Context) { 27 | // TO-DO? Updateable ViewController 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/ProteinSequenceView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProeteinSequenceView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 9/5/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ProteinSequenceView: View { 11 | var body: some View { 12 | ZStack { 13 | /* 14 | VisualEffectView(effect: UIBlurEffect(style: .systemThinMaterialDark)) 15 | ScrollView(.horizontal) { 16 | HStack { 17 | Text("MDSKGSSQKGSRLLLLLVVSNLLLCQGVVSTPVCPNGPGNCQVSLRDLFDRAVMVSHYIHDLSS") 18 | .foregroundColor(Color.white) 19 | .padding(.horizontal, 36+8) 20 | } 21 | } 22 | HStack { 23 | ZStack { 24 | VisualEffectView(effect: UIBlurEffect(style: .regular)) 25 | .frame(width: 36) 26 | Text("5'") 27 | .foregroundColor(.white) 28 | } 29 | Spacer() 30 | ZStack { 31 | VisualEffectView(effect: UIBlurEffect(style: .regular)) 32 | .frame(width: 36) 33 | Text("3'") 34 | .foregroundColor(.white) 35 | } 36 | } 37 | */ 38 | } 39 | .frame(height: 36) 40 | .cornerRadius(8) 41 | .overlay( 42 | RoundedRectangle(cornerRadius: 8) 43 | .stroke(Color(UIColor.opaqueSeparator), lineWidth: 3) 44 | ) 45 | } 46 | } 47 | 48 | struct ProteinSequenceView_Previews: PreviewProvider { 49 | static var previews: some View { 50 | ProteinSequenceView() 51 | .previewDevice("iPhone SE (2nd generation)") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/FileAtomElementPopover/CompositionItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositionItem.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 22/1/23. 6 | // 7 | 8 | import Charts 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct CompositionItem: Hashable, Identifiable { 13 | let id = UUID() 14 | let name: String 15 | let color: Color 16 | let count: Int 17 | let fraction: Double 18 | } 19 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/FileAtomElementPopover/FileCompositionChartView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileCompositionChartView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 2/7/23. 6 | // 7 | 8 | import Charts 9 | import SwiftUI 10 | 11 | struct FileCompositionChartView: View { 12 | 13 | let segments: [CompositionItem] 14 | @Binding var selectedSegmentID: CompositionItem.ID? 15 | 16 | @State var selectedValue: Int? 17 | 18 | var body: some View { 19 | Chart(segments, id: \.id) { segment in 20 | SectorMark( 21 | angle: .value("Fraction", segment.count), 22 | innerRadius: .ratio(0.618), 23 | angularInset: 1.5 24 | ) 25 | .cornerRadius(4) 26 | .foregroundStyle(segment.color) 27 | .opacity(segmentOpacity(segment: segment)) 28 | } 29 | .chartAngleSelection(value: $selectedValue) 30 | .onChange(of: selectedValue) { _, newValue in 31 | if let newValue { 32 | withAnimation { 33 | setSelectedSegment(from: newValue) 34 | } 35 | } 36 | } 37 | } 38 | 39 | @MainActor private func setSelectedSegment(from value: Int) { 40 | var currentCount: Int = 0 41 | for segment in segments { 42 | let startValue = currentCount 43 | let endValue = currentCount + segment.count 44 | if startValue <= value && endValue >= value { 45 | let hapticFeedback = UIImpactFeedbackGenerator(style: .light) 46 | hapticFeedback.prepare() 47 | hapticFeedback.impactOccurred() 48 | selectedSegmentID = segment.id 49 | return 50 | } 51 | currentCount += segment.count 52 | } 53 | selectedSegmentID = nil 54 | } 55 | 56 | private func segmentOpacity(segment: CompositionItem) -> Double { 57 | guard let selectedSegmentID else { return 1.0 } 58 | if segment.id == selectedSegmentID { 59 | return 1.0 60 | } else { 61 | return 0.3 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/FileSource/FileSourceRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileSourceRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 14/11/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FileSourceRow: View { 11 | 12 | var lineNumber: Int 13 | var line: String 14 | var hasWarning: Bool 15 | 16 | var body: some View { 17 | ZStack { 18 | if hasWarning { 19 | Color.yellow 20 | .opacity(0.3) 21 | } 22 | HStack { 23 | Text(String(lineNumber)) 24 | .frame(width: 36, alignment: .trailing) 25 | .font(.system(size: 9.5, design: .monospaced)) 26 | .foregroundColor(.white) 27 | Rectangle() 28 | .padding(.vertical, 4) 29 | .frame(width: 1) 30 | .foregroundColor(Color(UIColor.opaqueSeparator)) 31 | .opacity(0.5) 32 | Text(line) 33 | .font(.system(size: 9.5, design: .monospaced)) 34 | .foregroundColor(.white) 35 | Spacer() 36 | } 37 | .padding(.trailing, 12) 38 | } 39 | .frame(maxWidth: .infinity) 40 | .listRowBackground(Color.black) 41 | .listRowInsets(EdgeInsets()) 42 | } 43 | } 44 | 45 | struct FileSourceRow_Previews: PreviewProvider { 46 | static var previews: some View { 47 | List { 48 | FileSourceRow(lineNumber: 3352, 49 | line: "ATOM 39812 OP2 C 01930 94.465 121.850 130.597 1.00113.26 O", 50 | hasWarning: true) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/FileSource/FileSourceViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileSourceViewModel.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 14/11/21. 6 | // 7 | 8 | import BioViewerFoundation 9 | import Foundation 10 | 11 | @MainActor @Observable class FileSourceViewModel { 12 | 13 | /// Batch size for line loading 14 | let batchSize: Int = 200 15 | /// Distance to the end of the batch to prefetch next batch 16 | let prefetchDistance: Int = 50 17 | /// Number of batches already loaded 18 | var batchCount: Int = 0 19 | 20 | /// All the lines in the source 21 | var fileInfo: ProteinFileInfo? 22 | /// Lines currently loaded in the view 23 | var loadedLines: [String]? 24 | 25 | // MARK: - Initialization 26 | init(fileInfo: ProteinFileInfo?) { 27 | self.fileInfo = fileInfo 28 | self.loadedLines = [] 29 | 30 | guard let fileInfo = fileInfo else { 31 | return 32 | } 33 | guard let sourceLines = fileInfo.sourceLines else { 34 | return 35 | } 36 | 37 | loadedLines?.append(contentsOf: sourceLines[0.. Bool { 44 | return index == batchCount * batchSize - 1 - prefetchDistance 45 | } 46 | 47 | func loadMore() { 48 | guard let fileInfo = fileInfo else { 49 | return 50 | } 51 | guard let sourceLines = fileInfo.sourceLines else { 52 | return 53 | } 54 | let startRange = batchCount * batchSize 55 | let endRange = startRange + batchSize 56 | // FIXME: Sometimes this crashes with Index out of range 57 | loadedLines?.append(contentsOf: sourceLines[startRange.. Bool { 62 | guard let fileInfo = fileInfo else { 63 | return false 64 | } 65 | if fileInfo.warningIndices.contains(index + 1) { 66 | return true 67 | } 68 | return false 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/InfoSegmentedCapsule.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoAtomsCapsule.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 12/3/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct InfoCapsuleSegment: Hashable { 11 | let id = UUID() 12 | let fraction: Double 13 | let color: Color 14 | } 15 | 16 | struct InfoSegmentedCapsule: View { 17 | 18 | let segments: [InfoCapsuleSegment] 19 | 20 | var body: some View { 21 | GeometryReader { geometry in 22 | HStack(spacing: .zero) { 23 | ForEach(segments, id: \.self) { segment in 24 | segment.color 25 | .frame(width: segment.fraction * geometry.size.width) 26 | } 27 | } 28 | } 29 | .frame(height: 6) 30 | .mask(Capsule() 31 | .frame(height: 6) 32 | ) 33 | } 34 | } 35 | 36 | /* 37 | struct InfoAtomsCapsule_Previews: PreviewProvider { 38 | static var previews: some View { 39 | InfoAtomsCapsule() 40 | .environmentObject(ProteinViewModel()) 41 | } 42 | } 43 | */ 44 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/WorkspaceHelp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkspaceHelp.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 6/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct WorkspaceHelp: View { 11 | 12 | @Environment(\.dismiss) var dismiss 13 | 14 | var body: some View { 15 | NavigationView { 16 | VStack { 17 | Text(NSLocalizedString("What are BioViewer workspaces?", comment: "")) 18 | .font(.title) 19 | .bold() 20 | .frame(maxWidth: .infinity, alignment: .leading) 21 | Text(".bioviewer") 22 | .font(.largeTitle) 23 | .bold() 24 | .foregroundColor(.accentColor) 25 | .padding(12) 26 | Text(NSLocalizedString("bioViewer_workspace_description", comment: "")) 27 | .frame(maxWidth: .infinity, alignment: .leading) 28 | Spacer() 29 | } 30 | .padding(.horizontal) 31 | .navigationTitle(NSLocalizedString("Help", comment: "")) 32 | .navigationBarTitleDisplayMode(.inline) 33 | .navigationBarItems(leading: Button(NSLocalizedString("Close", comment: "")) { 34 | dismiss() 35 | }) 36 | } 37 | } 38 | } 39 | 40 | struct WorkspaceHelp_Previews: PreviewProvider { 41 | static var previews: some View { 42 | WorkspaceHelp() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Sidebar/Segments/FileSegmentProtein/WorkspaceRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WorkspaceRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 6/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct WorkspaceRow: View { 11 | 12 | @State var showWorkspaceHelp: Bool = false 13 | @EnvironmentObject var proteinViewModel: ProteinViewModel 14 | 15 | private enum Constants { 16 | #if targetEnvironment(macCatalyst) 17 | static let iconSize: CGFloat = 16 18 | #else 19 | static let iconSize: CGFloat = 24 20 | #endif 21 | } 22 | 23 | var body: some View { 24 | HStack { 25 | 26 | Button(action: { 27 | WorkspaceExporter.createWorkspace(proteinViewModel: proteinViewModel) 28 | }, label: { 29 | Image(systemName: "square.and.arrow.down") 30 | .resizable() 31 | .aspectRatio(contentMode: .fit) 32 | .frame(width: Constants.iconSize, height: Constants.iconSize) 33 | }) 34 | .foregroundColor(.accentColor) 35 | .buttonStyle(PlainButtonStyle()) 36 | 37 | Text(NSLocalizedString("Save as BioViewer workspace...", comment: "")) 38 | 39 | Spacer() 40 | 41 | Button(action: { 42 | showWorkspaceHelp.toggle() 43 | }, label: { 44 | Image(systemName: "questionmark.circle") 45 | }) 46 | .foregroundColor(.accentColor) 47 | .buttonStyle(PlainButtonStyle()) 48 | .sheet(isPresented: $showWorkspaceHelp) { 49 | WorkspaceHelp() 50 | } 51 | } 52 | .padding(.vertical, 8) 53 | } 54 | } 55 | 56 | // MARK: - Previews 57 | struct WorkspaceRow_Previews: PreviewProvider { 58 | static var previews: some View { 59 | List { 60 | WorkspaceRow() 61 | } 62 | .frame(width: 300) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Sidebar/Segments/FunctionsSegmentProtein.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FunctionsSegmentProtein.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 3/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct FunctionsSegmentProtein: View { 11 | 12 | @EnvironmentObject var proteinDataSource: ProteinDataSource 13 | 14 | var body: some View { 15 | List { 16 | // First section hast 64pt padding to account for the 17 | // space under the segmented control. 18 | Section(header: Text(NSLocalizedString("Protein properties", comment: "")) 19 | .padding(.top, 52) 20 | .padding(.bottom, 4), 21 | content: { 22 | ComputedPropertyRow(propertyName: "volume", 23 | units: "Å^{3}", 24 | value: .constant(8842.4), 25 | errorInterval: .constant(26.1)) 26 | }) 27 | .disabled(proteinDataSource.proteinCount == 0) 28 | 29 | } 30 | .environment(\.defaultMinListHeaderHeight, 0) 31 | .listStyle(GroupedListStyle()) 32 | } 33 | } 34 | 35 | struct FunctionsSegmentProtein_Previews: PreviewProvider { 36 | static var previews: some View { 37 | FunctionsSegmentProtein() 38 | .environmentObject(ProteinViewModel()) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Sidebar/Segments/SettingsSegmentProtein.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsSegmentProtein.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 23/11/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SettingsSegmentProtein: View { 11 | 12 | @EnvironmentObject var proteinViewModel: ProteinViewModel 13 | 14 | var body: some View { 15 | List { 16 | // First section hast 64pt padding to account for the 17 | // space under the segmented control. 18 | Section(header: Text(NSLocalizedString("Settings", comment: "")) 19 | .padding(.top, 52) 20 | .padding(.bottom, 4), 21 | content: { 22 | // TO-DO: 23 | SwitchRow(title: "Show FPS", toggledVariable: .constant(false)) 24 | SwitchRow(title: "Average framerate", toggledVariable: .constant(false)) 25 | SwitchRow(title: "Prefer RCSB file info", toggledVariable: .constant(false)) 26 | }) 27 | } 28 | .environment(\.defaultMinListHeaderHeight, 0) 29 | .listStyle(GroupedListStyle()) 30 | } 31 | } 32 | 33 | struct SettingsSegmentProtein_Previews: PreviewProvider { 34 | static var previews: some View { 35 | SettingsSegmentProtein() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Sidebar/Segments/TrajectorySegmentProtein.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrajectorySegmentProtein.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 18/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct TrajectorySegmentProtein: View { 11 | 12 | var body: some View { 13 | List { 14 | // First section hast 64pt padding to account for the 15 | // space under the segmented control. 16 | Section(header: Text(NSLocalizedString("Configurations", comment: "")) 17 | .padding(.top, 52) 18 | .padding(.bottom, 4), 19 | content: { 20 | SliderRow(title: NSLocalizedString("Frames per configuration", comment: ""), 21 | value: .constant(23), 22 | minValue: 1, 23 | maxValue: 100) 24 | // InputWithButtonRow() 25 | }) 26 | } 27 | .environment(\.defaultMinListHeaderHeight, 0) 28 | .listStyle(GroupedListStyle()) 29 | } 30 | } 31 | 32 | struct TrajectorySegmentProtein_Previews: PreviewProvider { 33 | static var previews: some View { 34 | TrajectorySegmentProtein() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Top toolbar/CameraControlToolbar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CameraControlToolbar.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 19/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CameraControlToolbar: View { 11 | 12 | @Environment(ToolbarConfig.self) var config: ToolbarConfig 13 | 14 | var body: some View { 15 | @Bindable var config = config 16 | HStack { 17 | Picker("Rotation mode", selection: $config.selectedTool) { 18 | Image(systemName: "rotate.3d") 19 | .resizable() 20 | .aspectRatio(contentMode: .fit) 21 | .frame(width: TopToolbar.Constants.buttonSize, height: TopToolbar.Constants.buttonSize) 22 | .foregroundColor(.accentColor).tag(0) 23 | Image(systemName: "move.3d") 24 | .resizable() 25 | .aspectRatio(contentMode: .fit) 26 | .frame(width: TopToolbar.Constants.buttonSize, height: TopToolbar.Constants.buttonSize) 27 | .foregroundColor(.accentColor).tag(1) 28 | } 29 | .pickerStyle(SegmentedPickerStyle()) 30 | .foregroundColor(.accentColor) 31 | .frame(width: 4 * TopToolbar.Constants.buttonSize) 32 | .disabled(config.autorotating) 33 | .contextMenu { 34 | Button(role: .destructive) { 35 | config.resetCamera() 36 | } label: { 37 | Label("Reset camera", systemImage: "arrow.uturn.backward") 38 | } 39 | } 40 | Button( 41 | action: { 42 | config.autorotating.toggle() 43 | }, label: { 44 | Image(systemName: config.autorotating 45 | ? "arrow.triangle.2.circlepath.circle.fill" 46 | : "arrow.triangle.2.circlepath.circle" 47 | ) 48 | .foregroundColor(.accentColor) 49 | } 50 | ) 51 | } 52 | } 53 | } 54 | 55 | struct CameraControlToolbar_Previews: PreviewProvider { 56 | static var previews: some View { 57 | CameraControlToolbar() 58 | .environment(ToolbarConfig()) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /BioViewer/Views/ProteinViews/Top toolbar/ToolbarConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToolbarConfig.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 19/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | enum CameraControlTool { 11 | static let rotate: Int = 0 12 | static let move: Int = 1 13 | } 14 | 15 | @MainActor @Observable class ToolbarConfig { 16 | 17 | weak var proteinViewModel: ProteinViewModel? 18 | 19 | // MARK: - Properties 20 | 21 | var selectedTool: Int = CameraControlTool.rotate 22 | 23 | var autorotating: Bool = false { 24 | didSet { 25 | Task { 26 | await proteinViewModel?.renderer.mutableState.setAutorotating(autorotating) 27 | } 28 | } 29 | } 30 | 31 | // MARK: - Actions 32 | 33 | func resetCamera() { 34 | Task { 35 | await proteinViewModel?.renderer.mutableState.resetCamera() 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /BioViewer/Views/SequenceViews/Rows/SequenceRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SequenceRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 8/5/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct SequenceRow: View { 11 | 12 | @State var sequence: [Character] = Array("MDSKGSSQKGSRLLLLLVVSNLLLCQGVVSTPVCPNGPGNCQVSLRDLFDRAVMVSHYIHDLSSEMFNEFDKRYAQGKGFITMALNSCHTSSLPTPEDKEQAQQTHHEVLMSLIL") 13 | 14 | var body: some View { 15 | LazyHStack(alignment: .top, spacing: 8) { 16 | ForEach(0.. Void) 13 | 14 | struct Constants { 15 | #if targetEnvironment(macCatalyst) 16 | static let buttonSize: CGFloat = 18 17 | #else 18 | static let buttonSize: CGFloat = 24 19 | #endif 20 | } 21 | 22 | var body: some View { 23 | Button( 24 | action: { 25 | closeAction() 26 | }, 27 | label: { 28 | ZStack { 29 | Circle() 30 | .fill(.white) 31 | .frame( 32 | width: Constants.buttonSize / 1.5, 33 | height: Constants.buttonSize / 1.5 34 | ) 35 | Image(systemName: "xmark.circle.fill") 36 | .resizable() 37 | .foregroundStyle(.red) 38 | } 39 | .frame(width: Constants.buttonSize, height: Constants.buttonSize) 40 | } 41 | ) 42 | #if targetEnvironment(macCatalyst) 43 | .buttonStyle(.plain) 44 | #endif 45 | .offset(x: -Constants.buttonSize / 2.5, y: -Constants.buttonSize / 2.5) 46 | } 47 | } 48 | 49 | #Preview { 50 | ZStack { 51 | Color.black 52 | BVDismissActionButton(closeAction: {}) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /BioViewer/Views/UI Elements/BioViewerProgress/BVProgressComponent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BVProgressComponent.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 11/11/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct BVProgressComponent: View { 11 | 12 | let title: String 13 | let progress: Double? 14 | let error: Error? 15 | let closeAction: (() -> Void)? 16 | 17 | var body: some View { 18 | ZStack(alignment: .topLeading) { 19 | VStack(spacing: .zero) { 20 | 21 | Text(title) 22 | .font(.caption) 23 | .bold() 24 | .padding(.top, 8) 25 | 26 | if let error { 27 | BVProgressFailedView() 28 | .frame(width: 96, height: 96) 29 | Text(error.localizedDescription) 30 | .frame(width: 96) 31 | .font(.caption) 32 | } else { 33 | BVProgressView(size: 96) 34 | .frame(width: 96, height: 96) 35 | if let progress { 36 | ProgressView(value: progress) 37 | #if targetEnvironment(macCatalyst) 38 | .padding(.horizontal, 12) 39 | #else 40 | .padding(.horizontal, 12) 41 | .padding(.bottom, 12) 42 | #endif 43 | } 44 | } 45 | } 46 | .padding(.bottom, 8) 47 | .frame(width: 128) 48 | .background(.thinMaterial) 49 | .clipShape(RoundedRectangle(cornerRadius: 12)) 50 | 51 | if let closeAction { 52 | BVDismissActionButton(closeAction: closeAction) 53 | } 54 | } 55 | } 56 | } 57 | 58 | #Preview { 59 | ZStack { 60 | Color.black 61 | BVProgressComponent(title: "Title", progress: 0.5, error: nil, closeAction: nil) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /BioViewer/Views/UI Elements/BioViewerProgress/BVProgressFailedView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BVProgressFailedView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 11/11/23. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct BVProgressFailedView: View { 11 | var body: some View { 12 | Image(systemName: "exclamationmark.octagon") 13 | .resizable() 14 | .padding(16) 15 | .aspectRatio(contentMode: .fit) 16 | } 17 | } 18 | 19 | #Preview { 20 | BVProgressFailedView() 21 | } 22 | -------------------------------------------------------------------------------- /BioViewer/Views/UI Elements/CoffeeViews/CoffeeRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoffeeRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 24/5/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CoffeeRow: View { 11 | var body: some View { 12 | HStack { 13 | Spacer() 14 | VStack { 15 | Image("PlaceholderCoffee") 16 | .resizable() 17 | .aspectRatio(contentMode: .fit) 18 | .frame(width: 100, height: 100, alignment: .center) 19 | Text("Bought on 12/07/2021") 20 | .frame(width: 100, height: 32) 21 | .font(.caption) 22 | .foregroundColor(Color(UIColor.secondaryLabel)) 23 | } 24 | Spacer() 25 | VStack { 26 | Image("PlaceholderCoffee") 27 | .resizable() 28 | .aspectRatio(contentMode: .fit) 29 | .frame(width: 100, height: 100, alignment: .center) 30 | Text("Bought on 12/07/2021") 31 | .frame(width: 100, height: 32) 32 | .font(.caption) 33 | .foregroundColor(Color(UIColor.secondaryLabel)) 34 | } 35 | Spacer() 36 | VStack { 37 | Image("PlaceholderCoffee") 38 | .resizable() 39 | .aspectRatio(contentMode: .fit) 40 | .frame(width: 100, height: 100, alignment: .center) 41 | Text("Bought on 12/07/2021") 42 | .frame(width: 100, height: 32) 43 | .font(.caption) 44 | .foregroundColor(Color(UIColor.secondaryLabel)) 45 | } 46 | Spacer() 47 | } 48 | .padding(8) 49 | } 50 | } 51 | 52 | struct CoffeeRow_Previews: PreviewProvider { 53 | static var previews: some View { 54 | CoffeeRow() 55 | .previewLayout(.sizeThatFits) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /BioViewer/Views/UI Elements/CoffeeViews/CoffeeTipRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TipRowView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 23/5/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CoffeeTipRow: View { 11 | 12 | var text: String 13 | var price: String 14 | 15 | var body: some View { 16 | HStack { 17 | Text(text) 18 | Spacer() 19 | Button(price, action: { 20 | // TO-DO: Handle in-app purchase 21 | }) 22 | .foregroundColor(.accentColor) 23 | // PlainButtonStyle() makes the list row not selectable, 24 | // as we want. 25 | .buttonStyle(PlainButtonStyle()) 26 | } 27 | } 28 | } 29 | 30 | struct TipRowView_Previews: PreviewProvider { 31 | static var previews: some View { 32 | CoffeeTipRow(text: "Small tip", price: "$0.99") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /BioViewer/Views/UI Elements/ColorPalettePopoverRow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorPalettePopoverRow.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 6/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ColorPalettePopoverRow: View { 11 | 12 | @Binding var selectedOption: Int 13 | let colorPalette: ColorPalette 14 | 15 | let paletteName: String 16 | let optionIndex: Int 17 | 18 | private enum Constants { 19 | #if targetEnvironment(macCatalyst) 20 | static let radioButtonSize: CGFloat = 16 21 | #else 22 | static let radioButtonSize: CGFloat = 20 23 | #endif 24 | } 25 | 26 | var body: some View { 27 | Button(action: { 28 | selectedOption = optionIndex 29 | }, label: { 30 | HStack { 31 | Image(systemName: selectedOption == optionIndex ? "checkmark.circle" : "circle") 32 | .padding(4) 33 | .font(Font.system(size: Constants.radioButtonSize, weight: .medium)) 34 | .foregroundColor(.accentColor) 35 | Text(paletteName) 36 | .foregroundColor(.accentColor) 37 | Spacer() 38 | ColorPaletteView(colorPalette: colorPalette) 39 | } 40 | .contentShape(Rectangle()) 41 | }) 42 | .buttonStyle(PlainButtonStyle()) 43 | } 44 | } 45 | 46 | struct ColorPalettePopoverRow_Previews: PreviewProvider { 47 | static var previews: some View { 48 | ColorPalettePopoverRow(selectedOption: .constant(0), 49 | colorPalette: ColorPalette(.default), 50 | paletteName: "TestPalette", 51 | optionIndex: 0) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /BioViewer/Views/UI Elements/ColorPaletteView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ColorPaletteView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 6/12/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ColorPaletteView: View { 11 | 12 | let colorPalette: ColorPalette 13 | 14 | var body: some View { 15 | VStack(spacing: 0) { 16 | VStack(spacing: 2) { 17 | HStack(spacing: 2) { 18 | colorPalette.color0 19 | colorPalette.color1 20 | colorPalette.color2 21 | } 22 | HStack(spacing: 2) { 23 | colorPalette.color3 24 | colorPalette.color4 25 | colorPalette.color5 26 | } 27 | } 28 | .padding(4) 29 | } 30 | .frame(width: 88, height: 36) 31 | .mask(RoundedRectangle(cornerRadius: 4) 32 | .padding(4)) 33 | .background(RoundedRectangle(cornerRadius: 8) 34 | .stroke(Color(uiColor: .separator), 35 | style: StrokeStyle(lineWidth: 1)) 36 | .background(RoundedRectangle(cornerRadius: 8) 37 | .fill(Color(uiColor: .tertiarySystemFill)))) 38 | } 39 | } 40 | 41 | struct ColorPaletteView_Previews: PreviewProvider { 42 | static var previews: some View { 43 | ColorPaletteView(colorPalette: ColorPalette(.default)) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /BioViewer/Views/UI Elements/CustomLinearProgressView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomLinearProgressView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 30/10/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CustomLinearProgressView: View { 11 | 12 | var value: Float? 13 | var total: Float 14 | 15 | var body: some View { 16 | if let value = value { 17 | GeometryReader { geometry in 18 | ZStack { 19 | Color(uiColor: UIColor.secondarySystemBackground) 20 | HStack { 21 | Color.accentColor 22 | .frame(width: geometry.size.width * CGFloat(value) / CGFloat(total)) 23 | Spacer() 24 | } 25 | } 26 | } 27 | .ignoresSafeArea() 28 | .frame(height: 2) 29 | } else { 30 | EmptyView() 31 | } 32 | } 33 | } 34 | 35 | struct MacLinearProgressView_Previews: PreviewProvider { 36 | static var previews: some View { 37 | CustomLinearProgressView(value: 0.2, total: 1.0) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /BioViewer/Views/UI Elements/Debug Views/FPSCounterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FPSCounterView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 15/5/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @MainActor @Observable class FPSCounterViewModel { 11 | 12 | let renderer: ProteinRenderer 13 | var averageFPSString = "-" 14 | 15 | private var displayLink: CADisplayLink? 16 | private var frameTimeArray = [CFTimeInterval]() 17 | private var lastIndex: Int = 0 18 | private var currentIndex: Int = 0 19 | private let maxSavedFrames: Int = 100 20 | 21 | init(renderer: ProteinRenderer) { 22 | self.renderer = renderer 23 | self.displayLink = CADisplayLink( 24 | target: self, 25 | selector: #selector(self.updateFrameTime) 26 | ) 27 | self.displayLink?.add(to: .main, forMode: .default) 28 | } 29 | 30 | @objc private func updateFrameTime() { 31 | 32 | // Retrieve last GPU frame time. 33 | let newFrameTime = renderer.lastFrameGPUTime 34 | 35 | // Avoid saving the same frame time several times if the renderer 36 | // is paused. 37 | if frameTimeArray.count < maxSavedFrames { 38 | guard newFrameTime != frameTimeArray.last else { return } 39 | frameTimeArray.append(newFrameTime) 40 | } else { 41 | guard newFrameTime != frameTimeArray[lastIndex] else { return } 42 | frameTimeArray[currentIndex] = newFrameTime 43 | } 44 | lastIndex = currentIndex 45 | currentIndex = (currentIndex + 1) % maxSavedFrames 46 | 47 | // Compute mean of the saved values 48 | let averageFrameTime = frameTimeArray.reduce(0, +) / Double(frameTimeArray.count) 49 | let variance = frameTimeArray.reduce(0, { 50 | $0 + ($1 - averageFrameTime) * ($1 - averageFrameTime) 51 | }) 52 | averageFPSString = String(format: "FPS: %.0f ± %.0f", 53 | 1 / averageFrameTime, 54 | 1 / sqrt(averageFrameTime - variance)) 55 | } 56 | } 57 | 58 | struct FPSCounterView: View { 59 | 60 | @State var viewModel: FPSCounterViewModel 61 | 62 | var body: some View { 63 | Text(viewModel.averageFPSString) 64 | .foregroundColor(.white) 65 | .background(.black) 66 | .monospacedDigit() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /BioViewer/Views/UI Elements/Debug Views/ResolutionView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResolutionView.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 22/5/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @MainActor @Observable class ResolutionViewModel { 11 | 12 | let renderer: ProteinRenderer 13 | var resolution: CGSize? 14 | 15 | private var displayLink: CADisplayLink? 16 | 17 | init(renderer: ProteinRenderer) { 18 | self.renderer = renderer 19 | self.displayLink = CADisplayLink( 20 | target: self, 21 | selector: #selector(self.updateFrameTime) 22 | ) 23 | self.displayLink?.add(to: .main, forMode: .default) 24 | } 25 | 26 | @objc private func updateFrameTime() { 27 | // Retrieve last GPU frame time. 28 | if renderer.isBenchmark { 29 | let benchmarkResolution = BenchmarkTextures.benchmarkResolution 30 | resolution = CGSize(width: benchmarkResolution, height: benchmarkResolution) 31 | } else { 32 | resolution = renderer.viewResolution 33 | } 34 | } 35 | } 36 | 37 | @MainActor struct ResolutionView: View { 38 | 39 | @State var viewModel: ResolutionViewModel 40 | 41 | var resolutionString: String { 42 | guard let resolution = viewModel.resolution else { 43 | return "-" 44 | } 45 | return "\(resolution.width)x\(resolution.height)" 46 | } 47 | 48 | var body: some View { 49 | Text(resolutionString) 50 | .foregroundColor(.white) 51 | .background(.black) 52 | .font(.footnote) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /BioViewer/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | /* 2 | Localizable.strings 3 | BioViewer 4 | 5 | Created by Raúl Montón Pinillos on 1/11/21. 6 | 7 | */ 8 | 9 | "chaperone_description" = "Chaperones are proteins that assist protein folding. Some of them create pockets where newly synthesized proteins can fold without being affected by the hydrophilic interactions of aggregating to other proteins in the cytoplasm."; 10 | 11 | "mhc_description" = "Major Histocompatibility Complex (MHC) proteins are a group of surface proteins essential for the adaptive immune system. Their main function is to present antigens (either self or foreign) to the cells involved in the adaptive immune response."; 12 | 13 | "apoptosome_description" = "Apoptosomes are one of the protein groups that mediate programmed cell death (apoptosis)."; 14 | 15 | "bioViewer_workspace_description" = "BioViewer workspace files are a type of container file that stores all the files you have on the view (PDBs, CIFs...) plus all the configuration options you have selected (color schemes, visualization type...), and any additional info that was added (description, authors...). 16 | 17 | BioViewer workspace files also contain a thumbnail image of your structures, so you can identify the file at a glance without opening it."; 18 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/.swiftpm/xcode/xcuserdata/andro.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | BioViewerFoundation-Package.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | BioViewerFoundation.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 4 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/.swiftpm/xcode/xcuserdata/raul.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | BioViewerFoundation-Package.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 2 11 | 12 | BioViewerFoundation.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 2 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "BioViewerFoundation", 8 | platforms: [.iOS(.v17), .macOS(.v14)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, making them visible to other packages. 11 | .library( 12 | name: "BioViewerFoundation", 13 | targets: ["BioViewerFoundation"] 14 | ) 15 | ], 16 | targets: [ 17 | // Targets are the basic building blocks of a package, defining a module or a test suite. 18 | // Targets can depend on other targets in this package and products from dependencies. 19 | .target( 20 | name: "BioViewerFoundation"), 21 | .testTarget( 22 | name: "BioViewerFoundationTests", 23 | dependencies: ["BioViewerFoundation"] 24 | ) 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Models/BoundingVolumes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BoundingVolumes.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 7/12/21. 6 | // 7 | 8 | import Foundation 9 | import simd 10 | 11 | public struct BoundingSphere: Sendable { 12 | public let center: simd_float3 13 | public let radius: Float 14 | } 15 | 16 | public struct BoundingBox: Sendable { 17 | public let minX: Float 18 | public let maxX: Float 19 | public let minY: Float 20 | public let maxY: Float 21 | public let minZ: Float 22 | public let maxZ: Float 23 | } 24 | 25 | public struct BoundingVolume: Sendable { 26 | public let sphere: BoundingSphere 27 | public let box: BoundingBox 28 | 29 | public static var zero: Self { 30 | return BoundingVolume( 31 | sphere: BoundingSphere(center: .zero, radius: .zero), 32 | box: BoundingBox(minX: .zero, maxX: .zero, minY: .zero, maxY: .zero, minZ: .zero, maxZ: .zero) 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Models/ProteinComposition/ProteinChainComposition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProteinChainComposition.swift 3 | // 4 | // 5 | // Created by Raúl Montón Pinillos on 23/2/24. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct ProteinChainComposition: Sendable { 11 | 12 | public var uniqueChainIDs = [ChainID]() 13 | /// Dictionary containing the number of atoms of each chain. 14 | public var chainIDCounts = [ChainID: Int]() 15 | /// The total count of atoms of all chains. 16 | public var totalCount: Int = 0 17 | 18 | public static func += (lhs: inout ProteinChainComposition, rhs: ProteinChainComposition) { 19 | lhs.chainIDCounts.merge(rhs.chainIDCounts, uniquingKeysWith: { lhsCount, rhsCount in 20 | return lhsCount + rhsCount 21 | }) 22 | lhs.uniqueChainIDs = Array(lhs.chainIDCounts.keys) 23 | lhs.totalCount += rhs.totalCount 24 | } 25 | 26 | // MARK: - Init 27 | 28 | public init() {} 29 | 30 | public init?(chainIDs: [ChainID]?) { 31 | guard let chainIDs else { return nil } 32 | self = .init(chainIDs: chainIDs) 33 | } 34 | 35 | public init(chainIDs: [ChainID]) { 36 | for chainID in chainIDs { 37 | if let currentCount = chainIDCounts[chainID] { 38 | chainIDCounts[chainID] = currentCount + 1 39 | } else { 40 | chainIDCounts[chainID] = 1 41 | uniqueChainIDs.append(chainID) 42 | } 43 | } 44 | uniqueChainIDs = uniqueChainIDs.sorted(by: { $0.displayName < $1.displayName }) 45 | for atomsInChainID in chainIDCounts.values { 46 | totalCount += atomsInChainID 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Models/ProteinComposition/ProteinElementComposition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProteinElementComposition.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 25/5/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct ProteinElementComposition: Sendable { 11 | 12 | /// Dictionary containing the number of atoms of each type of element. 13 | public var elementCounts = [AtomElement: Int]() 14 | /// The total count of atoms of all types. 15 | public var totalCount: Int = 0 16 | /// The total count of atoms of a type present in `AtomElement.importantElements`. 17 | public var importantElementCount: Int { 18 | var sum: Int = 0 19 | for element in AtomElement.importantElements { 20 | sum += elementCounts[element] ?? 0 21 | } 22 | return sum 23 | } 24 | 25 | public static func += (lhs: inout ProteinElementComposition, rhs: ProteinElementComposition) { 26 | lhs.elementCounts.merge(rhs.elementCounts, uniquingKeysWith: { lhsCount, rhsCount in 27 | return lhsCount + rhsCount 28 | }) 29 | lhs.totalCount += rhs.totalCount 30 | } 31 | 32 | // MARK: - Init 33 | 34 | public init() {} 35 | 36 | public init(elements: [AtomElement]) { 37 | for element in elements { 38 | if let currentCount = elementCounts[element] { 39 | elementCounts[element] = currentCount + 1 40 | } else { 41 | elementCounts[element] = 1 42 | } 43 | } 44 | for elementCount in elementCounts.values { 45 | totalCount += elementCount 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Models/ProteinComposition/ProteinResidueComposition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProteinResidueComposition.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 20/1/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct ProteinResidueComposition: Sendable { 11 | 12 | /// Dictionary containing the number of atoms of each type of residue. 13 | public var residueCounts = [Residue: Int]() 14 | /// The total count of atoms of all types. 15 | public var totalCount: Int = 0 16 | 17 | public static func += (lhs: inout ProteinResidueComposition, rhs: ProteinResidueComposition) { 18 | lhs.residueCounts.merge(rhs.residueCounts, uniquingKeysWith: { lhsCount, rhsCount in 19 | return lhsCount + rhsCount 20 | }) 21 | lhs.totalCount += rhs.totalCount 22 | } 23 | 24 | // MARK: - Init 25 | 26 | public init() {} 27 | 28 | public init?(residues: [Residue]?) { 29 | guard let residues else { return nil } 30 | self.init(residues: residues) 31 | } 32 | 33 | public init(residues: [Residue]) { 34 | for residue in residues { 35 | if let currentCount = residueCounts[residue] { 36 | residueCounts[residue] = currentCount + 1 37 | } else { 38 | residueCounts[residue] = 1 39 | } 40 | } 41 | for residueCount in residueCounts.values { 42 | totalCount += residueCount 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Models/ProteinFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProteinFile.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 6/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | // MARK: - ProteinFileType 11 | 12 | public enum ProteinFileType: Sendable { 13 | case staticStructure 14 | case dynamicStructure 15 | } 16 | 17 | // MARK: - ProteinFile 18 | 19 | public struct ProteinFile: Hashable, Sendable { 20 | 21 | /// Unique ID for the file (only used internally). 22 | public let id = UUID() 23 | /// The type of protein file type (whether it contains a static structure or several configurations of the same protein). 24 | public let fileType: ProteinFileType 25 | /// Name of the protein file (without the file extension). 26 | public let fileName: String 27 | /// Extension of the protein file (.pdb, .cif...). 28 | public let fileExtension: String 29 | /// Name of the protein file (including file extension). 30 | public var fileNameWithExtension: String { 31 | return fileName + "." + fileExtension 32 | } 33 | /// Size of the stored file, in bytes. 34 | public let byteSize: Int? 35 | /// File metadata. 36 | public let fileInfo: ProteinFileInfo 37 | /// Protein contained in the file. 38 | public var models: [Protein] 39 | 40 | // MARK: - Init 41 | 42 | public init(fileType: ProteinFileType, fileName: String, fileExtension: String, models: [Protein], fileInfo: ProteinFileInfo, byteSize: Int?) { 43 | self.fileType = fileType 44 | self.fileName = fileName 45 | self.fileExtension = fileExtension 46 | self.models = models 47 | self.fileInfo = fileInfo 48 | self.byteSize = byteSize 49 | } 50 | 51 | // MARK: - Hashable 52 | 53 | public static func == (lhs: ProteinFile, rhs: ProteinFile) -> Bool { 54 | lhs.id == rhs.id 55 | } 56 | 57 | public func hash(into hasher: inout Hasher) { 58 | hasher.combine(id) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Models/ProteinFileInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProteinFileInfo.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 14/11/21. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Class containing the data related to the imported protein file itself. 11 | public struct ProteinFileInfo: Sendable { 12 | 13 | /// PDB ID as in RCSB database. 14 | public var pdbID: String? 15 | /// Human-readable description of the protein. 16 | public var description: String? 17 | /// Authors of the file. 18 | public var authors: String? 19 | /// Full source file text 20 | public var sourceLines: [String]? 21 | 22 | /// List of all lines with warnings 23 | public var warningIndices: [Int] = [] 24 | 25 | // MARK: - Initialization 26 | 27 | public init() {} 28 | 29 | public init(pdbID: String?, description: String?, authors: String?, sourceLines: [String]?) { 30 | self.pdbID = pdbID 31 | self.description = description 32 | self.authors = authors 33 | self.sourceLines = sourceLines 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Types/BondStruct.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BondStruct.swift 3 | // 4 | // 5 | // Created by Raúl Montón Pinillos on 12/11/23. 6 | // 7 | 8 | import Foundation 9 | import simd 10 | 11 | public struct BondStruct: Sendable { 12 | /// Position of the first atom in world space. 13 | public let atomA: simd_float3 14 | /// Position of the first atom in world space. 15 | public let atomB: simd_float3 16 | /// Cylinder center in world space. 17 | public let cylinderCenter: simd_float3 18 | /// Bond radius. 19 | public let bondRadius: Float 20 | 21 | public init(atomA: simd_float3, atomB: simd_float3, cylinderCenter: simd_float3, bondRadius: Float) { 22 | self.atomA = atomA 23 | self.atomB = atomB 24 | self.cylinderCenter = cylinderCenter 25 | self.bondRadius = bondRadius 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/Sources/BioViewerFoundation/Types/SecondaryStructure.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecondaryStructure.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 5/3/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public enum SecondaryStructure: UInt8, CaseIterable, Sendable { 12 | case helix 13 | case sheet 14 | case loop 15 | case nonChain 16 | 17 | public var name: String { 18 | switch self { 19 | case .helix: 20 | return NSLocalizedString("Helix", comment: "") 21 | case .sheet: 22 | return NSLocalizedString("Sheet", comment: "") 23 | case .loop: 24 | return NSLocalizedString("Loop", comment: "") 25 | case .nonChain: 26 | return NSLocalizedString("Non-chain", comment: "") 27 | } 28 | } 29 | 30 | public var defaultColor: Color { 31 | switch self { 32 | case .helix: 33 | return Color(.displayP3, red: 0.423, green: 0.733, blue: 0.235, opacity: 1) 34 | case .sheet: 35 | return Color(.displayP3, red: 0.000, green: 0.590, blue: 1.000, opacity: 1) 36 | case .loop: 37 | return Color(.displayP3, red: 0.500, green: 0.500, blue: 0.500, opacity: 1) 38 | case .nonChain: 39 | return Color(.displayP3, red: 0.750, green: 0.750, blue: 0.750, opacity: 1) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /BioViewerPackages/BioViewerFoundation/Tests/BioViewerFoundationTests/BioViewerFoundationTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import BioViewerFoundation 3 | 4 | final class BioViewerFoundationTests: XCTestCase { 5 | func testExample() throws { 6 | // XCTest Documentation 7 | // https://developer.apple.com/documentation/xctest 8 | 9 | // Defining Test Cases and Test Methods 10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BioViewerPackages/CIFParser/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /BioViewerPackages/CIFParser/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BioViewerPackages/CIFParser/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "CIFParser", 8 | platforms: [.iOS(.v17), .macOS(.v14)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, making them visible to other packages. 11 | .library( 12 | name: "CIFParser", 13 | targets: ["CIFParser"]), 14 | ], 15 | 16 | dependencies: [ 17 | .package(path: "../BioViewerFoundation") 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package, defining a module or a test suite. 21 | // Targets can depend on other targets in this package and products from dependencies. 22 | .target( 23 | name: "CIFParser", 24 | dependencies: ["BioViewerFoundation"] 25 | ), 26 | .testTarget( 27 | name: "CIFParserTests", 28 | dependencies: ["CIFParser"] 29 | ), 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /BioViewerPackages/CIFParser/Sources/CIFParser/CIFConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CIFConstants.swift 3 | // 4 | // 5 | // Created by Raúl Montón Pinillos on 22/2/24. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum Directives { 11 | static let loop = "loop_" 12 | static let comment = "#" 13 | } 14 | public enum CategoryNames { 15 | 16 | static let categoriesToSave: [String] = { 17 | var categoriesToSave = [String]() 18 | categoriesToSave.append(Entry.id) 19 | categoriesToSave.append(AtomSite.groupPDB) 20 | categoriesToSave.append(AtomSite.typeSymbol) 21 | categoriesToSave.append(AtomSite.compID) 22 | categoriesToSave.append(AtomSite.authAsymID) 23 | categoriesToSave.append(AtomSite.cartnX) 24 | categoriesToSave.append(AtomSite.cartnY) 25 | categoriesToSave.append(AtomSite.cartnZ) 26 | return categoriesToSave 27 | }() 28 | 29 | public enum Entry { 30 | static let id = "_entry.id" 31 | } 32 | public enum AtomSite { 33 | static let groupPDB = "_atom_site.group_PDB" 34 | static let typeSymbol = "_atom_site.type_symbol" 35 | static let compID = "_atom_site.label_comp_id" 36 | static let authAsymID = "_atom_site.auth_asym_id" 37 | static let cartnX = "_atom_site.Cartn_x" 38 | static let cartnY = "_atom_site.Cartn_y" 39 | static let cartnZ = "_atom_site.Cartn_z" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /BioViewerPackages/CIFParser/Tests/CIFParserTests/CIFParserTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CIFParser 3 | 4 | final class CIFParserTests: XCTestCase { 5 | func testExample() throws { 6 | // XCTest Documentation 7 | // https://developer.apple.com/documentation/xctest 8 | 9 | // Defining Test Cases and Test Methods 10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BioViewerPackages/PDBParser/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BioViewerPackages/PDBParser/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BioViewerPackages/PDBParser/.swiftpm/xcode/xcuserdata/andro.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PDBParser.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 2 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /BioViewerPackages/PDBParser/.swiftpm/xcode/xcuserdata/raul.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | PDBParser-Package.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 1 11 | 12 | PDBParser.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 5 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /BioViewerPackages/PDBParser/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "PDBParser", 8 | platforms: [.iOS(.v17), .macOS(.v14)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, making them visible to other packages. 11 | .library( 12 | name: "PDBParser", 13 | targets: ["PDBParser"]), 14 | ], 15 | dependencies: [ 16 | .package(path: "../BioViewerFoundation") 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package, defining a module or a test suite. 20 | // Targets can depend on other targets in this package and products from dependencies. 21 | .target( 22 | name: "PDBParser", 23 | dependencies: ["BioViewerFoundation"] 24 | ), 25 | .testTarget( 26 | name: "PDBParserTests", 27 | dependencies: ["PDBParser"] 28 | ), 29 | ] 30 | ) 31 | -------------------------------------------------------------------------------- /BioViewerPackages/PDBParser/Sources/PDBParser/PDBParseError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PDBParseError.swift 3 | // BioViewer 4 | // 5 | // Created by Raúl Montón Pinillos on 15/1/23. 6 | // 7 | 8 | import Foundation 9 | 10 | enum PDBParseError: Error { 11 | case unexpectedLineLength 12 | case missingResidueID 13 | case invalidAtomCoordinates 14 | case missingHELIXInitResidueID 15 | case missingHELIXFinalResidueID 16 | } 17 | -------------------------------------------------------------------------------- /BioViewerPackages/PDBParser/Tests/PDBParserTests/PDBParserTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PDBParser 3 | 4 | final class PDBParserTests: XCTestCase { 5 | func testExample() throws { 6 | // XCTest Documentation 7 | // https://developer.apple.com/documentation/xctest 8 | 9 | // Defining Test Cases and Test Methods 10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BioViewerPackages/XYZParser/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BioViewerPackages/XYZParser/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BioViewerPackages/XYZParser/.swiftpm/xcode/xcuserdata/andro.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | XYZParser-Package.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | XYZParser.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 4 16 | 17 | XYZParserTests.xcscheme_^#shared#^_ 18 | 19 | orderHint 20 | 3 21 | 22 | 23 | SuppressBuildableAutocreation 24 | 25 | XYZParser 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /BioViewerPackages/XYZParser/.swiftpm/xcode/xcuserdata/raul.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | XYZParser-Package.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | XYZParser.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 3 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /BioViewerPackages/XYZParser/Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "XYZParser", 8 | platforms: [.iOS(.v17), .macOS(.v14)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, making them visible to other packages. 11 | .library( 12 | name: "XYZParser", 13 | targets: ["XYZParser"] 14 | ) 15 | ], 16 | dependencies: [ 17 | .package(path: "../BioViewerFoundation") 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package, defining a module or a test suite. 21 | // Targets can depend on other targets in this package and products from dependencies. 22 | .target( 23 | name: "XYZParser", 24 | dependencies: ["BioViewerFoundation"] 25 | ), 26 | .testTarget( 27 | name: "XYZParserTests", 28 | dependencies: ["XYZParser"] 29 | ) 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /BioViewerPackages/XYZParser/Sources/XYZParser/XYZConstants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XYZConstants.swift 3 | // 4 | // 5 | // Created by Raúl Montón Pinillos on 12/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | enum XYZConstants { 11 | /// Number of components in an atom coordinates line. 12 | static let atomLineNumberOfComponents: Int = 4 13 | } 14 | -------------------------------------------------------------------------------- /BioViewerPackages/XYZParser/Sources/XYZParser/XYZParsedConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParsedConfiguration.swift 3 | // 4 | // 5 | // Created by Raúl Montón Pinillos on 12/11/23. 6 | // 7 | 8 | import BioViewerFoundation 9 | import Foundation 10 | import simd 11 | 12 | class XYZParsedConfiguration { 13 | var id: Int 14 | var energy: Float? 15 | // Make one atom array per common element 16 | var atomArray = [simd_float3]() 17 | var atomElements = [AtomElement]() 18 | var atomArrayComposition = ProteinElementComposition() 19 | 20 | init(id: Int) { 21 | self.id = id 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /BioViewerPackages/XYZParser/Sources/XYZParser/XYZParserError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Raúl Montón Pinillos on 12/11/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public enum XYZParserError: Error { 11 | case noConfiguration 12 | case emptyAtomCount 13 | } 14 | 15 | extension XYZParserError: LocalizedError { 16 | public var errorDescription: String? { 17 | switch self { 18 | case .noConfiguration: 19 | return NSLocalizedString("There are no configurations in this file", comment: "") 20 | case .emptyAtomCount: 21 | return NSLocalizedString("Error: File does not contain any atom positions", comment: "") 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /BioViewerPackages/XYZParser/Tests/XYZParserTests/XYZParserTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import XYZParser 3 | 4 | final class XYZParserTests: XCTestCase { 5 | func testExample() throws { 6 | // XCTest Documentation 7 | // https://developer.apple.com/documentation/xctest 8 | 9 | // Defining Test Cases and Test Methods 10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /BioViewerTests/BioViewerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PDB_ViewerTests.swift 3 | // BioViewerTests 4 | // 5 | // Created by Raúl Montón Pinillos on 4/5/21. 6 | // 7 | 8 | import XCTest 9 | @testable import BioViewer 10 | 11 | class BioViewerTests: XCTestCase { 12 | 13 | var scheduler: MetalScheduler? 14 | var protein: Protein? 15 | 16 | override func setUpWithError() throws { 17 | // Put setup code here. This method is called before the invocation of each test method in the class. 18 | self.scheduler = MetalScheduler.shared 19 | let proteinSampleFile = Bundle.main.url(forResource: "3JBT", withExtension: "pdb")! 20 | let proteinData = try? Data(contentsOf: proteinSampleFile) 21 | self.protein = parsePDB(rawText: String(decoding: proteinData, as: UTF8.self)) 22 | } 23 | 24 | override func tearDownWithError() throws { 25 | // Put teardown code here. This method is called after the invocation of each test method in the class. 26 | } 27 | 28 | func testExample() throws { 29 | // This is an example of a functional test case. 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | let camera = Camera(nearPlane: 1, farPlane: 10, focalLength: 200) 32 | print(camera.focalLength) 33 | } 34 | 35 | func testPerformanceExample() throws { 36 | let measureOptions = XCTMeasureOptions.init() 37 | measureOptions.iterationCount = 100 38 | 39 | // Original implementation: 0.339s, 0.339s (25th May 2021) 40 | // Improved implementation: 0.328s, 0.327s (26th May 2021) 41 | self.measure(options: measureOptions, block: { 42 | scheduler?.createSASPoints(protein: self.protein!, sceneDelegate: ProteinViewSceneDelegate()) 43 | }) 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /BioViewerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /BioViewerUITests/BioViewerUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PDB_ViewerUITests.swift 3 | // BioViewerUITests 4 | // 5 | // Created by Raúl Montón Pinillos on 4/5/21. 6 | // 7 | 8 | import XCTest 9 | 10 | class BioViewerUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | func testLaunchPerformance() throws { 35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 36 | // This measures how long it takes to launch your application. 37 | measure(metrics: [XCTApplicationLaunchMetric()]) { 38 | XCUIApplication().launch() 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /BioViewerUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Privacy policy/en/PrivacyPolicy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | This app does not collect any data from the user, neither for us nor any third parties. -------------------------------------------------------------------------------- /PromoAssets/MetalFeatures.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/PromoAssets/MetalFeatures.png -------------------------------------------------------------------------------- /PromoAssets/Mockup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/PromoAssets/Mockup.png -------------------------------------------------------------------------------- /ProteinThumbnail/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ProteinThumbnail/Assets.xcassets/OverlayPDB.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "OverlayPDB.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ProteinThumbnail/Assets.xcassets/OverlayPDB.imageset/OverlayPDB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/ProteinThumbnail/Assets.xcassets/OverlayPDB.imageset/OverlayPDB.png -------------------------------------------------------------------------------- /ProteinThumbnail/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionAttributes 8 | 9 | QLSupportedContentTypes 10 | 11 | com.raulmonton.bioviewer.pdb 12 | 13 | QLThumbnailMinimumDimension 14 | 0 15 | 16 | NSExtensionPointIdentifier 17 | com.apple.quicklook.thumbnail 18 | NSExtensionPrincipalClass 19 | $(PRODUCT_MODULE_NAME).ThumbnailProvider 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ProteinThumbnail/ProteinThumbnail.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/ProteinVisualization/Figures/HighResH2O.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/docs/ProteinVisualization/Figures/HighResH2O.png -------------------------------------------------------------------------------- /docs/ProteinVisualization/Figures/PercentageCloseFiltering.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/docs/ProteinVisualization/Figures/PercentageCloseFiltering.png -------------------------------------------------------------------------------- /docs/ProteinVisualization/Figures/ShadowAcne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/docs/ProteinVisualization/Figures/ShadowAcne.png -------------------------------------------------------------------------------- /docs/ProteinVisualization/Figures/ShadowedDrawableTexture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/docs/ProteinVisualization/Figures/ShadowedDrawableTexture.png -------------------------------------------------------------------------------- /docs/ProteinVisualization/Figures/SunDepthTexture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Androp0v/BioViewer/0beba4461fd6f9cac5ae513916c79768295f175a/docs/ProteinVisualization/Figures/SunDepthTexture.png -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # BioViewer Documentation 2 | Documentation for BioViewer app. 3 | 4 | ## Protein visualization 5 | - [Casting shadows](ProteinVisualization/HardShadows.md) 6 | - [Drawing the molecular surface](ProteinVisualization/MolecularSurface.md) 7 | -------------------------------------------------------------------------------- /scripts/Plot3DPoints.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | from mpl_toolkits.mplot3d import Axes3D 4 | 5 | fig = plt.figure() 6 | ax = Axes3D(fig) 7 | 8 | points = np.array([]) # Points are copy-pasted here when debugging 9 | 10 | print(np.shape(points)) 11 | ax.scatter(points[:,0], points[:,1], points[:,2]) 12 | plt.show() -------------------------------------------------------------------------------- /scripts/UnitaryIcosahedron.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /scripts/UnitaryIcosahedron.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /scripts/UnitaryIcosahedron.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | # Program flags 4 | addRadiusAndPosition = True 5 | 6 | # Create empty list to store icosahedron points 7 | icosahedronPoints = [] 8 | 9 | # Icosahedron parameter 10 | t = ( 1.0 + np.sqrt(5.0) ) / 2.0 11 | 12 | # Add all icosahedron vertices 13 | icosahedronPoints.append( np.array( (-1, t, 0) )) 14 | icosahedronPoints.append( np.array( (1, t, 0) )) 15 | icosahedronPoints.append( np.array( (-1, -t, 0) )) 16 | icosahedronPoints.append( np.array( (1, -t, 0) )) 17 | 18 | icosahedronPoints.append( np.array( (0, -1, t) )) 19 | icosahedronPoints.append( np.array( (0, 1, t) )) 20 | icosahedronPoints.append( np.array( (0, -1, -t) )) 21 | icosahedronPoints.append( np.array( (0, 1, -t) )) 22 | 23 | icosahedronPoints.append( np.array( (t, 0, -1) )) 24 | icosahedronPoints.append( np.array( (t, 0, 1) )) 25 | icosahedronPoints.append( np.array( (-t, 0, -1) )) 26 | icosahedronPoints.append( np.array( (-t, 0, 1) )) 27 | 28 | # Normalice vertices to the unitary sphere (so it has a radius of 1) 29 | for i in range(len(icosahedronPoints)): 30 | icosahedronPoints[i] /= np.sqrt(icosahedronPoints[i][0]**2 31 | + icosahedronPoints[i][1]**2 32 | + icosahedronPoints[i][2]**2) 33 | 34 | 35 | ##### PRINTING RESULTS ##### 36 | 37 | if addRadiusAndPosition: 38 | # Print results ready to copy/paste in the Metal kernel function body 39 | for index, icosahedronVertex in enumerate(icosahedronPoints): 40 | print("generatedVertices[index+" 41 | + str(index) + "] = simd_float3(" 42 | + str(icosahedronVertex[0]) + ", " 43 | + str(icosahedronVertex[1]) + ", " 44 | + str(icosahedronVertex[2]) + ")" 45 | + " * radius + position;") 46 | else: 47 | # Print prettier results for the console 48 | for index, icosahedronVertex in enumerate(icosahedronPoints): 49 | print("Vertex " + str(index) + " = " 50 | + str(icosahedronVertex[0]) + ", " 51 | + str(icosahedronVertex[1]) + ", " 52 | + str(icosahedronVertex[2])) 53 | 54 | --------------------------------------------------------------------------------