diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000000000000000000000000000000000..787d4e89c18edd9fa1021145b148691833d09c9b --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,10 @@ +<!-- Thank you for contributing to Mol* --> + +# Description + + +## Actions + +- [ ] Added description of changes to the `[Unreleased]` section of `CHANGELOG.md` +- [ ] Updated headers of modified files +- [ ] Added my name to `package.json`'s `contributors` \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 966b67347d36bc37436d3ba78525db8a5a4275fc..93c043ab7c334b348ea377d93146bf0506bebd6b 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -6,7 +6,6 @@ "recommendations": [ "dbaeumer.vscode-eslint", "firsttris.vscode-jest-runner", - "msjsdiag.debugger-for-chrome", "slevesque.shader", "stpn.vscode-graphql", "wayou.vscode-todo-highlight" diff --git a/CHANGELOG.md b/CHANGELOG.md index 40b0426843bf46aaa4db84f662c14388229f14c6..5cef38d6f9c31c589e0dd89fa3ba0abbb53f3c11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,127 @@ Note that since we don't clearly distinguish between a public and private interf ## [Unreleased] +- Make `PluginContext.initContainer` checkered canvas background optional + +## [v3.23.0] - 2022-10-19 + +- Add `PluginContext.initContainer/mount/unmount` methods; these should make it easier to reuse a plugin context with both custom and built-in UI +- Add `PluginContext.canvas3dInitialized` +- `createPluginUI` now resolves after the 3d canvas has been initialized +- Change EM Volume Streaming default from `Whote Structure` to `Auto` + +## [v3.22.0] - 2022-10-17 + +- Replace `VolumeIsosurfaceParams.pickingGranularity` param with `Volume.PickingGranuality` + +## [v3.21.0] - 2022-10-17 + +- Add `VolumeIsosurfaceParams.pickingGranularity` param +- Prevent component controls collapsing when option is selected + +## [v3.20.0] - 2022-10-16 + +- [Breaking] Rename the ``model-index`` color theme to ``trajectory-index`` +- Add a new ``model-index`` color theme that uniquely colors each loaded model +- Add the new ``model-index`` and ``structure-index`` color themes as an option for the carbon color in the ``element-symbol`` and ``ilustrative`` color themes +- Add ``structure-index`` color theme that uniquely colors each root structure +- Add ``nearest`` method to ``Lookup3D`` +- Add mipmap-based blur for skybox backgrounds + +## [v3.19.0] - 2022-10-01 + +- Fix "empty textures" error on empty canvas +- Optimize BinaryCIF integer packing encoder +- Fix dual depth peeling when post-processing is off or when rendering direct-volumes +- Add ``cameraClipping.minNear`` parameter +- Fix black artifacts on specular highlights with transparent background + +## [v3.18.0] - 2022-09-17 + +- Integration of Dual depth peeling - OIT method +- Stereo camera improvements + - Fix param updates not applied + - Better param ranges and description + - Add timer.mark for left/right camera + +## [v3.17.0] - 2022-09-11 + +- [Fix] Clone ``Canvas3DParams`` when creating a ``Canvas3D`` instance to prevent shared state between multiple instances +- Add ``includeResidueTest`` option to ``alignAndSuperposeWithSIFTSMapping`` +- Add ``parentDisplay`` param for interactions representation. +- [Experimental] Add support for PyMOL, VMD, and Jmol atom expressions in selection scripts +- Support for ``failIfMajorPerformanceCaveat`` webgl attribute. Add ``PluginConfig.General.AllowMajorPerformanceCaveat`` and ``allow-major-performance-caveat`` Viewer GET param. +- Fix handling of PDB TER records (#549) +- Add support for getting multiple loci from a representation (``.getAllLoci()``) +- Add ``key`` property to intra- and inter-bonds for referencing source data +- Fix click event triggered after move + +## [v3.16.0] - 2022-08-25 + +- Support ``globalColorParams`` and ``globalSymmetryParams`` in common representation params +- Support ``label`` parameter in ``Viewer.loadStructureFromUrl`` +- Fix ``ViewportHelpContent`` Mouse Controls section + +## [v3.15.0] - 2022-08-23 + +- Fix wboit in Safari >=15 (add missing depth renderbuffer to wboit pass) +- Add 'Around Camera' option to Volume streaming +- Avoid queuing more than one update in Volume streaming + +## [v3.14.0] - 2022-08-20 + +- Expose inter-bonds compute params in structure +- Improve performance of inter/intra-bonds compute +- Fix defaultAttribs handling in Canvas3DContext.fromCanvas +- Confal pyramids extension improvements + - Add custom labels to Confal pyramids + - Improve naming of some internal types in Confal pyramids extension coordinate + - Add example mmCIF file with categories necessary to display Confal pyramids + - Change the lookup logic of NtC steps from residues +- Add support for download of gzipped files +- Don't filter IndexPairBonds by element-based rules in MOL/SDF and MOL2 (without symmetry) models +- Fix Glycam Saccharide Names used by default +- Fix GPU surfaces rendering in Safari with WebGL2 +- Add ``fov`` (Field of View) Canvas3D parameter +- Add ``sceneRadiusFactor`` Canvas3D parameter +- Add background pass (skybox, image, horizontal/radial gradient) + - Set simple-settings presets via ``PluginConfig.Background.Styles`` + - Example presets in new backgrounds extension + - Load skybox/image from URL or File (saved in session) + - Opacity, saturation, lightness controls for skybox/image + - Coverage (viewport or canvas) controls for image/gradient +- [Breaking] ``AssetManager`` needs to be passed to various graphics related classes +- Fix SSAO renderable initialization +- Reduce number of webgl state changes + - Add ``viewport`` and ``scissor`` to state object + - Add ``hasOpaque`` to scene object +- Handle edge cases where some renderables would not get (correctly) rendered + - Fix text background rendering for opaque text + - Fix helper scenes not shown when rendering directly to draw target +- Fix ``CustomElementProperty`` coloring not working + +## [v3.13.0] - 2022-07-24 + +- Fix: only update camera state if manualReset is off (#494) +- Improve handling principal axes of points in a plane +- Add 'material' annotation support for textures +- More effort to avoid using ``flat`` qualifier in shaders: add ``dVaryingGroup`` +- Enable ``immediateUpdate`` for iso level in isosurface and volume streaming controls +- Add support to download CCD from configurable URL + +## [v3.12.1] - 2022-07-20 + +- Fix plugin behavior dispose logic to correctly unsubscribe observables. + +## [v3.12.0] - 2022-07-17 + +- Add ``colorMarker`` option to Renderer. This disables the highlight and select marker at a shader level for faster rendering of large scenes in some cases. +- Bind shared textures only once per pass, not for each render item +- Fix missing 'material' annotation for some uniforms, causing unnecessary uniform updates +- Remove use of ``isnan`` in impostor shaders, not needed and causing slowdown +- Avoid using ``flat`` qualifier in shaders, causing slowdown +- Improve CellPack's ``adjustStyle`` option (disable ``colorMarker``, set component options, enable marking w/o ghost) +- Scan all entities when looking for ``struct_conn`` entries (fixes issue when the same ``label_asym_id`` is used in more than one entity) ## [v3.11.0] - 2022-07-04 diff --git a/README.md b/README.md index 10e926c509f71742f239fe3ccf7e064b7130e470..9125bac897959988a7bbb5405f62481fac94cf41 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,7 @@ and navigate to `build/viewer` **GraphQL schemas** - node node_modules//@graphql-codegen/cli/bin -c src/extensions/rcsb/graphql/codegen.yml + node node_modules/@graphql-codegen/cli/cjs/bin -c src/extensions/rcsb/graphql/codegen.yml ### Other scripts **Create chem comp bond table** @@ -152,7 +152,7 @@ Or node lib/commonjs/cli/cif2bcif E.g. - + node lib/commonjs/cli/cif2bcif src.cif out.bcif.gz node lib/commonjs/cli/cif2bcif src.bcif.gz out.cif diff --git a/examples/1bna_confal_pyramids.cif b/examples/1bna_confal_pyramids.cif new file mode 100644 index 0000000000000000000000000000000000000000..c4ab21a1b4e4973acd3ee1de28231f73e25e5249 --- /dev/null +++ b/examples/1bna_confal_pyramids.cif @@ -0,0 +1,1694 @@ +data_1BNA +# +_entry.id 1BNA +## +_audit_conform.dict_name mmcif_pdbx.dic +_audit_conform.dict_version 5.279 +_audit_conform.dict_location http://mmcif.pdb.org/dictionaries/ascii/mmcif_pdbx.dic +## +loop_ +_database_2.database_id +_database_2.database_code +PDB 1BNA +RCSB BDL001 +WWPDB D_1000171933 +## +_pdbx_database_status.status_code REL +_pdbx_database_status.entry_id 1BNA +_pdbx_database_status.recvd_initial_deposition_date 1981-01-26 +_pdbx_database_status.deposit_site BNL +_pdbx_database_status.process_site BNL +_pdbx_database_status.status_code_sf REL +_pdbx_database_status.status_code_mr ? +_pdbx_database_status.SG_entry ? +_pdbx_database_status.pdb_format_compatible Y +_pdbx_database_status.status_code_cs ? +## +loop_ +_audit_author.name +_audit_author.pdbx_ordinal +'Drew, H.R.' 1 +'Wing, R.M.' 2 +'Takano, T.' 3 +'Broka, C.' 4 +'Tanaka, S.' 5 +'Itakura, K.' 6 +'Dickerson, R.E.' 7 +## +loop_ +_citation.id +_citation.title +_citation.journal_abbrev +_citation.journal_volume +_citation.page_first +_citation.page_last +_citation.year +_citation.journal_id_ASTM +_citation.country +_citation.journal_id_ISSN +_citation.journal_id_CSD +_citation.book_publisher +_citation.pdbx_database_id_PubMed +_citation.pdbx_database_id_DOI +primary 'Structure of a B-DNA dodecamer: conformation and dynamics.' Proc.Natl.Acad.Sci.USA 78 2179 2183 1981 PNASA6 US 0027-8424 0040 ? 6941276 10.1073/pnas.78.4.2179 +1 'Kinematic Model for B-DNA' Proc.Natl.Acad.Sci.USA 78 7318 7322 1981 PNASA6 US 0027-8424 0040 ? ? ? +2 'Structure of a B-DNA Dodecamer. II. Influence of Base Sequence on Helix Structure' J.Mol.Biol. 149 761 786 1981 JMOBAK UK 0022-2836 0070 ? ? ? +3 'Structure of a B-DNA Dodecamer. III. Geometry of Hydration' J.Mol.Biol. 151 535 556 1981 JMOBAK UK 0022-2836 0070 ? ? ? +4 'Crystal Structure Analysis of a Complete Turn of B-DNA' Nature 287 755 758 1980 NATUAS UK 0028-0836 0006 ? ? ? +## +loop_ +_citation_author.citation_id +_citation_author.name +_citation_author.ordinal +primary 'Drew, H.R.' 1 +primary 'Wing, R.M.' 2 +primary 'Takano, T.' 3 +primary 'Broka, C.' 4 +primary 'Tanaka, S.' 5 +primary 'Itakura, K.' 6 +primary 'Dickerson, R.E.' 7 +1 'Dickerson, R.E.' 8 +1 'Drew, H.R.' 9 +2 'Dickerson, R.E.' 10 +2 'Drew, H.R.' 11 +3 'Drew, H.R.' 12 +3 'Dickerson, R.E.' 13 +4 'Wing, R.' 14 +4 'Drew, H.R.' 15 +4 'Takano, T.' 16 +4 'Broka, C.' 17 +4 'Tanaka, S.' 18 +4 'Itakura, K.' 19 +4 'Dickerson, R.E.' 20 +## +_cell.entry_id 1BNA +_cell.length_a 24.870 +_cell.length_b 40.390 +_cell.length_c 66.200 +_cell.angle_alpha 90.00 +_cell.angle_beta 90.00 +_cell.angle_gamma 90.00 +_cell.Z_PDB 8 +_cell.pdbx_unique_axis ? +## +_symmetry.entry_id 1BNA +_symmetry.space_group_name_H-M 'P 21 21 21' +_symmetry.pdbx_full_space_group_name_H-M ? +_symmetry.cell_setting ? +_symmetry.Int_Tables_number 19 +## +loop_ +_entity.id +_entity.type +_entity.src_method +_entity.pdbx_description +_entity.formula_weight +_entity.pdbx_number_of_molecules +_entity.pdbx_ec +_entity.pdbx_mutation +_entity.pdbx_fragment +_entity.details +1 polymer syn "DNA(5'-D(*CP*GP*CP*GP*AP*AP*TP*TP*CP*GP*CP*G)-3')" 3663.392 2 ? ? ? ? +2 water nat water 18.015 80 ? ? ? ? +## +_entity_poly.entity_id 1 +_entity_poly.type polydeoxyribonucleotide +_entity_poly.nstd_linkage no +_entity_poly.nstd_monomer no +_entity_poly.pdbx_seq_one_letter_code '(DC)(DG)(DC)(DG)(DA)(DA)(DT)(DT)(DC)(DG)(DC)(DG)' +_entity_poly.pdbx_seq_one_letter_code_can CGCGAATTCGCG +_entity_poly.pdbx_strand_id A,B +_entity_poly.pdbx_target_identifier ? +## +loop_ +_entity_poly_seq.entity_id +_entity_poly_seq.num +_entity_poly_seq.mon_id +_entity_poly_seq.hetero +1 1 DC n +1 2 DG n +1 3 DC n +1 4 DG n +1 5 DA n +1 6 DA n +1 7 DT n +1 8 DT n +1 9 DC n +1 10 DG n +1 11 DC n +1 12 DG n +## +_struct_ref.id 1 +_struct_ref.entity_id 1 +_struct_ref.db_name PDB +_struct_ref.db_code 1BNA +_struct_ref.pdbx_db_accession 1BNA +_struct_ref.pdbx_db_isoform ? +_struct_ref.pdbx_seq_one_letter_code ? +_struct_ref.pdbx_align_begin ? +## +loop_ +_struct_ref_seq.align_id +_struct_ref_seq.ref_id +_struct_ref_seq.pdbx_PDB_id_code +_struct_ref_seq.pdbx_strand_id +_struct_ref_seq.seq_align_beg +_struct_ref_seq.pdbx_seq_align_beg_ins_code +_struct_ref_seq.seq_align_end +_struct_ref_seq.pdbx_seq_align_end_ins_code +_struct_ref_seq.pdbx_db_accession +_struct_ref_seq.db_align_beg +_struct_ref_seq.pdbx_db_align_beg_ins_code +_struct_ref_seq.db_align_end +_struct_ref_seq.pdbx_db_align_end_ins_code +_struct_ref_seq.pdbx_auth_seq_align_beg +_struct_ref_seq.pdbx_auth_seq_align_end +1 1 1BNA A 1 ? 12 ? 1BNA 1 ? 12 ? 1 12 +2 1 1BNA B 1 ? 12 ? 1BNA 13 ? 24 ? 13 24 +## +loop_ +_chem_comp.id +_chem_comp.type +_chem_comp.mon_nstd_flag +_chem_comp.name +_chem_comp.pdbx_synonyms +_chem_comp.formula +_chem_comp.formula_weight +DA 'DNA linking' y "2'-DEOXYADENOSINE-5'-MONOPHOSPHATE" ? 'C10 H14 N5 O6 P' 331.222 +DC 'DNA linking' y "2'-DEOXYCYTIDINE-5'-MONOPHOSPHATE" ? 'C9 H14 N3 O7 P' 307.197 +DG 'DNA linking' y "2'-DEOXYGUANOSINE-5'-MONOPHOSPHATE" ? 'C10 H14 N5 O7 P' 347.221 +DT 'DNA linking' y "THYMIDINE-5'-MONOPHOSPHATE" ? 'C10 H15 N2 O8 P' 322.208 +HOH non-polymer . WATER ? 'H2 O' 18.015 +## +_exptl.entry_id 1BNA +_exptl.method 'X-RAY DIFFRACTION' +_exptl.crystals_number ? +## +_exptl_crystal.id 1 +_exptl_crystal.density_meas ? +_exptl_crystal.density_Matthews 2.27 +_exptl_crystal.density_percent_sol 45.79 +_exptl_crystal.description ? +## +_exptl_crystal_grow.crystal_id 1 +_exptl_crystal_grow.method 'VAPOR DIFFUSION' +_exptl_crystal_grow.temp 290.00 +_exptl_crystal_grow.temp_details ? +_exptl_crystal_grow.pH ? +_exptl_crystal_grow.pdbx_details 'VAPOR DIFFUSION, temperature 290.00K' +_exptl_crystal_grow.pdbx_pH_range ? +## +loop_ +_exptl_crystal_grow_comp.crystal_id +_exptl_crystal_grow_comp.id +_exptl_crystal_grow_comp.sol_id +_exptl_crystal_grow_comp.name +_exptl_crystal_grow_comp.volume +_exptl_crystal_grow_comp.conc +_exptl_crystal_grow_comp.details +1 1 1 WATER ? ? ? +1 2 1 'MG ACETATE' ? ? ? +1 3 1 SPERMINE_HCL ? ? ? +1 4 2 WATER ? ? ? +1 5 2 MPD ? ? ? +## +_diffrn.id 1 +_diffrn.crystal_id 1 +_diffrn.ambient_temp ? +_diffrn.ambient_temp_details ? +## +_diffrn_detector.diffrn_id 1 +_diffrn_detector.detector DIFFRACTOMETER +_diffrn_detector.type ? +_diffrn_detector.pdbx_collection_date ? +_diffrn_detector.details ? +## +_diffrn_radiation.diffrn_id 1 +_diffrn_radiation.wavelength_id 1 +_diffrn_radiation.pdbx_monochromatic_or_laue_m_l ? +_diffrn_radiation.monochromator ? +_diffrn_radiation.pdbx_diffrn_protocol ? +_diffrn_radiation.pdbx_scattering_type x-ray +## +_diffrn_radiation_wavelength.id 1 +_diffrn_radiation_wavelength.wavelength . +_diffrn_radiation_wavelength.wt 1.0 +## +_diffrn_source.diffrn_id 1 +_diffrn_source.source ? +_diffrn_source.type ? +_diffrn_source.pdbx_synchrotron_site ? +_diffrn_source.pdbx_synchrotron_beamline ? +_diffrn_source.pdbx_wavelength ? +_diffrn_source.pdbx_wavelength_list ? +## +_reflns.entry_id 1BNA +_reflns.observed_criterion_sigma_I ? +_reflns.observed_criterion_sigma_F ? +_reflns.d_resolution_low 8.0 +_reflns.d_resolution_high 1.900 +_reflns.number_obs 5534 +_reflns.number_all ? +_reflns.percent_possible_obs ? +_reflns.pdbx_Rmerge_I_obs ? +_reflns.pdbx_Rsym_value ? +_reflns.pdbx_netI_over_sigmaI ? +_reflns.B_iso_Wilson_estimate ? +_reflns.pdbx_redundancy ? +_reflns.pdbx_diffrn_id 1 +_reflns.pdbx_ordinal 1 +## +_refine.entry_id 1BNA +_refine.ls_number_reflns_obs 2725 +_refine.ls_number_reflns_all ? +_refine.pdbx_ls_sigma_I 2.000 +_refine.pdbx_ls_sigma_F ? +_refine.pdbx_data_cutoff_high_absF ? +_refine.pdbx_data_cutoff_low_absF ? +_refine.pdbx_data_cutoff_high_rms_absF ? +_refine.ls_d_res_low 8.000 +_refine.ls_d_res_high 1.900 +_refine.ls_percent_reflns_obs ? +_refine.ls_R_factor_obs 0.1780000 +_refine.ls_R_factor_all ? +_refine.ls_R_factor_R_work ? +_refine.ls_R_factor_R_free ? +_refine.ls_R_factor_R_free_error ? +_refine.ls_R_factor_R_free_error_details ? +_refine.ls_percent_reflns_R_free ? +_refine.ls_number_reflns_R_free ? +_refine.ls_number_parameters ? +_refine.ls_number_restraints ? +_refine.occupancy_min ? +_refine.occupancy_max ? +_refine.B_iso_mean ? +_refine.aniso_B[1][1] ? +_refine.aniso_B[2][2] ? +_refine.aniso_B[3][3] ? +_refine.aniso_B[1][2] ? +_refine.aniso_B[1][3] ? +_refine.aniso_B[2][3] ? +_refine.solvent_model_details ? +_refine.solvent_model_param_ksol ? +_refine.solvent_model_param_bsol ? +_refine.pdbx_ls_cross_valid_method ? +_refine.details ? +_refine.pdbx_starting_model ? +_refine.pdbx_method_to_determine_struct ? +_refine.pdbx_isotropic_thermal_model ? +_refine.pdbx_stereochemistry_target_values ? +_refine.pdbx_stereochem_target_val_spec_case ? +_refine.pdbx_R_Free_selection_details ? +_refine.pdbx_overall_ESU_R ? +_refine.pdbx_overall_ESU_R_Free ? +_refine.overall_SU_ML ? +_refine.overall_SU_B ? +_refine.pdbx_refine_id 'X-RAY DIFFRACTION' +_refine.pdbx_diffrn_id 1 +_refine.pdbx_TLS_residual_ADP_flag ? +_refine.correlation_coeff_Fo_to_Fc ? +_refine.correlation_coeff_Fo_to_Fc_free ? +_refine.pdbx_solvent_vdw_probe_radii ? +_refine.pdbx_solvent_ion_probe_radii ? +_refine.pdbx_solvent_shrinkage_radii ? +_refine.pdbx_overall_phase_error ? +_refine.overall_SU_R_Cruickshank_DPI ? +_refine.pdbx_overall_SU_R_free_Cruickshank_DPI ? +_refine.pdbx_overall_SU_R_Blow_DPI ? +_refine.pdbx_overall_SU_R_free_Blow_DPI ? +## +_refine_hist.pdbx_refine_id 'X-RAY DIFFRACTION' +_refine_hist.cycle_id LAST +_refine_hist.pdbx_number_atoms_protein 0 +_refine_hist.pdbx_number_atoms_nucleic_acid 486 +_refine_hist.pdbx_number_atoms_ligand 0 +_refine_hist.number_atoms_solvent 80 +_refine_hist.number_atoms_total 566 +_refine_hist.d_res_high 1.900 +_refine_hist.d_res_low 8.000 +## +_struct.entry_id 1BNA +_struct.title 'STRUCTURE OF A B-DNA DODECAMER. CONFORMATION AND DYNAMICS' +_struct.pdbx_descriptor "5'-D(*CP*GP*CP*GP*AP*AP*TP*TP*CP*GP*CP*G)-3',290 K" +_struct.pdbx_model_details ? +_struct.pdbx_CASP_flag ? +_struct.pdbx_model_type_details ? +## +_struct_keywords.entry_id 1BNA +_struct_keywords.pdbx_keywords DNA +_struct_keywords.text 'B-DNA, DOUBLE HELIX, DNA' +## +loop_ +_struct_asym.id +_struct_asym.pdbx_blank_PDB_chainid_flag +_struct_asym.pdbx_modified +_struct_asym.entity_id +_struct_asym.details +A N N 1 ? +B N N 1 ? +C N N 2 ? +D N N 2 ? +## +_struct_biol.id 1 +## +loop_ +_struct_conn.id +_struct_conn.conn_type_id +_struct_conn.pdbx_leaving_atom_flag +_struct_conn.pdbx_PDB_id +_struct_conn.ptnr1_label_asym_id +_struct_conn.ptnr1_label_comp_id +_struct_conn.ptnr1_label_seq_id +_struct_conn.ptnr1_label_atom_id +_struct_conn.pdbx_ptnr1_label_alt_id +_struct_conn.pdbx_ptnr1_PDB_ins_code +_struct_conn.pdbx_ptnr1_standard_comp_id +_struct_conn.ptnr1_symmetry +_struct_conn.ptnr2_label_asym_id +_struct_conn.ptnr2_label_comp_id +_struct_conn.ptnr2_label_seq_id +_struct_conn.ptnr2_label_atom_id +_struct_conn.pdbx_ptnr2_label_alt_id +_struct_conn.pdbx_ptnr2_PDB_ins_code +_struct_conn.ptnr1_auth_asym_id +_struct_conn.ptnr1_auth_comp_id +_struct_conn.ptnr1_auth_seq_id +_struct_conn.ptnr2_auth_asym_id +_struct_conn.ptnr2_auth_comp_id +_struct_conn.ptnr2_auth_seq_id +_struct_conn.ptnr2_symmetry +_struct_conn.pdbx_ptnr3_label_atom_id +_struct_conn.pdbx_ptnr3_label_seq_id +_struct_conn.pdbx_ptnr3_label_comp_id +_struct_conn.pdbx_ptnr3_label_asym_id +_struct_conn.pdbx_ptnr3_label_alt_id +_struct_conn.pdbx_ptnr3_PDB_ins_code +_struct_conn.details +_struct_conn.pdbx_dist_value +_struct_conn.pdbx_value_order +hydrog1 hydrog ? ? A DC 1 N3 ? ? ? 1_555 B DG 12 N1 ? ? A DC 1 B DG 24 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog2 hydrog ? ? A DC 1 N4 ? ? ? 1_555 B DG 12 O6 ? ? A DC 1 B DG 24 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog3 hydrog ? ? A DC 1 O2 ? ? ? 1_555 B DG 12 N2 ? ? A DC 1 B DG 24 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog4 hydrog ? ? A DG 2 N1 ? ? ? 1_555 B DC 11 N3 ? ? A DG 2 B DC 23 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog5 hydrog ? ? A DG 2 N2 ? ? ? 1_555 B DC 11 O2 ? ? A DG 2 B DC 23 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog6 hydrog ? ? A DG 2 O6 ? ? ? 1_555 B DC 11 N4 ? ? A DG 2 B DC 23 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog7 hydrog ? ? A DC 3 N3 ? ? ? 1_555 B DG 10 N1 ? ? A DC 3 B DG 22 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog8 hydrog ? ? A DC 3 N4 ? ? ? 1_555 B DG 10 O6 ? ? A DC 3 B DG 22 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog9 hydrog ? ? A DC 3 O2 ? ? ? 1_555 B DG 10 N2 ? ? A DC 3 B DG 22 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog10 hydrog ? ? A DG 4 N1 ? ? ? 1_555 B DC 9 N3 ? ? A DG 4 B DC 21 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog11 hydrog ? ? A DG 4 N2 ? ? ? 1_555 B DC 9 O2 ? ? A DG 4 B DC 21 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog12 hydrog ? ? A DG 4 O6 ? ? ? 1_555 B DC 9 N4 ? ? A DG 4 B DC 21 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog13 hydrog ? ? A DA 5 N1 ? ? ? 1_555 B DT 8 N3 ? ? A DA 5 B DT 20 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog14 hydrog ? ? A DA 5 N6 ? ? ? 1_555 B DT 8 O4 ? ? A DA 5 B DT 20 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog15 hydrog ? ? A DA 6 N1 ? ? ? 1_555 B DT 7 N3 ? ? A DA 6 B DT 19 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog16 hydrog ? ? A DA 6 N6 ? ? ? 1_555 B DT 7 O4 ? ? A DA 6 B DT 19 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog17 hydrog ? ? A DT 7 N3 ? ? ? 1_555 B DA 6 N1 ? ? A DT 7 B DA 18 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog18 hydrog ? ? A DT 7 O4 ? ? ? 1_555 B DA 6 N6 ? ? A DT 7 B DA 18 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog19 hydrog ? ? A DT 8 N3 ? ? ? 1_555 B DA 5 N1 ? ? A DT 8 B DA 17 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog20 hydrog ? ? A DT 8 O4 ? ? ? 1_555 B DA 5 N6 ? ? A DT 8 B DA 17 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog21 hydrog ? ? A DC 9 N3 ? ? ? 1_555 B DG 4 N1 ? ? A DC 9 B DG 16 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog22 hydrog ? ? A DC 9 N4 ? ? ? 1_555 B DG 4 O6 ? ? A DC 9 B DG 16 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog23 hydrog ? ? A DC 9 O2 ? ? ? 1_555 B DG 4 N2 ? ? A DC 9 B DG 16 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog24 hydrog ? ? A DG 10 N1 ? ? ? 1_555 B DC 3 N3 ? ? A DG 10 B DC 15 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog25 hydrog ? ? A DG 10 N2 ? ? ? 1_555 B DC 3 O2 ? ? A DG 10 B DC 15 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog26 hydrog ? ? A DG 10 O6 ? ? ? 1_555 B DC 3 N4 ? ? A DG 10 B DC 15 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog27 hydrog ? ? A DC 11 N3 ? ? ? 1_555 B DG 2 N1 ? ? A DC 11 B DG 14 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog28 hydrog ? ? A DC 11 N4 ? ? ? 1_555 B DG 2 O6 ? ? A DC 11 B DG 14 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog29 hydrog ? ? A DC 11 O2 ? ? ? 1_555 B DG 2 N2 ? ? A DC 11 B DG 14 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog30 hydrog ? ? A DG 12 N1 ? ? ? 1_555 B DC 1 N3 ? ? A DG 12 B DC 13 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog31 hydrog ? ? A DG 12 N2 ? ? ? 1_555 B DC 1 O2 ? ? A DG 12 B DC 13 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +hydrog32 hydrog ? ? A DG 12 O6 ? ? ? 1_555 B DC 1 N4 ? ? A DG 12 B DC 13 1_555 ? ? ? ? ? ? WATSON-CRICK ? ? +## +_struct_conn_type.id hydrog +_struct_conn_type.criteria ? +_struct_conn_type.reference ? +## +_database_PDB_matrix.entry_id 1BNA +_database_PDB_matrix.origx[1][1] 1.000000 +_database_PDB_matrix.origx[1][2] 0.000000 +_database_PDB_matrix.origx[1][3] 0.000000 +_database_PDB_matrix.origx[2][1] 0.000000 +_database_PDB_matrix.origx[2][2] 1.000000 +_database_PDB_matrix.origx[2][3] 0.000000 +_database_PDB_matrix.origx[3][1] 0.000000 +_database_PDB_matrix.origx[3][2] 0.000000 +_database_PDB_matrix.origx[3][3] 1.000000 +_database_PDB_matrix.origx_vector[1] 0.00000 +_database_PDB_matrix.origx_vector[2] 0.00000 +_database_PDB_matrix.origx_vector[3] 0.00000 +## +_atom_sites.entry_id 1BNA +_atom_sites.fract_transf_matrix[1][1] 0.040209 +_atom_sites.fract_transf_matrix[1][2] 0.000000 +_atom_sites.fract_transf_matrix[1][3] 0.000000 +_atom_sites.fract_transf_matrix[2][1] 0.000000 +_atom_sites.fract_transf_matrix[2][2] 0.024759 +_atom_sites.fract_transf_matrix[2][3] 0.000000 +_atom_sites.fract_transf_matrix[3][1] 0.000000 +_atom_sites.fract_transf_matrix[3][2] 0.000000 +_atom_sites.fract_transf_matrix[3][3] 0.015106 +_atom_sites.fract_transf_vector[1] 0.00000 +_atom_sites.fract_transf_vector[2] 0.00000 +_atom_sites.fract_transf_vector[3] 0.00000 +## +loop_ +_atom_type.symbol +C +N +O +P +## +loop_ +_atom_site.group_PDB +_atom_site.id +_atom_site.type_symbol +_atom_site.label_atom_id +_atom_site.label_alt_id +_atom_site.label_comp_id +_atom_site.label_asym_id +_atom_site.label_entity_id +_atom_site.label_seq_id +_atom_site.pdbx_PDB_ins_code +_atom_site.Cartn_x +_atom_site.Cartn_y +_atom_site.Cartn_z +_atom_site.occupancy +_atom_site.B_iso_or_equiv +_atom_site.pdbx_formal_charge +_atom_site.auth_seq_id +_atom_site.auth_comp_id +_atom_site.auth_asym_id +_atom_site.auth_atom_id +_atom_site.pdbx_PDB_model_num +ATOM 1 O "O5'" . DC A 1 1 ? 18.935 34.195 25.617 1.00 64.35 ? 1 DC A "O5'" 1 +ATOM 2 C "C5'" . DC A 1 1 ? 19.130 33.921 24.219 1.00 44.69 ? 1 DC A "C5'" 1 +ATOM 3 C "C4'" . DC A 1 1 ? 19.961 32.668 24.100 1.00 31.28 ? 1 DC A "C4'" 1 +ATOM 4 O "O4'" . DC A 1 1 ? 19.360 31.583 24.852 1.00 37.45 ? 1 DC A "O4'" 1 +ATOM 5 C "C3'" . DC A 1 1 ? 20.172 32.122 22.694 1.00 46.72 ? 1 DC A "C3'" 1 +ATOM 6 O "O3'" . DC A 1 1 ? 21.350 31.325 22.681 1.00 48.89 ? 1 DC A "O3'" 1 +ATOM 7 C "C2'" . DC A 1 1 ? 18.948 31.223 22.647 1.00 30.88 ? 1 DC A "C2'" 1 +ATOM 8 C "C1'" . DC A 1 1 ? 19.231 30.482 23.944 1.00 36.58 ? 1 DC A "C1'" 1 +ATOM 9 N N1 . DC A 1 1 ? 18.070 29.661 24.380 1.00 40.51 ? 1 DC A N1 1 +ATOM 10 C C2 . DC A 1 1 ? 18.224 28.454 25.015 1.00 16.62 ? 1 DC A C2 1 +ATOM 11 O O2 . DC A 1 1 ? 19.360 28.014 25.214 1.00 27.75 ? 1 DC A O2 1 +ATOM 12 N N3 . DC A 1 1 ? 17.143 27.761 25.377 1.00 20.55 ? 1 DC A N3 1 +ATOM 13 C C4 . DC A 1 1 ? 15.917 28.226 25.120 1.00 34.72 ? 1 DC A C4 1 +ATOM 14 N N4 . DC A 1 1 ? 14.828 27.477 25.444 1.00 40.31 ? 1 DC A N4 1 +ATOM 15 C C5 . DC A 1 1 ? 15.719 29.442 24.471 1.00 30.78 ? 1 DC A C5 1 +ATOM 16 C C6 . DC A 1 1 ? 16.843 30.171 24.101 1.00 25.90 ? 1 DC A C6 1 +ATOM 17 P P . DG A 1 2 ? 22.409 31.286 21.483 1.00 58.85 ? 2 DG A P 1 +ATOM 18 O OP1 . DG A 1 2 ? 23.536 32.157 21.851 1.00 57.82 ? 2 DG A OP1 1 +ATOM 19 O OP2 . DG A 1 2 ? 21.822 31.459 20.139 1.00 78.33 ? 2 DG A OP2 1 +ATOM 20 O "O5'" . DG A 1 2 ? 22.840 29.751 21.498 1.00 40.36 ? 2 DG A "O5'" 1 +ATOM 21 C "C5'" . DG A 1 2 ? 23.543 29.175 22.594 1.00 47.19 ? 2 DG A "C5'" 1 +ATOM 22 C "C4'" . DG A 1 2 ? 23.494 27.709 22.279 1.00 47.81 ? 2 DG A "C4'" 1 +ATOM 23 O "O4'" . DG A 1 2 ? 22.193 27.252 22.674 1.00 38.76 ? 2 DG A "O4'" 1 +ATOM 24 C "C3'" . DG A 1 2 ? 23.693 27.325 20.807 1.00 28.58 ? 2 DG A "C3'" 1 +ATOM 25 O "O3'" . DG A 1 2 ? 24.723 26.320 20.653 1.00 40.44 ? 2 DG A "O3'" 1 +ATOM 26 C "C2'" . DG A 1 2 ? 22.273 26.885 20.416 1.00 21.14 ? 2 DG A "C2'" 1 +ATOM 27 C "C1'" . DG A 1 2 ? 21.721 26.304 21.716 1.00 33.95 ? 2 DG A "C1'" 1 +ATOM 28 N N9 . DG A 1 2 ? 20.237 26.470 21.780 1.00 34.00 ? 2 DG A N9 1 +ATOM 29 C C8 . DG A 1 2 ? 19.526 27.584 21.429 1.00 36.47 ? 2 DG A C8 1 +ATOM 30 N N7 . DG A 1 2 ? 18.207 27.455 21.636 1.00 32.37 ? 2 DG A N7 1 +ATOM 31 C C5 . DG A 1 2 ? 18.083 26.212 22.142 1.00 15.06 ? 2 DG A C5 1 +ATOM 32 C C6 . DG A 1 2 ? 16.904 25.525 22.545 1.00 11.88 ? 2 DG A C6 1 +ATOM 33 O O6 . DG A 1 2 ? 15.739 25.916 22.518 1.00 21.30 ? 2 DG A O6 1 +ATOM 34 N N1 . DG A 1 2 ? 17.197 24.279 23.037 1.00 15.44 ? 2 DG A N1 1 +ATOM 35 C C2 . DG A 1 2 ? 18.434 23.717 23.155 1.00 9.63 ? 2 DG A C2 1 +ATOM 36 N N2 . DG A 1 2 ? 18.508 22.456 23.668 1.00 16.69 ? 2 DG A N2 1 +ATOM 37 N N3 . DG A 1 2 ? 19.537 24.360 22.770 1.00 30.98 ? 2 DG A N3 1 +ATOM 38 C C4 . DG A 1 2 ? 19.290 25.594 22.274 1.00 18.56 ? 2 DG A C4 1 +ATOM 39 P P . DC A 1 3 ? 25.064 25.621 19.252 1.00 44.67 ? 3 DC A P 1 +ATOM 40 O OP1 . DC A 1 3 ? 26.506 25.316 19.220 1.00 53.89 ? 3 DC A OP1 1 +ATOM 41 O OP2 . DC A 1 3 ? 24.559 26.412 18.115 1.00 57.79 ? 3 DC A OP2 1 +ATOM 42 O "O5'" . DC A 1 3 ? 24.260 24.246 19.327 1.00 35.42 ? 3 DC A "O5'" 1 +ATOM 43 C "C5'" . DC A 1 3 ? 24.584 23.285 20.335 1.00 45.75 ? 3 DC A "C5'" 1 +ATOM 44 C "C4'" . DC A 1 3 ? 23.523 22.233 20.245 1.00 43.02 ? 3 DC A "C4'" 1 +ATOM 45 O "O4'" . DC A 1 3 ? 22.256 22.844 20.453 1.00 36.85 ? 3 DC A "O4'" 1 +ATOM 46 C "C3'" . DC A 1 3 ? 23.424 21.557 18.903 1.00 40.14 ? 3 DC A "C3'" 1 +ATOM 47 O "O3'" . DC A 1 3 ? 24.121 20.309 18.928 1.00 49.62 ? 3 DC A "O3'" 1 +ATOM 48 C "C2'" . DC A 1 3 ? 21.930 21.406 18.661 1.00 53.79 ? 3 DC A "C2'" 1 +ATOM 49 C "C1'" . DC A 1 3 ? 21.278 21.966 19.909 1.00 22.18 ? 3 DC A "C1'" 1 +ATOM 50 N N1 . DC A 1 3 ? 20.196 22.889 19.521 1.00 25.44 ? 3 DC A N1 1 +ATOM 51 C C2 . DC A 1 3 ? 18.909 22.584 19.816 1.00 19.81 ? 3 DC A C2 1 +ATOM 52 O O2 . DC A 1 3 ? 18.685 21.512 20.382 1.00 29.92 ? 3 DC A O2 1 +ATOM 53 N N3 . DC A 1 3 ? 17.935 23.447 19.502 1.00 21.59 ? 3 DC A N3 1 +ATOM 54 C C4 . DC A 1 3 ? 18.217 24.603 18.897 1.00 14.01 ? 3 DC A C4 1 +ATOM 55 N N4 . DC A 1 3 ? 17.221 25.499 18.629 1.00 26.88 ? 3 DC A N4 1 +ATOM 56 C C5 . DC A 1 3 ? 19.526 24.945 18.571 1.00 27.59 ? 3 DC A C5 1 +ATOM 57 C C6 . DC A 1 3 ? 20.537 24.048 18.899 1.00 27.05 ? 3 DC A C6 1 +ATOM 58 P P . DG A 1 4 ? 24.249 19.412 17.617 1.00 44.54 ? 4 DG A P 1 +ATOM 59 O OP1 . DG A 1 4 ? 25.420 18.535 17.765 1.00 61.90 ? 4 DG A OP1 1 +ATOM 60 O OP2 . DG A 1 4 ? 24.208 20.296 16.440 1.00 37.36 ? 4 DG A OP2 1 +ATOM 61 O "O5'" . DG A 1 4 ? 22.931 18.537 17.670 1.00 32.01 ? 4 DG A "O5'" 1 +ATOM 62 C "C5'" . DG A 1 4 ? 22.714 17.625 18.753 1.00 37.89 ? 4 DG A "C5'" 1 +ATOM 63 C "C4'" . DG A 1 4 ? 21.393 16.960 18.505 1.00 53.00 ? 4 DG A "C4'" 1 +ATOM 64 O "O4'" . DG A 1 4 ? 20.353 17.952 18.496 1.00 38.79 ? 4 DG A "O4'" 1 +ATOM 65 C "C3'" . DG A 1 4 ? 21.264 16.229 17.176 1.00 56.72 ? 4 DG A "C3'" 1 +ATOM 66 O "O3'" . DG A 1 4 ? 20.284 15.214 17.238 1.00 64.12 ? 4 DG A "O3'" 1 +ATOM 67 C "C2'" . DG A 1 4 ? 20.793 17.368 16.288 1.00 40.81 ? 4 DG A "C2'" 1 +ATOM 68 C "C1'" . DG A 1 4 ? 19.716 17.901 17.218 1.00 30.52 ? 4 DG A "C1'" 1 +ATOM 69 N N9 . DG A 1 4 ? 19.305 19.281 16.869 1.00 28.53 ? 4 DG A N9 1 +ATOM 70 C C8 . DG A 1 4 ? 20.017 20.263 16.232 1.00 27.82 ? 4 DG A C8 1 +ATOM 71 N N7 . DG A 1 4 ? 19.313 21.394 16.077 1.00 28.01 ? 4 DG A N7 1 +ATOM 72 C C5 . DG A 1 4 ? 18.121 21.100 16.635 1.00 23.22 ? 4 DG A C5 1 +ATOM 73 C C6 . DG A 1 4 ? 16.952 21.904 16.749 1.00 29.21 ? 4 DG A C6 1 +ATOM 74 O O6 . DG A 1 4 ? 16.769 23.057 16.368 1.00 38.58 ? 4 DG A O6 1 +ATOM 75 N N1 . DG A 1 4 ? 15.933 21.214 17.352 1.00 27.94 ? 4 DG A N1 1 +ATOM 76 C C2 . DG A 1 4 ? 15.972 19.930 17.816 1.00 23.44 ? 4 DG A C2 1 +ATOM 77 N N2 . DG A 1 4 ? 14.831 19.416 18.353 1.00 42.64 ? 4 DG A N2 1 +ATOM 78 N N3 . DG A 1 4 ? 17.068 19.179 17.717 1.00 21.56 ? 4 DG A N3 1 +ATOM 79 C C4 . DG A 1 4 ? 18.084 19.825 17.121 1.00 23.44 ? 4 DG A C4 1 +ATOM 80 P P . DA A 1 5 ? 20.356 13.969 16.245 1.00 57.01 ? 5 DA A P 1 +ATOM 81 O OP1 . DA A 1 5 ? 21.116 12.891 16.892 1.00 58.59 ? 5 DA A OP1 1 +ATOM 82 O OP2 . DA A 1 5 ? 20.837 14.423 14.910 1.00 51.96 ? 5 DA A OP2 1 +ATOM 83 O "O5'" . DA A 1 5 ? 18.810 13.581 16.161 1.00 47.12 ? 5 DA A "O5'" 1 +ATOM 84 C "C5'" . DA A 1 5 ? 18.015 13.569 17.362 1.00 47.67 ? 5 DA A "C5'" 1 +ATOM 85 C "C4'" . DA A 1 5 ? 16.672 14.088 16.957 1.00 64.79 ? 5 DA A "C4'" 1 +ATOM 86 O "O4'" . DA A 1 5 ? 16.842 15.447 16.561 1.00 47.60 ? 5 DA A "O4'" 1 +ATOM 87 C "C3'" . DA A 1 5 ? 16.019 13.393 15.764 1.00 51.50 ? 5 DA A "C3'" 1 +ATOM 88 O "O3'" . DA A 1 5 ? 14.762 12.796 16.120 1.00 52.18 ? 5 DA A "O3'" 1 +ATOM 89 C "C2'" . DA A 1 5 ? 15.952 14.498 14.696 1.00 45.00 ? 5 DA A "C2'" 1 +ATOM 90 C "C1'" . DA A 1 5 ? 15.851 15.732 15.569 1.00 26.88 ? 5 DA A "C1'" 1 +ATOM 91 N N9 . DA A 1 5 ? 16.391 16.916 14.867 1.00 16.69 ? 5 DA A N9 1 +ATOM 92 C C8 . DA A 1 5 ? 17.658 17.103 14.382 1.00 28.14 ? 5 DA A C8 1 +ATOM 93 N N7 . DA A 1 5 ? 17.863 18.346 13.913 1.00 34.85 ? 5 DA A N7 1 +ATOM 94 C C5 . DA A 1 5 ? 16.673 18.953 14.098 1.00 22.49 ? 5 DA A C5 1 +ATOM 95 C C6 . DA A 1 5 ? 16.230 20.279 13.819 1.00 18.12 ? 5 DA A C6 1 +ATOM 96 N N6 . DA A 1 5 ? 17.045 21.222 13.268 1.00 29.30 ? 5 DA A N6 1 +ATOM 97 N N1 . DA A 1 5 ? 14.966 20.578 14.118 1.00 27.61 ? 5 DA A N1 1 +ATOM 98 C C2 . DA A 1 5 ? 14.178 19.652 14.669 1.00 18.53 ? 5 DA A C2 1 +ATOM 99 N N3 . DA A 1 5 ? 14.463 18.392 14.984 1.00 29.16 ? 5 DA A N3 1 +ATOM 100 C C4 . DA A 1 5 ? 15.750 18.110 14.661 1.00 15.08 ? 5 DA A C4 1 +ATOM 101 P P . DA A 1 6 ? 13.866 12.006 15.063 1.00 43.68 ? 6 DA A P 1 +ATOM 102 O OP1 . DA A 1 6 ? 13.028 11.039 15.800 1.00 42.55 ? 6 DA A OP1 1 +ATOM 103 O OP2 . DA A 1 6 ? 14.715 11.499 13.968 1.00 54.20 ? 6 DA A OP2 1 +ATOM 104 O "O5'" . DA A 1 6 ? 12.879 13.111 14.480 1.00 28.20 ? 6 DA A "O5'" 1 +ATOM 105 C "C5'" . DA A 1 6 ? 11.802 13.597 15.290 1.00 42.29 ? 6 DA A "C5'" 1 +ATOM 106 C "C4'" . DA A 1 6 ? 11.111 14.603 14.435 1.00 33.23 ? 6 DA A "C4'" 1 +ATOM 107 O "O4'" . DA A 1 6 ? 12.152 15.460 13.962 1.00 41.48 ? 6 DA A "O4'" 1 +ATOM 108 C "C3'" . DA A 1 6 ? 10.417 14.070 13.187 1.00 18.16 ? 6 DA A "C3'" 1 +ATOM 109 O "O3'" . DA A 1 6 ? 9.007 14.369 13.181 1.00 30.42 ? 6 DA A "O3'" 1 +ATOM 110 C "C2'" . DA A 1 6 ? 11.240 14.692 12.061 1.00 52.97 ? 6 DA A "C2'" 1 +ATOM 111 C "C1'" . DA A 1 6 ? 11.699 15.974 12.719 1.00 38.93 ? 6 DA A "C1'" 1 +ATOM 112 N N9 . DA A 1 6 ? 12.918 16.526 12.078 1.00 19.06 ? 6 DA A N9 1 +ATOM 113 C C8 . DA A 1 6 ? 14.115 15.899 11.868 1.00 17.83 ? 6 DA A C8 1 +ATOM 114 N N7 . DA A 1 6 ? 15.049 16.714 11.356 1.00 29.55 ? 6 DA A N7 1 +ATOM 115 C C5 . DA A 1 6 ? 14.416 17.901 11.246 1.00 19.88 ? 6 DA A C5 1 +ATOM 116 C C6 . DA A 1 6 ? 14.873 19.187 10.815 1.00 17.26 ? 6 DA A C6 1 +ATOM 117 N N6 . DA A 1 6 ? 16.161 19.418 10.427 1.00 19.85 ? 6 DA A N6 1 +ATOM 118 N N1 . DA A 1 6 ? 13.999 20.191 10.852 1.00 17.93 ? 6 DA A N1 1 +ATOM 119 C C2 . DA A 1 6 ? 12.753 19.962 11.272 1.00 23.00 ? 6 DA A C2 1 +ATOM 120 N N3 . DA A 1 6 ? 12.210 18.824 11.698 1.00 21.37 ? 6 DA A N3 1 +ATOM 121 C C4 . DA A 1 6 ? 13.116 17.823 11.657 1.00 15.93 ? 6 DA A C4 1 +ATOM 122 P P . DT A 1 7 ? 8.081 14.050 11.915 1.00 40.72 ? 7 DT A P 1 +ATOM 123 O OP1 . DT A 1 7 ? 6.668 13.960 12.342 1.00 46.75 ? 7 DT A OP1 1 +ATOM 124 O OP2 . DT A 1 7 ? 8.600 12.894 11.137 1.00 42.53 ? 7 DT A OP2 1 +ATOM 125 O "O5'" . DT A 1 7 ? 8.239 15.387 11.076 1.00 35.21 ? 7 DT A "O5'" 1 +ATOM 126 C "C5'" . DT A 1 7 ? 7.907 16.635 11.686 1.00 34.88 ? 7 DT A "C5'" 1 +ATOM 127 C "C4'" . DT A 1 7 ? 8.162 17.628 10.598 1.00 31.45 ? 7 DT A "C4'" 1 +ATOM 128 O "O4'" . DT A 1 7 ? 9.543 17.580 10.279 1.00 46.82 ? 7 DT A "O4'" 1 +ATOM 129 C "C3'" . DT A 1 7 ? 7.461 17.284 9.296 1.00 23.76 ? 7 DT A "C3'" 1 +ATOM 130 O "O3'" . DT A 1 7 ? 6.251 18.034 9.162 1.00 44.27 ? 7 DT A "O3'" 1 +ATOM 131 C "C2'" . DT A 1 7 ? 8.532 17.527 8.223 1.00 26.30 ? 7 DT A "C2'" 1 +ATOM 132 C "C1'" . DT A 1 7 ? 9.644 18.209 9.019 1.00 28.96 ? 7 DT A "C1'" 1 +ATOM 133 N N1 . DT A 1 7 ? 11.021 17.903 8.565 1.00 20.47 ? 7 DT A N1 1 +ATOM 134 C C2 . DT A 1 7 ? 11.822 18.923 8.176 1.00 28.01 ? 7 DT A C2 1 +ATOM 135 O O2 . DT A 1 7 ? 11.383 20.077 8.143 1.00 40.01 ? 7 DT A O2 1 +ATOM 136 N N3 . DT A 1 7 ? 13.119 18.641 7.852 1.00 27.94 ? 7 DT A N3 1 +ATOM 137 C C4 . DT A 1 7 ? 13.633 17.372 7.882 1.00 15.14 ? 7 DT A C4 1 +ATOM 138 O O4 . DT A 1 7 ? 14.830 17.222 7.619 1.00 32.54 ? 7 DT A O4 1 +ATOM 139 C C5 . DT A 1 7 ? 12.781 16.325 8.235 1.00 10.83 ? 7 DT A C5 1 +ATOM 140 C C7 . DT A 1 7 ? 13.269 14.902 8.236 1.00 36.33 ? 7 DT A C7 1 +ATOM 141 C C6 . DT A 1 7 ? 11.465 16.616 8.594 1.00 12.19 ? 7 DT A C6 1 +ATOM 142 P P . DT A 1 8 ? 5.384 17.990 7.824 1.00 49.10 ? 8 DT A P 1 +ATOM 143 O OP1 . DT A 1 8 ? 4.025 18.444 8.180 1.00 41.11 ? 8 DT A OP1 1 +ATOM 144 O OP2 . DT A 1 8 ? 5.458 16.668 7.160 1.00 39.21 ? 8 DT A OP2 1 +ATOM 145 O "O5'" . DT A 1 8 ? 6.086 19.118 6.927 1.00 48.80 ? 8 DT A "O5'" 1 +ATOM 146 C "C5'" . DT A 1 8 ? 6.146 20.478 7.418 1.00 34.73 ? 8 DT A "C5'" 1 +ATOM 147 C "C4'" . DT A 1 8 ? 6.995 21.229 6.438 1.00 28.73 ? 8 DT A "C4'" 1 +ATOM 148 O "O4'" . DT A 1 8 ? 8.188 20.458 6.284 1.00 39.07 ? 8 DT A "O4'" 1 +ATOM 149 C "C3'" . DT A 1 8 ? 6.418 21.332 5.029 1.00 37.88 ? 8 DT A "C3'" 1 +ATOM 150 O "O3'" . DT A 1 8 ? 5.967 22.667 4.696 1.00 52.04 ? 8 DT A "O3'" 1 +ATOM 151 C "C2'" . DT A 1 8 ? 7.513 20.718 4.139 1.00 32.80 ? 8 DT A "C2'" 1 +ATOM 152 C "C1'" . DT A 1 8 ? 8.736 20.855 5.034 1.00 36.58 ? 8 DT A "C1'" 1 +ATOM 153 N N1 . DT A 1 8 ? 9.823 19.876 4.759 1.00 24.57 ? 8 DT A N1 1 +ATOM 154 C C2 . DT A 1 8 ? 11.086 20.316 4.494 1.00 19.41 ? 8 DT A C2 1 +ATOM 155 O O2 . DT A 1 8 ? 11.324 21.516 4.389 1.00 32.74 ? 8 DT A O2 1 +ATOM 156 N N3 . DT A 1 8 ? 12.094 19.403 4.412 1.00 25.12 ? 8 DT A N3 1 +ATOM 157 C C4 . DT A 1 8 ? 11.876 18.060 4.551 1.00 31.35 ? 8 DT A C4 1 +ATOM 158 O O4 . DT A 1 8 ? 12.858 17.317 4.503 1.00 28.53 ? 8 DT A O4 1 +ATOM 159 C C5 . DT A 1 8 ? 10.569 17.611 4.765 1.00 22.80 ? 8 DT A C5 1 +ATOM 160 C C7 . DT A 1 8 ? 10.261 16.140 4.896 1.00 24.98 ? 8 DT A C7 1 +ATOM 161 C C6 . DT A 1 8 ? 9.545 18.548 4.904 1.00 20.28 ? 8 DT A C6 1 +ATOM 162 P P . DC A 1 9 ? 5.531 23.071 3.209 1.00 48.97 ? 9 DC A P 1 +ATOM 163 O OP1 . DC A 1 9 ? 4.648 24.244 3.269 1.00 62.33 ? 9 DC A OP1 1 +ATOM 164 O OP2 . DC A 1 9 ? 5.010 21.905 2.470 1.00 51.53 ? 9 DC A OP2 1 +ATOM 165 O "O5'" . DC A 1 9 ? 6.926 23.547 2.611 1.00 43.99 ? 9 DC A "O5'" 1 +ATOM 166 C "C5'" . DC A 1 9 ? 7.636 24.627 3.249 1.00 50.86 ? 9 DC A "C5'" 1 +ATOM 167 C "C4'" . DC A 1 9 ? 8.897 24.853 2.457 1.00 46.66 ? 9 DC A "C4'" 1 +ATOM 168 O "O4'" . DC A 1 9 ? 9.638 23.627 2.448 1.00 42.69 ? 9 DC A "O4'" 1 +ATOM 169 C "C3'" . DC A 1 9 ? 8.717 25.240 0.998 1.00 56.96 ? 9 DC A "C3'" 1 +ATOM 170 O "O3'" . DC A 1 9 ? 9.470 26.414 0.667 1.00 63.54 ? 9 DC A "O3'" 1 +ATOM 171 C "C2'" . DC A 1 9 ? 9.126 23.965 0.253 1.00 50.41 ? 9 DC A "C2'" 1 +ATOM 172 C "C1'" . DC A 1 9 ? 10.241 23.483 1.157 1.00 41.08 ? 9 DC A "C1'" 1 +ATOM 173 N N1 . DC A 1 9 ? 10.524 22.022 1.015 1.00 37.23 ? 9 DC A N1 1 +ATOM 174 C C2 . DC A 1 9 ? 11.814 21.603 0.840 1.00 40.54 ? 9 DC A C2 1 +ATOM 175 O O2 . DC A 1 9 ? 12.691 22.447 0.670 1.00 43.89 ? 9 DC A O2 1 +ATOM 176 N N3 . DC A 1 9 ? 12.106 20.297 0.873 1.00 32.57 ? 9 DC A N3 1 +ATOM 177 C C4 . DC A 1 9 ? 11.141 19.395 1.046 1.00 24.65 ? 9 DC A C4 1 +ATOM 178 N N4 . DC A 1 9 ? 11.461 18.075 1.089 1.00 27.84 ? 9 DC A N4 1 +ATOM 179 C C5 . DC A 1 9 ? 9.803 19.775 1.177 1.00 17.61 ? 9 DC A C5 1 +ATOM 180 C C6 . DC A 1 9 ? 9.499 21.133 1.167 1.00 30.63 ? 9 DC A C6 1 +ATOM 181 P P . DG A 1 10 ? 9.055 27.333 -0.581 1.00 65.48 ? 10 DG A P 1 +ATOM 182 O OP1 . DG A 1 10 ? 9.496 28.717 -0.258 1.00 59.09 ? 10 DG A OP1 1 +ATOM 183 O OP2 . DG A 1 10 ? 7.632 27.106 -0.947 1.00 45.71 ? 10 DG A OP2 1 +ATOM 184 O "O5'" . DG A 1 10 ? 9.954 26.765 -1.771 1.00 70.30 ? 10 DG A "O5'" 1 +ATOM 185 C "C5'" . DG A 1 10 ? 11.382 26.940 -1.720 1.00 71.73 ? 10 DG A "C5'" 1 +ATOM 186 C "C4'" . DG A 1 10 ? 11.972 26.090 -2.802 1.00 58.69 ? 10 DG A "C4'" 1 +ATOM 187 O "O4'" . DG A 1 10 ? 11.802 24.724 -2.404 1.00 41.03 ? 10 DG A "O4'" 1 +ATOM 188 C "C3'" . DG A 1 10 ? 11.327 26.178 -4.188 1.00 45.61 ? 10 DG A "C3'" 1 +ATOM 189 O "O3'" . DG A 1 10 ? 12.311 26.096 -5.214 1.00 52.70 ? 10 DG A "O3'" 1 +ATOM 190 C "C2'" . DG A 1 10 ? 10.414 24.962 -4.186 1.00 36.02 ? 10 DG A "C2'" 1 +ATOM 191 C "C1'" . DG A 1 10 ? 11.429 24.028 -3.587 1.00 50.90 ? 10 DG A "C1'" 1 +ATOM 192 N N9 . DG A 1 10 ? 10.890 22.713 -3.200 1.00 45.86 ? 10 DG A N9 1 +ATOM 193 C C8 . DG A 1 10 ? 9.616 22.315 -2.910 1.00 44.49 ? 10 DG A C8 1 +ATOM 194 N N7 . DG A 1 10 ? 9.541 21.009 -2.613 1.00 39.96 ? 10 DG A N7 1 +ATOM 195 C C5 . DG A 1 10 ? 10.818 20.588 -2.718 1.00 38.99 ? 10 DG A C5 1 +ATOM 196 C C6 . DG A 1 10 ? 11.376 19.292 -2.511 1.00 35.78 ? 10 DG A C6 1 +ATOM 197 O O6 . DG A 1 10 ? 10.813 18.252 -2.179 1.00 34.90 ? 10 DG A O6 1 +ATOM 198 N N1 . DG A 1 10 ? 12.729 19.299 -2.720 1.00 23.54 ? 10 DG A N1 1 +ATOM 199 C C2 . DG A 1 10 ? 13.498 20.365 -3.082 1.00 8.73 ? 10 DG A C2 1 +ATOM 200 N N2 . DG A 1 10 ? 14.834 20.169 -3.237 1.00 23.15 ? 10 DG A N2 1 +ATOM 201 N N3 . DG A 1 10 ? 12.982 21.573 -3.267 1.00 24.68 ? 10 DG A N3 1 +ATOM 202 C C4 . DG A 1 10 ? 11.656 21.601 -3.061 1.00 31.53 ? 10 DG A C4 1 +ATOM 203 P P . DC A 1 11 ? 12.763 27.421 -5.980 1.00 60.62 ? 11 DC A P 1 +ATOM 204 O OP1 . DC A 1 11 ? 12.796 28.572 -5.049 1.00 63.74 ? 11 DC A OP1 1 +ATOM 205 O OP2 . DC A 1 11 ? 11.886 27.542 -7.164 1.00 52.44 ? 11 DC A OP2 1 +ATOM 206 O "O5'" . DC A 1 11 ? 14.272 27.086 -6.366 1.00 57.57 ? 11 DC A "O5'" 1 +ATOM 207 C "C5'" . DC A 1 11 ? 15.275 27.108 -5.318 1.00 54.70 ? 11 DC A "C5'" 1 +ATOM 208 C "C4'" . DC A 1 11 ? 16.222 25.946 -5.510 1.00 72.51 ? 11 DC A "C4'" 1 +ATOM 209 O "O4'" . DC A 1 11 ? 15.443 24.754 -5.397 1.00 47.18 ? 11 DC A "O4'" 1 +ATOM 210 C "C3'" . DC A 1 11 ? 16.942 25.827 -6.848 1.00 29.82 ? 11 DC A "C3'" 1 +ATOM 211 O "O3'" . DC A 1 11 ? 18.340 25.511 -6.701 1.00 43.53 ? 11 DC A "O3'" 1 +ATOM 212 C "C2'" . DC A 1 11 ? 16.118 24.767 -7.578 1.00 51.34 ? 11 DC A "C2'" 1 +ATOM 213 C "C1'" . DC A 1 11 ? 15.856 23.836 -6.414 1.00 30.07 ? 11 DC A "C1'" 1 +ATOM 214 N N1 . DC A 1 11 ? 14.672 22.975 -6.637 1.00 23.25 ? 11 DC A N1 1 +ATOM 215 C C2 . DC A 1 11 ? 14.802 21.628 -6.529 1.00 20.38 ? 11 DC A C2 1 +ATOM 216 O O2 . DC A 1 11 ? 15.924 21.178 -6.314 1.00 38.77 ? 11 DC A O2 1 +ATOM 217 N N3 . DC A 1 11 ? 13.723 20.842 -6.627 1.00 15.92 ? 11 DC A N3 1 +ATOM 218 C C4 . DC A 1 11 ? 12.515 21.373 -6.836 1.00 15.82 ? 11 DC A C4 1 +ATOM 219 N N4 . DC A 1 11 ? 11.410 20.574 -6.872 1.00 28.04 ? 11 DC A N4 1 +ATOM 220 C C5 . DC A 1 11 ? 12.348 22.744 -6.978 1.00 26.17 ? 11 DC A C5 1 +ATOM 221 C C6 . DC A 1 11 ? 13.470 23.558 -6.869 1.00 35.50 ? 11 DC A C6 1 +ATOM 222 P P . DG A 1 12 ? 19.331 25.774 -7.925 1.00 55.98 ? 12 DG A P 1 +ATOM 223 O OP1 . DG A 1 12 ? 20.704 25.976 -7.408 1.00 45.83 ? 12 DG A OP1 1 +ATOM 224 O OP2 . DG A 1 12 ? 18.763 26.851 -8.758 1.00 44.26 ? 12 DG A OP2 1 +ATOM 225 O "O5'" . DG A 1 12 ? 19.302 24.412 -8.763 1.00 62.63 ? 12 DG A "O5'" 1 +ATOM 226 C "C5'" . DG A 1 12 ? 20.109 23.284 -8.359 1.00 69.50 ? 12 DG A "C5'" 1 +ATOM 227 C "C4'" . DG A 1 12 ? 19.748 22.167 -9.299 1.00 39.92 ? 12 DG A "C4'" 1 +ATOM 228 O "O4'" . DG A 1 12 ? 18.350 21.969 -9.139 1.00 32.00 ? 12 DG A "O4'" 1 +ATOM 229 C "C3'" . DG A 1 12 ? 19.921 22.404 -10.815 1.00 50.39 ? 12 DG A "C3'" 1 +ATOM 230 O "O3'" . DG A 1 12 ? 20.985 21.635 -11.401 1.00 64.13 ? 12 DG A "O3'" 1 +ATOM 231 C "C2'" . DG A 1 12 ? 18.535 22.062 -11.381 1.00 36.18 ? 12 DG A "C2'" 1 +ATOM 232 C "C1'" . DG A 1 12 ? 17.965 21.200 -10.269 1.00 24.79 ? 12 DG A "C1'" 1 +ATOM 233 N N9 . DG A 1 12 ? 16.493 21.220 -10.265 1.00 28.44 ? 12 DG A N9 1 +ATOM 234 C C8 . DG A 1 12 ? 15.663 22.289 -10.478 1.00 31.85 ? 12 DG A C8 1 +ATOM 235 N N7 . DG A 1 12 ? 14.368 21.958 -10.390 1.00 38.26 ? 12 DG A N7 1 +ATOM 236 C C5 . DG A 1 12 ? 14.388 20.640 -10.102 1.00 28.99 ? 12 DG A C5 1 +ATOM 237 C C6 . DG A 1 12 ? 13.301 19.742 -9.856 1.00 42.63 ? 12 DG A C6 1 +ATOM 238 O O6 . DG A 1 12 ? 12.091 19.967 -9.857 1.00 49.17 ? 12 DG A O6 1 +ATOM 239 N N1 . DG A 1 12 ? 13.750 18.466 -9.625 1.00 40.15 ? 12 DG A N1 1 +ATOM 240 C C2 . DG A 1 12 ? 15.042 18.043 -9.605 1.00 33.42 ? 12 DG A C2 1 +ATOM 241 N N2 . DG A 1 12 ? 15.259 16.717 -9.406 1.00 40.53 ? 12 DG A N2 1 +ATOM 242 N N3 . DG A 1 12 ? 16.061 18.885 -9.792 1.00 37.34 ? 12 DG A N3 1 +ATOM 243 C C4 . DG A 1 12 ? 15.660 20.156 -10.027 1.00 31.14 ? 12 DG A C4 1 +ATOM 244 O "O5'" . DC B 1 1 ? 7.458 11.884 -9.070 1.00 66.23 ? 13 DC B "O5'" 1 +ATOM 245 C "C5'" . DC B 1 1 ? 8.252 10.968 -9.854 1.00 71.49 ? 13 DC B "C5'" 1 +ATOM 246 C "C4'" . DC B 1 1 ? 9.714 11.141 -9.512 1.00 56.82 ? 13 DC B "C4'" 1 +ATOM 247 O "O4'" . DC B 1 1 ? 10.144 12.455 -9.908 1.00 57.92 ? 13 DC B "O4'" 1 +ATOM 248 C "C3'" . DC B 1 1 ? 10.103 10.989 -8.055 1.00 34.34 ? 13 DC B "C3'" 1 +ATOM 249 O "O3'" . DC B 1 1 ? 11.293 10.221 -7.904 1.00 42.11 ? 13 DC B "O3'" 1 +ATOM 250 C "C2'" . DC B 1 1 ? 10.254 12.437 -7.607 1.00 29.08 ? 13 DC B "C2'" 1 +ATOM 251 C "C1'" . DC B 1 1 ? 10.896 13.044 -8.837 1.00 38.40 ? 13 DC B "C1'" 1 +ATOM 252 N N1 . DC B 1 1 ? 10.575 14.487 -8.944 1.00 34.33 ? 13 DC B N1 1 +ATOM 253 C C2 . DC B 1 1 ? 11.559 15.430 -9.006 1.00 22.98 ? 13 DC B C2 1 +ATOM 254 O O2 . DC B 1 1 ? 12.725 15.066 -8.932 1.00 50.83 ? 13 DC B O2 1 +ATOM 255 N N3 . DC B 1 1 ? 11.246 16.714 -9.193 1.00 37.14 ? 13 DC B N3 1 +ATOM 256 C C4 . DC B 1 1 ? 9.980 17.088 -9.334 1.00 42.60 ? 13 DC B C4 1 +ATOM 257 N N4 . DC B 1 1 ? 9.698 18.395 -9.589 1.00 54.91 ? 13 DC B N4 1 +ATOM 258 C C5 . DC B 1 1 ? 8.939 16.162 -9.274 1.00 56.67 ? 13 DC B C5 1 +ATOM 259 C C6 . DC B 1 1 ? 9.265 14.824 -9.080 1.00 49.21 ? 13 DC B C6 1 +ATOM 260 P P . DG B 1 2 ? 11.602 9.510 -6.502 1.00 60.42 ? 14 DG B P 1 +ATOM 261 O OP1 . DG B 1 2 ? 11.666 8.032 -6.664 1.00 57.44 ? 14 DG B OP1 1 +ATOM 262 O OP2 . DG B 1 2 ? 10.644 10.010 -5.494 1.00 46.07 ? 14 DG B OP2 1 +ATOM 263 O "O5'" . DG B 1 2 ? 13.051 10.094 -6.177 1.00 50.94 ? 14 DG B "O5'" 1 +ATOM 264 C "C5'" . DG B 1 2 ? 14.100 10.021 -7.156 1.00 34.84 ? 14 DG B "C5'" 1 +ATOM 265 C "C4'" . DG B 1 2 ? 15.113 10.992 -6.657 1.00 48.06 ? 14 DG B "C4'" 1 +ATOM 266 O "O4'" . DG B 1 2 ? 14.556 12.300 -6.755 1.00 37.01 ? 14 DG B "O4'" 1 +ATOM 267 C "C3'" . DG B 1 2 ? 15.445 10.806 -5.189 1.00 50.58 ? 14 DG B "C3'" 1 +ATOM 268 O "O3'" . DG B 1 2 ? 16.836 10.560 -5.013 1.00 51.98 ? 14 DG B "O3'" 1 +ATOM 269 C "C2'" . DG B 1 2 ? 14.937 12.100 -4.529 1.00 40.32 ? 14 DG B "C2'" 1 +ATOM 270 C "C1'" . DG B 1 2 ? 15.058 13.086 -5.671 1.00 46.69 ? 14 DG B "C1'" 1 +ATOM 271 N N9 . DG B 1 2 ? 14.036 14.140 -5.536 1.00 29.17 ? 14 DG B N9 1 +ATOM 272 C C8 . DG B 1 2 ? 12.710 13.957 -5.259 1.00 23.48 ? 14 DG B C8 1 +ATOM 273 N N7 . DG B 1 2 ? 12.016 15.103 -5.269 1.00 37.54 ? 14 DG B N7 1 +ATOM 274 C C5 . DG B 1 2 ? 12.937 16.041 -5.558 1.00 26.27 ? 14 DG B C5 1 +ATOM 275 C C6 . DG B 1 2 ? 12.761 17.451 -5.710 1.00 40.82 ? 14 DG B C6 1 +ATOM 276 O O6 . DG B 1 2 ? 11.723 18.111 -5.630 1.00 44.39 ? 14 DG B O6 1 +ATOM 277 N N1 . DG B 1 2 ? 13.952 18.079 -5.973 1.00 19.52 ? 14 DG B N1 1 +ATOM 278 C C2 . DG B 1 2 ? 15.171 17.485 -6.107 1.00 18.48 ? 14 DG B C2 1 +ATOM 279 N N2 . DG B 1 2 ? 16.244 18.292 -6.325 1.00 36.58 ? 14 DG B N2 1 +ATOM 280 N N3 . DG B 1 2 ? 15.329 16.161 -5.986 1.00 46.96 ? 14 DG B N3 1 +ATOM 281 C C4 . DG B 1 2 ? 14.179 15.499 -5.721 1.00 35.70 ? 14 DG B C4 1 +ATOM 282 P P . DC B 1 3 ? 17.478 10.380 -3.569 1.00 46.26 ? 15 DC B P 1 +ATOM 283 O OP1 . DC B 1 3 ? 18.665 9.516 -3.729 1.00 46.07 ? 15 DC B OP1 1 +ATOM 284 O OP2 . DC B 1 3 ? 16.427 9.940 -2.633 1.00 40.43 ? 15 DC B OP2 1 +ATOM 285 O "O5'" . DC B 1 3 ? 17.957 11.865 -3.208 1.00 40.97 ? 15 DC B "O5'" 1 +ATOM 286 C "C5'" . DC B 1 3 ? 18.963 12.531 -3.996 1.00 28.78 ? 15 DC B "C5'" 1 +ATOM 287 C "C4'" . DC B 1 3 ? 18.936 13.958 -3.536 1.00 32.84 ? 15 DC B "C4'" 1 +ATOM 288 O "O4'" . DC B 1 3 ? 17.592 14.409 -3.622 1.00 37.24 ? 15 DC B "O4'" 1 +ATOM 289 C "C3'" . DC B 1 3 ? 19.253 14.139 -2.066 1.00 43.98 ? 15 DC B "C3'" 1 +ATOM 290 O "O3'" . DC B 1 3 ? 20.659 14.219 -1.858 1.00 40.90 ? 15 DC B "O3'" 1 +ATOM 291 C "C2'" . DC B 1 3 ? 18.520 15.417 -1.728 1.00 36.26 ? 15 DC B "C2'" 1 +ATOM 292 C "C1'" . DC B 1 3 ? 17.545 15.602 -2.872 1.00 20.54 ? 15 DC B "C1'" 1 +ATOM 293 N N1 . DC B 1 3 ? 16.145 15.696 -2.428 1.00 23.10 ? 15 DC B N1 1 +ATOM 294 C C2 . DC B 1 3 ? 15.507 16.886 -2.558 1.00 32.12 ? 15 DC B C2 1 +ATOM 295 O O2 . DC B 1 3 ? 16.162 17.846 -2.957 1.00 30.04 ? 15 DC B O2 1 +ATOM 296 N N3 . DC B 1 3 ? 14.209 16.983 -2.264 1.00 32.94 ? 15 DC B N3 1 +ATOM 297 C C4 . DC B 1 3 ? 13.536 15.919 -1.825 1.00 16.43 ? 15 DC B C4 1 +ATOM 298 N N4 . DC B 1 3 ? 12.205 16.017 -1.553 1.00 34.91 ? 15 DC B N4 1 +ATOM 299 C C5 . DC B 1 3 ? 14.164 14.689 -1.652 1.00 22.75 ? 15 DC B C5 1 +ATOM 300 C C6 . DC B 1 3 ? 15.509 14.584 -1.979 1.00 26.42 ? 15 DC B C6 1 +ATOM 301 P P . DG B 1 4 ? 21.304 14.529 -0.436 1.00 42.39 ? 16 DG B P 1 +ATOM 302 O OP1 . DG B 1 4 ? 22.696 14.087 -0.524 1.00 60.41 ? 16 DG B OP1 1 +ATOM 303 O OP2 . DG B 1 4 ? 20.488 13.954 0.650 1.00 51.09 ? 16 DG B OP2 1 +ATOM 304 O "O5'" . DG B 1 4 ? 21.306 16.117 -0.363 1.00 45.08 ? 16 DG B "O5'" 1 +ATOM 305 C "C5'" . DG B 1 4 ? 22.177 16.876 -1.212 1.00 33.20 ? 16 DG B "C5'" 1 +ATOM 306 C "C4'" . DG B 1 4 ? 21.739 18.292 -1.021 1.00 24.95 ? 16 DG B "C4'" 1 +ATOM 307 O "O4'" . DG B 1 4 ? 20.305 18.225 -1.048 1.00 32.83 ? 16 DG B "O4'" 1 +ATOM 308 C "C3'" . DG B 1 4 ? 22.101 18.959 0.293 1.00 41.12 ? 16 DG B "C3'" 1 +ATOM 309 O "O3'" . DG B 1 4 ? 22.592 20.293 0.097 1.00 53.45 ? 16 DG B "O3'" 1 +ATOM 310 C "C2'" . DG B 1 4 ? 20.820 18.829 1.121 1.00 28.93 ? 16 DG B "C2'" 1 +ATOM 311 C "C1'" . DG B 1 4 ? 19.765 18.985 0.046 1.00 37.44 ? 16 DG B "C1'" 1 +ATOM 312 N N9 . DG B 1 4 ? 18.513 18.299 0.468 1.00 17.75 ? 16 DG B N9 1 +ATOM 313 C C8 . DG B 1 4 ? 18.363 17.062 1.039 1.00 17.96 ? 16 DG B C8 1 +ATOM 314 N N7 . DG B 1 4 ? 17.080 16.744 1.281 1.00 24.14 ? 16 DG B N7 1 +ATOM 315 C C5 . DG B 1 4 ? 16.400 17.832 0.868 1.00 9.96 ? 16 DG B C5 1 +ATOM 316 C C6 . DG B 1 4 ? 14.996 18.090 0.882 1.00 18.10 ? 16 DG B C6 1 +ATOM 317 O O6 . DG B 1 4 ? 14.082 17.378 1.280 1.00 31.13 ? 16 DG B O6 1 +ATOM 318 N N1 . DG B 1 4 ? 14.712 19.349 0.418 1.00 17.72 ? 16 DG B N1 1 +ATOM 319 C C2 . DG B 1 4 ? 15.606 20.268 -0.027 1.00 16.23 ? 16 DG B C2 1 +ATOM 320 N N2 . DG B 1 4 ? 15.134 21.493 -0.382 1.00 33.42 ? 16 DG B N2 1 +ATOM 321 N N3 . DG B 1 4 ? 16.912 20.017 -0.072 1.00 26.37 ? 16 DG B N3 1 +ATOM 322 C C4 . DG B 1 4 ? 17.236 18.794 0.384 1.00 31.72 ? 16 DG B C4 1 +ATOM 323 P P . DA B 1 5 ? 22.904 21.238 1.339 1.00 46.87 ? 17 DA B P 1 +ATOM 324 O OP1 . DA B 1 5 ? 23.994 22.183 1.025 1.00 47.75 ? 17 DA B OP1 1 +ATOM 325 O OP2 . DA B 1 5 ? 23.104 20.390 2.538 1.00 46.81 ? 17 DA B OP2 1 +ATOM 326 O "O5'" . DA B 1 5 ? 21.577 22.107 1.390 1.00 39.51 ? 17 DA B "O5'" 1 +ATOM 327 C "C5'" . DA B 1 5 ? 21.216 22.833 0.200 1.00 30.37 ? 17 DA B "C5'" 1 +ATOM 328 C "C4'" . DA B 1 5 ? 20.101 23.788 0.484 1.00 35.43 ? 17 DA B "C4'" 1 +ATOM 329 O "O4'" . DA B 1 5 ? 18.913 23.054 0.816 1.00 43.05 ? 17 DA B "O4'" 1 +ATOM 330 C "C3'" . DA B 1 5 ? 20.347 24.743 1.633 1.00 44.50 ? 17 DA B "C3'" 1 +ATOM 331 O "O3'" . DA B 1 5 ? 19.732 26.010 1.411 1.00 78.59 ? 17 DA B "O3'" 1 +ATOM 332 C "C2'" . DA B 1 5 ? 19.752 23.945 2.791 1.00 44.42 ? 17 DA B "C2'" 1 +ATOM 333 C "C1'" . DA B 1 5 ? 18.497 23.393 2.145 1.00 42.55 ? 17 DA B "C1'" 1 +ATOM 334 N N9 . DA B 1 5 ? 18.079 22.095 2.758 1.00 34.56 ? 17 DA B N9 1 +ATOM 335 C C8 . DA B 1 5 ? 18.847 21.020 3.133 1.00 20.07 ? 17 DA B C8 1 +ATOM 336 N N7 . DA B 1 5 ? 18.114 19.984 3.584 1.00 27.60 ? 17 DA B N7 1 +ATOM 337 C C5 . DA B 1 5 ? 16.842 20.424 3.488 1.00 18.80 ? 17 DA B C5 1 +ATOM 338 C C6 . DA B 1 5 ? 15.577 19.817 3.786 1.00 32.58 ? 17 DA B C6 1 +ATOM 339 N N6 . DA B 1 5 ? 15.448 18.537 4.242 1.00 29.54 ? 17 DA B N6 1 +ATOM 340 N N1 . DA B 1 5 ? 14.482 20.557 3.593 1.00 35.01 ? 17 DA B N1 1 +ATOM 341 C C2 . DA B 1 5 ? 14.597 21.801 3.118 1.00 36.47 ? 17 DA B C2 1 +ATOM 342 N N3 . DA B 1 5 ? 15.700 22.472 2.783 1.00 38.96 ? 17 DA B N3 1 +ATOM 343 C C4 . DA B 1 5 ? 16.791 21.706 3.002 1.00 28.24 ? 17 DA B C4 1 +ATOM 344 P P . DA B 1 6 ? 19.803 27.141 2.526 1.00 46.11 ? 18 DA B P 1 +ATOM 345 O OP1 . DA B 1 6 ? 19.796 28.478 1.888 1.00 49.20 ? 18 DA B OP1 1 +ATOM 346 O OP2 . DA B 1 6 ? 20.953 26.858 3.426 1.00 43.48 ? 18 DA B OP2 1 +ATOM 347 O "O5'" . DA B 1 6 ? 18.396 26.939 3.241 1.00 40.83 ? 18 DA B "O5'" 1 +ATOM 348 C "C5'" . DA B 1 6 ? 17.203 27.028 2.452 1.00 40.72 ? 18 DA B "C5'" 1 +ATOM 349 C "C4'" . DA B 1 6 ? 16.035 26.958 3.388 1.00 66.52 ? 18 DA B "C4'" 1 +ATOM 350 O "O4'" . DA B 1 6 ? 15.856 25.612 3.850 1.00 44.25 ? 18 DA B "O4'" 1 +ATOM 351 C "C3'" . DA B 1 6 ? 16.101 27.861 4.615 1.00 63.34 ? 18 DA B "C3'" 1 +ATOM 352 O "O3'" . DA B 1 6 ? 14.890 28.608 4.757 1.00 55.65 ? 18 DA B "O3'" 1 +ATOM 353 C "C2'" . DA B 1 6 ? 16.368 26.844 5.724 1.00 34.49 ? 18 DA B "C2'" 1 +ATOM 354 C "C1'" . DA B 1 6 ? 15.561 25.655 5.243 1.00 29.45 ? 18 DA B "C1'" 1 +ATOM 355 N N9 . DA B 1 6 ? 16.104 24.373 5.755 1.00 20.03 ? 18 DA B N9 1 +ATOM 356 C C8 . DA B 1 6 ? 17.411 23.967 5.830 1.00 16.51 ? 18 DA B C8 1 +ATOM 357 N N7 . DA B 1 6 ? 17.539 22.706 6.276 1.00 20.58 ? 18 DA B N7 1 +ATOM 358 C C5 . DA B 1 6 ? 16.266 22.309 6.480 1.00 21.66 ? 18 DA B C5 1 +ATOM 359 C C6 . DA B 1 6 ? 15.715 21.073 6.933 1.00 17.93 ? 18 DA B C6 1 +ATOM 360 N N6 . DA B 1 6 ? 16.483 19.994 7.243 1.00 20.37 ? 18 DA B N6 1 +ATOM 361 N N1 . DA B 1 6 ? 14.389 20.994 7.036 1.00 20.81 ? 18 DA B N1 1 +ATOM 362 C C2 . DA B 1 6 ? 13.636 22.041 6.708 1.00 26.77 ? 18 DA B C2 1 +ATOM 363 N N3 . DA B 1 6 ? 14.019 23.234 6.265 1.00 26.83 ? 18 DA B N3 1 +ATOM 364 C C4 . DA B 1 6 ? 15.367 23.291 6.174 1.00 27.48 ? 18 DA B C4 1 +ATOM 365 P P . DT B 1 7 ? 14.604 29.545 6.020 1.00 48.40 ? 19 DT B P 1 +ATOM 366 O OP1 . DT B 1 7 ? 13.792 30.696 5.582 1.00 50.18 ? 19 DT B OP1 1 +ATOM 367 O OP2 . DT B 1 7 ? 15.852 29.836 6.749 1.00 44.42 ? 19 DT B OP2 1 +ATOM 368 O "O5'" . DT B 1 7 ? 13.633 28.628 6.885 1.00 53.86 ? 19 DT B "O5'" 1 +ATOM 369 C "C5'" . DT B 1 7 ? 12.398 28.171 6.303 1.00 55.04 ? 19 DT B "C5'" 1 +ATOM 370 C "C4'" . DT B 1 7 ? 11.809 27.217 7.302 1.00 44.86 ? 19 DT B "C4'" 1 +ATOM 371 O "O4'" . DT B 1 7 ? 12.767 26.184 7.534 1.00 48.52 ? 19 DT B "O4'" 1 +ATOM 372 C "C3'" . DT B 1 7 ? 11.515 27.822 8.669 1.00 41.77 ? 19 DT B "C3'" 1 +ATOM 373 O "O3'" . DT B 1 7 ? 10.103 27.952 8.891 1.00 57.02 ? 19 DT B "O3'" 1 +ATOM 374 C "C2'" . DT B 1 7 ? 12.267 26.906 9.630 1.00 39.28 ? 19 DT B "C2'" 1 +ATOM 375 C "C1'" . DT B 1 7 ? 12.426 25.645 8.799 1.00 27.68 ? 19 DT B "C1'" 1 +ATOM 376 N N1 . DT B 1 7 ? 13.609 24.850 9.205 1.00 21.67 ? 19 DT B N1 1 +ATOM 377 C C2 . DT B 1 7 ? 13.442 23.575 9.656 1.00 31.71 ? 19 DT B C2 1 +ATOM 378 O O2 . DT B 1 7 ? 12.311 23.101 9.802 1.00 36.00 ? 19 DT B O2 1 +ATOM 379 N N3 . DT B 1 7 ? 14.551 22.825 9.913 1.00 24.66 ? 19 DT B N3 1 +ATOM 380 C C4 . DT B 1 7 ? 15.815 23.321 9.777 1.00 40.64 ? 19 DT B C4 1 +ATOM 381 O O4 . DT B 1 7 ? 16.755 22.570 10.029 1.00 31.47 ? 19 DT B O4 1 +ATOM 382 C C5 . DT B 1 7 ? 15.972 24.647 9.362 1.00 31.79 ? 19 DT B C5 1 +ATOM 383 C C7 . DT B 1 7 ? 17.345 25.239 9.234 1.00 30.05 ? 19 DT B C7 1 +ATOM 384 C C6 . DT B 1 7 ? 14.844 25.405 9.048 1.00 14.35 ? 19 DT B C6 1 +ATOM 385 P P . DT B 1 8 ? 9.513 28.533 10.260 1.00 48.24 ? 20 DT B P 1 +ATOM 386 O OP1 . DT B 1 8 ? 8.145 29.007 9.998 1.00 41.28 ? 20 DT B OP1 1 +ATOM 387 O OP2 . DT B 1 8 ? 10.455 29.513 10.841 1.00 53.39 ? 20 DT B OP2 1 +ATOM 388 O "O5'" . DT B 1 8 ? 9.395 27.223 11.153 1.00 36.57 ? 20 DT B "O5'" 1 +ATOM 389 C "C5'" . DT B 1 8 ? 8.576 26.148 10.664 1.00 50.41 ? 20 DT B "C5'" 1 +ATOM 390 C "C4'" . DT B 1 8 ? 8.655 25.060 11.678 1.00 32.08 ? 20 DT B "C4'" 1 +ATOM 391 O "O4'" . DT B 1 8 ? 10.003 24.615 11.764 1.00 48.38 ? 20 DT B "O4'" 1 +ATOM 392 C "C3'" . DT B 1 8 ? 8.272 25.471 13.087 1.00 29.99 ? 20 DT B "C3'" 1 +ATOM 393 O "O3'" . DT B 1 8 ? 7.199 24.657 13.553 1.00 45.14 ? 20 DT B "O3'" 1 +ATOM 394 C "C2'" . DT B 1 8 ? 9.586 25.307 13.860 1.00 32.42 ? 20 DT B "C2'" 1 +ATOM 395 C "C1'" . DT B 1 8 ? 10.190 24.148 13.089 1.00 39.56 ? 20 DT B "C1'" 1 +ATOM 396 N N1 . DT B 1 8 ? 11.660 24.070 13.205 1.00 20.36 ? 20 DT B N1 1 +ATOM 397 C C2 . DT B 1 8 ? 12.257 22.880 13.486 1.00 27.55 ? 20 DT B C2 1 +ATOM 398 O O2 . DT B 1 8 ? 11.583 21.866 13.691 1.00 38.33 ? 20 DT B O2 1 +ATOM 399 N N3 . DT B 1 8 ? 13.620 22.829 13.497 1.00 29.60 ? 20 DT B N3 1 +ATOM 400 C C4 . DT B 1 8 ? 14.402 23.914 13.225 1.00 30.11 ? 20 DT B C4 1 +ATOM 401 O O4 . DT B 1 8 ? 15.625 23.764 13.252 1.00 32.92 ? 20 DT B O4 1 +ATOM 402 C C5 . DT B 1 8 ? 13.774 25.126 12.933 1.00 24.11 ? 20 DT B C5 1 +ATOM 403 C C7 . DT B 1 8 ? 14.563 26.358 12.612 1.00 23.96 ? 20 DT B C7 1 +ATOM 404 C C6 . DT B 1 8 ? 12.385 25.187 12.926 1.00 19.78 ? 20 DT B C6 1 +ATOM 405 P P . DC B 1 9 ? 6.594 24.823 15.016 1.00 54.73 ? 21 DC B P 1 +ATOM 406 O OP1 . DC B 1 9 ? 5.169 24.424 14.987 1.00 53.98 ? 21 DC B OP1 1 +ATOM 407 O OP2 . DC B 1 9 ? 6.870 26.189 15.511 1.00 65.53 ? 21 DC B OP2 1 +ATOM 408 O "O5'" . DC B 1 9 ? 7.409 23.731 15.839 1.00 50.67 ? 21 DC B "O5'" 1 +ATOM 409 C "C5'" . DC B 1 9 ? 7.331 22.352 15.433 1.00 60.86 ? 21 DC B "C5'" 1 +ATOM 410 C "C4'" . DC B 1 9 ? 8.100 21.598 16.461 1.00 40.86 ? 21 DC B "C4'" 1 +ATOM 411 O "O4'" . DC B 1 9 ? 9.478 21.902 16.263 1.00 36.88 ? 21 DC B "O4'" 1 +ATOM 412 C "C3'" . DC B 1 9 ? 7.766 22.045 17.879 1.00 53.80 ? 21 DC B "C3'" 1 +ATOM 413 O "O3'" . DC B 1 9 ? 7.036 21.041 18.611 1.00 79.04 ? 21 DC B "O3'" 1 +ATOM 414 C "C2'" . DC B 1 9 ? 9.123 22.414 18.469 1.00 48.43 ? 21 DC B "C2'" 1 +ATOM 415 C "C1'" . DC B 1 9 ? 10.107 21.743 17.523 1.00 36.51 ? 21 DC B "C1'" 1 +ATOM 416 N N1 . DC B 1 9 ? 11.328 22.556 17.331 1.00 24.72 ? 21 DC B N1 1 +ATOM 417 C C2 . DC B 1 9 ? 12.534 21.939 17.329 1.00 30.96 ? 21 DC B C2 1 +ATOM 418 O O2 . DC B 1 9 ? 12.560 20.731 17.579 1.00 34.53 ? 21 DC B O2 1 +ATOM 419 N N3 . DC B 1 9 ? 13.639 22.639 17.035 1.00 31.69 ? 21 DC B N3 1 +ATOM 420 C C4 . DC B 1 9 ? 13.560 23.938 16.739 1.00 21.53 ? 21 DC B C4 1 +ATOM 421 N N4 . DC B 1 9 ? 14.685 24.628 16.404 1.00 23.72 ? 21 DC B N4 1 +ATOM 422 C C5 . DC B 1 9 ? 12.338 24.609 16.736 1.00 30.74 ? 21 DC B C5 1 +ATOM 423 C C6 . DC B 1 9 ? 11.193 23.878 17.035 1.00 27.58 ? 21 DC B C6 1 +ATOM 424 P P . DG B 1 10 ? 6.509 21.324 20.099 1.00 56.50 ? 22 DG B P 1 +ATOM 425 O OP1 . DG B 1 10 ? 5.387 20.397 20.396 1.00 50.81 ? 22 DG B OP1 1 +ATOM 426 O OP2 . DG B 1 10 ? 6.235 22.774 20.306 1.00 53.84 ? 22 DG B OP2 1 +ATOM 427 O "O5'" . DG B 1 10 ? 7.767 20.924 20.993 1.00 66.30 ? 22 DG B "O5'" 1 +ATOM 428 C "C5'" . DG B 1 10 ? 8.216 19.559 21.073 1.00 73.42 ? 22 DG B "C5'" 1 +ATOM 429 C "C4'" . DG B 1 10 ? 9.422 19.557 21.977 1.00 42.96 ? 22 DG B "C4'" 1 +ATOM 430 O "O4'" . DG B 1 10 ? 10.493 20.260 21.319 1.00 52.87 ? 22 DG B "O4'" 1 +ATOM 431 C "C3'" . DG B 1 10 ? 9.267 20.267 23.325 1.00 38.51 ? 22 DG B "C3'" 1 +ATOM 432 O "O3'" . DG B 1 10 ? 10.088 19.657 24.293 1.00 60.28 ? 22 DG B "O3'" 1 +ATOM 433 C "C2'" . DG B 1 10 ? 9.751 21.670 22.990 1.00 22.00 ? 22 DG B "C2'" 1 +ATOM 434 C "C1'" . DG B 1 10 ? 10.988 21.226 22.256 1.00 24.85 ? 22 DG B "C1'" 1 +ATOM 435 N N9 . DG B 1 10 ? 11.599 22.357 21.543 1.00 25.91 ? 22 DG B N9 1 +ATOM 436 C C8 . DG B 1 10 ? 11.037 23.545 21.159 1.00 23.91 ? 22 DG B C8 1 +ATOM 437 N N7 . DG B 1 10 ? 11.921 24.362 20.566 1.00 39.18 ? 22 DG B N7 1 +ATOM 438 C C5 . DG B 1 10 ? 13.072 23.653 20.580 1.00 25.66 ? 22 DG B C5 1 +ATOM 439 C C6 . DG B 1 10 ? 14.370 24.003 20.102 1.00 28.34 ? 22 DG B C6 1 +ATOM 440 O O6 . DG B 1 10 ? 14.747 25.057 19.585 1.00 31.85 ? 22 DG B O6 1 +ATOM 441 N N1 . DG B 1 10 ? 15.268 22.983 20.308 1.00 25.22 ? 22 DG B N1 1 +ATOM 442 C C2 . DG B 1 10 ? 15.023 21.776 20.891 1.00 11.07 ? 22 DG B C2 1 +ATOM 443 N N2 . DG B 1 10 ? 16.066 20.914 21.038 1.00 25.92 ? 22 DG B N2 1 +ATOM 444 N N3 . DG B 1 10 ? 13.815 21.452 21.350 1.00 19.05 ? 22 DG B N3 1 +ATOM 445 C C4 . DG B 1 10 ? 12.902 22.429 21.151 1.00 23.69 ? 22 DG B C4 1 +ATOM 446 P P . DC B 1 11 ? 9.477 18.627 25.340 1.00 55.93 ? 23 DC B P 1 +ATOM 447 O OP1 . DC B 1 11 ? 8.767 17.534 24.627 1.00 45.14 ? 23 DC B OP1 1 +ATOM 448 O OP2 . DC B 1 11 ? 8.670 19.409 26.312 1.00 41.61 ? 23 DC B OP2 1 +ATOM 449 O "O5'" . DC B 1 11 ? 10.807 18.067 26.034 1.00 59.70 ? 23 DC B "O5'" 1 +ATOM 450 C "C5'" . DC B 1 11 ? 11.688 17.170 25.310 1.00 63.13 ? 23 DC B "C5'" 1 +ATOM 451 C "C4'" . DC B 1 11 ? 13.115 17.573 25.593 1.00 27.86 ? 23 DC B "C4'" 1 +ATOM 452 O "O4'" . DC B 1 11 ? 13.284 18.804 24.893 1.00 50.51 ? 23 DC B "O4'" 1 +ATOM 453 C "C3'" . DC B 1 11 ? 13.441 17.879 27.059 1.00 46.45 ? 23 DC B "C3'" 1 +ATOM 454 O "O3'" . DC B 1 11 ? 14.341 16.938 27.677 1.00 57.21 ? 23 DC B "O3'" 1 +ATOM 455 C "C2'" . DC B 1 11 ? 13.928 19.322 27.025 1.00 68.01 ? 23 DC B "C2'" 1 +ATOM 456 C "C1'" . DC B 1 11 ? 14.312 19.508 25.568 1.00 32.05 ? 23 DC B "C1'" 1 +ATOM 457 N N1 . DC B 1 11 ? 14.144 20.932 25.170 1.00 23.28 ? 23 DC B N1 1 +ATOM 458 C C2 . DC B 1 11 ? 15.199 21.595 24.630 1.00 20.62 ? 23 DC B C2 1 +ATOM 459 O O2 . DC B 1 11 ? 16.257 20.984 24.504 1.00 29.62 ? 23 DC B O2 1 +ATOM 460 N N3 . DC B 1 11 ? 15.067 22.877 24.257 1.00 39.00 ? 23 DC B N3 1 +ATOM 461 C C4 . DC B 1 11 ? 13.898 23.510 24.404 1.00 30.44 ? 23 DC B C4 1 +ATOM 462 N N4 . DC B 1 11 ? 13.771 24.813 24.018 1.00 34.66 ? 23 DC B N4 1 +ATOM 463 C C5 . DC B 1 11 ? 12.795 22.866 24.967 1.00 27.74 ? 23 DC B C5 1 +ATOM 464 C C6 . DC B 1 11 ? 12.935 21.540 25.359 1.00 24.58 ? 23 DC B C6 1 +ATOM 465 P P . DG B 1 12 ? 14.658 17.064 29.247 1.00 53.70 ? 24 DG B P 1 +ATOM 466 O OP1 . DG B 1 12 ? 14.863 15.717 29.825 1.00 61.79 ? 24 DG B OP1 1 +ATOM 467 O OP2 . DG B 1 12 ? 13.633 17.912 29.920 1.00 36.06 ? 24 DG B OP2 1 +ATOM 468 O "O5'" . DG B 1 12 ? 16.033 17.880 29.284 1.00 34.06 ? 24 DG B "O5'" 1 +ATOM 469 C "C5'" . DG B 1 12 ? 17.243 17.320 28.742 1.00 46.57 ? 24 DG B "C5'" 1 +ATOM 470 C "C4'" . DG B 1 12 ? 18.208 18.464 28.758 1.00 50.89 ? 24 DG B "C4'" 1 +ATOM 471 O "O4'" . DG B 1 12 ? 17.716 19.428 27.829 1.00 32.02 ? 24 DG B "O4'" 1 +ATOM 472 C "C3'" . DG B 1 12 ? 18.230 19.236 30.058 1.00 30.38 ? 24 DG B "C3'" 1 +ATOM 473 O "O3'" . DG B 1 12 ? 18.978 18.583 31.084 1.00 61.06 ? 24 DG B "O3'" 1 +ATOM 474 C "C2'" . DG B 1 12 ? 18.885 20.519 29.578 1.00 53.33 ? 24 DG B "C2'" 1 +ATOM 475 C "C1'" . DG B 1 12 ? 18.276 20.693 28.188 1.00 35.03 ? 24 DG B "C1'" 1 +ATOM 476 N N9 . DG B 1 12 ? 17.164 21.659 28.139 1.00 30.25 ? 24 DG B N9 1 +ATOM 477 C C8 . DG B 1 12 ? 15.874 21.536 28.580 1.00 30.86 ? 24 DG B C8 1 +ATOM 478 N N7 . DG B 1 12 ? 15.129 22.614 28.308 1.00 44.08 ? 24 DG B N7 1 +ATOM 479 C C5 . DG B 1 12 ? 15.990 23.436 27.673 1.00 16.87 ? 24 DG B C5 1 +ATOM 480 C C6 . DG B 1 12 ? 15.765 24.729 27.117 1.00 19.36 ? 24 DG B C6 1 +ATOM 481 O O6 . DG B 1 12 ? 14.719 25.373 27.067 1.00 33.30 ? 24 DG B O6 1 +ATOM 482 N N1 . DG B 1 12 ? 16.926 25.257 26.604 1.00 15.78 ? 24 DG B N1 1 +ATOM 483 C C2 . DG B 1 12 ? 18.157 24.666 26.579 1.00 11.92 ? 24 DG B C2 1 +ATOM 484 N N2 . DG B 1 12 ? 19.208 25.386 26.096 1.00 29.76 ? 24 DG B N2 1 +ATOM 485 N N3 . DG B 1 12 ? 18.350 23.438 27.053 1.00 21.95 ? 24 DG B N3 1 +ATOM 486 C C4 . DG B 1 12 ? 17.231 22.893 27.570 1.00 13.89 ? 24 DG B C4 1 +HETATM 487 O O . HOH C 2 . ? 19.736 30.706 18.656 1.00 51.86 ? 25 HOH A O 1 +HETATM 488 O O . HOH C 2 . ? 10.879 26.039 -8.906 1.00 47.07 ? 31 HOH A O 1 +HETATM 489 O O . HOH C 2 . ? 18.320 24.816 14.948 1.00 47.72 ? 32 HOH A O 1 +HETATM 490 O O . HOH C 2 . ? 9.821 13.442 8.572 1.00 45.76 ? 36 HOH A O 1 +HETATM 491 O O . HOH C 2 . ? 8.915 15.602 -3.388 1.00 50.97 ? 38 HOH A O 1 +HETATM 492 O O . HOH C 2 . ? 17.505 26.340 -10.581 1.00 51.90 ? 39 HOH A O 1 +HETATM 493 O O . HOH C 2 . ? 28.496 23.515 18.349 1.00 45.37 ? 40 HOH A O 1 +HETATM 494 O O . HOH C 2 . ? 11.346 24.175 4.920 1.00 45.03 ? 41 HOH A O 1 +HETATM 495 O O . HOH C 2 . ? 9.098 16.119 1.277 1.00 51.80 ? 50 HOH A O 1 +HETATM 496 O O . HOH C 2 . ? 16.488 29.195 19.861 1.00 54.92 ? 54 HOH A O 1 +HETATM 497 O O . HOH C 2 . ? 22.078 25.894 15.396 1.00 62.20 ? 55 HOH A O 1 +HETATM 498 O O . HOH C 2 . ? 7.133 14.448 4.647 1.00 57.15 ? 58 HOH A O 1 +HETATM 499 O O . HOH C 2 . ? 14.095 28.151 21.614 1.00 53.85 ? 62 HOH A O 1 +HETATM 500 O O . HOH C 2 . ? 27.164 31.710 20.331 1.00 56.84 ? 64 HOH A O 1 +HETATM 501 O O . HOH C 2 . ? 15.295 11.873 12.209 1.00 57.34 ? 65 HOH A O 1 +HETATM 502 O O . HOH C 2 . ? 18.180 16.604 9.966 1.00 61.52 ? 66 HOH A O 1 +HETATM 503 O O . HOH C 2 . ? 6.216 17.035 1.672 1.00 62.91 ? 67 HOH A O 1 +HETATM 504 O O . HOH C 2 . ? 7.055 25.519 -2.053 1.00 55.96 ? 70 HOH A O 1 +HETATM 505 O O . HOH C 2 . ? 12.454 11.354 9.415 1.00 68.40 ? 74 HOH A O 1 +HETATM 506 O O . HOH C 2 . ? 11.492 29.103 20.090 1.00 67.46 ? 76 HOH A O 1 +HETATM 507 O O . HOH C 2 . ? 14.220 29.189 20.392 1.00 48.22 ? 77 HOH A O 1 +HETATM 508 O O . HOH C 2 . ? 6.138 19.149 13.844 1.00 62.26 ? 78 HOH A O 1 +HETATM 509 O O . HOH C 2 . ? 17.315 9.638 13.392 1.00 65.70 ? 79 HOH A O 1 +HETATM 510 O O . HOH C 2 . ? 18.951 25.757 12.989 1.00 66.47 ? 80 HOH A O 1 +HETATM 511 O O . HOH C 2 . ? 20.460 18.861 12.664 1.00 63.00 ? 81 HOH A O 1 +HETATM 512 O O . HOH C 2 . ? 3.529 19.338 12.599 1.00 65.32 ? 82 HOH A O 1 +HETATM 513 O O . HOH C 2 . ? 16.223 12.351 9.406 1.00 63.59 ? 84 HOH A O 1 +HETATM 514 O O . HOH C 2 . ? 12.989 29.901 -9.282 1.00 64.97 ? 85 HOH A O 1 +HETATM 515 O O . HOH C 2 . ? 17.510 30.569 18.702 1.00 61.79 ? 86 HOH A O 1 +HETATM 516 O O . HOH C 2 . ? 25.377 12.891 19.011 1.00 73.80 ? 87 HOH A O 1 +HETATM 517 O O . HOH C 2 . ? 13.610 15.742 18.593 1.00 69.48 ? 88 HOH A O 1 +HETATM 518 O O . HOH C 2 . ? 18.012 32.598 15.262 1.00 67.52 ? 89 HOH A O 1 +HETATM 519 O O . HOH C 2 . ? 8.723 13.216 6.359 1.00 70.66 ? 92 HOH A O 1 +HETATM 520 O O . HOH C 2 . ? 18.779 13.814 11.704 1.00 71.14 ? 97 HOH A O 1 +HETATM 521 O O . HOH C 2 . ? 12.227 25.192 -10.299 1.00 70.46 ? 99 HOH A O 1 +HETATM 522 O O . HOH C 2 . ? 12.292 30.291 27.102 1.00 73.04 ? 100 HOH A O 1 +HETATM 523 O O . HOH C 2 . ? 20.170 23.000 12.999 1.00 73.63 ? 102 HOH A O 1 +HETATM 524 O O . HOH D 2 . ? 14.354 27.683 16.369 1.00 40.92 ? 26 HOH B O 1 +HETATM 525 O O . HOH D 2 . ? 9.864 22.509 9.123 1.00 39.67 ? 27 HOH B O 1 +HETATM 526 O O . HOH D 2 . ? 19.526 19.144 7.481 1.00 51.15 ? 28 HOH B O 1 +HETATM 527 O O . HOH D 2 . ? 25.754 12.744 -1.835 1.00 51.80 ? 29 HOH B O 1 +HETATM 528 O O . HOH D 2 . ? 7.478 20.604 -9.000 1.00 44.82 ? 30 HOH B O 1 +HETATM 529 O O . HOH D 2 . ? 9.012 24.586 7.009 1.00 43.42 ? 33 HOH B O 1 +HETATM 530 O O . HOH D 2 . ? 10.152 19.917 13.381 1.00 48.04 ? 34 HOH B O 1 +HETATM 531 O O . HOH D 2 . ? 7.764 21.397 11.075 1.00 41.41 ? 35 HOH B O 1 +HETATM 532 O O . HOH D 2 . ? 13.239 14.428 2.049 1.00 55.54 ? 37 HOH B O 1 +HETATM 533 O O . HOH D 2 . ? 12.601 23.000 29.167 1.00 51.36 ? 42 HOH B O 1 +HETATM 534 O O . HOH D 2 . ? 10.440 25.542 24.443 1.00 56.79 ? 43 HOH B O 1 +HETATM 535 O O . HOH D 2 . ? 16.979 28.689 16.284 1.00 50.41 ? 44 HOH B O 1 +HETATM 536 O O . HOH D 2 . ? 4.794 22.966 13.368 1.00 45.95 ? 45 HOH B O 1 +HETATM 537 O O . HOH D 2 . ? 4.208 25.591 10.828 1.00 51.06 ? 46 HOH B O 1 +HETATM 538 O O . HOH D 2 . ? 6.362 24.374 9.188 1.00 51.85 ? 47 HOH B O 1 +HETATM 539 O O . HOH D 2 . ? 7.688 28.411 7.883 1.00 49.33 ? 48 HOH B O 1 +HETATM 540 O O . HOH D 2 . ? 18.379 17.074 4.809 1.00 50.72 ? 49 HOH B O 1 +HETATM 541 O O . HOH D 2 . ? 26.464 23.826 1.396 1.00 53.21 ? 51 HOH B O 1 +HETATM 542 O O . HOH D 2 . ? 11.014 11.318 -2.909 1.00 51.36 ? 52 HOH B O 1 +HETATM 543 O O . HOH D 2 . ? 9.476 27.782 26.498 1.00 60.04 ? 53 HOH B O 1 +HETATM 544 O O . HOH D 2 . ? 5.522 27.411 9.017 1.00 62.36 ? 56 HOH B O 1 +HETATM 545 O O . HOH D 2 . ? 18.456 28.409 8.821 1.00 59.63 ? 57 HOH B O 1 +HETATM 546 O O . HOH D 2 . ? 22.610 15.544 3.846 1.00 57.52 ? 59 HOH B O 1 +HETATM 547 O O . HOH D 2 . ? 24.407 13.162 2.229 1.00 52.30 ? 60 HOH B O 1 +HETATM 548 O O . HOH D 2 . ? 7.988 11.556 -2.976 1.00 59.14 ? 61 HOH B O 1 +HETATM 549 O O . HOH D 2 . ? 14.213 27.722 18.905 1.00 57.29 ? 63 HOH B O 1 +HETATM 550 O O . HOH D 2 . ? 19.101 11.433 1.080 1.00 59.79 ? 68 HOH B O 1 +HETATM 551 O O . HOH D 2 . ? 12.607 10.967 0.261 1.00 60.87 ? 69 HOH B O 1 +HETATM 552 O O . HOH D 2 . ? 15.062 26.024 -0.766 1.00 56.35 ? 71 HOH B O 1 +HETATM 553 O O . HOH D 2 . ? 16.380 6.413 -4.784 1.00 59.07 ? 72 HOH B O 1 +HETATM 554 O O . HOH D 2 . ? 14.059 5.751 -6.198 1.00 56.68 ? 73 HOH B O 1 +HETATM 555 O O . HOH D 2 . ? 9.613 17.039 29.793 1.00 63.48 ? 75 HOH B O 1 +HETATM 556 O O . HOH D 2 . ? 25.276 15.890 -1.301 1.00 64.53 ? 83 HOH B O 1 +HETATM 557 O O . HOH D 2 . ? 2.622 23.030 10.332 1.00 68.01 ? 90 HOH B O 1 +HETATM 558 O O . HOH D 2 . ? 19.701 22.518 9.511 1.00 70.25 ? 91 HOH B O 1 +HETATM 559 O O . HOH D 2 . ? 19.727 29.488 6.155 1.00 69.43 ? 93 HOH B O 1 +HETATM 560 O O . HOH D 2 . ? 17.241 11.563 4.511 1.00 72.18 ? 94 HOH B O 1 +HETATM 561 O O . HOH D 2 . ? 26.545 19.404 -1.091 1.00 70.14 ? 95 HOH B O 1 +HETATM 562 O O . HOH D 2 . ? 9.697 18.315 14.885 1.00 69.10 ? 96 HOH B O 1 +HETATM 563 O O . HOH D 2 . ? 14.292 25.159 2.287 1.00 68.44 ? 98 HOH B O 1 +HETATM 564 O O . HOH D 2 . ? 9.396 27.092 16.993 1.00 72.98 ? 101 HOH B O 1 +HETATM 565 O O . HOH D 2 . ? 19.987 21.691 6.802 1.00 72.66 ? 103 HOH B O 1 +HETATM 566 O O . HOH D 2 . ? 18.692 31.584 4.596 1.00 72.98 ? 104 HOH B O 1 +## +loop_ +_pdbx_poly_seq_scheme.asym_id +_pdbx_poly_seq_scheme.entity_id +_pdbx_poly_seq_scheme.seq_id +_pdbx_poly_seq_scheme.mon_id +_pdbx_poly_seq_scheme.ndb_seq_num +_pdbx_poly_seq_scheme.pdb_seq_num +_pdbx_poly_seq_scheme.auth_seq_num +_pdbx_poly_seq_scheme.pdb_mon_id +_pdbx_poly_seq_scheme.auth_mon_id +_pdbx_poly_seq_scheme.pdb_strand_id +_pdbx_poly_seq_scheme.pdb_ins_code +_pdbx_poly_seq_scheme.hetero +A 1 1 DC 1 1 1 DC C A . n +A 1 2 DG 2 2 2 DG G A . n +A 1 3 DC 3 3 3 DC C A . n +A 1 4 DG 4 4 4 DG G A . n +A 1 5 DA 5 5 5 DA A A . n +A 1 6 DA 6 6 6 DA A A . n +A 1 7 DT 7 7 7 DT T A . n +A 1 8 DT 8 8 8 DT T A . n +A 1 9 DC 9 9 9 DC C A . n +A 1 10 DG 10 10 10 DG G A . n +A 1 11 DC 11 11 11 DC C A . n +A 1 12 DG 12 12 12 DG G A . n +B 1 1 DC 1 13 13 DC C B . n +B 1 2 DG 2 14 14 DG G B . n +B 1 3 DC 3 15 15 DC C B . n +B 1 4 DG 4 16 16 DG G B . n +B 1 5 DA 5 17 17 DA A B . n +B 1 6 DA 6 18 18 DA A B . n +B 1 7 DT 7 19 19 DT T B . n +B 1 8 DT 8 20 20 DT T B . n +B 1 9 DC 9 21 21 DC C B . n +B 1 10 DG 10 22 22 DG G B . n +B 1 11 DC 11 23 23 DC C B . n +B 1 12 DG 12 24 24 DG G B . n +## +loop_ +_pdbx_nonpoly_scheme.asym_id +_pdbx_nonpoly_scheme.entity_id +_pdbx_nonpoly_scheme.mon_id +_pdbx_nonpoly_scheme.ndb_seq_num +_pdbx_nonpoly_scheme.pdb_seq_num +_pdbx_nonpoly_scheme.auth_seq_num +_pdbx_nonpoly_scheme.pdb_mon_id +_pdbx_nonpoly_scheme.auth_mon_id +_pdbx_nonpoly_scheme.pdb_strand_id +_pdbx_nonpoly_scheme.pdb_ins_code +C 2 HOH 1 25 25 HOH HOH A . +C 2 HOH 2 31 31 HOH HOH A . +C 2 HOH 3 32 32 HOH HOH A . +C 2 HOH 4 36 36 HOH HOH A . +C 2 HOH 5 38 38 HOH HOH A . +C 2 HOH 6 39 39 HOH HOH A . +C 2 HOH 7 40 40 HOH HOH A . +C 2 HOH 8 41 41 HOH HOH A . +C 2 HOH 9 50 50 HOH HOH A . +C 2 HOH 10 54 54 HOH HOH A . +C 2 HOH 11 55 55 HOH HOH A . +C 2 HOH 12 58 58 HOH HOH A . +C 2 HOH 13 62 62 HOH HOH A . +C 2 HOH 14 64 64 HOH HOH A . +C 2 HOH 15 65 65 HOH HOH A . +C 2 HOH 16 66 66 HOH HOH A . +C 2 HOH 17 67 67 HOH HOH A . +C 2 HOH 18 70 70 HOH HOH A . +C 2 HOH 19 74 74 HOH HOH A . +C 2 HOH 20 76 76 HOH HOH A . +C 2 HOH 21 77 77 HOH HOH A . +C 2 HOH 22 78 78 HOH HOH A . +C 2 HOH 23 79 79 HOH HOH A . +C 2 HOH 24 80 80 HOH HOH A . +C 2 HOH 25 81 81 HOH HOH A . +C 2 HOH 26 82 82 HOH HOH A . +C 2 HOH 27 84 84 HOH HOH A . +C 2 HOH 28 85 85 HOH HOH A . +C 2 HOH 29 86 86 HOH HOH A . +C 2 HOH 30 87 87 HOH HOH A . +C 2 HOH 31 88 88 HOH HOH A . +C 2 HOH 32 89 89 HOH HOH A . +C 2 HOH 33 92 92 HOH HOH A . +C 2 HOH 34 97 97 HOH HOH A . +C 2 HOH 35 99 99 HOH HOH A . +C 2 HOH 36 100 100 HOH HOH A . +C 2 HOH 37 102 102 HOH HOH A . +D 2 HOH 1 26 26 HOH HOH B . +D 2 HOH 2 27 27 HOH HOH B . +D 2 HOH 3 28 28 HOH HOH B . +D 2 HOH 4 29 29 HOH HOH B . +D 2 HOH 5 30 30 HOH HOH B . +D 2 HOH 6 33 33 HOH HOH B . +D 2 HOH 7 34 34 HOH HOH B . +D 2 HOH 8 35 35 HOH HOH B . +D 2 HOH 9 37 37 HOH HOH B . +D 2 HOH 10 42 42 HOH HOH B . +D 2 HOH 11 43 43 HOH HOH B . +D 2 HOH 12 44 44 HOH HOH B . +D 2 HOH 13 45 45 HOH HOH B . +D 2 HOH 14 46 46 HOH HOH B . +D 2 HOH 15 47 47 HOH HOH B . +D 2 HOH 16 48 48 HOH HOH B . +D 2 HOH 17 49 49 HOH HOH B . +D 2 HOH 18 51 51 HOH HOH B . +D 2 HOH 19 52 52 HOH HOH B . +D 2 HOH 20 53 53 HOH HOH B . +D 2 HOH 21 56 56 HOH HOH B . +D 2 HOH 22 57 57 HOH HOH B . +D 2 HOH 23 59 59 HOH HOH B . +D 2 HOH 24 60 60 HOH HOH B . +D 2 HOH 25 61 61 HOH HOH B . +D 2 HOH 26 63 63 HOH HOH B . +D 2 HOH 27 68 68 HOH HOH B . +D 2 HOH 28 69 69 HOH HOH B . +D 2 HOH 29 71 71 HOH HOH B . +D 2 HOH 30 72 72 HOH HOH B . +D 2 HOH 31 73 73 HOH HOH B . +D 2 HOH 32 75 75 HOH HOH B . +D 2 HOH 33 83 83 HOH HOH B . +D 2 HOH 34 90 90 HOH HOH B . +D 2 HOH 35 91 91 HOH HOH B . +D 2 HOH 36 93 93 HOH HOH B . +D 2 HOH 37 94 94 HOH HOH B . +D 2 HOH 38 95 95 HOH HOH B . +D 2 HOH 39 96 96 HOH HOH B . +D 2 HOH 40 98 98 HOH HOH B . +D 2 HOH 41 101 101 HOH HOH B . +D 2 HOH 42 103 103 HOH HOH B . +D 2 HOH 43 104 104 HOH HOH B . +## +_pdbx_struct_assembly.id 1 +_pdbx_struct_assembly.details author_defined_assembly +_pdbx_struct_assembly.method_details ? +_pdbx_struct_assembly.oligomeric_details dimeric +_pdbx_struct_assembly.oligomeric_count 2 +## +_pdbx_struct_assembly_gen.assembly_id 1 +_pdbx_struct_assembly_gen.oper_expression 1 +_pdbx_struct_assembly_gen.asym_id_list A,B,C,D +## +_pdbx_struct_oper_list.id 1 +_pdbx_struct_oper_list.type 'identity operation' +_pdbx_struct_oper_list.name 1_555 +_pdbx_struct_oper_list.symmetry_operation x,y,z +_pdbx_struct_oper_list.matrix[1][1] 1.0000000000 +_pdbx_struct_oper_list.matrix[1][2] 0.0000000000 +_pdbx_struct_oper_list.matrix[1][3] 0.0000000000 +_pdbx_struct_oper_list.vector[1] 0.0000000000 +_pdbx_struct_oper_list.matrix[2][1] 0.0000000000 +_pdbx_struct_oper_list.matrix[2][2] 1.0000000000 +_pdbx_struct_oper_list.matrix[2][3] 0.0000000000 +_pdbx_struct_oper_list.vector[2] 0.0000000000 +_pdbx_struct_oper_list.matrix[3][1] 0.0000000000 +_pdbx_struct_oper_list.matrix[3][2] 0.0000000000 +_pdbx_struct_oper_list.matrix[3][3] 1.0000000000 +_pdbx_struct_oper_list.vector[3] 0.0000000000 +## +loop_ +_pdbx_audit_revision_history.ordinal +_pdbx_audit_revision_history.data_content_type +_pdbx_audit_revision_history.major_revision +_pdbx_audit_revision_history.minor_revision +_pdbx_audit_revision_history.revision_date +1 'Structure model' 1 0 1981-05-21 +2 'Structure model' 1 1 2008-05-22 +3 'Structure model' 1 2 2011-07-13 +## +_pdbx_audit_revision_details.ordinal 1 +_pdbx_audit_revision_details.revision_ordinal 1 +_pdbx_audit_revision_details.data_content_type 'Structure model' +_pdbx_audit_revision_details.provider repository +_pdbx_audit_revision_details.type 'Initial release' +_pdbx_audit_revision_details.description ? +## +loop_ +_pdbx_audit_revision_group.ordinal +_pdbx_audit_revision_group.revision_ordinal +_pdbx_audit_revision_group.data_content_type +_pdbx_audit_revision_group.group +1 2 'Structure model' 'Version format compliance' +2 3 'Structure model' 'Version format compliance' +## +loop_ +_refine_B_iso.class +_refine_B_iso.details +_refine_B_iso.treatment +_refine_B_iso.pdbx_refine_id +'ALL ATOMS' TR isotropic 'X-RAY DIFFRACTION' +'ALL WATERS' TR isotropic 'X-RAY DIFFRACTION' +## +loop_ +_refine_occupancy.class +_refine_occupancy.treatment +_refine_occupancy.pdbx_refine_id +'ALL ATOMS' fix 'X-RAY DIFFRACTION' +'ALL WATERS' fix 'X-RAY DIFFRACTION' +## +_software.name JACK-LEVITT +_software.classification refinement +_software.version . +_software.citation_id ? +_software.pdbx_ordinal 1 +## +loop_ +_pdbx_validate_close_contact.id +_pdbx_validate_close_contact.PDB_model_num +_pdbx_validate_close_contact.auth_atom_id_1 +_pdbx_validate_close_contact.auth_asym_id_1 +_pdbx_validate_close_contact.auth_comp_id_1 +_pdbx_validate_close_contact.auth_seq_id_1 +_pdbx_validate_close_contact.PDB_ins_code_1 +_pdbx_validate_close_contact.label_alt_id_1 +_pdbx_validate_close_contact.auth_atom_id_2 +_pdbx_validate_close_contact.auth_asym_id_2 +_pdbx_validate_close_contact.auth_comp_id_2 +_pdbx_validate_close_contact.auth_seq_id_2 +_pdbx_validate_close_contact.PDB_ins_code_2 +_pdbx_validate_close_contact.label_alt_id_2 +_pdbx_validate_close_contact.dist +1 1 O A HOH 62 ? ? O A HOH 77 ? ? 1.61 +2 1 OP2 A DA 6 ? ? O A HOH 65 ? ? 1.89 +3 1 OP2 A DG 10 ? ? O A HOH 70 ? ? 2.02 +4 1 O A HOH 54 ? ? O A HOH 86 ? ? 2.07 +5 1 O A HOH 77 ? ? O B HOH 63 ? ? 2.09 +6 1 O A HOH 31 ? ? O A HOH 99 ? ? 2.12 +## +loop_ +_pdbx_validate_rmsd_bond.id +_pdbx_validate_rmsd_bond.PDB_model_num +_pdbx_validate_rmsd_bond.auth_atom_id_1 +_pdbx_validate_rmsd_bond.auth_asym_id_1 +_pdbx_validate_rmsd_bond.auth_comp_id_1 +_pdbx_validate_rmsd_bond.auth_seq_id_1 +_pdbx_validate_rmsd_bond.PDB_ins_code_1 +_pdbx_validate_rmsd_bond.label_alt_id_1 +_pdbx_validate_rmsd_bond.auth_atom_id_2 +_pdbx_validate_rmsd_bond.auth_asym_id_2 +_pdbx_validate_rmsd_bond.auth_comp_id_2 +_pdbx_validate_rmsd_bond.auth_seq_id_2 +_pdbx_validate_rmsd_bond.PDB_ins_code_2 +_pdbx_validate_rmsd_bond.label_alt_id_2 +_pdbx_validate_rmsd_bond.bond_value +_pdbx_validate_rmsd_bond.bond_target_value +_pdbx_validate_rmsd_bond.bond_deviation +_pdbx_validate_rmsd_bond.bond_standard_deviation +_pdbx_validate_rmsd_bond.linker_flag + 1 1 C5 A DC 1 ? ? C6 A DC 1 ? ? 1.390 1.339 0.051 0.008 N + 2 1 C5 A DG 2 ? ? N7 A DG 2 ? ? 1.348 1.388 -0.040 0.006 N + 3 1 N7 A DG 2 ? ? C8 A DG 2 ? ? 1.341 1.305 0.036 0.006 N + 4 1 C5 A DC 3 ? ? C6 A DC 3 ? ? 1.391 1.339 0.052 0.008 N + 5 1 C5 A DG 4 ? ? N7 A DG 4 ? ? 1.349 1.388 -0.039 0.006 N + 6 1 N7 A DG 4 ? ? C8 A DG 4 ? ? 1.341 1.305 0.036 0.006 N + 7 1 C5 A DA 5 ? ? N7 A DA 5 ? ? 1.349 1.388 -0.039 0.006 N + 8 1 C5 A DA 6 ? ? N7 A DA 6 ? ? 1.350 1.388 -0.038 0.006 N + 9 1 C5 A DT 7 ? ? C6 A DT 7 ? ? 1.395 1.339 0.056 0.007 N +10 1 C5 A DT 8 ? ? C6 A DT 8 ? ? 1.395 1.339 0.056 0.007 N +11 1 C5 A DC 9 ? ? C6 A DC 9 ? ? 1.392 1.339 0.053 0.008 N +12 1 C5 A DG 10 ? ? N7 A DG 10 ? ? 1.349 1.388 -0.039 0.006 N +13 1 N7 A DG 10 ? ? C8 A DG 10 ? ? 1.341 1.305 0.036 0.006 N +14 1 C5 A DC 11 ? ? C6 A DC 11 ? ? 1.390 1.339 0.051 0.008 N +15 1 C5 A DG 12 ? ? N7 A DG 12 ? ? 1.349 1.388 -0.039 0.006 N +16 1 C5 B DC 13 ? ? C6 B DC 13 ? ? 1.391 1.339 0.052 0.008 N +17 1 C5 B DG 14 ? ? N7 B DG 14 ? ? 1.346 1.388 -0.042 0.006 N +18 1 C5 B DC 15 ? ? C6 B DC 15 ? ? 1.388 1.339 0.049 0.008 N +19 1 C5 B DG 16 ? ? N7 B DG 16 ? ? 1.348 1.388 -0.040 0.006 N +20 1 N7 B DG 16 ? ? C8 B DG 16 ? ? 1.344 1.305 0.039 0.006 N +21 1 C5 B DA 17 ? ? N7 B DA 17 ? ? 1.349 1.388 -0.039 0.006 N +22 1 C5 B DA 18 ? ? N7 B DA 18 ? ? 1.349 1.388 -0.039 0.006 N +23 1 C5 B DT 19 ? ? C6 B DT 19 ? ? 1.395 1.339 0.056 0.007 N +24 1 C5 B DT 20 ? ? C6 B DT 20 ? ? 1.390 1.339 0.051 0.007 N +25 1 C5 B DC 21 ? ? C6 B DC 21 ? ? 1.391 1.339 0.052 0.008 N +26 1 C5 B DG 22 ? ? N7 B DG 22 ? ? 1.352 1.388 -0.036 0.006 N +27 1 N7 B DG 22 ? ? C8 B DG 22 ? ? 1.342 1.305 0.037 0.006 N +28 1 C5 B DC 23 ? ? C6 B DC 23 ? ? 1.390 1.339 0.051 0.008 N +29 1 C5 B DG 24 ? ? N7 B DG 24 ? ? 1.349 1.388 -0.039 0.006 N +## +loop_ +_pdbx_validate_rmsd_angle.id +_pdbx_validate_rmsd_angle.PDB_model_num +_pdbx_validate_rmsd_angle.auth_atom_id_1 +_pdbx_validate_rmsd_angle.auth_asym_id_1 +_pdbx_validate_rmsd_angle.auth_comp_id_1 +_pdbx_validate_rmsd_angle.auth_seq_id_1 +_pdbx_validate_rmsd_angle.PDB_ins_code_1 +_pdbx_validate_rmsd_angle.label_alt_id_1 +_pdbx_validate_rmsd_angle.auth_atom_id_2 +_pdbx_validate_rmsd_angle.auth_asym_id_2 +_pdbx_validate_rmsd_angle.auth_comp_id_2 +_pdbx_validate_rmsd_angle.auth_seq_id_2 +_pdbx_validate_rmsd_angle.PDB_ins_code_2 +_pdbx_validate_rmsd_angle.label_alt_id_2 +_pdbx_validate_rmsd_angle.auth_atom_id_3 +_pdbx_validate_rmsd_angle.auth_asym_id_3 +_pdbx_validate_rmsd_angle.auth_comp_id_3 +_pdbx_validate_rmsd_angle.auth_seq_id_3 +_pdbx_validate_rmsd_angle.PDB_ins_code_3 +_pdbx_validate_rmsd_angle.label_alt_id_3 +_pdbx_validate_rmsd_angle.angle_value +_pdbx_validate_rmsd_angle.angle_target_value +_pdbx_validate_rmsd_angle.angle_deviation +_pdbx_validate_rmsd_angle.angle_standard_deviation +_pdbx_validate_rmsd_angle.linker_flag + 1 1 "C4'" A DC 1 ? ? "C3'" A DC 1 ? ? "C2'" A DC 1 ? ? 97.42 102.20 -4.78 0.70 N + 2 1 "C3'" A DC 1 ? ? "C2'" A DC 1 ? ? "C1'" A DC 1 ? ? 96.43 102.40 -5.97 0.80 N + 3 1 "O4'" A DC 1 ? ? "C1'" A DC 1 ? ? "C2'" A DC 1 ? ? 100.54 105.90 -5.36 0.80 N + 4 1 "O5'" A DG 2 ? ? "C5'" A DG 2 ? ? "C4'" A DG 2 ? ? 102.57 109.40 -6.83 0.80 N + 5 1 "O4'" A DG 2 ? ? "C1'" A DG 2 ? ? N9 A DG 2 ? ? 103.05 108.00 -4.95 0.70 N + 6 1 "O4'" A DC 3 ? ? "C1'" A DC 3 ? ? N1 A DC 3 ? ? 102.64 108.00 -5.36 0.70 N + 7 1 "C3'" A DG 4 ? ? "C2'" A DG 4 ? ? "C1'" A DG 4 ? ? 97.18 102.40 -5.22 0.80 N + 8 1 "O4'" A DA 5 ? ? "C1'" A DA 5 ? ? "C2'" A DA 5 ? ? 101.02 105.90 -4.88 0.80 N + 9 1 "O4'" A DA 5 ? ? "C1'" A DA 5 ? ? N9 A DA 5 ? ? 103.63 108.00 -4.37 0.70 N +10 1 "O4'" A DA 6 ? ? "C1'" A DA 6 ? ? "C2'" A DA 6 ? ? 99.84 105.90 -6.06 0.80 N +11 1 "O5'" A DT 7 ? ? "C5'" A DT 7 ? ? "C4'" A DT 7 ? ? 103.29 109.40 -6.11 0.80 N +12 1 N1 A DT 7 ? ? C2 A DT 7 ? ? N3 A DT 7 ? ? 118.31 114.60 3.71 0.60 N +13 1 C2 A DT 7 ? ? N3 A DT 7 ? ? C4 A DT 7 ? ? 122.84 127.20 -4.36 0.60 N +14 1 C5 A DT 7 ? ? C6 A DT 7 ? ? N1 A DT 7 ? ? 119.96 123.70 -3.74 0.60 N +15 1 "O4'" A DT 8 ? ? "C1'" A DT 8 ? ? "C2'" A DT 8 ? ? 100.50 105.90 -5.40 0.80 N +16 1 N1 A DT 8 ? ? C2 A DT 8 ? ? N3 A DT 8 ? ? 118.74 114.60 4.14 0.60 N +17 1 C2 A DT 8 ? ? N3 A DT 8 ? ? C4 A DT 8 ? ? 122.27 127.20 -4.93 0.60 N +18 1 C5 A DT 8 ? ? C6 A DT 8 ? ? N1 A DT 8 ? ? 119.58 123.70 -4.12 0.60 N +19 1 "C3'" A DG 10 ? ? "C2'" A DG 10 ? ? "C1'" A DG 10 ? ? 95.28 102.40 -7.12 0.80 N +20 1 "O4'" A DG 12 ? ? "C1'" A DG 12 ? ? "C2'" A DG 12 ? ? 100.00 105.90 -5.90 0.80 N +21 1 "O4'" B DC 13 ? ? "C1'" B DC 13 ? ? N1 B DC 13 ? ? 103.43 108.00 -4.57 0.70 N +22 1 "O5'" B DG 14 ? ? "C5'" B DG 14 ? ? "C4'" B DG 14 ? ? 103.60 109.40 -5.80 0.80 N +23 1 "O4'" B DG 14 ? ? "C1'" B DG 14 ? ? "C2'" B DG 14 ? ? 100.71 105.90 -5.19 0.80 N +24 1 "O4'" B DG 14 ? ? "C1'" B DG 14 ? ? N9 B DG 14 ? ? 102.65 108.00 -5.35 0.70 N +25 1 "O5'" B DG 16 ? ? "C5'" B DG 16 ? ? "C4'" B DG 16 ? ? 104.35 109.40 -5.05 0.80 N +26 1 "O4'" B DA 18 ? ? "C1'" B DA 18 ? ? N9 B DA 18 ? ? 103.63 108.00 -4.37 0.70 N +27 1 N1 B DT 19 ? ? C2 B DT 19 ? ? N3 B DT 19 ? ? 118.52 114.60 3.92 0.60 N +28 1 C2 B DT 19 ? ? N3 B DT 19 ? ? C4 B DT 19 ? ? 122.33 127.20 -4.87 0.60 N +29 1 C5 B DT 19 ? ? C6 B DT 19 ? ? N1 B DT 19 ? ? 119.05 123.70 -4.65 0.60 N +30 1 "O4'" B DT 20 ? ? "C1'" B DT 20 ? ? "C2'" B DT 20 ? ? 99.84 105.90 -6.06 0.80 N +31 1 "O4'" B DT 20 ? ? "C1'" B DT 20 ? ? N1 B DT 20 ? ? 102.84 108.00 -5.16 0.70 N +32 1 N1 B DT 20 ? ? C2 B DT 20 ? ? N3 B DT 20 ? ? 118.22 114.60 3.62 0.60 N +33 1 C2 B DT 20 ? ? N3 B DT 20 ? ? C4 B DT 20 ? ? 122.77 127.20 -4.43 0.60 N +34 1 C5 B DT 20 ? ? C6 B DT 20 ? ? N1 B DT 20 ? ? 119.69 123.70 -4.01 0.60 N +35 1 C6 B DT 20 ? ? C5 B DT 20 ? ? C7 B DT 20 ? ? 119.28 122.90 -3.62 0.60 N +36 1 "O4'" B DC 21 ? ? "C1'" B DC 21 ? ? N1 B DC 21 ? ? 100.91 108.00 -7.09 0.70 N +37 1 "C3'" B DG 22 ? ? "C2'" B DG 22 ? ? "C1'" B DG 22 ? ? 95.55 102.40 -6.85 0.80 N +38 1 "O4'" B DG 22 ? ? "C1'" B DG 22 ? ? N9 B DG 22 ? ? 110.17 108.30 1.87 0.30 N +39 1 "O5'" B DG 24 ? ? "C5'" B DG 24 ? ? "C4'" B DG 24 ? ? 103.92 109.40 -5.48 0.80 N +## +_ndb_struct_conf_na.entry_id 1BNA +_ndb_struct_conf_na.feature 'b-form double helix' +## +loop_ +_ndb_struct_na_base_pair.model_number +_ndb_struct_na_base_pair.i_label_asym_id +_ndb_struct_na_base_pair.i_label_comp_id +_ndb_struct_na_base_pair.i_label_seq_id +_ndb_struct_na_base_pair.i_symmetry +_ndb_struct_na_base_pair.j_label_asym_id +_ndb_struct_na_base_pair.j_label_comp_id +_ndb_struct_na_base_pair.j_label_seq_id +_ndb_struct_na_base_pair.j_symmetry +_ndb_struct_na_base_pair.shear +_ndb_struct_na_base_pair.stretch +_ndb_struct_na_base_pair.stagger +_ndb_struct_na_base_pair.buckle +_ndb_struct_na_base_pair.propeller +_ndb_struct_na_base_pair.opening +_ndb_struct_na_base_pair.pair_number +_ndb_struct_na_base_pair.pair_name +_ndb_struct_na_base_pair.i_auth_asym_id +_ndb_struct_na_base_pair.i_auth_seq_id +_ndb_struct_na_base_pair.i_PDB_ins_code +_ndb_struct_na_base_pair.j_auth_asym_id +_ndb_struct_na_base_pair.j_auth_seq_id +_ndb_struct_na_base_pair.j_PDB_ins_code +_ndb_struct_na_base_pair.hbond_type_28 +_ndb_struct_na_base_pair.hbond_type_12 +1 A DC 1 1_555 B DG 12 1_555 -0.422 -0.268 0.060 2.762 -14.200 -3.666 1 A_DC1:DG24_B A 1 ? B 24 ? 19 1 +1 A DG 2 1_555 B DC 11 1_555 -0.024 -0.266 0.249 -4.455 -10.846 -4.022 2 A_DG2:DC23_B A 2 ? B 23 ? 19 1 +1 A DC 3 1_555 B DG 10 1_555 0.003 -0.248 0.213 -6.940 -3.928 -2.346 3 A_DC3:DG22_B A 3 ? B 22 ? 19 1 +1 A DG 4 1_555 B DC 9 1_555 -0.371 -0.442 -0.180 9.308 -10.394 -1.297 4 A_DG4:DC21_B A 4 ? B 21 ? 19 1 +1 A DA 5 1_555 B DT 8 1_555 0.272 -0.222 0.035 5.035 -16.362 1.835 5 A_DA5:DT20_B A 5 ? B 20 ? 20 1 +1 A DA 6 1_555 B DT 7 1_555 -0.092 -0.042 0.166 3.544 -18.130 5.558 6 A_DA6:DT19_B A 6 ? B 19 ? 20 1 +1 A DT 7 1_555 B DA 6 1_555 0.317 -0.117 0.133 0.829 -17.701 7.931 7 A_DT7:DA18_B A 7 ? B 18 ? 20 1 +1 A DT 8 1_555 B DA 5 1_555 0.249 -0.215 -0.099 -1.329 -17.674 0.828 8 A_DT8:DA17_B A 8 ? B 17 ? 20 1 +1 A DC 9 1_555 B DG 4 1_555 -0.019 -0.251 -0.060 -10.176 -17.254 -0.867 9 A_DC9:DG16_B A 9 ? B 16 ? 19 1 +1 A DG 10 1_555 B DC 3 1_555 0.087 -0.278 0.272 1.665 -5.307 -1.129 10 A_DG10:DC15_B A 10 ? B 15 ? 19 1 +1 A DC 11 1_555 B DG 2 1_555 0.069 -0.284 0.586 -3.958 -18.046 -5.616 11 A_DC11:DG14_B A 11 ? B 14 ? 19 1 +1 A DG 12 1_555 B DC 1 1_555 -0.529 -0.109 0.261 6.598 1.957 -3.864 12 A_DG12:DC13_B A 12 ? B 13 ? 19 1 +## +loop_ +_ndb_struct_na_base_pair_step.model_number +_ndb_struct_na_base_pair_step.i_label_asym_id_1 +_ndb_struct_na_base_pair_step.i_label_comp_id_1 +_ndb_struct_na_base_pair_step.i_label_seq_id_1 +_ndb_struct_na_base_pair_step.i_symmetry_1 +_ndb_struct_na_base_pair_step.j_label_asym_id_1 +_ndb_struct_na_base_pair_step.j_label_comp_id_1 +_ndb_struct_na_base_pair_step.j_label_seq_id_1 +_ndb_struct_na_base_pair_step.j_symmetry_1 +_ndb_struct_na_base_pair_step.i_label_asym_id_2 +_ndb_struct_na_base_pair_step.i_label_comp_id_2 +_ndb_struct_na_base_pair_step.i_label_seq_id_2 +_ndb_struct_na_base_pair_step.i_symmetry_2 +_ndb_struct_na_base_pair_step.j_label_asym_id_2 +_ndb_struct_na_base_pair_step.j_label_comp_id_2 +_ndb_struct_na_base_pair_step.j_label_seq_id_2 +_ndb_struct_na_base_pair_step.j_symmetry_2 +_ndb_struct_na_base_pair_step.shift +_ndb_struct_na_base_pair_step.slide +_ndb_struct_na_base_pair_step.rise +_ndb_struct_na_base_pair_step.tilt +_ndb_struct_na_base_pair_step.roll +_ndb_struct_na_base_pair_step.twist +_ndb_struct_na_base_pair_step.x_displacement +_ndb_struct_na_base_pair_step.y_displacement +_ndb_struct_na_base_pair_step.helical_rise +_ndb_struct_na_base_pair_step.inclination +_ndb_struct_na_base_pair_step.tip +_ndb_struct_na_base_pair_step.helical_twist +_ndb_struct_na_base_pair_step.step_number +_ndb_struct_na_base_pair_step.step_name +_ndb_struct_na_base_pair_step.i_auth_asym_id_1 +_ndb_struct_na_base_pair_step.i_auth_seq_id_1 +_ndb_struct_na_base_pair_step.i_PDB_ins_code_1 +_ndb_struct_na_base_pair_step.j_auth_asym_id_1 +_ndb_struct_na_base_pair_step.j_auth_seq_id_1 +_ndb_struct_na_base_pair_step.j_PDB_ins_code_1 +_ndb_struct_na_base_pair_step.i_auth_asym_id_2 +_ndb_struct_na_base_pair_step.i_auth_seq_id_2 +_ndb_struct_na_base_pair_step.i_PDB_ins_code_2 +_ndb_struct_na_base_pair_step.j_auth_asym_id_2 +_ndb_struct_na_base_pair_step.j_auth_seq_id_2 +_ndb_struct_na_base_pair_step.j_PDB_ins_code_2 +1 A DC 1 1_555 B DG 12 1_555 A DG 2 1_555 B DC 11 1_555 -0.362 0.149 3.524 -3.397 6.425 40.311 -0.551 0.114 3.524 9.231 4.881 40.934 1 AA_DC1DG2:DC23DG24_BB A 1 ? B 24 ? A 2 ? B 23 ? +1 A DG 2 1_555 B DC 11 1_555 A DC 3 1_555 B DG 10 1_555 0.498 0.227 3.523 0.805 -4.734 38.147 0.977 -0.648 3.480 -7.209 -1.225 38.437 2 AA_DG2DC3:DG22DC23_BB A 2 ? B 23 ? A 3 ? B 22 ? +1 A DC 3 1_555 B DG 10 1_555 A DG 4 1_555 B DC 9 1_555 -0.324 0.689 3.041 3.631 7.947 24.466 -0.563 1.680 3.033 18.033 -8.240 25.957 3 AA_DC3DG4:DC21DG22_BB A 3 ? B 22 ? A 4 ? B 21 ? +1 A DG 4 1_555 B DC 9 1_555 A DA 5 1_555 B DT 8 1_555 0.008 0.071 3.360 -2.678 3.162 40.897 -0.252 -0.310 3.349 4.511 3.821 41.097 4 AA_DG4DA5:DT20DC21_BB A 4 ? B 21 ? A 5 ? B 20 ? +1 A DA 5 1_555 B DT 8 1_555 A DA 6 1_555 B DT 7 1_555 0.101 -0.312 3.318 -0.705 0.950 35.351 -0.655 -0.272 3.306 1.564 1.160 35.370 5 AA_DA5DA6:DT19DT20_BB A 5 ? B 20 ? A 6 ? B 19 ? +1 A DA 6 1_555 B DT 7 1_555 A DT 7 1_555 B DA 6 1_555 0.329 -0.603 3.341 1.827 -2.755 34.760 -0.576 -0.264 3.390 -4.598 -3.049 34.912 6 AA_DA6DT7:DA18DT19_BB A 6 ? B 19 ? A 7 ? B 18 ? +1 A DT 7 1_555 B DA 6 1_555 A DT 8 1_555 B DA 5 1_555 -0.306 -0.175 3.318 2.964 0.725 35.393 -0.395 0.939 3.279 1.190 -4.864 35.520 7 AA_DT7DT8:DA17DA18_BB A 7 ? B 18 ? A 8 ? B 17 ? +1 A DT 8 1_555 B DA 5 1_555 A DC 9 1_555 B DG 4 1_555 0.020 -0.033 3.394 0.331 -0.053 39.272 -0.042 0.011 3.394 -0.079 -0.493 39.273 8 AA_DT8DC9:DG16DA17_BB A 8 ? B 17 ? A 9 ? B 16 ? +1 A DC 9 1_555 B DG 4 1_555 A DG 10 1_555 B DC 3 1_555 0.381 0.864 3.239 -3.294 3.860 29.397 0.874 -1.427 3.262 7.535 6.431 29.823 9 AA_DC9DG10:DC15DG16_BB A 9 ? B 16 ? A 10 ? B 15 ? +1 A DG 10 1_555 B DC 3 1_555 A DC 11 1_555 B DG 2 1_555 -1.303 0.418 3.682 -4.681 -12.201 40.779 1.959 1.257 3.543 -16.990 6.519 42.737 10 AA_DG10DC11:DG14DC15_BB A 10 ? B 15 ? A 11 ? B 14 ? +1 A DC 11 1_555 B DG 2 1_555 A DG 12 1_555 B DC 1 1_555 0.773 0.057 3.226 3.143 -3.090 32.624 0.626 -0.830 3.265 -5.469 -5.562 32.912 11 AA_DC11DG12:DC13DG14_BB A 11 ? B 14 ? A 12 ? B 13 ? +## +_pdbx_entity_nonpoly.entity_id 2 +_pdbx_entity_nonpoly.name water +_pdbx_entity_nonpoly.comp_id HOH +## +_ndb_struct_ntc_overall.entry_id 1BNA +_ndb_struct_ntc_overall.confal_score 0 +_ndb_struct_ntc_overall.confal_percentile 0 +_ndb_struct_ntc_overall.ntc_version 6.6.6 +_ndb_struct_ntc_overall.cana_version 6.6.6 +_ndb_struct_ntc_overall.num_steps 22 +_ndb_struct_ntc_overall.num_classified 22 +_ndb_struct_ntc_overall.num_unclassified 0 +_ndb_struct_ntc_overall.num_unclassified_rmsd_close 0 +## +loop_ +_ndb_struct_ntc_step.id +_ndb_struct_ntc_step.name +_ndb_struct_ntc_step.PDB_model_number +_ndb_struct_ntc_step.label_entity_id_1 +_ndb_struct_ntc_step.label_asym_id_1 +_ndb_struct_ntc_step.label_seq_id_1 +_ndb_struct_ntc_step.label_comp_id_1 +_ndb_struct_ntc_step.label_alt_id_1 +_ndb_struct_ntc_step.label_entity_id_2 +_ndb_struct_ntc_step.label_asym_id_2 +_ndb_struct_ntc_step.label_seq_id_2 +_ndb_struct_ntc_step.label_comp_id_2 +_ndb_struct_ntc_step.label_alt_id_2 +_ndb_struct_ntc_step.auth_asym_id_1 +_ndb_struct_ntc_step.auth_seq_id_1 +_ndb_struct_ntc_step.auth_asym_id_2 +_ndb_struct_ntc_step.auth_seq_id_2 +_ndb_struct_ntc_step.PDB_ins_code_1 +_ndb_struct_ntc_step.PDB_ins_code_2 + 1 1bna_A_DC_1_DG_2 1 1 A 1 DC . 1 A 2 DG . A 1 A 2 . . + 2 1bna_A_DG_2_DC_3 1 1 A 2 DG . 1 A 3 DC . A 2 A 3 . . + 3 1bna_A_DC_3_DG_4 1 1 A 3 DC . 1 A 4 DG . A 3 A 4 . . + 4 1bna_A_DG_4_DA_5 1 1 A 4 DG . 1 A 5 DA . A 4 A 5 . . + 5 1bna_A_DA_5_DA_6 1 1 A 5 DA . 1 A 6 DA . A 5 A 6 . . + 6 1bna_A_DA_6_DT_7 1 1 A 6 DA . 1 A 7 DT . A 6 A 7 . . + 7 1bna_A_DT_7_DT_8 1 1 A 7 DT . 1 A 8 DT . A 7 A 8 . . + 8 1bna_A_DT_8_DC_9 1 1 A 8 DT . 1 A 9 DC . A 8 A 9 . . + 9 1bna_A_DC_9_DG_10 1 1 A 9 DC . 1 A 10 DG . A 9 A 10 . . +10 1bna_A_DG_10_DC_11 1 1 A 10 DG . 1 A 11 DC . A 10 A 11 . . +11 1bna_A_DC_11_DG_12 1 1 A 11 DC . 1 A 12 DG . A 11 A 12 . . +12 1bna_B_DC_13_DG_14 1 1 B 1 DC . 1 B 2 DG . B 13 B 14 . . +13 1bna_B_DG_14_DC_15 1 1 B 2 DG . 1 B 3 DC . B 14 B 15 . . +14 1bna_B_DC_15_DG_16 1 1 B 3 DC . 1 B 4 DG . B 15 B 16 . . +15 1bna_B_DG_16_DA_17 1 1 B 4 DG . 1 B 5 DA . B 16 B 17 . . +16 1bna_B_DA_17_DA_18 1 1 B 5 DA . 1 B 6 DA . B 17 B 18 . . +17 1bna_B_DA_18_DT_19 1 1 B 6 DA . 1 B 7 DT . B 18 B 19 . . +18 1bna_B_DT_19_DT_20 1 1 B 7 DT . 1 B 8 DT . B 19 B 20 . . +19 1bna_B_DT_20_DC_21 1 1 B 8 DT . 1 B 9 DC . B 20 B 21 . . +20 1bna_B_DC_21_DG_22 1 1 B 9 DC . 1 B 10 DG . B 21 B 22 . . +21 1bna_B_DG_22_DC_23 1 1 B 10 DG . 1 B 11 DC . B 22 B 23 . . +22 1bna_B_DC_23_DG_24 1 1 B 11 DC . 1 B 12 DG . B 23 B 24 . . +## +loop_ +_ndb_struct_ntc_step_summary.step_id +_ndb_struct_ntc_step_summary.assigned_CANA +_ndb_struct_ntc_step_summary.assigned_NtC +_ndb_struct_ntc_step_summary.confal_score +_ndb_struct_ntc_step_summary.euclidean_distance_NtC_ideal +_ndb_struct_ntc_step_summary.cartesian_rmsd_closest_NtC_representative +_ndb_struct_ntc_step_summary.closest_CANA +_ndb_struct_ntc_step_summary.closest_NtC +_ndb_struct_ntc_step_summary.closest_step_golden + 1 B12 BB04 33 40.0 0.367 B12 BB04 1mjo_F_DG_10_DA_11 + 2 B-A BA05 80 21.0 0.310 B-A BA05 4i2o_W_DA_11_DT_12 + 3 A-B AB01 36 25.1 0.177 A-B AB01 1n1o_B_DC_115_DG_116 + 4 B12 BB04 16 39.9 0.216 B12 BB04 1kx5_J_DG_-56_DA_-55 + 5 BBB BB01 68 30.9 0.190 BBB BB01 3opi_B_DT_219_DT_220 + 6 B-A BA05 69 25.9 0.274 B-A BA05 1g8n_A_DA_6_DT_7 + 7 BBB BB01 41 43.1 0.284 BBB BB01 3opi_A_DT_107_DT_108 + 8 BBB BB01 67 32.4 0.248 BBB BB01 4f3u_B_DT_19_DT_20 + 9 BBB BB00 77 33.4 0.373 BBB BB00 1edr_A_DC_9_DG_10 +10 BB2 BB07 70 37.3 0.387 BB2 BB07 1s32_J_DC_193_DT_194 +11 BBB BB01 54 36.1 0.382 BBB BB01 1aay_B_DG_4_DT_5 +12 BBB BB00 48 49.0 0.277 BBB BB00 1eo4_C_DC_9_DT_10 +13 B-A BA05 59 29.7 0.288 B-A BA05 1g8n_B_DG_14_DC_15 +14 A-B AB01 63 32.0 0.260 A-B AB01 1dsz_D_DC_1543_DT_1544 +15 BBB BB00 84 23.0 0.229 BBB BB00 1p3l_J_DT_237_DT_238 +16 BBB BB00 88 18.5 0.133 BBB BB00 1mus_B_DA_15_DG_16 +17 BBB BB01 64 24.6 0.197 BBB BB01 463d_A_DA_6_DT_7 +18 BBB BB01 67 32.2 0.183 BBB BB01 4mkw_A_DT_7_DT_8 +19 BBB BB01 78 19.4 0.144 BBB BB01 1g75_A_DA_5_DA_6 +20 BBB BB00 16 39.3 0.271 BBB BB00 1dpn_B_DC_21_DG_22 +21 B-A BA17 91 21.2 0.185 B-A BA17 3utb_J_DC_-5_DT_-4 +22 B-A BA05 38 40.9 0.373 B-A BA05 1qna_F_DC_218_DC_219 +## +loop_ +_ndb_struct_ntc_step_parameters.step_id +_ndb_struct_ntc_step_parameters.tor_delta_1 +_ndb_struct_ntc_step_parameters.tor_epsilon_1 +_ndb_struct_ntc_step_parameters.tor_zeta_1 +_ndb_struct_ntc_step_parameters.tor_alpha_2 +_ndb_struct_ntc_step_parameters.tor_beta_2 +_ndb_struct_ntc_step_parameters.tor_gamma_2 +_ndb_struct_ntc_step_parameters.tor_delta_2 +_ndb_struct_ntc_step_parameters.tor_chi_1 +_ndb_struct_ntc_step_parameters.tor_chi_2 +_ndb_struct_ntc_step_parameters.dist_NN +_ndb_struct_ntc_step_parameters.dist_CC +_ndb_struct_ntc_step_parameters.tor_NCCN +_ndb_struct_ntc_step_parameters.diff_tor_delta_1 +_ndb_struct_ntc_step_parameters.diff_tor_epsilon_1 +_ndb_struct_ntc_step_parameters.diff_tor_zeta_1 +_ndb_struct_ntc_step_parameters.diff_tor_alpha_2 +_ndb_struct_ntc_step_parameters.diff_tor_beta_2 +_ndb_struct_ntc_step_parameters.diff_tor_gamma_2 +_ndb_struct_ntc_step_parameters.diff_tor_delta_2 +_ndb_struct_ntc_step_parameters.diff_tor_chi_1 +_ndb_struct_ntc_step_parameters.diff_tor_chi_2 +_ndb_struct_ntc_step_parameters.diff_dist_NN +_ndb_struct_ntc_step_parameters.diff_dist_CC +_ndb_struct_ntc_step_parameters.diff_tor_NCCN +_ndb_struct_ntc_step_parameters.confal_tor_delta_1 +_ndb_struct_ntc_step_parameters.confal_tor_epsilon_1 +_ndb_struct_ntc_step_parameters.confal_tor_zeta_1 +_ndb_struct_ntc_step_parameters.confal_tor_alpha_2 +_ndb_struct_ntc_step_parameters.confal_tor_beta_2 +_ndb_struct_ntc_step_parameters.confal_tor_gamma_2 +_ndb_struct_ntc_step_parameters.confal_tor_delta_2 +_ndb_struct_ntc_step_parameters.confal_tor_chi_1 +_ndb_struct_ntc_step_parameters.confal_tor_chi_2 +_ndb_struct_ntc_step_parameters.confal_dist_NN +_ndb_struct_ntc_step_parameters.confal_dist_CC +_ndb_struct_ntc_step_parameters.confal_tor_NCCN +_ndb_struct_ntc_step_parameters.details + 1 156.8 218.7 216.1 294.4 169.8 40.1 128.1 255.0 249.5 4.65 5.35 32.3 16.7 17.4 1.9 -20.4 17.2 -6.0 -11.9 -7.5 -3.1 0.00 0.23 3.7 5.5 54.2 99.2 31.0 47.8 85.4 26.1 87.5 97.7 100.0 80.6 94.5 . + 2 128.1 174.2 262.2 297.4 171.8 58.8 98.3 249.5 224.9 4.23 4.72 31.8 -3.3 -10.2 -6.5 1.5 3.0 6.5 -5.9 -1.4 -10.5 0.13 0.08 7.6 97.9 55.2 75.3 99.4 92.9 84.2 87.5 99.4 74.3 74.0 88.4 58.9 . + 3 98.3 183.3 272.4 297.1 180.1 57.2 155.7 224.9 266.6 4.57 5.12 22.2 12.1 -2.9 -8.8 -3.9 1.6 2.8 13.8 2.1 10.6 -0.12 -0.16 4.5 8.9 96.9 77.0 94.8 99.1 96.7 8.7 98.8 70.1 93.1 90.4 89.4 . + 4 155.7 204.7 207.5 317.0 142.8 52.4 119.6 266.6 233.7 4.25 4.73 35.8 15.6 3.4 -6.7 2.2 -9.9 6.3 -20.4 4.1 -18.8 -0.39 -0.39 7.2 8.0 97.7 90.4 98.6 78.3 83.7 1.9 96.2 42.1 58.4 53.1 80.6 . + 5 119.6 179.9 267.8 286.7 179.7 66.0 121.1 233.7 237.8 4.47 5.04 27.7 -11.1 -1.0 2.2 -14.7 3.5 17.4 1.0 -13.8 -6.0 0.17 0.19 2.2 74.5 99.6 97.0 50.5 92.8 27.4 99.0 51.5 81.9 83.0 74.6 94.1 . + 6 121.1 173.7 271.5 303.4 180.8 52.2 98.9 237.8 232.7 4.22 4.79 25.8 -10.3 -10.7 2.8 7.5 12.0 -0.1 -5.3 -13.0 -2.7 0.12 0.14 1.7 81.1 51.8 95.1 85.3 30.2 100.0 89.7 57.2 98.0 77.7 65.4 97.5 . + 7 98.9 173.6 274.1 300.8 173.4 64.1 108.9 232.7 234.3 4.45 4.87 23.8 -31.8 -7.4 8.6 -0.6 -2.9 15.5 -11.3 -14.9 -9.6 0.15 0.02 -1.8 9.0 82.7 64.4 99.9 95.1 36.0 23.4 46.2 60.3 86.5 99.8 96.0 . + 8 108.9 170.6 270.7 301.5 180.5 60.5 128.7 234.3 240.5 4.37 4.92 29.0 -21.9 -10.3 5.1 0.1 4.3 11.9 8.5 -13.2 -3.4 0.07 0.07 3.4 32.2 68.8 85.6 100.0 89.4 54.7 43.4 54.2 93.9 96.9 96.4 85.6 . + 9 128.7 203.1 266.0 292.7 169.1 47.2 142.9 240.5 270.4 4.29 4.92 28.2 -9.1 20.0 7.8 -11.0 -10.5 3.0 4.7 -12.1 12.2 -0.09 -0.03 2.6 63.3 40.2 89.6 76.0 80.5 95.6 84.8 71.2 75.4 97.6 99.8 96.3 . +10 142.9 256.7 150.2 286.1 139.3 56.3 135.7 270.4 234.9 5.12 5.26 44.3 -0.8 9.4 -19.3 -10.4 -1.7 10.2 -5.5 -0.3 -25.5 0.16 0.05 -1.7 99.4 83.3 39.6 74.7 99.0 58.0 83.5 100.0 31.7 90.7 98.5 99.5 . +11 135.7 198.2 270.4 278.5 175.7 57.2 110.7 234.9 248.0 4.42 5.12 24.0 4.9 17.2 4.9 -22.9 -0.6 8.5 -9.5 -12.6 4.1 0.12 0.27 -1.6 94.4 35.4 86.8 18.9 99.8 73.3 35.6 57.3 91.2 91.0 54.7 96.7 . +12 136.7 201.4 235.1 308.7 163.9 49.0 121.9 232.4 243.6 4.87 5.23 30.1 -1.1 18.2 -23.1 5.0 -15.7 4.8 -16.3 -20.2 -14.7 0.50 0.28 4.6 99.3 46.8 38.2 94.6 61.4 88.8 14.3 38.7 66.3 45.3 77.2 89.4 . +13 121.9 177.7 267.0 297.0 168.8 60.4 85.7 243.6 226.2 4.07 4.51 37.5 -9.5 -6.7 -1.8 1.1 0.0 8.1 -18.6 -7.3 -9.2 -0.04 -0.13 13.3 83.6 77.5 97.9 99.6 100.0 76.6 26.6 84.0 79.9 97.0 69.8 19.9 . +14 85.7 174.8 274.5 290.8 171.1 73.2 135.9 226.2 245.2 4.56 4.99 26.2 -0.6 -11.4 -6.7 -10.2 -7.5 18.8 -5.9 3.5 -10.7 -0.13 -0.29 8.4 99.4 61.8 85.9 68.6 81.8 21.3 64.1 96.7 69.8 92.2 72.2 67.5 . +15 135.9 174.1 261.6 303.4 190.5 53.8 146.6 245.2 253.6 4.45 5.04 33.3 -1.9 -9.0 3.4 -0.3 10.9 9.6 8.5 -7.3 -4.7 0.08 0.10 7.7 98.0 83.2 97.9 100.0 79.2 61.9 59.1 88.2 95.9 97.9 97.0 72.5 . +16 146.6 176.9 262.9 302.9 186.4 47.7 130.2 253.6 251.7 4.25 4.83 28.9 8.8 -6.2 4.7 -0.8 6.8 3.5 -7.9 1.0 -6.5 -0.12 -0.12 3.3 65.6 91.7 96.1 99.9 91.3 93.9 63.0 99.8 92.1 95.3 95.7 94.2 . +17 130.2 174.4 258.7 301.7 173.6 60.0 109.2 251.7 228.7 4.28 4.74 25.3 -0.5 -6.5 -6.8 0.3 -2.6 11.4 -10.9 4.2 -15.2 -0.02 -0.11 -0.3 99.9 86.1 75.6 100.0 95.9 57.3 25.3 94.1 28.3 99.8 90.2 99.9 . +18 109.2 178.8 271.7 301.4 179.5 55.3 122.4 228.7 239.5 4.52 5.06 23.1 -21.5 -2.2 6.2 -0.0 3.3 6.7 2.2 -18.8 -4.3 0.21 0.21 -2.4 33.2 98.4 79.7 100.0 93.7 82.5 94.4 29.1 90.2 73.9 69.4 92.6 . +19 122.4 178.5 265.5 300.9 184.6 45.0 110.3 239.5 245.7 4.41 5.04 27.7 -8.4 -2.4 -0.1 -0.5 8.4 -3.7 -9.8 -8.0 1.8 0.10 0.19 2.2 84.7 98.0 100.0 99.9 65.3 94.4 33.2 80.1 98.2 93.1 73.9 94.1 . +20 110.3 183.3 273.5 293.2 179.1 50.2 149.7 245.7 271.6 4.23 4.84 22.1 -27.5 0.2 15.4 -10.5 -0.4 6.0 11.6 -6.8 13.4 -0.15 -0.11 -3.4 1.6 100.0 65.3 78.1 100.0 83.1 37.3 89.7 71.1 93.2 96.5 93.9 . +21 149.7 259.9 171.6 287.8 138.5 44.6 112.8 271.6 234.7 4.65 5.00 34.3 0.4 6.6 -5.0 -7.1 7.8 0.4 15.0 0.4 2.1 0.11 0.10 -3.9 100.0 91.2 94.0 89.3 86.2 99.9 57.4 100.0 98.9 96.4 96.2 96.2 . +22 112.8 185.6 263.2 295.0 170.6 46.6 78.7 234.7 224.8 4.30 4.90 35.4 -18.6 1.3 -5.5 -0.9 1.8 -5.7 -25.5 -16.2 -10.6 0.19 0.25 11.2 50.3 99.1 81.7 99.8 97.5 87.7 8.2 42.1 74.2 51.2 26.3 31.8 . +## +loop_ +_ndb_struct_sugar_step_parameters.step_id +_ndb_struct_sugar_step_parameters.P_1 +_ndb_struct_sugar_step_parameters.tau_1 +_ndb_struct_sugar_step_parameters.Pn_1 +_ndb_struct_sugar_step_parameters.P_2 +_ndb_struct_sugar_step_parameters.tau_2 +_ndb_struct_sugar_step_parameters.Pn_2 +_ndb_struct_sugar_step_parameters.nu_1_1 +_ndb_struct_sugar_step_parameters.nu_1_2 +_ndb_struct_sugar_step_parameters.nu_1_3 +_ndb_struct_sugar_step_parameters.nu_1_4 +_ndb_struct_sugar_step_parameters.nu_1_5 +_ndb_struct_sugar_step_parameters.nu_2_1 +_ndb_struct_sugar_step_parameters.nu_2_2 +_ndb_struct_sugar_step_parameters.nu_2_3 +_ndb_struct_sugar_step_parameters.nu_2_4 +_ndb_struct_sugar_step_parameters.nu_2_5 +_ndb_struct_sugar_step_parameters.diff_nu_1_1 +_ndb_struct_sugar_step_parameters.diff_nu_1_2 +_ndb_struct_sugar_step_parameters.diff_nu_1_3 +_ndb_struct_sugar_step_parameters.diff_nu_1_4 +_ndb_struct_sugar_step_parameters.diff_nu_1_5 +_ndb_struct_sugar_step_parameters.diff_nu_2_1 +_ndb_struct_sugar_step_parameters.diff_nu_2_2 +_ndb_struct_sugar_step_parameters.diff_nu_2_3 +_ndb_struct_sugar_step_parameters.diff_nu_2_4 +_ndb_struct_sugar_step_parameters.diff_nu_2_5 + 1 161.2 55.3 C2end 139.5 41.7 C1exo 326.0 54.5 307.6 33.7 0.0 323.9 41.9 328.3 10.8 16.1 -6.6 16.4 -18.5 14.8 -5.1 -12.8 6.9 1.1 -9.2 14.2 + 2 139.5 41.7 C1exo 92.8 38.3 O4end 323.9 41.9 328.3 10.8 16.1 322.4 23.3 358.1 339.7 36.6 -10.4 9.2 -4.3 -2.2 8.3 2.1 -5.3 5.9 -5.1 2.4 + 3 92.8 38.3 O4end 166.3 48.5 C2end 322.4 23.3 358.1 339.7 36.6 333.3 46.3 312.9 33.4 355.6 -17.8 25.5 -23.5 13.6 3.0 -5.9 12.9 -14.3 12.0 -3.9 + 4 166.3 48.5 C2end 128.6 46.3 C1exo 333.3 46.3 312.9 33.4 355.6 315.3 44.3 331.1 3.4 25.8 0.6 8.2 -13.3 14.5 -9.5 -21.4 9.3 3.9 -16.6 23.9 + 5 128.6 46.3 C1exo 126.9 49.8 C1exo 315.3 44.3 331.1 3.4 25.8 311.9 47.5 330.1 2.2 29.0 -19.6 12.1 -1.8 -9.6 18.4 -14.7 14.1 -8.5 -0.1 9.5 + 6 126.9 49.8 C1exo 101.3 47.3 O4end 311.9 47.5 330.1 2.2 29.0 311.9 34.1 350.7 341.5 42.1 -22.4 14.9 -2.5 -10.8 21.1 -8.4 5.5 -1.5 -3.4 7.8 + 7 101.3 47.3 O4end 115.6 49.2 C1exo 311.9 34.1 350.7 341.5 42.1 309.9 42.8 338.7 352.6 36.2 -23.0 2.0 17.8 -31.5 34.6 -16.7 9.4 0.1 -9.7 16.8 + 8 115.6 49.2 C1exo 140.4 46.4 C1exo 309.9 42.8 338.7 352.6 36.2 319.6 46.3 324.3 12.9 17.1 -25.0 10.7 5.8 -20.4 28.8 -6.9 12.8 -14.4 10.6 -2.4 + 9 140.4 46.4 C1exo 145.8 53.4 C2end 319.6 46.3 324.3 12.9 17.1 315.9 54.5 315.9 20.4 14.1 -17.9 13.0 -4.7 -5.7 14.8 -17.3 17.5 -11.5 2.5 8.7 +10 145.8 53.4 C2end 147.5 47.0 C2end 315.9 54.5 315.9 20.4 14.1 322.3 47.0 320.3 18.6 11.7 -13.3 12.3 -7.0 0.0 7.8 -18.6 14.7 -6.8 -3.8 14.0 +11 147.5 47.0 C2end 113.8 51.7 C1exo 322.3 47.0 320.3 18.6 11.7 307.1 44.2 339.1 350.6 39.1 -12.6 14.9 -12.6 5.6 4.3 -19.5 10.7 0.5 -11.7 19.6 +12 153.2 43.1 C2end 128.5 45.0 C1exo 328.7 42.9 321.5 20.8 6.4 316.6 42.7 332.0 3.1 25.4 -8.8 9.7 -7.5 2.3 4.1 -16.6 5.7 4.6 -14.8 19.9 +13 128.5 45.0 C1exo 67.7 43.8 C4exo 316.6 42.7 332.0 3.1 25.4 326.1 9.4 16.6 323.0 44.6 -17.8 10.0 -0.7 -9.9 17.6 5.7 -19.2 24.4 -21.8 10.4 +14 67.7 43.8 C4exo 149.3 41.0 C2end 326.1 9.4 16.6 323.0 44.6 328.2 40.8 324.7 17.3 9.0 -14.2 11.6 -5.1 -3.1 11.0 -11.0 7.5 -2.5 -4.2 9.5 +15 149.3 41.0 C2end 169.3 42.3 C2end 328.2 40.8 324.7 17.3 9.0 338.9 39.2 318.4 30.1 354.0 -9.3 7.6 -4.3 -1.3 6.6 5.8 2.2 -8.9 12.2 -11.5 +16 169.3 42.3 C2end 147.0 42.3 C2end 338.9 39.2 318.4 30.1 354.0 326.3 43.1 324.5 16.5 10.6 1.4 6.0 -10.6 11.5 -8.3 -6.8 6.1 -2.8 -1.4 5.1 +17 147.0 42.3 C2end 116.3 47.5 C1exo 326.3 43.1 324.5 16.5 10.6 312.0 42.0 339.0 353.4 34.5 -8.6 11.0 -8.4 3.5 3.1 -14.6 8.5 0.3 -8.9 15.0 +18 116.3 47.5 C1exo 129.6 49.9 C1exo 312.0 42.0 339.0 353.4 34.5 312.2 48.2 328.2 4.7 27.1 -22.9 9.9 6.0 -19.6 27.0 -14.4 14.7 -10.5 2.5 7.6 +19 129.6 49.9 C1exo 113.6 42.9 C1exo 312.2 48.2 328.2 4.7 27.1 316.4 36.8 342.8 352.0 32.7 -22.7 16.0 -4.7 -8.3 19.6 -10.1 3.3 4.2 -10.3 13.2 +20 113.6 42.9 C1exo 156.3 51.0 C2end 316.4 36.8 342.8 352.0 32.7 324.4 50.9 313.3 27.9 4.3 -21.0 3.6 13.8 -26.6 30.3 -8.8 14.0 -14.0 10.0 -1.2 +21 156.3 51.0 C2end 116.9 44.2 C1exo 324.4 50.9 313.3 27.9 4.3 315.3 39.3 340.0 354.3 31.7 -5.1 6.9 -6.2 3.4 0.8 -16.4 25.5 -24.9 15.8 0.5 +22 116.9 44.2 C1exo 34.7 45.5 C3end 315.3 39.3 340.0 354.3 31.7 347.2 343.3 37.4 313.7 37.4 -19.0 6.6 7.3 -18.7 23.9 26.9 -45.3 45.2 -31.2 3.2 +## +# + diff --git a/package-lock.json b/package-lock.json index 5acf9fba0766a64a8b2c37ec7bf200c239bf765c..1f882cb3578af5905d3e9d9c54e0c7fae325e90a 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/package.json b/package.json index ece9b6560bf7eecac8db21e68935222dcd031c0d..87946d0b46c5fd22acbd8e9c2a8690b840a22760 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "molstar", - "version": "3.11.0", + "version": "3.23.0", "description": "A comprehensive macromolecular library.", "homepage": "https://github.com/molstar/molstar#readme", "repository": { @@ -20,7 +20,7 @@ "rebuild": "npm run clean && npm run build", "build-viewer": "npm run build-tsc && npm run build-extra && npm run build-webpack-viewer", "build-tsc": "concurrently \"tsc --incremental\" \"tsc --build tsconfig.commonjs.json --incremental\"", - "build-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/", + "build-extra": "cpx \"src/**/*.{scss,html,ico,jpg}\" lib/", "build-webpack": "webpack --mode production --config ./webpack.config.production.js", "build-webpack-viewer": "webpack --mode production --config ./webpack.config.viewer.js", "watch": "concurrently -c \"green,green,gray,gray\" --names \"tsc,srv,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-servers\" \"npm:watch-extra\" \"npm:watch-webpack\"", @@ -28,7 +28,7 @@ "watch-viewer-debug": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer-debug\"", "watch-tsc": "tsc --watch --incremental", "watch-servers": "tsc --build tsconfig.commonjs.json --watch --incremental", - "watch-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/ --watch", + "watch-extra": "cpx \"src/**/*.{scss,html,ico,jpg}\" lib/ --watch", "watch-webpack": "webpack -w --mode development --stats minimal", "watch-webpack-viewer": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.js", "watch-webpack-viewer-debug": "webpack -w --mode development --stats minimal --config ./webpack.config.viewer.debug.js", @@ -75,7 +75,9 @@ "node_modules", "lib" ], - "testURL": "http://localhost/", + "testEnvironmentOptions": { + "url": "http://localhost/" + }, "testRegex": "\\.spec\\.ts$" }, "author": "Mol* Contributors", @@ -87,70 +89,73 @@ "Ludovic Autin <autin@scripps.edu>", "Michal Malý <michal.maly@ibt.cas.cz>", "Jiřà Černý <jiri.cerny@ibt.cas.cz>", - "Panagiotis Tourlas <panagiot_tourlov@hotmail.com>" + "Panagiotis Tourlas <panagiot_tourlov@hotmail.com>", + "Adam Midlik <midlik@gmail.com>", + "Koya Sakuma <koya.sakuma.work@gmail.com>", + "Gianluca Tomasello <giagitom@gmail.com>" ], "license": "MIT", "devDependencies": { - "@graphql-codegen/add": "^3.1.1", - "@graphql-codegen/cli": "^2.6.2", - "@graphql-codegen/time": "^3.1.1", - "@graphql-codegen/typescript": "^2.5.1", - "@graphql-codegen/typescript-graphql-files-modules": "^2.1.1", - "@graphql-codegen/typescript-graphql-request": "^4.4.10", - "@graphql-codegen/typescript-operations": "^2.4.2", + "@graphql-codegen/add": "^3.2.1", + "@graphql-codegen/cli": "^2.13.7", + "@graphql-codegen/time": "^3.2.1", + "@graphql-codegen/typescript": "^2.7.4", + "@graphql-codegen/typescript-graphql-files-modules": "^2.2.1", + "@graphql-codegen/typescript-graphql-request": "^4.5.6", + "@graphql-codegen/typescript-operations": "^2.5.4", "@types/cors": "^2.8.12", - "@types/gl": "^4.1.0", - "@types/jest": "^28.1.3", - "@types/react": "^18.0.14", - "@types/react-dom": "^18.0.5", - "@typescript-eslint/eslint-plugin": "^5.29.0", - "@typescript-eslint/parser": "^5.29.0", + "@types/gl": "^4.1.1", + "@types/jest": "^29.1.2", + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "@typescript-eslint/eslint-plugin": "^5.40.0", + "@typescript-eslint/parser": "^5.40.0", "benchmark": "^2.1.4", - "concurrently": "^7.2.2", + "concurrently": "^7.4.0", "cpx2": "^4.2.0", "crypto-browserify": "^3.12.0", "css-loader": "^6.7.1", - "eslint": "^8.18.0", + "eslint": "^8.25.0", "extra-watch-webpack-plugin": "^1.0.3", "file-loader": "^6.2.0", "fs-extra": "^10.1.0", - "graphql": "^16.5.0", + "graphql": "^16.6.0", "http-server": "^14.1.1", - "jest": "^28.1.1", + "jest": "^29.2.0", "mini-css-extract-plugin": "^2.6.1", "path-browserify": "^1.0.1", "raw-loader": "^4.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "sass": "^1.53.0", - "sass-loader": "^13.0.1", - "simple-git": "^3.10.0", + "sass": "^1.55.0", + "sass-loader": "^13.1.0", + "simple-git": "^3.14.1", "stream-browserify": "^3.0.0", "style-loader": "^3.3.1", - "ts-jest": "^28.0.5", - "typescript": "^4.7.4", - "webpack": "^5.73.0", + "ts-jest": "^29.0.3", + "typescript": "^4.8.4", + "webpack": "^5.74.0", "webpack-cli": "^4.10.0" }, "dependencies": { "@types/argparse": "^2.0.10", - "@types/benchmark": "^2.1.1", + "@types/benchmark": "^2.1.2", "@types/compression": "1.7.2", - "@types/express": "^4.17.13", - "@types/node": "^16.11.41", + "@types/express": "^4.17.14", + "@types/node": "^16.11.66", "@types/node-fetch": "^2.6.2", "@types/swagger-ui-dist": "3.30.1", "argparse": "^2.0.1", - "body-parser": "^1.20.0", + "body-parser": "^1.20.1", "compression": "^1.7.4", "cors": "^2.8.5", - "express": "^4.18.1", + "express": "^4.18.2", "h264-mp4-encoder": "^1.0.12", "immer": "^9.0.15", "immutable": "^4.1.0", "node-fetch": "^2.6.7", - "rxjs": "^7.5.5", - "swagger-ui-dist": "^4.12.0", + "rxjs": "^7.5.7", + "swagger-ui-dist": "^4.14.3", "tslib": "^2.4.0", "util.promisify": "^1.1.1", "xhr2": "^0.2.1" diff --git a/src/apps/docking-viewer/index.ts b/src/apps/docking-viewer/index.ts index 8242e62030159354074c3e99d9e3f95bea9b57a9..2d7f5d71c97b7ed6a7aa473727b1143ef74935e5 100644 --- a/src/apps/docking-viewer/index.ts +++ b/src/apps/docking-viewer/index.ts @@ -58,20 +58,22 @@ class Viewer { } static async create(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) { - const o = { ...DefaultViewerOptions, ...{ - layoutIsExpanded: false, - layoutShowControls: false, - layoutShowRemoteState: false, - layoutShowSequence: true, - layoutShowLog: false, - layoutShowLeftPanel: true, - - viewportShowExpand: true, - viewportShowControls: false, - viewportShowSettings: false, - viewportShowSelectionMode: false, - viewportShowAnimation: false, - } }; + const o = { + ...DefaultViewerOptions, ...{ + layoutIsExpanded: false, + layoutShowControls: false, + layoutShowRemoteState: false, + layoutShowSequence: true, + layoutShowLog: false, + layoutShowLeftPanel: true, + + viewportShowExpand: true, + viewportShowControls: false, + viewportShowSettings: false, + viewportShowSelectionMode: false, + viewportShowAnimation: false, + } + }; const defaultSpec = DefaultPluginUISpec(); const spec: PluginUISpec = { @@ -135,18 +137,16 @@ class Viewer { } }; - plugin.behaviors.canvas3d.initialized.subscribe(v => { - if (v) { - PluginCommands.Canvas3D.SetSettings(plugin, { settings: { - renderer: { - ...plugin.canvas3d!.props.renderer, - backgroundColor: ColorNames.white, - }, - camera: { - ...plugin.canvas3d!.props.camera, - helper: { axes: { name: 'off', params: {} } } - } - } }); + PluginCommands.Canvas3D.SetSettings(plugin, { + settings: { + renderer: { + ...plugin.canvas3d!.props.renderer, + backgroundColor: ColorNames.white, + }, + camera: { + ...plugin.canvas3d!.props.camera, + helper: { axes: { name: 'off', params: {} } } + } } }); @@ -166,7 +166,7 @@ class Viewer { structures.push({ ref: structureProperties?.ref || structure.ref }); } - // remove current structuresfrom hierarchy as they will be merged + // remove current structures from hierarchy as they will be merged // TODO only works with using loadStructuresFromUrlsAndMerge once // need some more API metho to work with the hierarchy this.plugin.managers.structure.hierarchy.updateCurrent(this.plugin.managers.structure.hierarchy.current.structures, 'remove'); diff --git a/src/apps/docking-viewer/viewport.tsx b/src/apps/docking-viewer/viewport.tsx index 78e8412b5ef4dc70f035bf8a3bb03c2f6a379a4e..67976ac1ef065cd73fc79f58c8e1e57bb263d487 100644 --- a/src/apps/docking-viewer/viewport.tsx +++ b/src/apps/docking-viewer/viewport.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -202,14 +202,14 @@ const InteractionsPreset = StructureRepresentationPresetProvider({ const components = { ligand: await presetStaticComponent(plugin, structureCell, 'ligand'), surroundings: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandSurroundings, `surroundings`), - interactions: await plugin.builders.structure.tryCreateComponentFromSelection(structureCell, ligandPlusSurroundings, `interactions`) + interactions: await presetStaticComponent(plugin, structureCell, 'ligand'), }; const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, params); const representations = { ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.3 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ligand' }), ballAndStick: builder.buildRepresentation(update, components.surroundings, { type: 'ball-and-stick', typeParams: { ...typeParams, material: CustomMaterial, sizeFactor: 0.1, sizeAspectRatio: 1 }, color: 'element-symbol', colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ball-and-stick' }), - interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }), + interactions: builder.buildRepresentation(update, components.interactions, { type: InteractionsRepresentationProvider, typeParams: { ...typeParams, material: CustomMaterial, includeParent: true, parentDisplay: 'between' }, color: InteractionTypeColorThemeProvider }, { tag: 'interactions' }), label: builder.buildRepresentation(update, components.surroundings, { type: 'label', typeParams: { ...typeParams, material: CustomMaterial, background: false, borderWidth: 0.1 }, color: 'uniform', colorParams: { value: Color(0x000000) } }, { tag: 'label' }), }; diff --git a/src/apps/viewer/app.ts b/src/apps/viewer/app.ts index 2ab5c5eafb53157ff9ca975cb4041cbf990f8167..93920c9d94511f926731d070b237c4ca0012c379 100644 --- a/src/apps/viewer/app.ts +++ b/src/apps/viewer/app.ts @@ -46,6 +46,7 @@ import { Color } from '../../mol-util/color'; import '../../mol-util/polyfill'; import { ObjectKeys } from '../../mol-util/type-helpers'; import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants'; +import { Backgrounds } from '../../extensions/backgrounds'; export { PLUGIN_VERSION as version } from '../../mol-plugin/version'; export { setDebugMode, setProductionMode, setTimingMode } from '../../mol-util/debug'; @@ -55,6 +56,7 @@ const CustomFormats = [ ]; const Extensions = { + 'backgrounds': PluginSpec.Behavior(Backgrounds), 'cellpack': PluginSpec.Behavior(CellPack), 'dnatco-confal-pyramids': PluginSpec.Behavior(DnatcoConfalPyramids), 'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport), @@ -86,7 +88,9 @@ const DefaultViewerOptions = { pickScale: PluginConfig.General.PickScale.defaultValue, pickPadding: PluginConfig.General.PickPadding.defaultValue, enableWboit: PluginConfig.General.EnableWboit.defaultValue, + enableDpoit: PluginConfig.General.EnableDpoit.defaultValue, preferWebgl1: PluginConfig.General.PreferWebGl1.defaultValue, + allowMajorPerformanceCaveat: PluginConfig.General.AllowMajorPerformanceCaveat.defaultValue, viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue, viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue, @@ -156,7 +160,9 @@ export class Viewer { [PluginConfig.General.PickScale, o.pickScale], [PluginConfig.General.PickPadding, o.pickPadding], [PluginConfig.General.EnableWboit, o.enableWboit], + [PluginConfig.General.EnableDpoit, o.enableDpoit], [PluginConfig.General.PreferWebGl1, o.preferWebgl1], + [PluginConfig.General.AllowMajorPerformanceCaveat, o.allowMajorPerformanceCaveat], [PluginConfig.Viewport.ShowExpand, o.viewportShowExpand], [PluginConfig.Viewport.ShowControls, o.viewportShowControls], [PluginConfig.Viewport.ShowSettings, o.viewportShowSettings], @@ -197,7 +203,7 @@ export class Viewer { return PluginCommands.State.Snapshots.OpenUrl(this.plugin, { url, type }); } - loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions) { + loadStructureFromUrl(url: string, format: BuiltInTrajectoryFormat = 'mmcif', isBinary = false, options?: LoadStructureOptions & { label?: string }) { const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin); return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, { source: { @@ -206,6 +212,7 @@ export class Viewer { url: Asset.Url(url), format: format as any, isBinary, + label: options?.label, options: { ...params.source.params.options, representationParams: options?.representationParams as any }, } } @@ -494,4 +501,4 @@ export const ViewerAutoPreset = StructureRepresentationPresetProvider({ return await PresetStructureRepresentations.auto.apply(ref, params, plugin); } } -}); \ No newline at end of file +}); diff --git a/src/apps/viewer/embedded.html b/src/apps/viewer/embedded.html index 8533dc365cab388d7d2dae84f216ff55f0840c42..a309a84bdec9dec2d8e9d13488e5f38a8cd5fb0e 100644 --- a/src/apps/viewer/embedded.html +++ b/src/apps/viewer/embedded.html @@ -38,6 +38,15 @@ viewer.loadPdb('7bv2'); viewer.loadEmdb('EMD-30210', { detail: 6 }); // viewer.loadAllModelsOrAssemblyFromUrl('https://cs.litemol.org/5ire/full', 'mmcif', false, { representationParams: { theme: { globalName: 'operator-name' } } }) + // viewer.loadStructureFromUrl('my url', 'pdb', false, { + // representationParams: { + // theme: { + // globalName: 'uniform', + // globalColorParams: { value: 0xff0000 } + // } + // }, + // label: 'my structure' + // }); }); </script> </body> diff --git a/src/apps/viewer/index.html b/src/apps/viewer/index.html index c63978410e9ee354bfa5ac2d916dd2104d941b1d..71d41ff6787f13b3ac4a0f40a3d57a1a0fd25cad 100644 --- a/src/apps/viewer/index.html +++ b/src/apps/viewer/index.html @@ -60,7 +60,9 @@ var pickScale = getParam('pick-scale', '[^&]+').trim(); var pickPadding = getParam('pick-padding', '[^&]+').trim(); var disableWboit = getParam('disable-wboit', '[^&]+').trim() === '1'; + var enableDpoit = getParam('enable-dpoit', '[^&]+').trim() === '1'; var preferWebgl1 = getParam('prefer-webgl1', '[^&]+').trim() === '1' || void 0; + var allowMajorPerformanceCaveat = getParam('allow-major-performance-caveat', '[^&]+').trim() === '1'; molstar.Viewer.create('app', { layoutShowControls: !hideControls, @@ -74,8 +76,10 @@ pixelScale: parseFloat(pixelScale) || 1, pickScale: parseFloat(pickScale) || 0.25, pickPadding: isNaN(parseFloat(pickPadding)) ? 1 : parseFloat(pickPadding), - enableWboit: disableWboit ? false : void 0, // use default value if disable-wboit is not set + enableWboit: (disableWboit || enableDpoit) ? false : void 0, // use default value if disable-wboit is not set + enableDpoit: enableDpoit ? true : void 0, preferWebgl1: preferWebgl1, + allowMajorPerformanceCaveat: allowMajorPerformanceCaveat, }).then(viewer => { var snapshotId = getParam('snapshot-id', '[^&]+').trim(); if (snapshotId) viewer.setRemoteSnapshot(snapshotId); diff --git a/src/cli/chem-comp-dict/create-ions.ts b/src/cli/chem-comp-dict/create-ions.ts index dcb798d47f71745a11b07cc3356cebc625280077..5ad5c784dee66da3bad9602473d18168d08aa4e6 100644 --- a/src/cli/chem-comp-dict/create-ions.ts +++ b/src/cli/chem-comp-dict/create-ions.ts @@ -15,7 +15,7 @@ const writeFile = util.promisify(fs.writeFile); import { DatabaseCollection } from '../../mol-data/db'; import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd'; -import { ensureDataAvailable, readCCD } from './util'; +import { DefaultDataOptions, ensureDataAvailable, readCCD } from './util'; function extractIonNames(ccd: DatabaseCollection<CCD_Schema>) { const ionNames: string[] = []; @@ -44,8 +44,8 @@ export const IonNames = new Set(${JSON.stringify(ionNames).replace(/"/g, "'").re writeFile(filePath, output); } -async function run(out: string, forceDownload = false) { - await ensureDataAvailable(forceDownload); +async function run(out: string, options = DefaultDataOptions) { + await ensureDataAvailable(options); const ccd = await readCCD(); const ionNames = extractIonNames(ccd); if (!fs.existsSync(path.dirname(out))) { @@ -65,10 +65,15 @@ parser.add_argument('--forceDownload', '-f', { action: 'store_true', help: 'Force download of CCD and PVCD.' }); +parser.add_argument('--ccdUrl', '-c', { + help: 'Fetch the CCD from a custom URL. This forces download of the CCD.', + required: false +}); interface Args { out: string, forceDownload?: boolean, + ccdUrl?: string } const args: Args = parser.parse_args(); -run(args.out, args.forceDownload); +run(args.out, { forceDownload: args.forceDownload, ccdUrl: args.ccdUrl }); diff --git a/src/cli/chem-comp-dict/create-saccharides.ts b/src/cli/chem-comp-dict/create-saccharides.ts index 5850522d09e95f3f4643a916acadec82aa0a1ac9..dd629b65918a7ad7f0762647c650c0074b4ce526 100644 --- a/src/cli/chem-comp-dict/create-saccharides.ts +++ b/src/cli/chem-comp-dict/create-saccharides.ts @@ -14,7 +14,7 @@ const writeFile = util.promisify(fs.writeFile); import { DatabaseCollection } from '../../mol-data/db'; import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd'; -import { ensureDataAvailable, readCCD } from './util'; +import { DefaultDataOptions, ensureDataAvailable, readCCD } from './util'; function extractSaccharideNames(ccd: DatabaseCollection<CCD_Schema>) { const saccharideNames: string[] = []; @@ -47,8 +47,8 @@ export const SaccharideNames = new Set(${JSON.stringify(ionNames).replace(/"/g, writeFile(filePath, output); } -async function run(out: string, forceDownload = false) { - await ensureDataAvailable(forceDownload); +async function run(out: string, options = DefaultDataOptions) { + await ensureDataAvailable(options); const ccd = await readCCD(); const saccharideNames = extractSaccharideNames(ccd); if (!fs.existsSync(path.dirname(out))) { @@ -68,10 +68,15 @@ parser.add_argument('--forceDownload', '-f', { action: 'store_true', help: 'Force download of CCD and PVCD.' }); +parser.add_argument('--ccdUrl', '-c', { + help: 'Fetch the CCD from a custom URL. This forces download of the CCD.', + required: false +}); interface Args { out: string, forceDownload?: boolean, + ccdUrl?: string } const args: Args = parser.parse_args(); -run(args.out, args.forceDownload); +run(args.out, { forceDownload: args.forceDownload, ccdUrl: args.ccdUrl }); diff --git a/src/cli/chem-comp-dict/create-table.ts b/src/cli/chem-comp-dict/create-table.ts index 7cf2aeade71d069980e6d809bdb903ed74fa48ac..f26b884e3d73009ff8b438de66f7255b04aa4a10 100644 --- a/src/cli/chem-comp-dict/create-table.ts +++ b/src/cli/chem-comp-dict/create-table.ts @@ -18,7 +18,7 @@ import { SetUtils } from '../../mol-util/set'; import { DefaultMap } from '../../mol-util/map'; import { mmCIF_chemCompBond_schema } from '../../mol-io/reader/cif/schema/mmcif-extras'; import { ccd_chemCompAtom_schema } from '../../mol-io/reader/cif/schema/ccd-extras'; -import { ensureDataAvailable, getEncodedCif, readCCD, readPVCD } from './util'; +import { DefaultDataOptions, ensureDataAvailable, getEncodedCif, readCCD, readPVCD } from './util'; type CCB = Table<CCD_Schema['chem_comp_bond']> type CCA = Table<CCD_Schema['chem_comp_atom']> @@ -239,8 +239,8 @@ function createAtoms(ccd: DatabaseCollection<CCD_Schema>, pvcd: DatabaseCollecti ); } -async function run(out: string, binary = false, forceDownload = false, ccaOut?: string) { - await ensureDataAvailable(forceDownload); +async function run(out: string, binary = false, options = DefaultDataOptions, ccaOut?: string) { + await ensureDataAvailable(options); const ccd = await readCCD(); const pvcd = await readPVCD(); @@ -283,12 +283,22 @@ parser.add_argument('--ccaOut', '-a', { help: 'Optional generated file output path for chem_comp_atom data.', required: false }); +parser.add_argument('--ccdUrl', '-c', { + help: 'Fetch the CCD from a custom URL. This forces download of the CCD.', + required: false +}); +parser.add_argument('--pvcdUrl', '-p', { + help: 'Fetch the PVCD from a custom URL. This forces download of the PVCD.', + required: false +}); interface Args { out: string, forceDownload?: boolean, binary?: boolean, - ccaOut?: string + ccaOut?: string, + ccdUrl?: string, + pvcdUrl?: string } const args: Args = parser.parse_args(); -run(args.out, args.binary, args.forceDownload, args.ccaOut); +run(args.out, args.binary, { forceDownload: args.forceDownload, ccdUrl: args.ccdUrl, pvcdUrl: args.pvcdUrl }, args.ccaOut); diff --git a/src/cli/chem-comp-dict/util.ts b/src/cli/chem-comp-dict/util.ts index 5de81ee16b1e67812b03af06083b20195e7dc9c9..751f2005744cc0637d4d8d21e0a954d30c189a8b 100644 --- a/src/cli/chem-comp-dict/util.ts +++ b/src/cli/chem-comp-dict/util.ts @@ -35,9 +35,9 @@ export async function ensureAvailable(path: string, url: string, forceDownload = } } -export async function ensureDataAvailable(forceDownload = false) { - await ensureAvailable(CCD_PATH, CCD_URL, forceDownload); - await ensureAvailable(PVCD_PATH, PVCD_URL, forceDownload); +export async function ensureDataAvailable(options: DataOptions) { + await ensureAvailable(CCD_PATH, options.ccdUrl || CCD_URL, !!options.ccdUrl || options.forceDownload); + await ensureAvailable(PVCD_PATH, options.pvcdUrl || PVCD_URL, !!options.pvcdUrl || options.forceDownload); } export async function readFileAsCollection<S extends Database.Schema>(path: string, schema: S) { @@ -68,6 +68,16 @@ export function getEncodedCif(name: string, database: Database<Database.Schema>, return encoder.getData(); } +export type DataOptions = { + ccdUrl?: string, + pvcdUrl?: string, + forceDownload?: boolean +} + +export const DefaultDataOptions: DataOptions = { + forceDownload: false +}; + const DATA_DIR = path.join(__dirname, '..', '..', '..', '..', 'build/data'); const CCD_PATH = path.join(DATA_DIR, 'components.cif'); const PVCD_PATH = path.join(DATA_DIR, 'aa-variants-v1.cif'); diff --git a/src/cli/cifschema/util/cif-dic.ts b/src/cli/cifschema/util/cif-dic.ts index 6bf344e2f301f1c73c78e0002d8df39729a02f64..2f5f75efc7d4f66a04d1f81711699587c0bcd914 100644 --- a/src/cli/cifschema/util/cif-dic.ts +++ b/src/cli/cifschema/util/cif-dic.ts @@ -71,6 +71,7 @@ export function getFieldType(type: string, description: string, values?: string[ case 'ec-type': case 'ucode-alphanum-csv': case 'id_list': + case 'entity_id_list': return ListCol('str', ',', description); case 'id_list_spc': return ListCol('str', ' ', description); diff --git a/src/examples/alpha-orbitals/controls.tsx b/src/examples/alpha-orbitals/controls.tsx index bbde98561a7f76137e9b7456338871f3a1efe9dc..11c8147d9453dcd1107a33a760c3c7ed600bc5c7 100644 --- a/src/examples/alpha-orbitals/controls.tsx +++ b/src/examples/alpha-orbitals/controls.tsx @@ -4,16 +4,16 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import * as ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { AlphaOrbitalsExample } from '.'; import { ParameterControls } from '../../mol-plugin-ui/controls/parameters'; import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior'; import { PluginContextContainer } from '../../mol-plugin-ui/plugin'; export function mountControls(orbitals: AlphaOrbitalsExample, parent: Element) { - ReactDOM.render(<PluginContextContainer plugin={orbitals.plugin}> + createRoot(parent).render(<PluginContextContainer plugin={orbitals.plugin}> <Controls orbitals={orbitals} /> - </PluginContextContainer>, parent); + </PluginContextContainer>); } function Controls({ orbitals }: { orbitals: AlphaOrbitalsExample }) { diff --git a/src/examples/alpha-orbitals/index.ts b/src/examples/alpha-orbitals/index.ts index 7239bcc511664789ab4878dab94cf4b8dc8b2564..cf463bfeefd8d92aa74137c562be5d416d286983 100644 --- a/src/examples/alpha-orbitals/index.ts +++ b/src/examples/alpha-orbitals/index.ts @@ -82,24 +82,20 @@ export class AlphaOrbitalsExample { this.plugin.managers.interactivity.setProps({ granularity: 'element' }); - this.plugin.behaviors.canvas3d.initialized.subscribe(init => { - if (!init) return; - - if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) { - PluginCommands.Toast.Show(this.plugin, { - title: 'Error', - message: `Browser/device does not support required WebGL extension (OES_texture_float).` - }); - return; - } - - this.load({ - moleculeSdf: DemoMoleculeSDF, - ...DemoOrbitals + if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) { + PluginCommands.Toast.Show(this.plugin, { + title: 'Error', + message: `Browser/device does not support required WebGL extension (OES_texture_float).` }); + return; + } - mountControls(this, document.getElementById('controls')!); + this.load({ + moleculeSdf: DemoMoleculeSDF, + ...DemoOrbitals }); + + mountControls(this, document.getElementById('controls')!); } readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any); diff --git a/src/extensions/backgrounds/images/cells.jpg b/src/extensions/backgrounds/images/cells.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3502c798c5d012acd67640ca8cbbb2341014c71f Binary files /dev/null and b/src/extensions/backgrounds/images/cells.jpg differ diff --git a/src/extensions/backgrounds/index.ts b/src/extensions/backgrounds/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c5bbdc99e1f013f3658ffa660cc0df4889db5e44 --- /dev/null +++ b/src/extensions/backgrounds/index.ts @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { PluginBehavior } from '../../mol-plugin/behavior/behavior'; +import { PluginConfig } from '../../mol-plugin/config'; +import { Color } from '../../mol-util/color/color'; + +// from https://visualsonline.cancer.gov/details.cfm?imageid=2304, public domain +import image_cells from './images/cells.jpg'; + +// created with http://alexcpeterson.com/spacescape/ +import face_nebula_nx from './skyboxes/nebula/nebula_left2.jpg'; +import face_nebula_ny from './skyboxes/nebula/nebula_bottom4.jpg'; +import face_nebula_nz from './skyboxes/nebula/nebula_back6.jpg'; +import face_nebula_px from './skyboxes/nebula/nebula_right1.jpg'; +import face_nebula_py from './skyboxes/nebula/nebula_top3.jpg'; +import face_nebula_pz from './skyboxes/nebula/nebula_front5.jpg'; + +export const Backgrounds = PluginBehavior.create<{ }>({ + name: 'extension-backgrounds', + category: 'misc', + display: { + name: 'Backgrounds' + }, + ctor: class extends PluginBehavior.Handler<{ }> { + register(): void { + this.ctx.config.set(PluginConfig.Background.Styles, [ + [{ + variant: { + name: 'radialGradient', + params: { + centerColor: Color(0xFFFFFF), + edgeColor: Color(0x808080), + ratio: 0.2, + coverage: 'viewport', + } + } + }, 'Light Radial Gradient'], + [{ + variant: { + name: 'image', + params: { + source: { + name: 'url', + params: image_cells + }, + lightness: 0, + saturation: 0, + opacity: 1, + coverage: 'viewport', + } + } + }, 'Normal Cells Image'], + [{ + variant: { + name: 'skybox', + params: { + faces: { + name: 'urls', + params: { + nx: face_nebula_nx, + ny: face_nebula_ny, + nz: face_nebula_nz, + px: face_nebula_px, + py: face_nebula_py, + pz: face_nebula_pz, + } + }, + lightness: 0, + saturation: 0, + opacity: 1, + blur: 0.3, + } + } + }, 'Purple Nebula Skybox'], + ]); + } + + update() { + return false; + } + + unregister() { + this.ctx.config.set(PluginConfig.Background.Styles, []); + } + }, + params: () => ({ }) +}); diff --git a/src/extensions/backgrounds/skyboxes/nebula/nebula_back6.jpg b/src/extensions/backgrounds/skyboxes/nebula/nebula_back6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4e2f0fd8d977272a4bc2af9cf982fe866eb52186 Binary files /dev/null and b/src/extensions/backgrounds/skyboxes/nebula/nebula_back6.jpg differ diff --git a/src/extensions/backgrounds/skyboxes/nebula/nebula_bottom4.jpg b/src/extensions/backgrounds/skyboxes/nebula/nebula_bottom4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2be6e805e2ea0103230cf51637218e115cf0a76d Binary files /dev/null and b/src/extensions/backgrounds/skyboxes/nebula/nebula_bottom4.jpg differ diff --git a/src/extensions/backgrounds/skyboxes/nebula/nebula_front5.jpg b/src/extensions/backgrounds/skyboxes/nebula/nebula_front5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e9c0674db6f28f3d0fa02421e473517790d51f2d Binary files /dev/null and b/src/extensions/backgrounds/skyboxes/nebula/nebula_front5.jpg differ diff --git a/src/extensions/backgrounds/skyboxes/nebula/nebula_left2.jpg b/src/extensions/backgrounds/skyboxes/nebula/nebula_left2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..810037b66dc4601b24b1b1b9adf514f626247b6c Binary files /dev/null and b/src/extensions/backgrounds/skyboxes/nebula/nebula_left2.jpg differ diff --git a/src/extensions/backgrounds/skyboxes/nebula/nebula_right1.jpg b/src/extensions/backgrounds/skyboxes/nebula/nebula_right1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..059d46bf8e7d8d18ce12259679f0c53ad4a5dc27 Binary files /dev/null and b/src/extensions/backgrounds/skyboxes/nebula/nebula_right1.jpg differ diff --git a/src/extensions/backgrounds/skyboxes/nebula/nebula_top3.jpg b/src/extensions/backgrounds/skyboxes/nebula/nebula_top3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..831b81964a253fbbd08b0ba3aad74100ce55fad1 Binary files /dev/null and b/src/extensions/backgrounds/skyboxes/nebula/nebula_top3.jpg differ diff --git a/src/extensions/backgrounds/typings.d.ts b/src/extensions/backgrounds/typings.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..83e4393576a0b35df23ec0583a74e20387a0b97e --- /dev/null +++ b/src/extensions/backgrounds/typings.d.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +declare module '*.jpg' { + const value: string; + export = value; +} diff --git a/src/extensions/cellpack/model.ts b/src/extensions/cellpack/model.ts index a9babdc05b52181522f83db0b3569ed3a19a3b6a..80fb0b3d979a76bff36e18de6724ace45748852c 100644 --- a/src/extensions/cellpack/model.ts +++ b/src/extensions/cellpack/model.ts @@ -581,9 +581,20 @@ export const LoadCellPackModel = StateAction.build({ })(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => { if (params.preset.adjustStyle) { ctx.managers.interactivity.setProps({ granularity: 'chain' }); + ctx.managers.structure.component.setOptions({ + ... ctx.managers.structure.component.state.options, + visualQuality: 'custom', + ignoreLight: true, + showHydrogens: false, + }); ctx.canvas3d?.setProps({ multiSample: { mode: 'off' }, cameraClipping: { far: false }, + renderer: { colorMarker: false }, + marking: { + enabled: true, + ghostEdgeStrength: 1, + }, postprocessing: { occlusion: { name: 'on', diff --git a/src/extensions/dnatco/confal-pyramids/behavior.ts b/src/extensions/dnatco/confal-pyramids/behavior.ts index 6293e2b6cd60b8710dd2c0c3f68ea074980b2b52..7c34704ae344e745aa3ad5b0e0fc948d199dd588 100644 --- a/src/extensions/dnatco/confal-pyramids/behavior.ts +++ b/src/extensions/dnatco/confal-pyramids/behavior.ts @@ -8,7 +8,7 @@ import { ConfalPyramidsColorThemeProvider } from './color'; import { ConfalPyramids, ConfalPyramidsProvider } from './property'; import { ConfalPyramidsRepresentationProvider } from './representation'; -import { Loci } from '../../../mol-model/loci'; +import { ConfalPyramidsTypes } from './types'; import { PluginBehavior } from '../../../mol-plugin/behavior/behavior'; import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../mol-plugin-state/builder/structure/representation-preset'; import { StateObjectRef } from '../../../mol-state'; @@ -56,21 +56,10 @@ export const DnatcoConfalPyramids = PluginBehavior.create<{ autoAttach: boolean, description: 'Schematic depiction of conformer class and confal value.', }, ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showToolTip: boolean }> { - private provider = ConfalPyramidsProvider; - private labelConfalPyramids = { - label: (loci: Loci): string | undefined => { - if (!this.params.showToolTip) return void 0; - - /* TODO: Implement this */ - return void 0; - } - }; - register(): void { this.ctx.customModelProperties.register(this.provider, this.params.autoAttach); - this.ctx.managers.lociLabels.addProvider(this.labelConfalPyramids); this.ctx.representation.structure.themes.colorThemeRegistry.add(ConfalPyramidsColorThemeProvider); this.ctx.representation.structure.registry.add(ConfalPyramidsRepresentationProvider); @@ -88,7 +77,6 @@ export const DnatcoConfalPyramids = PluginBehavior.create<{ autoAttach: boolean, unregister() { this.ctx.customModelProperties.unregister(ConfalPyramidsProvider.descriptor.name); - this.ctx.managers.lociLabels.removeProvider(this.labelConfalPyramids); this.ctx.representation.structure.registry.remove(ConfalPyramidsRepresentationProvider); this.ctx.representation.structure.themes.colorThemeRegistry.remove(ConfalPyramidsColorThemeProvider); @@ -101,3 +89,13 @@ export const DnatcoConfalPyramids = PluginBehavior.create<{ autoAttach: boolean, showToolTip: PD.Boolean(true) }) }); + +export function confalPyramidLabel(halfPyramid: ConfalPyramidsTypes.HalfPyramid) { + const { step } = halfPyramid; + return ` + <b>${step.auth_asym_id_1}</b> | + <b>${step.label_comp_id_1} ${step.auth_seq_id_1}${step.PDB_ins_code_1}${step.label_alt_id_1.length > 0 ? ` (alt ${step.label_alt_id_1})` : ''} + ${step.label_comp_id_2} ${step.auth_seq_id_2}${step.PDB_ins_code_2}${step.label_alt_id_2.length > 0 ? ` (alt ${step.label_alt_id_2})` : ''} </b><br /> + <i>NtC:</i> ${step.NtC} | <i>Confal score:</i> ${step.confal_score} | <i>RMSD:</i> ${step.rmsd.toFixed(2)} + `; +} diff --git a/src/extensions/dnatco/confal-pyramids/color.ts b/src/extensions/dnatco/confal-pyramids/color.ts index 8bc71f0f37a411013cb26c629561cf44aa1192dc..1cf4ad87052d33ce98c58c19fe73c39bc8f08726 100644 --- a/src/extensions/dnatco/confal-pyramids/color.ts +++ b/src/extensions/dnatco/confal-pyramids/color.ts @@ -247,8 +247,8 @@ export function ConfalPyramidsColorTheme(ctx: ThemeDataContext, props: PD.Values function color(location: Location, isSecondary: boolean): Color { if (CPT.isLocation(location)) { - const { pyramid, isLower } = location.data; - const key = pyramid.NtC + `_${isLower ? 'Lwr' : 'Upr'}` as keyof PyramidsColors; + const { step, isLower } = location.data; + const key = step.NtC + `_${isLower ? 'Lwr' : 'Upr'}` as keyof PyramidsColors; return colorMap[key] ?? ErrorColor; } diff --git a/src/extensions/dnatco/confal-pyramids/property.ts b/src/extensions/dnatco/confal-pyramids/property.ts index 4417ebbc18816abdf6cd7fece1af9077b7d88574..5ca7bada8ce41ae7c0ceb7c51291a891ce06ab8d 100644 --- a/src/extensions/dnatco/confal-pyramids/property.ts +++ b/src/extensions/dnatco/confal-pyramids/property.ts @@ -16,7 +16,7 @@ import { PropertyWrapper } from '../../../mol-model-props/common/wrapper'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif'; -export type ConfalPyramids = PropertyWrapper<CPT.PyramidsData | undefined >; +export type ConfalPyramids = PropertyWrapper<CPT.Steps | undefined>; export namespace ConfalPyramids { export const Schema = { @@ -105,34 +105,42 @@ export const ConfalPyramidsProvider: CustomModelProperty.Provider<ConfalPyramids type StepsSummaryTable = Table<typeof ConfalPyramids.Schema.ndb_struct_ntc_step_summary>; -function createPyramidsFromCif(model: Model, - steps: Table<typeof ConfalPyramids.Schema.ndb_struct_ntc_step>, - stepsSummary: StepsSummaryTable): CPT.PyramidsData { - const pyramids = new Array<CPT.Pyramid>(); - const names = new Map<string, number>(); - const locations = new Array<CPT.Location>(); - let hasMultipleModels = false; +function createPyramidsFromCif( + model: Model, + cifSteps: Table<typeof ConfalPyramids.Schema.ndb_struct_ntc_step>, + stepsSummary: StepsSummaryTable +): CPT.Steps { + const steps = new Array<CPT.Step>(); + const mapping = new Array<CPT.MappedChains>(); const { id, PDB_model_number, name, auth_asym_id_1, auth_seq_id_1, label_comp_id_1, label_alt_id_1, PDB_ins_code_1, auth_asym_id_2, auth_seq_id_2, label_comp_id_2, label_alt_id_2, PDB_ins_code_2, - _rowCount } = steps; + _rowCount + } = cifSteps; if (_rowCount !== stepsSummary._rowCount) throw new Error('Inconsistent mmCIF data'); for (let i = 0; i < _rowCount; i++) { - const model_num = PDB_model_number.value(i); - if (model_num !== model.modelNum) - hasMultipleModels = true; - - const { _NtC, _confal_score } = getNtCAndConfalScore(id.value(i), i, stepsSummary); - - const pyramid = { - PDB_model_number: model_num, + const { + NtC, + confal_score, + rmsd + } = getSummaryData(id.value(i), i, stepsSummary); + const modelNum = PDB_model_number.value(i); + const chainId = auth_asym_id_1.value(i); + const seqId = auth_seq_id_1.value(i); + const modelIdx = modelNum - 1; + + if (mapping.length <= modelIdx || !mapping[modelIdx]) + mapping[modelIdx] = new Map<string, CPT.MappedResidues>(); + + const step = { + PDB_model_number: modelNum, name: name.value(i), - auth_asym_id_1: auth_asym_id_1.value(i), - auth_seq_id_1: auth_seq_id_1.value(i), + auth_asym_id_1: chainId, + auth_seq_id_1: seqId, label_comp_id_1: label_comp_id_1.value(i), label_alt_id_1: label_alt_id_1.value(i), PDB_ins_code_1: PDB_ins_code_1.value(i), @@ -141,30 +149,41 @@ function createPyramidsFromCif(model: Model, label_comp_id_2: label_comp_id_2.value(i), label_alt_id_2: label_alt_id_2.value(i), PDB_ins_code_2: PDB_ins_code_2.value(i), - confal_score: _confal_score, - NtC: _NtC + confal_score, + NtC, + rmsd, }; - pyramids.push(pyramid); - names.set(pyramid.name, pyramids.length - 1); + steps.push(step); - locations.push(CPT.Location(pyramid, false)); - locations.push(CPT.Location(pyramid, true)); + const mappedChains = mapping[modelIdx]; + const residuesOnChain = mappedChains.get(chainId) ?? new Map<number, number[]>(); + const stepsForResidue = residuesOnChain.get(seqId) ?? []; + stepsForResidue.push(steps.length - 1); + + residuesOnChain.set(seqId, stepsForResidue); + mappedChains.set(chainId, residuesOnChain); + mapping[modelIdx] = mappedChains; } - return { pyramids, names, locations, hasMultipleModels }; + return { steps, mapping }; } -function getNtCAndConfalScore(id: number, i: number, stepsSummary: StepsSummaryTable) { - const { step_id, confal_score, assigned_NtC } = stepsSummary; +function getSummaryData(id: number, i: number, stepsSummary: StepsSummaryTable) { + const { + step_id, + confal_score, + assigned_NtC, + cartesian_rmsd_closest_NtC_representative, + } = stepsSummary; // Assume that step_ids in ntc_step_summary are in the same order as steps in ntc_step for (let j = i; j < stepsSummary._rowCount; j++) { - if (id === step_id.value(j)) return { _NtC: assigned_NtC.value(j), _confal_score: confal_score.value(j) }; + if (id === step_id.value(j)) return { NtC: assigned_NtC.value(j), confal_score: confal_score.value(j), rmsd: cartesian_rmsd_closest_NtC_representative.value(j) }; } // Safety net for cases where the previous assumption is not met for (let j = 0; j < i; j++) { - if (id === step_id.value(j)) return { _NtC: assigned_NtC.value(j), _confal_score: confal_score.value(j) }; + if (id === step_id.value(j)) return { NtC: assigned_NtC.value(j), confal_score: confal_score.value(j), rmsd: cartesian_rmsd_closest_NtC_representative.value(j) }; } throw new Error('Inconsistent mmCIF data'); } diff --git a/src/extensions/dnatco/confal-pyramids/representation.ts b/src/extensions/dnatco/confal-pyramids/representation.ts index b0ce90b53e355ad33fcf2379bbe4445949868ed8..1c7d4f95b1e05aaa06298becd8a4d7f89ecba911 100644 --- a/src/extensions/dnatco/confal-pyramids/representation.ts +++ b/src/extensions/dnatco/confal-pyramids/representation.ts @@ -6,7 +6,7 @@ */ import { ConfalPyramids, ConfalPyramidsProvider } from './property'; -import { ConfalPyramidsUtil } from './util'; +import { ConfalPyramidsIterator } from './util'; import { ConfalPyramidsTypes as CPT } from './types'; import { Interval } from '../../../mol-data/int'; import { Mesh } from '../../../mol-geo/geometry/mesh/mesh'; @@ -16,14 +16,14 @@ import { PrimitiveBuilder } from '../../../mol-geo/primitive/primitive'; import { LocationIterator } from '../../../mol-geo/util/location-iterator'; import { Mat4, Vec3 } from '../../../mol-math/linear-algebra'; import { EmptyLoci, Loci } from '../../../mol-model/loci'; -import { Structure, StructureProperties, Unit } from '../../../mol-model/structure'; +import { Structure, Unit } from '../../../mol-model/structure'; import { CustomProperty } from '../../../mol-model-props/common/custom-property'; import { Representation, RepresentationContext, RepresentationParamsGetter } from '../../../mol-repr/representation'; import { StructureRepresentation, StructureRepresentationProvider, StructureRepresentationStateBuilder, UnitsRepresentation } from '../../../mol-repr/structure/representation'; import { UnitsMeshParams, UnitsMeshVisual, UnitsVisual } from '../../../mol-repr/structure/units-visual'; import { VisualUpdateState } from '../../../mol-repr/util'; import { VisualContext } from '../../../mol-repr/visual'; -import { getAltResidueLociFromId, StructureGroup } from '../../../mol-repr/structure/visual/util/common'; +import { StructureGroup } from '../../../mol-repr/structure/visual/util/common'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { Theme, ThemeRegistryContext } from '../../../mol-theme/theme'; import { NullLocation } from '../../../mol-model/location'; @@ -32,6 +32,12 @@ const t = Mat4.identity(); const w = Vec3.zero(); const mp = Vec3.zero(); +const posO3 = Vec3(); +const posP = Vec3(); +const posOP1 = Vec3(); +const posOP2 = Vec3(); +const posO5 = Vec3(); + function calcMidpoint(mp: Vec3, v: Vec3, w: Vec3) { Vec3.sub(mp, v, w); Vec3.scale(mp, mp, 0.5); @@ -53,64 +59,76 @@ function createConfalPyramidsIterator(structureGroup: StructureGroup): LocationI const { structure, group } = structureGroup; const instanceCount = group.units.length; - const prop = ConfalPyramidsProvider.get(structure.model).value; - if (prop === undefined || prop.data === undefined) { - return LocationIterator(0, 1, 1, () => NullLocation); - } + const data = ConfalPyramidsProvider.get(structure.model)?.value?.data; + if (!data) return LocationIterator(0, 1, 1, () => NullLocation); - const { locations } = prop.data; + const halfPyramidsCount = data.steps.length * 2; const getLocation = (groupIndex: number, instanceIndex: number) => { - if (locations.length <= groupIndex) return NullLocation; - return locations[groupIndex]; + if (halfPyramidsCount <= groupIndex) return NullLocation; + const idx = Math.floor(groupIndex / 2); // Map groupIndex to a step, see createConfalPyramidsMesh() for full explanation + return CPT.Location(data.steps[idx], groupIndex % 2 === 1); }; - return LocationIterator(locations.length, instanceCount, 1, getLocation); + return LocationIterator(halfPyramidsCount, instanceCount, 1, getLocation); } function createConfalPyramidsMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<ConfalPyramidsMeshParams>, mesh?: Mesh) { if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh); - const prop = ConfalPyramidsProvider.get(structure.model).value; - if (prop === undefined || prop.data === undefined) return Mesh.createEmpty(mesh); - - const { pyramids } = prop.data; - if (pyramids.length === 0) return Mesh.createEmpty(mesh); - - const mb = MeshBuilder.createState(512, 512, mesh); - - const handler = (pyramid: CPT.Pyramid, first: ConfalPyramidsUtil.FirstResidueAtoms, second: ConfalPyramidsUtil.SecondResidueAtoms, firsLocIndex: number, secondLocIndex: number) => { - if (firsLocIndex === -1 || secondLocIndex === -1) - throw new Error('Invalid location index'); - - const scale = (pyramid.confal_score - 20.0) / 100.0; - const O3 = first.O3.pos; - const OP1 = second.OP1.pos; const OP2 = second.OP2.pos; const O5 = second.O5.pos; const P = second.P.pos; - - shiftVertex(O3, P, scale); - shiftVertex(OP1, P, scale); - shiftVertex(OP2, P, scale); - shiftVertex(O5, P, scale); - calcMidpoint(mp, O3, O5); - - mb.currentGroup = firsLocIndex; - let pb = PrimitiveBuilder(3); - /* Upper part (for first residue in step) */ - pb.add(O3, OP1, OP2); - pb.add(O3, mp, OP1); - pb.add(O3, OP2, mp); - MeshBuilder.addPrimitive(mb, t, pb.getPrimitive()); - - /* Lower part (for second residue in step */ - mb.currentGroup = secondLocIndex; - pb = PrimitiveBuilder(3); - pb.add(mp, O5, OP1); - pb.add(mp, OP2, O5); - pb.add(O5, OP2, OP1); - MeshBuilder.addPrimitive(mb, t, pb.getPrimitive()); - }; - - const walker = new ConfalPyramidsUtil.UnitWalker(structure, unit, handler); - walker.walk(); + const data = ConfalPyramidsProvider.get(structure.model)?.value?.data; + if (!data) return Mesh.createEmpty(mesh); + + const { steps, mapping } = data; + if (steps.length === 0) return Mesh.createEmpty(mesh); + const vertexCount = (6 * steps.length) / mapping.length; + + const mb = MeshBuilder.createState(vertexCount, vertexCount / 10, mesh); + + const it = new ConfalPyramidsIterator(structure, unit); + while (it.hasNext) { + const allPoints = it.move(); + + for (const points of allPoints) { + const { O3, P, OP1, OP2, O5, confalScore } = points; + const scale = (confalScore - 20.0) / 100.0; + // Steps can be drawn in a different order than they are stored. + // To make sure that we can get from the drawn pyramid back to the step in represents, + // we need to use an appropriate groupId. The stepIdx passed from the iterator + // is an index into the array of all steps in the structure. + // Since a step is drawn as two "half-pyramids" we need two ids to map to a single step. + // To do that, we just multiply the index by 2. idx*2 marks the "upper" half-pyramid, + // (idx*2)+1 the "lower" half-pyramid. + const groupIdx = points.stepIdx * 2; + + unit.conformation.invariantPosition(O3, posO3); + unit.conformation.invariantPosition(P, posP); + unit.conformation.invariantPosition(OP1, posOP1); + unit.conformation.invariantPosition(OP2, posOP2); + unit.conformation.invariantPosition(O5, posO5); + + shiftVertex(posO3, posP, scale); + shiftVertex(posOP1, posP, scale); + shiftVertex(posOP2, posP, scale); + shiftVertex(posO5, posP, scale); + calcMidpoint(mp, posO3, posO5); + + mb.currentGroup = groupIdx; + let pb = PrimitiveBuilder(3); + /* Upper part (for first residue in step) */ + pb.add(posO3, posOP1, posOP2); + pb.add(posO3, mp, posOP1); + pb.add(posO3, posOP2, mp); + MeshBuilder.addPrimitive(mb, t, pb.getPrimitive()); + + /* Lower part (for second residue in step) */ + mb.currentGroup = groupIdx + 1; + pb = PrimitiveBuilder(3); + pb.add(mp, posO5, posOP1); + pb.add(mp, posOP2, posO5); + pb.add(posO5, posOP2, posOP1); + MeshBuilder.addPrimitive(mb, t, pb.getPrimitive()); + } + } return MeshBuilder.getMesh(mb); } @@ -124,16 +142,17 @@ function getConfalPyramidLoci(pickingId: PickingId, structureGroup: StructureGro const unit = structureGroup.group.units[instanceId]; if (!Unit.isAtomic(unit)) return EmptyLoci; - const prop = ConfalPyramidsProvider.get(structure.model).value; - if (prop === undefined || prop.data === undefined) return EmptyLoci; + const data = ConfalPyramidsProvider.get(structure.model)?.value?.data; + if (!data) return EmptyLoci; + + const halfPyramidsCount = data.steps.length * 2; - const { locations } = prop.data; + if (halfPyramidsCount <= groupId) return EmptyLoci; - if (locations.length <= groupId) return EmptyLoci; - const altId = StructureProperties.atom.label_alt_id(CPT.toElementLocation(locations[groupId])); - const rI = unit.residueIndex[locations[groupId].element.element]; + const idx = Math.floor(groupId / 2); // Map groupIndex to a step, see createConfalPyramidsMesh() for full explanation + const step = data.steps[idx]; - return getAltResidueLociFromId(structure, unit, rI, altId); + return CPT.Loci({ step, isLower: groupId % 2 === 1 }, [{}]); } function eachConfalPyramid(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) { diff --git a/src/extensions/dnatco/confal-pyramids/types.ts b/src/extensions/dnatco/confal-pyramids/types.ts index b7a94099885cb2aa5e37094cb6bb100c355b2392..f2b898822333d901e9d99789e1a93fd1ef4f4cf2 100644 --- a/src/extensions/dnatco/confal-pyramids/types.ts +++ b/src/extensions/dnatco/confal-pyramids/types.ts @@ -6,10 +6,13 @@ */ import { DataLocation } from '../../../mol-model/location'; -import { ElementIndex, Structure, StructureElement, Unit } from '../../../mol-model/structure'; +import { DataLoci } from '../../../mol-model/loci'; +import { confalPyramidLabel } from './behavior'; export namespace ConfalPyramidsTypes { - export type Pyramid = { + export const DataTag = 'dnatco-confal-half-pyramid'; + + export type Step = { PDB_model_number: number, name: string, auth_asym_id_1: string, @@ -23,38 +26,40 @@ export namespace ConfalPyramidsTypes { label_alt_id_2: string, PDB_ins_code_2: string, confal_score: number, - NtC: string + NtC: string, + rmsd: number, } - export interface PyramidsData { - pyramids: Array<Pyramid>, - names: Map<string, number>, - locations: Array<Location>, - hasMultipleModels: boolean - } + export type MappedChains = Map<string, MappedResidues>; + export type MappedResidues = Map<number, number[]>; - export interface LocationData { - readonly pyramid: Pyramid - readonly isLower: boolean; + export interface Steps { + steps: Array<Step>, + mapping: MappedChains[], } - export interface Element { - structure: Structure; - unit: Unit.Atomic; - element: ElementIndex; + export interface HalfPyramid { + step: Step, + isLower: boolean, } - export interface Location extends DataLocation<LocationData, Element> {} + export interface Location extends DataLocation<HalfPyramid, {}> {} - export function Location(pyramid: Pyramid, isLower: boolean, structure?: Structure, unit?: Unit.Atomic, element?: ElementIndex) { - return DataLocation('pyramid', { pyramid, isLower }, { structure: structure as any, unit: unit as any, element: element as any }); + export function Location(step: Step, isLower: boolean) { + return DataLocation(DataTag, { step, isLower }, {}); } export function isLocation(x: any): x is Location { - return !!x && x.kind === 'data-location' && x.tag === 'pyramid'; + return !!x && x.kind === 'data-location' && x.tag === DataTag; + } + + export interface Loci extends DataLoci<HalfPyramid, {}> {} + + export function Loci(data: HalfPyramid, elements: ReadonlyArray<{}>): Loci { + return DataLoci(DataTag, data, elements, undefined, () => confalPyramidLabel(data)); } - export function toElementLocation(location: Location) { - return StructureElement.Location.create(location.element.structure, location.element.unit, location.element.element); + export function isLoci(x: any): x is Loci { + return !!x && x.kind === 'data-loci' && x.tag === DataTag; } } diff --git a/src/extensions/dnatco/confal-pyramids/util.ts b/src/extensions/dnatco/confal-pyramids/util.ts index 467734af416ccba7b34fdf65e64b3e80abf30535..af4e9793f259d202a52ccb0b896c7156bea87ae4 100644 --- a/src/extensions/dnatco/confal-pyramids/util.ts +++ b/src/extensions/dnatco/confal-pyramids/util.ts @@ -8,288 +8,120 @@ import { ConfalPyramidsProvider } from './property'; import { ConfalPyramidsTypes as CPT } from './types'; import { Segmentation } from '../../../mol-data/int'; -import { Vec3 } from '../../../mol-math/linear-algebra'; import { ChainIndex, ElementIndex, ResidueIndex, Structure, StructureElement, StructureProperties, Unit } from '../../../mol-model/structure'; -export namespace ConfalPyramidsUtil { - type Residue = Segmentation.Segment<ResidueIndex>; +type Residue = Segmentation.Segment<ResidueIndex>; - export type AtomInfo = { - pos: Vec3, - index: ElementIndex, - fakeAltId: string, - }; +export type Pyramid = { + O3: ElementIndex, + P: ElementIndex, + OP1: ElementIndex, + OP2: ElementIndex, + O5: ElementIndex, + confalScore: number, + stepIdx: number, +}; - export type FirstResidueAtoms = { - O3: AtomInfo, - }; +const EmptyStepIndices = new Array<number>(); - export type SecondResidueAtoms = { - OP1: AtomInfo, - OP2: AtomInfo, - O5: AtomInfo, - P: AtomInfo, - }; - - type ResidueInfo = { - PDB_model_num: number, - asym_id: string, - auth_asym_id: string, - seq_id: number, - auth_seq_id: number, - comp_id: string, - alt_id: string, - ins_code: string, - }; +function copyResidue(r?: Residue) { + return r ? { index: r.index, start: r.start, end: r.end } : void 0; +} - export type Handler = (pyramid: CPT.Pyramid, first: FirstResidueAtoms, second: SecondResidueAtoms, firstLocIndex: number, secondLocIndex: number) => void; +function getAtomIndex(loc: StructureElement.Location, residue: Residue, names: string[], altId: string): ElementIndex { + for (let eI = residue.start; eI < residue.end; eI++) { + loc.element = loc.unit.elements[eI]; + const elName = StructureProperties.atom.label_atom_id(loc); + const elAltId = StructureProperties.atom.label_alt_id(loc); - function residueInfoFromLocation(loc: StructureElement.Location): ResidueInfo { - return { - PDB_model_num: StructureProperties.unit.model_num(loc), - asym_id: StructureProperties.chain.label_asym_id(loc), - auth_asym_id: StructureProperties.chain.auth_asym_id(loc), - seq_id: StructureProperties.residue.label_seq_id(loc), - auth_seq_id: StructureProperties.residue.auth_seq_id(loc), - comp_id: StructureProperties.atom.label_comp_id(loc), - alt_id: StructureProperties.atom.label_alt_id(loc), - ins_code: StructureProperties.residue.pdbx_PDB_ins_code(loc) - }; + if (names.includes(elName) && (elAltId === altId || elAltId.length === 0)) + return loc.element; } - export function hasMultipleModels(unit: Unit.Atomic): boolean { - const prop = ConfalPyramidsProvider.get(unit.model).value; - if (prop === undefined || prop.data === undefined) throw new Error('No custom properties data'); - return prop.data.hasMultipleModels; - } + return -1 as ElementIndex; +} - function getPossibleAltIds(residue: Residue, structure: Structure, unit: Unit.Atomic): string[] { - const possibleAltIds: string[] = []; +function getPyramid(loc: StructureElement.Location, one: Residue, two: Residue, altIdOne: string, altIdTwo: string, confalScore: number, stepIdx: number): Pyramid { + const O3 = getAtomIndex(loc, one, ['O3\'', 'O3*'], altIdOne); + const P = getAtomIndex(loc, two, ['P'], altIdTwo); + const OP1 = getAtomIndex(loc, two, ['OP1'], altIdTwo); + const OP2 = getAtomIndex(loc, two, ['OP2'], altIdTwo); + const O5 = getAtomIndex(loc, two, ['O5\'', 'O5*'], altIdTwo); - const loc = StructureElement.Location.create(structure, unit, -1 as ElementIndex); - for (let rI = residue.start; rI <= residue.end - 1; rI++) { - loc.element = unit.elements[rI]; - const altId = StructureProperties.atom.label_alt_id(loc); - if (altId !== '' && !possibleAltIds.includes(altId)) possibleAltIds.push(altId); - } + return { O3, P, OP1, OP2, O5, confalScore, stepIdx }; +} - return possibleAltIds; +export class ConfalPyramidsIterator { + private chainIt: Segmentation.SegmentIterator<ChainIndex>; + private residueIt: Segmentation.SegmentIterator<ResidueIndex>; + private residueOne?: Residue; + private residueTwo: Residue; + private data?: CPT.Steps; + private loc: StructureElement.Location; + + private getStepIndices(r: Residue) { + this.loc.element = this.loc.unit.elements[r.start]; + + const modelIdx = StructureProperties.unit.model_num(this.loc) - 1; + const chainId = StructureProperties.chain.auth_asym_id(this.loc); + const seqId = StructureProperties.residue.auth_seq_id(this.loc); + + const chains = this.data!.mapping[modelIdx]; + if (!chains) return EmptyStepIndices; + const residues = chains.get(chainId); + if (!residues) return EmptyStepIndices; + return residues.get(seqId) ?? EmptyStepIndices; } - class Utility { - protected getPyramidByName(name: string): { pyramid: CPT.Pyramid | undefined, index: number } { - const index = this.data.names.get(name); - if (index === undefined) return { pyramid: undefined, index: -1 }; - - return { pyramid: this.data.pyramids[index], index }; - } - - protected stepToName(entry_id: string, modelNum: number, locFirst: StructureElement.Location, locSecond: StructureElement.Location, fakeAltId_1: string, fakeAltId_2: string) { - const first = residueInfoFromLocation(locFirst); - const second = residueInfoFromLocation(locSecond); - const model_id = this.hasMultipleModels ? `-m${modelNum}` : ''; - const alt_id_1 = fakeAltId_1 !== '' ? `.${fakeAltId_1}` : (first.alt_id.length ? `.${first.alt_id}` : ''); - const alt_id_2 = fakeAltId_2 !== '' ? `.${fakeAltId_2}` : (second.alt_id.length ? `.${second.alt_id}` : ''); - const ins_code_1 = first.ins_code.length ? `.${first.ins_code}` : ''; - const ins_code_2 = second.ins_code.length ? `.${second.ins_code}` : ''; - - return `${entry_id}${model_id}_${first.auth_asym_id}_${first.comp_id}${alt_id_1}_${first.auth_seq_id}${ins_code_1}_${second.comp_id}${alt_id_2}_${second.auth_seq_id}${ins_code_2}`; - } - - constructor(unit: Unit.Atomic) { - const prop = ConfalPyramidsProvider.get(unit.model).value; - if (prop === undefined || prop.data === undefined) throw new Error('No custom properties data'); - - this.data = prop.data; - this.hasMultipleModels = hasMultipleModels(unit); + private moveStep() { + this.residueOne = copyResidue(this.residueTwo); + this.residueTwo = copyResidue(this.residueIt.move())!; - this.entryId = unit.model.entryId.toLowerCase(); - this.modelNum = unit.model.modelNum; - } - - protected readonly data: CPT.PyramidsData; - protected readonly hasMultipleModels: boolean; - protected readonly entryId: string; - protected readonly modelNum: number; + return this.toPyramids(this.residueOne!, this.residueTwo); } - export class UnitWalker extends Utility { - private getAtomIndices(names: string[], residue: Residue): ElementIndex[] { - const indices: ElementIndex[] = []; - - const loc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex); - for (let rI = residue.start; rI <= residue.end - 1; rI++) { - loc.element = this.unit.elements[rI]; - const thisName = StructureProperties.atom.label_atom_id(loc); - if (names.includes(thisName)) indices.push(loc.element); - } - - if (indices.length === 0) { - let namesStr = ''; - for (const n of names) - namesStr += `${n} `; - - throw new Error(`Element [${namesStr}] not found on residue ${residue.index}`); - } - - return indices; - } - - private getAtomPositions(indices: ElementIndex[]): Vec3[] { - const pos = this.unit.conformation.invariantPosition; - const positions: Vec3[] = []; - - for (const eI of indices) { - const v = Vec3.zero(); - pos(eI, v); - positions.push(v); - } - - return positions; - } - - private handleStep(firstAtoms: FirstResidueAtoms[], secondAtoms: SecondResidueAtoms[]) { - const modelNum = this.hasMultipleModels ? this.modelNum : -1; - let ok = false; - - const firstLoc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex); - const secondLoc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex); - for (let i = 0; i < firstAtoms.length; i++) { - const first = firstAtoms[i]; - for (let j = 0; j < secondAtoms.length; j++) { - const second = secondAtoms[j]; - firstLoc.element = first.O3.index; - secondLoc.element = second.OP1.index; + private toPyramids(one: Residue, two: Residue) { + const indices = this.getStepIndices(one); - const name = this.stepToName(this.entryId, modelNum, firstLoc, secondLoc, first.O3.fakeAltId, second.OP1.fakeAltId); - const { pyramid, index } = this.getPyramidByName(name); - if (pyramid !== undefined) { - const setLoc = (loc: CPT.Location, eI: ElementIndex) => { - loc.element.structure = this.structure; - loc.element.unit = this.unit; - loc.element.element = eI; - }; - - const locIndex = index * 2; - setLoc(this.data.locations[locIndex], firstLoc.element); - setLoc(this.data.locations[locIndex + 1], secondLoc.element); - this.handler(pyramid, first, second, locIndex, locIndex + 1); - ok = true; - } - } - } - - if (!ok) throw new Error('Bogus step'); - } - - private processFirstResidue(residue: Residue, possibleAltIds: string[]) { - const indO3 = this.getAtomIndices(['O3\'', 'O3*'], residue); - const posO3 = this.getAtomPositions(indO3); - - const altPos: FirstResidueAtoms[] = [ - { O3: { pos: posO3[0], index: indO3[0], fakeAltId: '' } } - ]; - - for (let i = 1; i < indO3.length; i++) { - altPos.push({ O3: { pos: posO3[i], index: indO3[i], fakeAltId: '' } }); - } - - if (altPos.length === 1 && possibleAltIds.length > 1) { - /* We have some alternate positions on the residue but O3 does not have any - fake them */ - altPos[0].O3.fakeAltId = possibleAltIds[0]; - - for (let i = 1; i < possibleAltIds.length; i++) - altPos.push({ O3: { pos: posO3[0], index: indO3[0], fakeAltId: possibleAltIds[i] } }); - } - - return altPos; + const points = []; + for (const idx of indices) { + const step = this.data!.steps[idx]; + points.push(getPyramid(this.loc, one, two, step.label_alt_id_1, step.label_alt_id_2, step.confal_score, idx)); } - private processSecondResidue(residue: Residue, possibleAltIds: string[]) { - const indOP1 = this.getAtomIndices(['OP1'], residue); - const indOP2 = this.getAtomIndices(['OP2'], residue); - const indO5 = this.getAtomIndices(['O5\'', 'O5*'], residue); - const indP = this.getAtomIndices(['P'], residue); - - const posOP1 = this.getAtomPositions(indOP1); - const posOP2 = this.getAtomPositions(indOP2); - const posO5 = this.getAtomPositions(indO5); - const posP = this.getAtomPositions(indP); - - const infoOP1: AtomInfo[] = []; - /* We use OP1 as "pivotal" atom. There is no specific reason - * to pick OP1, it is as good a choice as any other atom - */ - if (indOP1.length === 1 && possibleAltIds.length > 1) { - /* No altIds on OP1, fake them */ - for (const altId of possibleAltIds) - infoOP1.push({ pos: posOP1[0], index: indOP1[0], fakeAltId: altId }); - } else { - for (let i = 0; i < indOP1.length; i++) - infoOP1.push({ pos: posOP1[i], index: indOP1[i], fakeAltId: '' }); - } - - const mkInfo = (i: number, indices: ElementIndex[], positions: Vec3[], altId: string) => { - if (i >= indices.length) { - const last = indices.length - 1; - return { pos: positions[last], index: indices[last], fakeAltId: altId }; - } - - return { pos: positions[i], index: indices[i], fakeAltId: altId }; - }; - - const altPos: SecondResidueAtoms[] = []; - for (let i = 0; i < infoOP1.length; i++) { - const altId = infoOP1[i].fakeAltId; - - const OP2 = mkInfo(i, indOP2, posOP2, altId); - const O5 = mkInfo(i, indO5, posO5, altId); - const P = mkInfo(i, indP, posP, altId); - - altPos.push({ OP1: infoOP1[i], OP2, O5, P }); - } - - return altPos; - } - - private step(residue: Residue): { firstAtoms: FirstResidueAtoms[], secondAtoms: SecondResidueAtoms[] } { - const firstPossibleAltIds = getPossibleAltIds(residue, this.structure, this.unit); - const firstAtoms = this.processFirstResidue(residue, firstPossibleAltIds); + return points; + } - residue = this.residueIt.move(); + constructor(structure: Structure, unit: Unit) { + this.chainIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, unit.elements); + this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements); - const secondPossibleAltIds = getPossibleAltIds(residue, this.structure, this.unit); - const secondAtoms = this.processSecondResidue(residue, secondPossibleAltIds); + const prop = ConfalPyramidsProvider.get(unit.model).value; + this.data = prop?.data; - return { firstAtoms, secondAtoms }; + if (this.chainIt.hasNext) { + this.residueIt.setSegment(this.chainIt.move()); + if (this.residueIt.hasNext) + this.residueTwo = this.residueIt.move(); } - walk() { - while (this.chainIt.hasNext) { - this.residueIt.setSegment(this.chainIt.move()); - - let residue = this.residueIt.move(); - while (this.residueIt.hasNext) { - try { - const { firstAtoms, secondAtoms } = this.step(residue); - - this.handleStep(firstAtoms, secondAtoms); - } catch (error) { - /* Skip and move along */ - residue = this.residueIt.move(); - } - } - } - } + this.loc = StructureElement.Location.create(structure, unit, -1 as ElementIndex); + } - constructor(private structure: Structure, private unit: Unit.Atomic, private handler: Handler) { - super(unit); + get hasNext() { + if (!this.data) + return false; + return this.residueIt.hasNext + ? true + : this.chainIt.hasNext; + } - this.chainIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, unit.elements); - this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements); + move() { + if (this.residueIt.hasNext) { + return this.moveStep(); + } else { + this.residueIt.setSegment(this.chainIt.move()); + return this.moveStep(); } - - private chainIt: Segmentation.SegmentIterator<ChainIndex>; - private residueIt: Segmentation.SegmentIterator<ResidueIndex>; } } diff --git a/src/extensions/geo-export/ui.tsx b/src/extensions/geo-export/ui.tsx index 837c59021f5fb38fa342a315846ccd3eb8277466..78aa038e85dd6dcd5c6433940c2ea3f6a4e300c0 100644 --- a/src/extensions/geo-export/ui.tsx +++ b/src/extensions/geo-export/ui.tsx @@ -60,6 +60,8 @@ export class GeometryExporterUI extends CollapsableControls<{}, State> { } componentDidMount() { + if (!this.plugin.canvas3d) return; + const merged = merge( this.controls.behaviors.params, this.plugin.canvas3d!.reprCount diff --git a/src/extensions/mp4-export/controls.ts b/src/extensions/mp4-export/controls.ts index a8edd32816a919b64d0ce1337dc6e6084d2ed5bd..b6dadcc5e80f79a5f6a78201e6cde93c389e2012 100644 --- a/src/extensions/mp4-export/controls.ts +++ b/src/extensions/mp4-export/controls.ts @@ -118,11 +118,13 @@ export class Mp4Controls extends PluginComponent { } private init() { + if (!this.plugin.canvas3d) return; + this.subscribe(this.plugin.managers.animation.events.updated.pipe(debounceTime(16)), () => { this.sync(); }); - this.subscribe(this.plugin.canvas3d?.resized!, () => this.syncInfo()); + this.subscribe(this.plugin.canvas3d.resized, () => this.syncInfo()); this.subscribe(this.plugin.helpers.viewportScreenshot?.events.previewed!, () => this.syncInfo()); this.subscribe(this.plugin.behaviors.state.isBusy, b => this.updateCanApply(b)); diff --git a/src/extensions/mp4-export/encoder.ts b/src/extensions/mp4-export/encoder.ts index 5dc12af421808abee78e69e341f7b4d7ae263e4f..38e96599220a377c94704939d9a951be46d1ff17 100644 --- a/src/extensions/mp4-export/encoder.ts +++ b/src/extensions/mp4-export/encoder.ts @@ -69,6 +69,7 @@ export async function encodeMp4Animation<A extends PluginStateAnimation>(plugin: const dt = durationMs / N; await ctx.update({ message: 'Rendering...', isIndeterminate: false, current: 0, max: N + 1 }); + await params.pass.updateBackground(); await plugin.managers.animation.play(params.animation.definition, params.animation.params); stoppedAnimation = false; diff --git a/src/extensions/rcsb/graphql/types.ts b/src/extensions/rcsb/graphql/types.ts index a113cbbe299abb84df59e65f4ee6d21cf6894ea2..58fc86f4e00d4ef5bf0743dbc2e23756466d3edf 100644 --- a/src/extensions/rcsb/graphql/types.ts +++ b/src/extensions/rcsb/graphql/types.ts @@ -4,7 +4,7 @@ export type InputMaybe<T> = Maybe<T>; export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] }; export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> }; export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> }; -// Generated on 2022-06-26T14:02:35-07:00 +// Generated on 2022-08-20T16:36:05-07:00 /** All built-in and custom scalars, mapped to their actual values */ export type Scalars = { @@ -13,11 +13,8 @@ export type Scalars = { Boolean: boolean; Int: number; Float: number; - /** Built-in scalar representing an instant in time */ Date: any; - /** Built-in scalar for dynamic values */ ObjectScalar: any; - /** Use SPQR's SchemaPrinter to remove this from SDL */ UNREPRESENTABLE: any; }; diff --git a/src/mol-canvas3d/camera.ts b/src/mol-canvas3d/camera.ts index 2b82c50df22dd208c232a961817a14dc06d11247..169478586e24ba8d072321be640a24eccfb887e2 100644 --- a/src/mol-canvas3d/camera.ts +++ b/src/mol-canvas3d/camera.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -260,7 +260,8 @@ namespace Camera { radius: 0, radiusMax: 10, fog: 50, - clipFar: true + clipFar: true, + minNear: 5, }; } @@ -276,6 +277,7 @@ namespace Camera { radiusMax: number fog: number clipFar: boolean + minNear: number } export function copySnapshot(out: Snapshot, source?: Partial<Snapshot>) { @@ -292,6 +294,7 @@ namespace Camera { if (typeof source.radiusMax !== 'undefined') out.radiusMax = source.radiusMax; if (typeof source.fog !== 'undefined') out.fog = source.fog; if (typeof source.clipFar !== 'undefined') out.clipFar = source.clipFar; + if (typeof source.minNear !== 'undefined') out.minNear = source.minNear; return out; } @@ -303,6 +306,7 @@ namespace Camera { && a.radiusMax === b.radiusMax && a.fog === b.fog && a.clipFar === b.clipFar + && a.minNear === b.minNear && Vec3.exactEquals(a.position, b.position) && Vec3.exactEquals(a.up, b.up) && Vec3.exactEquals(a.target, b.target); @@ -370,7 +374,7 @@ function updatePers(camera: Camera) { } function updateClip(camera: Camera) { - let { radius, radiusMax, mode, fog, clipFar } = camera.state; + let { radius, radiusMax, mode, fog, clipFar, minNear } = camera.state; if (radius < 0.01) radius = 0.01; const normalizedFar = clipFar ? radius : radiusMax; @@ -384,12 +388,12 @@ function updateClip(camera: Camera) { if (mode === 'perspective') { // set at least to 5 to avoid slow sphere impostor rendering - near = Math.max(Math.min(radiusMax, 5), near); - far = Math.max(5, far); + near = Math.max(Math.min(radiusMax, minNear), near); + far = Math.max(minNear, far); } else { // not too close to 0 as it causes issues with outline rendering - near = Math.max(Math.min(radiusMax, 5), near); - far = Math.max(5, far); + near = Math.max(Math.min(radiusMax, minNear), near); + far = Math.max(minNear, far); } if (near === far) { diff --git a/src/mol-canvas3d/camera/stereo.ts b/src/mol-canvas3d/camera/stereo.ts index 0111427191417c5e81137c60bff2f1144b38b560..4742dd93f1bdb4d8b69e6c866af6be6e7d3e4d44 100644 --- a/src/mol-canvas3d/camera/stereo.ts +++ b/src/mol-canvas3d/camera/stereo.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -13,8 +13,8 @@ import { Camera, ICamera } from '../camera'; import { Viewport } from './util'; export const StereoCameraParams = { - eyeSeparation: PD.Numeric(0.064, { min: 0.01, max: 0.5, step: 0.001 }), - focus: PD.Numeric(10, { min: 1, max: 100, step: 0.01 }), + eyeSeparation: PD.Numeric(0.062, { min: 0.02, max: 0.1, step: 0.001 }, { description: 'Distance between left and right camera.' }), + focus: PD.Numeric(10, { min: 1, max: 20, step: 0.1 }, { description: 'Apparent object distance.' }), }; export const DefaultStereoCameraProps = PD.getDefaultValues(StereoCameraParams); export type StereoCameraProps = PD.Values<typeof StereoCameraParams> diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 2af6cf0a438d2c36b3585a12660eab8207c619ef..ed05d3a357ba32df5d8d88e57981a2b654d5c205 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -3,6 +3,7 @@ * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> + * @author Gianluca Tomasello <giagitom@gmail.com> */ import { BehaviorSubject, Subscription } from 'rxjs'; @@ -39,7 +40,10 @@ import { Helper } from './helper/helper'; import { Passes } from './passes/passes'; import { shallowEqual } from '../mol-util'; import { MarkingParams } from './passes/marking'; -import { GraphicsRenderVariantsBlended, GraphicsRenderVariantsWboit } from '../mol-gl/webgl/render-item'; +import { GraphicsRenderVariantsBlended, GraphicsRenderVariantsWboit, GraphicsRenderVariantsDpoit } from '../mol-gl/webgl/render-item'; +import { degToRad, radToDeg } from '../mol-math/misc'; +import { AssetManager } from '../mol-util/assets'; +import { deepClone } from '../mol-util/object'; export const Canvas3DParams = { camera: PD.Group({ @@ -49,6 +53,7 @@ export const Canvas3DParams = { on: PD.Group(StereoCameraParams), off: PD.Group({}) }, { cycle: true, hideIf: p => p?.mode !== 'perspective' }), + fov: PD.Numeric(45, { min: 10, max: 130, step: 1 }, { label: 'Field of View' }), manualReset: PD.Boolean(false, { isHidden: true }), }, { pivot: 'mode' }), cameraFog: PD.MappedStatic('on', { @@ -60,6 +65,7 @@ export const Canvas3DParams = { cameraClipping: PD.Group({ radius: PD.Numeric(100, { min: 0, max: 99, step: 1 }, { label: 'Clipping', description: 'How much of the scene to show.' }), far: PD.Boolean(true, { description: 'Hide scene in the distance' }), + minNear: PD.Numeric(5, { min: 0.1, max: 10, step: 0.1 }, { description: 'Note, may cause performance issues rendering impostors when set too small and cause issues with outline rendering when too close to 0.' }), }, { pivot: 'radius' }), viewport: PD.MappedStatic('canvas', { canvas: PD.Group({}), @@ -78,7 +84,9 @@ export const Canvas3DParams = { }), cameraResetDurationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'The time it takes to reset the camera.' }), + sceneRadiusFactor: PD.Numeric(1, { min: 1, max: 10, step: 0.1 }), transparentBackground: PD.Boolean(false), + dpoitIterations: PD.Numeric(2, { min: 1, max: 10, step: 1 }), multiSample: PD.Group(MultiSampleParams), postprocessing: PD.Group(PostprocessingParams), @@ -106,11 +114,13 @@ interface Canvas3DContext { readonly attribs: Readonly<Canvas3DContext.Attribs> readonly contextLost: BehaviorSubject<now.Timestamp> readonly contextRestored: BehaviorSubject<now.Timestamp> + readonly assetManager: AssetManager dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => void } namespace Canvas3DContext { export const DefaultAttribs = { + failIfMajorPerformanceCaveat: false, /** true by default to avoid issues with Safari (Jan 2021) */ antialias: true, /** true to support multiple Canvas3D objects with a single context */ @@ -120,14 +130,19 @@ namespace Canvas3DContext { /** extra pixels to around target to check in case target is empty */ pickPadding: 1, enableWboit: true, + enableDpoit: false, preferWebGl1: false }; export type Attribs = typeof DefaultAttribs - export function fromCanvas(canvas: HTMLCanvasElement, attribs: Partial<Attribs> = {}): Canvas3DContext { + export function fromCanvas(canvas: HTMLCanvasElement, assetManager: AssetManager, attribs: Partial<Attribs> = {}): Canvas3DContext { const a = { ...DefaultAttribs, ...attribs }; - const { antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a; + + if (a.enableWboit && a.enableDpoit) throw new Error('Multiple transparency methods not allowed.'); + + const { failIfMajorPerformanceCaveat, antialias, preserveDrawingBuffer, pixelScale, preferWebGl1 } = a; const gl = getGLContext(canvas, { + failIfMajorPerformanceCaveat, antialias, preserveDrawingBuffer, alpha: true, // the renderer requires an alpha channel @@ -139,7 +154,7 @@ namespace Canvas3DContext { const input = InputObserver.fromElement(canvas, { pixelScale, preventGestures: true }); const webgl = createContext(gl, { pixelScale }); - const passes = new Passes(webgl, attribs); + const passes = new Passes(webgl, assetManager, a); if (isDebugMode) { const loseContextExt = gl.getExtension('WEBGL_lose_context'); @@ -192,6 +207,7 @@ namespace Canvas3DContext { attribs: a, contextLost, contextRestored: webgl.contextRestored, + assetManager, dispose: (options?: Partial<{ doNotForceWebGLContextLoss: boolean }>) => { input.dispose(); @@ -278,8 +294,8 @@ namespace Canvas3D { export interface DragEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, pageStart: Vec2, pageEnd: Vec2 } export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, page?: Vec2, position?: Vec3 } - export function create({ webgl, input, passes, attribs }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D { - const p: Canvas3DProps = { ...DefaultCanvas3DParams, ...props }; + export function create({ webgl, input, passes, attribs, assetManager }: Canvas3DContext, props: Partial<Canvas3DProps> = {}): Canvas3D { + const p: Canvas3DProps = { ...deepClone(DefaultCanvas3DParams), ...deepClone(props) }; const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>(); const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>(); @@ -296,14 +312,19 @@ namespace Canvas3D { let width = 128; let height = 128; updateViewport(); + const scene = Scene.create(webgl, passes.draw.dpoitEnabled ? GraphicsRenderVariantsDpoit : (passes.draw.wboitEnabled ? GraphicsRenderVariantsWboit : GraphicsRenderVariantsBlended)); - const scene = Scene.create(webgl, passes.draw.wboitEnabled ? GraphicsRenderVariantsWboit : GraphicsRenderVariantsBlended); + function getSceneRadius() { + return scene.boundingSphere.radius * p.sceneRadiusFactor; + } const camera = new Camera({ position: Vec3.create(0, 0, 100), mode: p.camera.mode, fog: p.cameraFog.name === 'on' ? p.cameraFog.params.intensity : 0, - clipFar: p.cameraClipping.far + clipFar: p.cameraClipping.far, + minNear: p.cameraClipping.minNear, + fov: degToRad(p.camera.fov), }, { x, y, width, height }, { pixelScale: attribs.pixelScale }); const stereoCamera = new StereoCamera(camera, p.camera.stereo.params); @@ -315,6 +336,10 @@ namespace Canvas3D { const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input, camera, p.interaction); const multiSampleHelper = new MultiSampleHelper(passes.multiSample); + passes.draw.postprocessing.background.update(camera, p.postprocessing.background, changed => { + if (changed) requestDraw(); + }); + let cameraResetRequested = false; let nextCameraResetDuration: number | undefined = void 0; let nextCameraResetSnapshot: Camera.SnapshotProvider | undefined = void 0; @@ -395,7 +420,7 @@ namespace Canvas3D { y > gl.drawingBufferHeight || y + height < 0 ) return false; - const markingUpdated = resolveMarking(); + const markingUpdated = resolveMarking() && (renderer.props.colorMarker || p.marking.enabled); let didRender = false; controls.update(currentTime); @@ -523,7 +548,7 @@ namespace Canvas3D { const focus = camera.getFocus(center, radius); const next = typeof nextCameraResetSnapshot === 'function' ? nextCameraResetSnapshot(scene, camera) : nextCameraResetSnapshot; const snapshot = next ? { ...focus, ...next } : focus; - camera.setState({ ...snapshot, radiusMax: scene.boundingSphere.radius }, duration); + camera.setState({ ...snapshot, radiusMax: getSceneRadius() }, duration); } nextCameraResetDuration = void 0; @@ -574,7 +599,7 @@ namespace Canvas3D { } if (oldBoundingSphereVisible.radius === 0) nextCameraResetDuration = 0; - camera.setState({ radiusMax: scene.boundingSphere.radius }, 0); + if (!p.camera.manualReset) camera.setState({ radiusMax: getSceneRadius() }, 0); reprCount.next(reprRenderObjects.size); if (isDebugMode) consoleStats(); @@ -650,7 +675,7 @@ namespace Canvas3D { function getProps(): Canvas3DProps { const radius = scene.boundingSphere.radius > 0 - ? 100 - Math.round((camera.transition.target.radius / scene.boundingSphere.radius) * 100) + ? 100 - Math.round((camera.transition.target.radius / getSceneRadius()) * 100) : 0; return { @@ -658,14 +683,17 @@ namespace Canvas3D { mode: camera.state.mode, helper: { ...helper.camera.props }, stereo: { ...p.camera.stereo }, + fov: Math.round(radToDeg(camera.state.fov)), manualReset: !!p.camera.manualReset }, cameraFog: camera.state.fog > 0 ? { name: 'on' as const, params: { intensity: camera.state.fog } } : { name: 'off' as const, params: {} }, - cameraClipping: { far: camera.state.clipFar, radius }, + cameraClipping: { far: camera.state.clipFar, radius, minNear: camera.state.minNear }, cameraResetDurationMs: p.cameraResetDurationMs, + sceneRadiusFactor: p.sceneRadiusFactor, transparentBackground: p.transparentBackground, + dpoitIterations: p.dpoitIterations, viewport: p.viewport, postprocessing: { ...p.postprocessing }, @@ -767,10 +795,19 @@ namespace Canvas3D { ? produce(getProps(), properties as any) : properties; + if (props.sceneRadiusFactor !== undefined) { + p.sceneRadiusFactor = props.sceneRadiusFactor; + camera.setState({ radiusMax: getSceneRadius() }, 0); + } + const cameraState: Partial<Camera.Snapshot> = Object.create(null); if (props.camera && props.camera.mode !== undefined && props.camera.mode !== camera.state.mode) { cameraState.mode = props.camera.mode; } + const oldFov = Math.round(radToDeg(camera.state.fov)); + if (props.camera && props.camera.fov !== undefined && props.camera.fov !== oldFov) { + cameraState.fov = degToRad(props.camera.fov); + } if (props.cameraFog !== undefined && props.cameraFog.params) { const newFog = props.cameraFog.name === 'on' ? props.cameraFog.params.intensity : 0; if (newFog !== camera.state.fog) cameraState.fog = newFog; @@ -779,8 +816,11 @@ namespace Canvas3D { if (props.cameraClipping.far !== undefined && props.cameraClipping.far !== camera.state.clipFar) { cameraState.clipFar = props.cameraClipping.far; } + if (props.cameraClipping.minNear !== undefined && props.cameraClipping.minNear !== camera.state.minNear) { + cameraState.minNear = props.cameraClipping.minNear; + } if (props.cameraClipping.radius !== undefined) { - const radius = (scene.boundingSphere.radius / 100) * (100 - props.cameraClipping.radius); + const radius = (getSceneRadius() / 100) * (100 - props.cameraClipping.radius); if (radius > 0 && radius !== cameraState.radius) { // if radius = 0, NaNs happen cameraState.radius = Math.max(radius, 0.01); @@ -791,9 +831,13 @@ namespace Canvas3D { if (props.camera?.helper) helper.camera.setProps(props.camera.helper); if (props.camera?.manualReset !== undefined) p.camera.manualReset = props.camera.manualReset; - if (props.camera?.stereo !== undefined) Object.assign(p.camera.stereo, props.camera.stereo); + if (props.camera?.stereo !== undefined) { + Object.assign(p.camera.stereo, props.camera.stereo); + stereoCamera.setProps(p.camera.stereo.params); + } if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs; if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground; + if (props.dpoitIterations !== undefined) p.dpoitIterations = props.dpoitIterations; if (props.viewport !== undefined) { const doNotUpdate = p.viewport === props.viewport || (p.viewport.name === props.viewport.name && shallowEqual(p.viewport.params, props.viewport.params)); @@ -805,6 +849,12 @@ namespace Canvas3D { } } + if (props.postprocessing?.background) { + Object.assign(p.postprocessing.background, props.postprocessing.background); + passes.draw.postprocessing.background.update(camera, p.postprocessing.background, changed => { + if (changed && !doNotRequestDraw) requestDraw(); + }); + } if (props.postprocessing) Object.assign(p.postprocessing, props.postprocessing); if (props.marking) Object.assign(p.marking, props.marking); if (props.multiSample) Object.assign(p.multiSample, props.multiSample); @@ -823,7 +873,7 @@ namespace Canvas3D { } }, getImagePass: (props: Partial<ImageProps> = {}) => { - return new ImagePass(webgl, renderer, scene, camera, helper, passes.draw.wboitEnabled, props); + return new ImagePass(webgl, assetManager, renderer, scene, camera, helper, passes.draw.wboitEnabled, passes.draw.dpoitEnabled, props); }, getRenderObjects(): GraphicsRenderObject[] { const renderObjects: GraphicsRenderObject[] = []; @@ -888,4 +938,4 @@ namespace Canvas3D { Viewport.set(controls.viewport, x, y, width, height); } } -} \ No newline at end of file +} diff --git a/src/mol-canvas3d/passes/background.ts b/src/mol-canvas3d/passes/background.ts new file mode 100644 index 0000000000000000000000000000000000000000..14d194b6baa418f1e381cf47b182735590df2be2 --- /dev/null +++ b/src/mol-canvas3d/passes/background.ts @@ -0,0 +1,467 @@ +/** + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { QuadPositions, } from '../../mol-gl/compute/util'; +import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable'; +import { AttributeSpec, DefineSpec, TextureSpec, UniformSpec, Values, ValueSpec } from '../../mol-gl/renderable/schema'; +import { ShaderCode } from '../../mol-gl/shader-code'; +import { background_frag } from '../../mol-gl/shader/background.frag'; +import { background_vert } from '../../mol-gl/shader/background.vert'; +import { WebGLContext } from '../../mol-gl/webgl/context'; +import { createComputeRenderItem } from '../../mol-gl/webgl/render-item'; +import { createNullTexture, CubeFaces, Texture } from '../../mol-gl/webgl/texture'; +import { Mat4 } from '../../mol-math/linear-algebra/3d/mat4'; +import { ValueCell } from '../../mol-util/value-cell'; +import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { isTimingMode } from '../../mol-util/debug'; +import { Camera, ICamera } from '../camera'; +import { Vec3 } from '../../mol-math/linear-algebra/3d/vec3'; +import { Vec2 } from '../../mol-math/linear-algebra/3d/vec2'; +import { Color } from '../../mol-util/color'; +import { Asset, AssetManager } from '../../mol-util/assets'; +import { Vec4 } from '../../mol-math/linear-algebra/3d/vec4'; + +const SharedParams = { + opacity: PD.Numeric(1, { min: 0.0, max: 1.0, step: 0.01 }), + saturation: PD.Numeric(0, { min: -1, max: 1, step: 0.01 }), + lightness: PD.Numeric(0, { min: -1, max: 1, step: 0.01 }), +}; + +const SkyboxParams = { + faces: PD.MappedStatic('urls', { + urls: PD.Group({ + nx: PD.Text('', { label: 'Negative X / Left' }), + ny: PD.Text('', { label: 'Negative Y / Bottom' }), + nz: PD.Text('', { label: 'Negative Z / Back' }), + px: PD.Text('', { label: 'Positive X / Right' }), + py: PD.Text('', { label: 'Positive Y / Top' }), + pz: PD.Text('', { label: 'Positive Z / Front' }), + }, { isExpanded: true, label: 'URLs' }), + files: PD.Group({ + nx: PD.File({ label: 'Negative X / Left', accept: 'image/*' }), + ny: PD.File({ label: 'Negative Y / Bottom', accept: 'image/*' }), + nz: PD.File({ label: 'Negative Z / Back', accept: 'image/*' }), + px: PD.File({ label: 'Positive X / Right', accept: 'image/*' }), + py: PD.File({ label: 'Positive Y / Top', accept: 'image/*' }), + pz: PD.File({ label: 'Positive Z / Front', accept: 'image/*' }), + }, { isExpanded: true, label: 'Files' }), + }), + blur: PD.Numeric(0, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'Note, this only works in WebGL2 or when "EXT_shader_texture_lod" is available.' }), + ...SharedParams, +}; +type SkyboxProps = PD.Values<typeof SkyboxParams> + +const ImageParams = { + source: PD.MappedStatic('url', { + url: PD.Text(''), + file: PD.File({ accept: 'image/*' }), + }), + ...SharedParams, + coverage: PD.Select('viewport', PD.arrayToOptions(['viewport', 'canvas'])), +}; +type ImageProps = PD.Values<typeof ImageParams> + +const HorizontalGradientParams = { + topColor: PD.Color(Color(0xDDDDDD)), + bottomColor: PD.Color(Color(0xEEEEEE)), + ratio: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }), + coverage: PD.Select('viewport', PD.arrayToOptions(['viewport', 'canvas'])), +}; + +const RadialGradientParams = { + centerColor: PD.Color(Color(0xDDDDDD)), + edgeColor: PD.Color(Color(0xEEEEEE)), + ratio: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }), + coverage: PD.Select('viewport', PD.arrayToOptions(['viewport', 'canvas'])), +}; + +export const BackgroundParams = { + variant: PD.MappedStatic('off', { + off: PD.EmptyGroup(), + skybox: PD.Group(SkyboxParams, { isExpanded: true }), + image: PD.Group(ImageParams, { isExpanded: true }), + horizontalGradient: PD.Group(HorizontalGradientParams, { isExpanded: true }), + radialGradient: PD.Group(RadialGradientParams, { isExpanded: true }), + }, { label: 'Environment' }), +}; +export type BackgroundProps = PD.Values<typeof BackgroundParams> + +export class BackgroundPass { + private renderable: BackgroundRenderable; + + private skybox: { + texture: Texture + props: SkyboxProps + assets: Asset[] + loaded: boolean + } | undefined; + + private image: { + texture: Texture + props: ImageProps + asset: Asset + loaded: boolean + } | undefined; + + private readonly camera = new Camera(); + private readonly target = Vec3(); + private readonly position = Vec3(); + private readonly dir = Vec3(); + + readonly texture: Texture; + + constructor(private readonly webgl: WebGLContext, private readonly assetManager: AssetManager, width: number, height: number) { + this.renderable = getBackgroundRenderable(webgl, width, height); + } + + setSize(width: number, height: number) { + const [w, h] = this.renderable.values.uTexSize.ref.value; + + if (width !== w || height !== h) { + ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height)); + } + } + + private clearSkybox() { + if (this.skybox !== undefined) { + this.skybox.texture.destroy(); + this.skybox.assets.forEach(a => this.assetManager.release(a)); + this.skybox = undefined; + } + } + + private updateSkybox(camera: ICamera, props: SkyboxProps, onload?: (changed: boolean) => void) { + const tf = this.skybox?.props.faces; + const f = props.faces.params; + if (!f.nx || !f.ny || !f.nz || !f.px || !f.py || !f.pz) { + this.clearSkybox(); + onload?.(false); + return; + } + if (!this.skybox || !tf || !areSkyboxTexturePropsEqual(props.faces, this.skybox.props.faces)) { + this.clearSkybox(); + const { texture, assets } = getSkyboxTexture(this.webgl, this.assetManager, props.faces, errored => { + if (this.skybox) this.skybox.loaded = !errored; + onload?.(true); + }); + this.skybox = { texture, props: { ...props }, assets, loaded: false }; + ValueCell.update(this.renderable.values.tSkybox, texture); + this.renderable.update(); + } else { + onload?.(false); + } + if (!this.skybox) return; + + let cam = camera; + if (camera.state.mode === 'orthographic') { + this.camera.setState({ ...camera.state, mode: 'perspective' }); + this.camera.update(); + cam = this.camera; + } + + const m = this.renderable.values.uViewDirectionProjectionInverse.ref.value; + Vec3.sub(this.dir, cam.state.position, cam.state.target); + Vec3.setMagnitude(this.dir, this.dir, 0.1); + Vec3.copy(this.position, this.dir); + Mat4.lookAt(m, this.position, this.target, cam.state.up); + Mat4.mul(m, cam.projection, m); + Mat4.invert(m, m); + ValueCell.update(this.renderable.values.uViewDirectionProjectionInverse, m); + + ValueCell.updateIfChanged(this.renderable.values.uBlur, props.blur); + ValueCell.updateIfChanged(this.renderable.values.uOpacity, props.opacity); + ValueCell.updateIfChanged(this.renderable.values.uSaturation, props.saturation); + ValueCell.updateIfChanged(this.renderable.values.uLightness, props.lightness); + ValueCell.updateIfChanged(this.renderable.values.dVariant, 'skybox'); + this.renderable.update(); + } + + private clearImage() { + if (this.image !== undefined) { + this.image.texture.destroy(); + this.assetManager.release(this.image.asset); + this.image = undefined; + } + } + + private updateImage(props: ImageProps, onload?: (loaded: boolean) => void) { + if (!props.source.params) { + this.clearImage(); + onload?.(false); + return; + } + if (!this.image || !this.image.props.source.params || !areImageTexturePropsEqual(props.source, this.image.props.source)) { + this.clearImage(); + const { texture, asset } = getImageTexture(this.webgl, this.assetManager, props.source, errored => { + if (this.image) this.image.loaded = !errored; + onload?.(true); + }); + this.image = { texture, props: { ...props }, asset, loaded: false }; + ValueCell.update(this.renderable.values.tImage, texture); + this.renderable.update(); + } else { + onload?.(false); + } + if (!this.image) return; + + ValueCell.updateIfChanged(this.renderable.values.uOpacity, props.opacity); + ValueCell.updateIfChanged(this.renderable.values.uSaturation, props.saturation); + ValueCell.updateIfChanged(this.renderable.values.uLightness, props.lightness); + ValueCell.updateIfChanged(this.renderable.values.uViewportAdjusted, props.coverage === 'viewport' ? true : false); + ValueCell.updateIfChanged(this.renderable.values.dVariant, 'image'); + this.renderable.update(); + } + + private updateImageScaling() { + const v = this.renderable.values; + const [w, h] = v.uTexSize.ref.value; + const iw = this.image?.texture.getWidth() || 0; + const ih = this.image?.texture.getHeight() || 0; + const r = w / h; + const ir = iw / ih; + // responsive scaling with offset + if (r < ir) { + ValueCell.update(v.uImageScale, Vec2.set(v.uImageScale.ref.value, iw * h / ih, h)); + } else { + ValueCell.update(v.uImageScale, Vec2.set(v.uImageScale.ref.value, w, ih * w / iw)); + } + const [rw, rh] = v.uImageScale.ref.value; + const sr = rw / rh; + if (sr > r) { + ValueCell.update(v.uImageOffset, Vec2.set(v.uImageOffset.ref.value, (1 - r / sr) / 2, 0)); + } else { + ValueCell.update(v.uImageOffset, Vec2.set(v.uImageOffset.ref.value, 0, (1 - sr / r) / 2)); + } + } + + private updateGradient(colorA: Color, colorB: Color, ratio: number, variant: 'horizontalGradient' | 'radialGradient', viewportAdjusted: boolean) { + ValueCell.update(this.renderable.values.uGradientColorA, Color.toVec3Normalized(this.renderable.values.uGradientColorA.ref.value, colorA)); + ValueCell.update(this.renderable.values.uGradientColorB, Color.toVec3Normalized(this.renderable.values.uGradientColorB.ref.value, colorB)); + ValueCell.updateIfChanged(this.renderable.values.uGradientRatio, ratio); + ValueCell.updateIfChanged(this.renderable.values.uViewportAdjusted, viewportAdjusted); + ValueCell.updateIfChanged(this.renderable.values.dVariant, variant); + this.renderable.update(); + } + + update(camera: ICamera, props: BackgroundProps, onload?: (changed: boolean) => void) { + if (props.variant.name === 'off') { + this.clearSkybox(); + this.clearImage(); + onload?.(false); + return; + } else if (props.variant.name === 'skybox') { + this.clearImage(); + this.updateSkybox(camera, props.variant.params, onload); + } else if (props.variant.name === 'image') { + this.clearSkybox(); + this.updateImage(props.variant.params, onload); + } else if (props.variant.name === 'horizontalGradient') { + this.clearSkybox(); + this.clearImage(); + this.updateGradient(props.variant.params.topColor, props.variant.params.bottomColor, props.variant.params.ratio, props.variant.name, props.variant.params.coverage === 'viewport' ? true : false); + onload?.(false); + } else if (props.variant.name === 'radialGradient') { + this.clearSkybox(); + this.clearImage(); + this.updateGradient(props.variant.params.centerColor, props.variant.params.edgeColor, props.variant.params.ratio, props.variant.name, props.variant.params.coverage === 'viewport' ? true : false); + onload?.(false); + } + + const { x, y, width, height } = camera.viewport; + ValueCell.update(this.renderable.values.uViewport, Vec4.set(this.renderable.values.uViewport.ref.value, x, y, width, height)); + } + + isEnabled(props: BackgroundProps) { + return !!( + (this.skybox && this.skybox.loaded) || + (this.image && this.image.loaded) || + props.variant.name === 'horizontalGradient' || + props.variant.name === 'radialGradient' + ); + } + + private isReady() { + return !!( + (this.skybox && this.skybox.loaded) || + (this.image && this.image.loaded) || + this.renderable.values.dVariant.ref.value === 'horizontalGradient' || + this.renderable.values.dVariant.ref.value === 'radialGradient' + ); + } + + render() { + if (!this.isReady()) return; + + if (this.renderable.values.dVariant.ref.value === 'image') { + this.updateImageScaling(); + } + + if (isTimingMode) this.webgl.timer.mark('BackgroundPass.render'); + this.renderable.render(); + if (isTimingMode) this.webgl.timer.markEnd('BackgroundPass.render'); + } + + dispose() { + this.clearSkybox(); + this.clearImage(); + } +} + +// + +const SkyboxName = 'background-skybox'; + +type CubeAssets = { [k in keyof CubeFaces]: Asset }; + +function getCubeAssets(assetManager: AssetManager, faces: SkyboxProps['faces']): CubeAssets { + if (faces.name === 'urls') { + return { + nx: Asset.getUrlAsset(assetManager, faces.params.nx), + ny: Asset.getUrlAsset(assetManager, faces.params.ny), + nz: Asset.getUrlAsset(assetManager, faces.params.nz), + px: Asset.getUrlAsset(assetManager, faces.params.px), + py: Asset.getUrlAsset(assetManager, faces.params.py), + pz: Asset.getUrlAsset(assetManager, faces.params.pz), + }; + } else { + return { + nx: faces.params.nx!, + ny: faces.params.ny!, + nz: faces.params.nz!, + px: faces.params.px!, + py: faces.params.py!, + pz: faces.params.pz!, + }; + } +} + +function getCubeFaces(assetManager: AssetManager, cubeAssets: CubeAssets): CubeFaces { + const resolve = (asset: Asset) => { + return assetManager.resolve(asset, 'binary').run().then(a => new Blob([a.data])); + }; + + return { + nx: resolve(cubeAssets.nx), + ny: resolve(cubeAssets.ny), + nz: resolve(cubeAssets.nz), + px: resolve(cubeAssets.px), + py: resolve(cubeAssets.py), + pz: resolve(cubeAssets.pz), + }; +} + +function getSkyboxHash(faces: SkyboxProps['faces']) { + if (faces.name === 'urls') { + return `${SkyboxName}_${faces.params.nx}|${faces.params.ny}|${faces.params.nz}|${faces.params.px}|${faces.params.py}|${faces.params.pz}`; + } else { + return `${SkyboxName}_${faces.params.nx?.id}|${faces.params.ny?.id}|${faces.params.nz?.id}|${faces.params.px?.id}|${faces.params.py?.id}|${faces.params.pz?.id}`; + } +} + +function areSkyboxTexturePropsEqual(facesA: SkyboxProps['faces'], facesB: SkyboxProps['faces']) { + return getSkyboxHash(facesA) === getSkyboxHash(facesB); +} + +function getSkyboxTexture(ctx: WebGLContext, assetManager: AssetManager, faces: SkyboxProps['faces'], onload?: (errored?: boolean) => void): { texture: Texture, assets: Asset[] } { + const cubeAssets = getCubeAssets(assetManager, faces); + const cubeFaces = getCubeFaces(assetManager, cubeAssets); + const assets = [cubeAssets.nx, cubeAssets.ny, cubeAssets.nz, cubeAssets.px, cubeAssets.py, cubeAssets.pz]; + const texture = ctx.resources.cubeTexture(cubeFaces, true, onload); + return { texture, assets }; +} + +// + +const ImageName = 'background-image'; + +function getImageHash(source: ImageProps['source']) { + if (source.name === 'url') { + return `${ImageName}_${source.params}`; + } else { + return `${ImageName}_${source.params?.id}`; + } +} + +function areImageTexturePropsEqual(sourceA: ImageProps['source'], sourceB: ImageProps['source']) { + return getImageHash(sourceA) === getImageHash(sourceB); +} + +function getImageTexture(ctx: WebGLContext, assetManager: AssetManager, source: ImageProps['source'], onload?: (errored?: boolean) => void): { texture: Texture, asset: Asset } { + const texture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear'); + const img = new Image(); + img.onload = () => { + texture.load(img); + onload?.(); + }; + img.onerror = () => { + onload?.(true); + }; + const asset = source.name === 'url' + ? Asset.getUrlAsset(assetManager, source.params) + : source.params!; + assetManager.resolve(asset, 'binary').run().then(a => { + const blob = new Blob([a.data]); + img.src = URL.createObjectURL(blob); + }); + return { texture, asset }; +} + +// + +const BackgroundSchema = { + drawCount: ValueSpec('number'), + instanceCount: ValueSpec('number'), + aPosition: AttributeSpec('float32', 2, 0), + tSkybox: TextureSpec('texture', 'rgba', 'ubyte', 'linear'), + tImage: TextureSpec('texture', 'rgba', 'ubyte', 'linear'), + uImageScale: UniformSpec('v2'), + uImageOffset: UniformSpec('v2'), + uTexSize: UniformSpec('v2'), + uViewport: UniformSpec('v4'), + uViewportAdjusted: UniformSpec('b'), + uViewDirectionProjectionInverse: UniformSpec('m4'), + uGradientColorA: UniformSpec('v3'), + uGradientColorB: UniformSpec('v3'), + uGradientRatio: UniformSpec('f'), + uBlur: UniformSpec('f'), + uOpacity: UniformSpec('f'), + uSaturation: UniformSpec('f'), + uLightness: UniformSpec('f'), + dVariant: DefineSpec('string', ['skybox', 'image', 'verticalGradient', 'horizontalGradient', 'radialGradient']), +}; +const SkyboxShaderCode = ShaderCode('background', background_vert, background_frag, { + shaderTextureLod: 'optional' +}); +type BackgroundRenderable = ComputeRenderable<Values<typeof BackgroundSchema>> + +function getBackgroundRenderable(ctx: WebGLContext, width: number, height: number): BackgroundRenderable { + const values: Values<typeof BackgroundSchema> = { + drawCount: ValueCell.create(6), + instanceCount: ValueCell.create(1), + aPosition: ValueCell.create(QuadPositions), + tSkybox: ValueCell.create(createNullTexture()), + tImage: ValueCell.create(createNullTexture()), + uImageScale: ValueCell.create(Vec2()), + uImageOffset: ValueCell.create(Vec2()), + uTexSize: ValueCell.create(Vec2.create(width, height)), + uViewport: ValueCell.create(Vec4()), + uViewportAdjusted: ValueCell.create(true), + uViewDirectionProjectionInverse: ValueCell.create(Mat4()), + uGradientColorA: ValueCell.create(Vec3()), + uGradientColorB: ValueCell.create(Vec3()), + uGradientRatio: ValueCell.create(0.5), + uBlur: ValueCell.create(0), + uOpacity: ValueCell.create(1), + uSaturation: ValueCell.create(0), + uLightness: ValueCell.create(0), + dVariant: ValueCell.create('skybox'), + }; + + const schema = { ...BackgroundSchema }; + const renderItem = createComputeRenderItem(ctx, 'triangles', SkyboxShaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} diff --git a/src/mol-canvas3d/passes/dpoit.ts b/src/mol-canvas3d/passes/dpoit.ts new file mode 100644 index 0000000000000000000000000000000000000000..634d4ec658a4c532dc693ffcbdbeee3ef5682d7c --- /dev/null +++ b/src/mol-canvas3d/passes/dpoit.ts @@ -0,0 +1,309 @@ +/** + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Gianluca Tomasello <giagitom@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * + * Adapted from https://github.com/tsherif/webgl2examples, The MIT License, Copyright © 2017 Tarek Sherif, Shuai Shao + */ + +import { QuadSchema, QuadValues } from '../../mol-gl/compute/util'; +import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable'; +import { TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema'; +import { ShaderCode } from '../../mol-gl/shader-code'; +import { WebGLContext } from '../../mol-gl/webgl/context'; +import { createComputeRenderItem } from '../../mol-gl/webgl/render-item'; +import { Texture } from '../../mol-gl/webgl/texture'; +import { ValueCell } from '../../mol-util'; +import { quad_vert } from '../../mol-gl/shader/quad.vert'; +import { evaluateDpoit_frag } from '../../mol-gl/shader/evaluate-dpoit.frag'; +import { blendBackDpoit_frag } from '../../mol-gl/shader/blend-back-dpoit.frag'; +import { Framebuffer } from '../../mol-gl/webgl/framebuffer'; +import { Vec2 } from '../../mol-math/linear-algebra'; +import { isDebugMode, isTimingMode } from '../../mol-util/debug'; +import { isWebGL2 } from '../../mol-gl/webgl/compat'; + +const BlendBackDpoitSchema = { + ...QuadSchema, + tDpoitBackColor: TextureSpec('texture', 'rgba', 'float', 'nearest'), + uTexSize: UniformSpec('v2'), +}; +const BlendBackDpoitShaderCode = ShaderCode('blend-back-dpoit', quad_vert, blendBackDpoit_frag); +type BlendBackDpoitRenderable = ComputeRenderable<Values<typeof BlendBackDpoitSchema>> + +function getBlendBackDpoitRenderable(ctx: WebGLContext, dopitBlendBackTexture: Texture): BlendBackDpoitRenderable { + const values: Values<typeof BlendBackDpoitSchema> = { + ...QuadValues, + tDpoitBackColor: ValueCell.create(dopitBlendBackTexture), + uTexSize: ValueCell.create(Vec2.create(dopitBlendBackTexture.getWidth(), dopitBlendBackTexture.getHeight())), + }; + + const schema = { ...BlendBackDpoitSchema }; + const renderItem = createComputeRenderItem(ctx, 'triangles', BlendBackDpoitShaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} + +const EvaluateDpoitSchema = { + ...QuadSchema, + tDpoitFrontColor: TextureSpec('texture', 'rgba', 'float', 'nearest'), + uTexSize: UniformSpec('v2'), +}; +const EvaluateDpoitShaderCode = ShaderCode('evaluate-dpoit', quad_vert, evaluateDpoit_frag); +type EvaluateDpoitRenderable = ComputeRenderable<Values<typeof EvaluateDpoitSchema>> + +function getEvaluateDpoitRenderable(ctx: WebGLContext, dpoitFrontColorTexture: Texture): EvaluateDpoitRenderable { + const values: Values<typeof EvaluateDpoitSchema> = { + ...QuadValues, + tDpoitFrontColor: ValueCell.create(dpoitFrontColorTexture), + uTexSize: ValueCell.create(Vec2.create(dpoitFrontColorTexture.getWidth(), dpoitFrontColorTexture.getHeight())), + }; + + const schema = { ...EvaluateDpoitSchema }; + const renderItem = createComputeRenderItem(ctx, 'triangles', EvaluateDpoitShaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} + +export class DpoitPass { + private readonly DEPTH_CLEAR_VALUE = -99999.0; // NOTE same constant is set in shaders + private readonly MAX_DEPTH = 1.0; + private readonly MIN_DEPTH = 0.0; + + private passCount = 0; + private writeId: number; + private readId: number; + + private readonly blendBackRenderable: BlendBackDpoitRenderable; + private readonly renderable: EvaluateDpoitRenderable; + + private readonly depthFramebuffers: Framebuffer[]; + private readonly colorFramebuffers: Framebuffer[]; + + private readonly depthTextures: Texture[]; + private readonly colorFrontTextures: Texture[]; + private readonly colorBackTextures: Texture[]; + + private _supported = false; + get supported() { + return this._supported; + } + + bind() { + const { state, gl, extensions: { blendMinMax } } = this.webgl; + + // initialize + this.passCount = 0; + + this.depthFramebuffers[0].bind(); + state.clearColor(this.DEPTH_CLEAR_VALUE, this.DEPTH_CLEAR_VALUE, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + this.depthFramebuffers[1].bind(); + state.clearColor(-this.MIN_DEPTH, this.MAX_DEPTH, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + this.colorFramebuffers[0].bind(); + state.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + this.colorFramebuffers[1].bind(); + state.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + this.depthFramebuffers[0].bind(); + state.blendEquation(blendMinMax!.MAX); + state.depthMask(false); + + return { + depth: this.depthTextures[1], + frontColor: this.colorFrontTextures[1], + backColor: this.colorBackTextures[1] + }; + } + + bindDualDepthPeeling() { + const { state, gl, extensions: { blendMinMax } } = this.webgl; + + this.readId = this.passCount % 2; + this.writeId = 1 - this.readId; // ping-pong: 0 or 1 + + this.passCount += 1; // increment for next pass + + this.depthFramebuffers[this.writeId].bind(); + state.clearColor(this.DEPTH_CLEAR_VALUE, this.DEPTH_CLEAR_VALUE, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + this.colorFramebuffers[this.writeId].bind(); + state.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + + this.depthFramebuffers[this.writeId].bind(); + state.blendEquation(blendMinMax!.MAX); + state.depthMask(false); + + return { + depth: this.depthTextures[this.readId], + frontColor: this.colorFrontTextures[this.readId], + backColor: this.colorBackTextures[this.readId] + }; + } + + renderBlendBack() { + if (isTimingMode) this.webgl.timer.mark('DpoitPass.renderBlendBack'); + const { state, gl } = this.webgl; + + state.blendEquation(gl.FUNC_ADD); + state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + + ValueCell.update(this.blendBackRenderable.values.tDpoitBackColor, this.colorBackTextures[this.writeId]); + + this.blendBackRenderable.update(); + this.blendBackRenderable.render(); + if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.renderBlendBack'); + } + + render() { + if (isTimingMode) this.webgl.timer.mark('DpoitPass.render'); + const { state, gl } = this.webgl; + + state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + + ValueCell.update(this.renderable.values.tDpoitFrontColor, this.colorFrontTextures[this.writeId]); + + this.renderable.update(); + this.renderable.render(); + if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.render'); + } + + setSize(width: number, height: number) { + const [w, h] = this.renderable.values.uTexSize.ref.value; + if (width !== w || height !== h) { + for (let i = 0; i < 2; i++) { + this.depthTextures[i].define(width, height); + this.colorFrontTextures[i].define(width, height); + this.colorBackTextures[i].define(width, height); + } + ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height)); + ValueCell.update(this.blendBackRenderable.values.uTexSize, Vec2.set(this.blendBackRenderable.values.uTexSize.ref.value, width, height)); + } + } + + reset() { + if (this._supported) this._init(); + } + + private _init() { + const { extensions: { drawBuffers } } = this.webgl; + for (let i = 0; i < 2; i++) { + // depth + this.depthFramebuffers[i].bind(); + drawBuffers!.drawBuffers([ + drawBuffers!.COLOR_ATTACHMENT0, + drawBuffers!.COLOR_ATTACHMENT1, + drawBuffers!.COLOR_ATTACHMENT2 + ]); + + this.colorFrontTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color0'); + this.colorBackTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color1'); + this.depthTextures[i].attachFramebuffer(this.depthFramebuffers[i], 'color2'); + + // color + this.colorFramebuffers[i].bind(); + drawBuffers!.drawBuffers([ + drawBuffers!.COLOR_ATTACHMENT0, + drawBuffers!.COLOR_ATTACHMENT1 + ]); + + this.colorFrontTextures[i].attachFramebuffer(this.colorFramebuffers[i], 'color0'); + this.colorBackTextures[i].attachFramebuffer(this.colorFramebuffers[i], 'color1'); + } + } + + static isSupported(webgl: WebGLContext) { + const { extensions: { drawBuffers, textureFloat, colorBufferFloat, blendMinMax } } = webgl; + if (!textureFloat || !colorBufferFloat || !drawBuffers || !blendMinMax) { + if (isDebugMode) { + const missing: string[] = []; + if (!textureFloat) missing.push('textureFloat'); + if (!colorBufferFloat) missing.push('colorBufferFloat'); + if (!drawBuffers) missing.push('drawBuffers'); + if (!blendMinMax) missing.push('blendMinMax'); + console.log(`Missing "${missing.join('", "')}" extensions required for "dpoit"`); + } + return false; + } else { + return true; + } + } + + constructor(private webgl: WebGLContext, width: number, height: number) { + if (!DpoitPass.isSupported(webgl)) return; + + const { resources, extensions: { colorBufferHalfFloat, textureHalfFloat } } = webgl; + + // textures + + if (isWebGL2(webgl.gl)) { + this.depthTextures = [ + resources.texture('image-float32', 'rg', 'float', 'nearest'), + resources.texture('image-float32', 'rg', 'float', 'nearest') + ]; + + this.colorFrontTextures = colorBufferHalfFloat && textureHalfFloat ? [ + resources.texture('image-float16', 'rgba', 'fp16', 'nearest'), + resources.texture('image-float16', 'rgba', 'fp16', 'nearest') + ] : [ + resources.texture('image-float32', 'rgba', 'float', 'nearest'), + resources.texture('image-float32', 'rgba', 'float', 'nearest') + ]; + + this.colorBackTextures = colorBufferHalfFloat && textureHalfFloat ? [ + resources.texture('image-float16', 'rgba', 'fp16', 'nearest'), + resources.texture('image-float16', 'rgba', 'fp16', 'nearest') + ] : [ + resources.texture('image-float32', 'rgba', 'float', 'nearest'), + resources.texture('image-float32', 'rgba', 'float', 'nearest') + ]; + } else { + // in webgl1 drawbuffers must be in the same format for some reason + + this.depthTextures = [ + resources.texture('image-float32', 'rgba', 'float', 'nearest'), + resources.texture('image-float32', 'rgba', 'float', 'nearest') + ]; + + this.colorFrontTextures = [ + resources.texture('image-float32', 'rgba', 'float', 'nearest'), + resources.texture('image-float32', 'rgba', 'float', 'nearest') + ]; + + this.colorBackTextures = [ + resources.texture('image-float32', 'rgba', 'float', 'nearest'), + resources.texture('image-float32', 'rgba', 'float', 'nearest') + ]; + } + + this.depthTextures[0].define(width, height); + this.depthTextures[1].define(width, height); + + this.colorFrontTextures[0].define(width, height); + this.colorFrontTextures[1].define(width, height); + + this.colorBackTextures[0].define(width, height); + this.colorBackTextures[1].define(width, height); + + // framebuffers + + this.depthFramebuffers = [resources.framebuffer(), resources.framebuffer()]; + this.colorFramebuffers = [resources.framebuffer(), resources.framebuffer()]; + + // renderables + + this.blendBackRenderable = getBlendBackDpoitRenderable(webgl, this.colorBackTextures[0]); + this.renderable = getEvaluateDpoitRenderable(webgl, this.colorFrontTextures[0]); + + this._supported = true; + this._init(); + } +} diff --git a/src/mol-canvas3d/passes/draw.ts b/src/mol-canvas3d/passes/draw.ts index 0bb002d84c9df136b5bb8ce43025f07b8b583652..34282e8408d16a0c59251178c6ab14843914d707 100644 --- a/src/mol-canvas3d/passes/draw.ts +++ b/src/mol-canvas3d/passes/draw.ts @@ -3,6 +3,7 @@ * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Ãron Samuel Kovács <aron.kovacs@mail.muni.cz> + * @author Gianluca Tomasello <giagitom@gmail.com> */ import { WebGLContext } from '../../mol-gl/webgl/context'; @@ -17,15 +18,18 @@ import { Helper } from '../helper/helper'; import { StereoCamera } from '../camera/stereo'; import { WboitPass } from './wboit'; +import { DpoitPass } from './dpoit'; import { AntialiasingPass, PostprocessingPass, PostprocessingProps } from './postprocessing'; import { MarkingPass, MarkingProps } from './marking'; import { CopyRenderable, createCopyRenderable } from '../../mol-gl/compute/util'; import { isTimingMode } from '../../mol-util/debug'; +import { AssetManager } from '../../mol-util/assets'; type Props = { - postprocessing: PostprocessingProps - marking: MarkingProps + postprocessing: PostprocessingProps; + marking: MarkingProps; transparentBackground: boolean; + dpoitIterations: number; } type RenderContext = { @@ -50,7 +54,8 @@ export class DrawPass { private copyFboTarget: CopyRenderable; private copyFboPostprocessing: CopyRenderable; - private wboit: WboitPass | undefined; + private readonly wboit: WboitPass | undefined; + private readonly dpoit: DpoitPass | undefined; private readonly marking: MarkingPass; readonly postprocessing: PostprocessingPass; private readonly antialiasing: AntialiasingPass; @@ -59,11 +64,13 @@ export class DrawPass { return !!this.wboit?.supported; } - constructor(private webgl: WebGLContext, width: number, height: number, enableWboit: boolean) { - const { extensions, resources, isWebGL2 } = webgl; + get dpoitEnabled() { + return !!this.dpoit?.supported; + } + constructor(private webgl: WebGLContext, assetManager: AssetManager, width: number, height: number, enableWboit: boolean, enableDpoit: boolean) { + const { extensions, resources, isWebGL2 } = webgl; this.drawTarget = createNullRenderTarget(webgl.gl); - this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear'); this.packedDepth = !extensions.depthTexture; @@ -78,8 +85,9 @@ export class DrawPass { } this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined; + this.dpoit = enableDpoit ? new DpoitPass(webgl, width, height) : undefined; this.marking = new MarkingPass(webgl, width, height); - this.postprocessing = new PostprocessingPass(webgl, this); + this.postprocessing = new PostprocessingPass(webgl, assetManager, this); this.antialiasing = new AntialiasingPass(webgl, this); this.copyFboTarget = createCopyRenderable(webgl, this.colorTarget.texture); @@ -88,6 +96,7 @@ export class DrawPass { reset() { this.wboit?.reset(); + this.dpoit?.reset(); } setSize(width: number, height: number) { @@ -111,23 +120,80 @@ export class DrawPass { this.wboit.setSize(width, height); } + if (this.dpoit?.supported) { + this.dpoit.setSize(width, height); + } + this.marking.setSize(width, height); this.postprocessing.setSize(width, height); this.antialiasing.setSize(width, height); } } + private _renderDpoit(renderer: Renderer, camera: ICamera, scene: Scene, iterations: number, transparentBackground: boolean, postprocessingProps: PostprocessingProps) { + if (!this.dpoit?.supported) throw new Error('expected dpoit to be supported'); + + this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); + renderer.clear(true); + + // render opaque primitives + if (scene.hasOpaque) { + renderer.renderDpoitOpaque(scene.primitives, camera, null); + } + + if (PostprocessingPass.isEnabled(postprocessingProps)) { + if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) { + this.depthTargetTransparent.bind(); + renderer.clearDepth(true); + if (scene.opacityAverage < 1) { + renderer.renderDepthTransparent(scene.primitives, camera, this.depthTextureOpaque); + } + } + + this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps); + } + + this.depthTextureOpaque.detachFramebuffer(this.colorTarget.framebuffer, 'depth'); + + // render transparent primitives + if (scene.opacityAverage < 1) { + const target = PostprocessingPass.isEnabled(postprocessingProps) + ? this.postprocessing.target : this.colorTarget; + + const dpoitTextures = this.dpoit.bind(); + renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures); + + for (let i = 0; i < iterations; i++) { + if (isTimingMode) this.webgl.timer.mark('DpoitPass.layer'); + const dpoitTextures = this.dpoit.bindDualDepthPeeling(); + renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures); + + target.bind(); + this.dpoit.renderBlendBack(); + if (isTimingMode) this.webgl.timer.markEnd('DpoitPass.layer'); + } + + // evaluate dpoit + target.bind(); + this.dpoit.render(); + } + + // render transparent volumes + if (scene.volumes.renderables.length > 0) { + renderer.renderDpoitVolume(scene.volumes, camera, this.depthTextureOpaque); + } + } + private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, transparentBackground: boolean, postprocessingProps: PostprocessingProps) { if (!this.wboit?.supported) throw new Error('expected wboit to be supported'); - this.colorTarget.bind(); + this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); renderer.clear(true); // render opaque primitives - this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); - this.colorTarget.bind(); - renderer.clearDepth(); - renderer.renderWboitOpaque(scene.primitives, camera, null); + if (scene.hasOpaque) { + renderer.renderWboitOpaque(scene.primitives, camera, null); + } if (PostprocessingPass.isEnabled(postprocessingProps)) { if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) { @@ -165,14 +231,17 @@ export class DrawPass { if (toDrawingBuffer) { this.drawTarget.bind(); } else { - this.colorTarget.bind(); if (!this.packedDepth) { this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); + } else { + this.colorTarget.bind(); } } renderer.clear(true); - renderer.renderBlendedOpaque(scene.primitives, camera, null); + if (scene.hasOpaque) { + renderer.renderBlendedOpaque(scene.primitives, camera, null); + } if (!toDrawingBuffer) { // do a depth pass if not rendering to drawing buffer and @@ -235,7 +304,7 @@ export class DrawPass { } } - private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, props: Props) { + private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) { const volumeRendering = scene.volumes.renderables.length > 0; const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing); const antialiasingEnabled = AntialiasingPass.isEnabled(props.postprocessing); @@ -245,54 +314,54 @@ export class DrawPass { renderer.setViewport(x, y, width, height); renderer.update(camera); - if (props.transparentBackground && !antialiasingEnabled && toDrawingBuffer) { + if (transparentBackground && !antialiasingEnabled && toDrawingBuffer) { this.drawTarget.bind(); renderer.clear(false); } if (this.wboitEnabled) { - this._renderWboit(renderer, camera, scene, props.transparentBackground, props.postprocessing); + this._renderWboit(renderer, camera, scene, transparentBackground, props.postprocessing); + } else if (this.dpoitEnabled) { + this._renderDpoit(renderer, camera, scene, props.dpoitIterations, transparentBackground, props.postprocessing); } else { - this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, props.transparentBackground, props.postprocessing); + this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, props.postprocessing); } - if (postprocessingEnabled) { - this.postprocessing.target.bind(); - } else if (!toDrawingBuffer || volumeRendering || this.wboitEnabled) { - this.colorTarget.bind(); - } else { - this.drawTarget.bind(); - } - - if (markingEnabled) { - if (scene.markerAverage > 0) { - const markingDepthTest = props.marking.ghostEdgeStrength < 1; - if (markingDepthTest && scene.markerAverage !== 1) { - this.marking.depthTarget.bind(); - renderer.clear(false, true); - renderer.renderMarkingDepth(scene.primitives, camera, null); - } + const target = postprocessingEnabled + ? this.postprocessing.target + : !toDrawingBuffer || volumeRendering || this.wboitEnabled || this.dpoitEnabled + ? this.colorTarget + : this.drawTarget; - this.marking.maskTarget.bind(); + if (markingEnabled && scene.markerAverage > 0) { + const markingDepthTest = props.marking.ghostEdgeStrength < 1; + if (markingDepthTest && scene.markerAverage !== 1) { + this.marking.depthTarget.bind(); renderer.clear(false, true); - renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null); - - this.marking.update(props.marking); - this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget); + renderer.renderMarkingDepth(scene.primitives, camera, null); } + + this.marking.maskTarget.bind(); + renderer.clear(false, true); + renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null); + + this.marking.update(props.marking); + this.marking.render(camera.viewport, target); + } else { + target.bind(); } if (helper.debug.isEnabled) { helper.debug.syncVisibility(); - renderer.renderBlended(helper.debug.scene, camera, null); + renderer.renderBlended(helper.debug.scene, camera); } if (helper.handle.isEnabled) { - renderer.renderBlended(helper.handle.scene, camera, null); + renderer.renderBlended(helper.handle.scene, camera); } if (helper.camera.isEnabled) { helper.camera.update(camera); renderer.update(helper.camera.camera); - renderer.renderBlended(helper.camera.scene, helper.camera.camera, null); + renderer.renderBlended(helper.camera.scene, helper.camera.camera); } if (antialiasingEnabled) { @@ -303,7 +372,7 @@ export class DrawPass { this.webgl.state.disable(this.webgl.gl.DEPTH_TEST); if (postprocessingEnabled) { this.copyFboPostprocessing.render(); - } else if (volumeRendering || this.wboitEnabled) { + } else if (volumeRendering || this.wboitEnabled || this.dpoitEnabled) { this.copyFboTarget.render(); } } @@ -314,15 +383,23 @@ export class DrawPass { render(ctx: RenderContext, props: Props, toDrawingBuffer: boolean) { if (isTimingMode) this.webgl.timer.mark('DrawPass.render'); const { renderer, camera, scene, helper } = ctx; - renderer.setTransparentBackground(props.transparentBackground); + + this.postprocessing.setTransparentBackground(props.transparentBackground); + const transparentBackground = props.transparentBackground || this.postprocessing.background.isEnabled(props.postprocessing.background); + + renderer.setTransparentBackground(transparentBackground); renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight()); renderer.setPixelRatio(this.webgl.pixelRatio); if (StereoCamera.is(camera)) { - this._render(renderer, camera.left, scene, helper, toDrawingBuffer, props); - this._render(renderer, camera.right, scene, helper, toDrawingBuffer, props); + if (isTimingMode) this.webgl.timer.mark('StereoCamera.left'); + this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, props); + if (isTimingMode) this.webgl.timer.markEnd('StereoCamera.left'); + if (isTimingMode) this.webgl.timer.mark('StereoCamera.right'); + this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, props); + if (isTimingMode) this.webgl.timer.markEnd('StereoCamera.right'); } else { - this._render(renderer, camera, scene, helper, toDrawingBuffer, props); + this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props); } if (isTimingMode) this.webgl.timer.markEnd('DrawPass.render'); } @@ -335,4 +412,4 @@ export class DrawPass { } return this.colorTarget; } -} \ No newline at end of file +} diff --git a/src/mol-canvas3d/passes/fxaa.ts b/src/mol-canvas3d/passes/fxaa.ts index ff1a0e878775ca6a898c0983382e10ac834c9091..bbb02430284d72c25be9f93b76bf863970e018ee 100644 --- a/src/mol-canvas3d/passes/fxaa.ts +++ b/src/mol-canvas3d/passes/fxaa.ts @@ -44,8 +44,8 @@ export class FxaaPass { state.depthMask(false); const { x, y, width, height } = viewport; - gl.viewport(x, y, width, height); - gl.scissor(x, y, width, height); + state.viewport(x, y, width, height); + state.scissor(x, y, width, height); state.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); diff --git a/src/mol-canvas3d/passes/image.ts b/src/mol-canvas3d/passes/image.ts index 68acfa4c4b6977b36669d0b3a7afdbc6b01b96a2..46434fdbdd0e0a7eb43206158a0188aadc20e363 100644 --- a/src/mol-canvas3d/passes/image.ts +++ b/src/mol-canvas3d/passes/image.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -18,9 +18,11 @@ import { PixelData } from '../../mol-util/image'; import { Helper } from '../helper/helper'; import { CameraHelper, CameraHelperParams } from '../helper/camera-helper'; import { MarkingParams } from './marking'; +import { AssetManager } from '../../mol-util/assets'; export const ImageParams = { transparentBackground: PD.Boolean(false), + dpoitIterations: PD.Numeric(2, { min: 1, max: 10, step: 1 }), multiSample: PD.Group(MultiSampleParams), postprocessing: PD.Group(PostprocessingParams), marking: PD.Group(MarkingParams), @@ -47,10 +49,10 @@ export class ImagePass { get width() { return this._width; } get height() { return this._height; } - constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, props: Partial<ImageProps>) { + constructor(private webgl: WebGLContext, assetManager: AssetManager, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, enableDpoit: boolean, props: Partial<ImageProps>) { this.props = { ...PD.getDefaultValues(ImageParams), ...props }; - this.drawPass = new DrawPass(webgl, 128, 128, enableWboit); + this.drawPass = new DrawPass(webgl, assetManager, 128, 128, enableWboit, enableDpoit); this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass); this.multiSampleHelper = new MultiSampleHelper(this.multiSamplePass); @@ -63,6 +65,14 @@ export class ImagePass { this.setSize(1024, 768); } + updateBackground() { + return new Promise<void>(resolve => { + this.drawPass.postprocessing.background.update(this.camera, this.props.postprocessing.background, () => { + resolve(); + }); + }); + } + setSize(width: number, height: number) { if (width === this._width && height === this._height) return; diff --git a/src/mol-canvas3d/passes/marking.ts b/src/mol-canvas3d/passes/marking.ts index 73cde519fa611d2160c7a2c1788c5120146cf1e4..2093b5f2d1e2d0f818fbdd6519c2021f7654218d 100644 --- a/src/mol-canvas3d/passes/marking.ts +++ b/src/mol-canvas3d/passes/marking.ts @@ -64,8 +64,8 @@ export class MarkingPass { state.depthMask(false); const { x, y, width, height } = viewport; - gl.viewport(x, y, width, height); - gl.scissor(x, y, width, height); + state.viewport(x, y, width, height); + state.scissor(x, y, width, height); state.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); @@ -82,8 +82,8 @@ export class MarkingPass { state.depthMask(false); const { x, y, width, height } = viewport; - gl.viewport(x, y, width, height); - gl.scissor(x, y, width, height); + state.viewport(x, y, width, height); + state.scissor(x, y, width, height); } setSize(width: number, height: number) { diff --git a/src/mol-canvas3d/passes/multi-sample.ts b/src/mol-canvas3d/passes/multi-sample.ts index 82c861372d1c7677531d3861298695e764014da4..cb559ab81ef45666364f37bc4f61cb3e56234435 100644 --- a/src/mol-canvas3d/passes/multi-sample.ts +++ b/src/mol-canvas3d/passes/multi-sample.ts @@ -61,6 +61,7 @@ type Props = { postprocessing: PostprocessingProps marking: MarkingProps transparentBackground: boolean; + dpoitIterations: number; } type RenderContext = { @@ -176,8 +177,8 @@ export class MultiSamplePass { state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE); state.disable(gl.DEPTH_TEST); state.depthMask(false); - gl.viewport(x, y, width, height); - gl.scissor(x, y, width, height); + state.viewport(x, y, width, height); + state.scissor(x, y, width, height); if (i === 0) { state.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); @@ -192,8 +193,8 @@ export class MultiSamplePass { compose.update(); this.bindOutputTarget(toDrawingBuffer); - gl.viewport(x, y, width, height); - gl.scissor(x, y, width, height); + state.viewport(x, y, width, height); + state.scissor(x, y, width, height); state.disable(gl.BLEND); compose.render(); @@ -231,8 +232,8 @@ export class MultiSamplePass { state.disable(gl.BLEND); state.disable(gl.DEPTH_TEST); state.depthMask(false); - gl.viewport(x, y, width, height); - gl.scissor(x, y, width, height); + state.viewport(x, y, width, height); + state.scissor(x, y, width, height); compose.render(); sampleIndex += 1; } else { @@ -267,8 +268,8 @@ export class MultiSamplePass { state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE); state.disable(gl.DEPTH_TEST); state.depthMask(false); - gl.viewport(x, y, width, height); - gl.scissor(x, y, width, height); + state.viewport(x, y, width, height); + state.scissor(x, y, width, height); if (sampleIndex === 0) { state.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); @@ -283,8 +284,8 @@ export class MultiSamplePass { drawPass.postprocessing.setOcclusionOffset(0, 0); this.bindOutputTarget(toDrawingBuffer); - gl.viewport(x, y, width, height); - gl.scissor(x, y, width, height); + state.viewport(x, y, width, height); + state.scissor(x, y, width, height); const accumulationWeight = sampleIndex * sampleWeight; if (accumulationWeight > 0) { diff --git a/src/mol-canvas3d/passes/passes.ts b/src/mol-canvas3d/passes/passes.ts index 208795e33bb2af60d8966f2f857fb87dc594087a..8974562c2e936241bf5a07a859ba40d6ade39d74 100644 --- a/src/mol-canvas3d/passes/passes.ts +++ b/src/mol-canvas3d/passes/passes.ts @@ -8,22 +8,26 @@ import { DrawPass } from './draw'; import { PickPass } from './pick'; import { MultiSamplePass } from './multi-sample'; import { WebGLContext } from '../../mol-gl/webgl/context'; +import { AssetManager } from '../../mol-util/assets'; export class Passes { readonly draw: DrawPass; readonly pick: PickPass; readonly multiSample: MultiSamplePass; - constructor(private webgl: WebGLContext, attribs: Partial<{ pickScale: number, enableWboit: boolean }> = {}) { + constructor(private webgl: WebGLContext, assetManager: AssetManager, attribs: Partial<{ pickScale: number, enableWboit: boolean, enableDpoit: boolean }> = {}) { const { gl } = webgl; - this.draw = new DrawPass(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false); + this.draw = new DrawPass(webgl, assetManager, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false, attribs.enableDpoit || false); this.pick = new PickPass(webgl, this.draw, attribs.pickScale || 0.25); this.multiSample = new MultiSamplePass(webgl, this.draw); } updateSize() { const { gl } = this.webgl; - this.draw.setSize(gl.drawingBufferWidth, gl.drawingBufferHeight); + // Avoid setting dimensions to 0x0 because it causes "empty textures are not allowed" error. + const width = Math.max(gl.drawingBufferWidth, 2); + const height = Math.max(gl.drawingBufferHeight, 2); + this.draw.setSize(width, height); this.pick.syncSize(); this.multiSample.syncSize(); } diff --git a/src/mol-canvas3d/passes/postprocessing.ts b/src/mol-canvas3d/passes/postprocessing.ts index fd41b8abe62071ec6a85734b6db554753e1ed03d..591517976863a47013460eb3158cfeeb699619a4 100644 --- a/src/mol-canvas3d/passes/postprocessing.ts +++ b/src/mol-canvas3d/passes/postprocessing.ts @@ -28,6 +28,8 @@ import { Color } from '../../mol-util/color'; import { FxaaParams, FxaaPass } from './fxaa'; import { SmaaParams, SmaaPass } from './smaa'; import { isTimingMode } from '../../mol-util/debug'; +import { BackgroundParams, BackgroundPass } from './background'; +import { AssetManager } from '../../mol-util/assets'; const OutlinesSchema = { ...QuadSchema, @@ -91,7 +93,7 @@ function getSsaoRenderable(ctx: WebGLContext, depthTexture: Texture): SsaoRender ...QuadValues, tDepth: ValueCell.create(depthTexture), - uSamples: ValueCell.create([0.0, 0.0, 1.0]), + uSamples: ValueCell.create(getSamples(32)), dNSamples: ValueCell.create(32), uProjection: ValueCell.create(Mat4.identity()), @@ -138,7 +140,7 @@ function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, dir tSsaoDepth: ValueCell.create(ssaoDepthTexture), uTexSize: ValueCell.create(Vec2.create(ssaoDepthTexture.getWidth(), ssaoDepthTexture.getHeight())), - uKernel: ValueCell.create([0.0]), + uKernel: ValueCell.create(getBlurKernel(15)), dOcclusionKernelSize: ValueCell.create(15), uBlurDirectionX: ValueCell.create(direction === 'horizontal' ? 1 : 0), @@ -171,15 +173,26 @@ function getBlurKernel(kernelSize: number): number[] { return kernel; } -function getSamples(vectorSamples: Vec3[], nSamples: number): number[] { +const RandomHemisphereVector: Vec3[] = []; +for (let i = 0; i < 256; i++) { + const v = Vec3(); + v[0] = Math.random() * 2.0 - 1.0; + v[1] = Math.random() * 2.0 - 1.0; + v[2] = Math.random(); + Vec3.normalize(v, v); + Vec3.scale(v, v, Math.random()); + RandomHemisphereVector.push(v); +} + +function getSamples(nSamples: number): number[] { const samples = []; for (let i = 0; i < nSamples; i++) { let scale = (i * i + 2.0 * i + 1) / (nSamples * nSamples); scale = 0.1 + scale * (1.0 - 0.1); - samples.push(vectorSamples[i][0] * scale); - samples.push(vectorSamples[i][1] * scale); - samples.push(vectorSamples[i][2] * scale); + samples.push(RandomHemisphereVector[i][0] * scale); + samples.push(RandomHemisphereVector[i][1] * scale); + samples.push(RandomHemisphereVector[i][2] * scale); } return samples; @@ -274,12 +287,13 @@ export const PostprocessingParams = { smaa: PD.Group(SmaaParams), off: PD.Group({}) }, { options: [['fxaa', 'FXAA'], ['smaa', 'SMAA'], ['off', 'Off']], description: 'Smooth pixel edges' }), + background: PD.Group(BackgroundParams, { isFlat: true }), }; export type PostprocessingProps = PD.Values<typeof PostprocessingParams> export class PostprocessingPass { static isEnabled(props: PostprocessingProps) { - return props.occlusion.name === 'on' || props.outline.name === 'on'; + return props.occlusion.name === 'on' || props.outline.name === 'on' || props.background.variant.name !== 'off'; } static isOutlineEnabled(props: PostprocessingProps) { @@ -291,7 +305,6 @@ export class PostprocessingPass { private readonly outlinesTarget: RenderTarget; private readonly outlinesRenderable: OutlinesRenderable; - private readonly randomHemisphereVector: Vec3[]; private readonly ssaoFramebuffer: Framebuffer; private readonly ssaoBlurFirstPassFramebuffer: Framebuffer; private readonly ssaoBlurSecondPassFramebuffer: Framebuffer; @@ -318,7 +331,10 @@ export class PostprocessingPass { return Math.min(1, 1 / this.webgl.pixelRatio) * this.downsampleFactor; } - constructor(private webgl: WebGLContext, private drawPass: DrawPass) { + private readonly bgColor = Vec3(); + readonly background: BackgroundPass; + + constructor(private readonly webgl: WebGLContext, assetManager: AssetManager, private readonly drawPass: DrawPass) { const { colorTarget, depthTextureTransparent, depthTextureOpaque } = drawPass; const width = colorTarget.getWidth(); const height = colorTarget.getHeight(); @@ -334,16 +350,6 @@ export class PostprocessingPass { this.outlinesTarget = webgl.createRenderTarget(width, height, false); this.outlinesRenderable = getOutlinesRenderable(webgl, depthTextureOpaque, depthTextureTransparent); - this.randomHemisphereVector = []; - for (let i = 0; i < 256; i++) { - const v = Vec3(); - v[0] = Math.random() * 2.0 - 1.0; - v[1] = Math.random() * 2.0 - 1.0; - v[2] = Math.random(); - Vec3.normalize(v, v); - Vec3.scale(v, v, Math.random()); - this.randomHemisphereVector.push(v); - } this.ssaoFramebuffer = webgl.resources.framebuffer(); this.ssaoBlurFirstPassFramebuffer = webgl.resources.framebuffer(); this.ssaoBlurSecondPassFramebuffer = webgl.resources.framebuffer(); @@ -368,6 +374,8 @@ export class PostprocessingPass { this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal'); this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical'); this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTextureOpaque, depthTextureTransparent, this.outlinesTarget.texture, this.ssaoDepthTexture); + + this.background = new BackgroundPass(webgl, assetManager, width, height); } setSize(width: number, height: number) { @@ -391,6 +399,8 @@ export class PostprocessingPass { ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh)); ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh)); ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh)); + + this.background.setSize(width, height); } } @@ -440,7 +450,7 @@ export class PostprocessingPass { needsUpdateSsao = true; this.nSamples = props.occlusion.params.samples; - ValueCell.update(this.ssaoRenderable.values.uSamples, getSamples(this.randomHemisphereVector, this.nSamples)); + ValueCell.update(this.ssaoRenderable.values.uSamples, getSamples(this.nSamples)); ValueCell.updateIfChanged(this.ssaoRenderable.values.dNSamples, this.nSamples); } ValueCell.updateIfChanged(this.ssaoRenderable.values.uRadius, Math.pow(2, props.occlusion.params.radius)); @@ -538,8 +548,8 @@ export class PostprocessingPass { state.depthMask(false); const { x, y, width, height } = camera.viewport; - gl.viewport(x, y, width, height); - gl.scissor(x, y, width, height); + state.viewport(x, y, width, height); + state.scissor(x, y, width, height); } private occlusionOffset: [x: number, y: number] = [0, 0]; @@ -549,6 +559,11 @@ export class PostprocessingPass { ValueCell.update(this.renderable.values.uOcclusionOffset, Vec2.set(this.renderable.values.uOcclusionOffset.ref.value, x, y)); } + private transparentBackground = false; + setTransparentBackground(value: boolean) { + this.transparentBackground = value; + } + render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) { if (isTimingMode) this.webgl.timer.mark('PostprocessingPass.render'); this.updateState(camera, transparentBackground, backgroundColor, props); @@ -583,8 +598,23 @@ export class PostprocessingPass { } const { gl, state } = this.webgl; - state.clearColor(0, 0, 0, 1); - gl.clear(gl.COLOR_BUFFER_BIT); + + this.background.update(camera, props.background); + if (this.background.isEnabled(props.background)) { + if (this.transparentBackground) { + state.clearColor(0, 0, 0, 0); + } else { + Color.toVec3Normalized(this.bgColor, backgroundColor); + state.clearColor(this.bgColor[0], this.bgColor[1], this.bgColor[2], 1); + } + gl.clear(gl.COLOR_BUFFER_BIT); + state.enable(gl.BLEND); + state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + this.background.render(); + } else { + state.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + } this.renderable.render(); if (isTimingMode) this.webgl.timer.markEnd('PostprocessingPass.render'); diff --git a/src/mol-canvas3d/passes/smaa.ts b/src/mol-canvas3d/passes/smaa.ts index 3002b2ff33f3b4bd244675dc264ec4aa49f30c36..79752e4da69232a69081ab6795baf71c7488ba42 100644 --- a/src/mol-canvas3d/passes/smaa.ts +++ b/src/mol-canvas3d/passes/smaa.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -11,7 +11,7 @@ import { ShaderCode } from '../../mol-gl/shader-code'; import { WebGLContext } from '../../mol-gl/webgl/context'; import { createComputeRenderItem } from '../../mol-gl/webgl/render-item'; import { RenderTarget } from '../../mol-gl/webgl/render-target'; -import { createTexture, loadImageTexture, Texture } from '../../mol-gl/webgl/texture'; +import { loadImageTexture, Texture } from '../../mol-gl/webgl/texture'; import { Vec2, Vec4 } from '../../mol-math/linear-algebra'; import { ValueCell } from '../../mol-util'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; @@ -71,9 +71,10 @@ export class SmaaPass { state.depthMask(false); const { x, y, width, height } = viewport; - gl.viewport(x, y, width, height); - gl.scissor(x, y, width, height); + state.viewport(x, y, width, height); + state.scissor(x, y, width, height); + state.colorMask(true, true, true, true); state.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); @@ -191,8 +192,8 @@ function getWeightsRenderable(ctx: WebGLContext, edgesTexture: Texture): Weights const width = edgesTexture.getWidth(); const height = edgesTexture.getHeight(); - const areaTexture = createTexture(ctx.gl, ctx.extensions, 'image-uint8', 'rgb', 'ubyte', 'linear'); - const searchTexture = createTexture(ctx.gl, ctx.extensions, 'image-uint8', 'rgba', 'ubyte', 'nearest'); + const areaTexture = ctx.resources.texture('image-uint8', 'rgb', 'ubyte', 'linear'); + const searchTexture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); const values: Values<typeof WeightsSchema> = { ...QuadValues, diff --git a/src/mol-canvas3d/passes/wboit.ts b/src/mol-canvas3d/passes/wboit.ts index c78f809a1e4b619d866b14557a936bbf4a0ffd15..210a94f51c1efb0ecd5d95a5932b6f2d958e4351 100644 --- a/src/mol-canvas3d/passes/wboit.ts +++ b/src/mol-canvas3d/passes/wboit.ts @@ -18,6 +18,8 @@ import { evaluateWboit_frag } from '../../mol-gl/shader/evaluate-wboit.frag'; import { Framebuffer } from '../../mol-gl/webgl/framebuffer'; import { Vec2 } from '../../mol-math/linear-algebra'; import { isDebugMode, isTimingMode } from '../../mol-util/debug'; +import { isWebGL2 } from '../../mol-gl/webgl/compat'; +import { Renderbuffer } from '../../mol-gl/webgl/renderbuffer'; const EvaluateWboitSchema = { ...QuadSchema, @@ -50,6 +52,7 @@ export class WboitPass { private readonly framebuffer: Framebuffer; private readonly textureA: Texture; private readonly textureB: Texture; + private readonly depthRenderbuffer: Renderbuffer; private _supported = false; get supported() { @@ -87,6 +90,7 @@ export class WboitPass { if (width !== w || height !== h) { this.textureA.define(width, height); this.textureB.define(width, height); + this.depthRenderbuffer.setSize(width, height); ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height)); } } @@ -106,6 +110,8 @@ export class WboitPass { this.textureA.attachFramebuffer(this.framebuffer, 'color0'); this.textureB.attachFramebuffer(this.framebuffer, 'color1'); + + this.depthRenderbuffer.attachFramebuffer(this.framebuffer); } static isSupported(webgl: WebGLContext) { @@ -128,7 +134,7 @@ export class WboitPass { constructor(private webgl: WebGLContext, width: number, height: number) { if (!WboitPass.isSupported(webgl)) return; - const { resources } = webgl; + const { resources, gl } = webgl; this.textureA = resources.texture('image-float32', 'rgba', 'float', 'nearest'); this.textureA.define(width, height); @@ -136,6 +142,10 @@ export class WboitPass { this.textureB = resources.texture('image-float32', 'rgba', 'float', 'nearest'); this.textureB.define(width, height); + this.depthRenderbuffer = isWebGL2(gl) + ? resources.renderbuffer('depth32f', 'depth', width, height) + : resources.renderbuffer('depth16', 'depth', width, height); + this.renderable = getEvaluateWboitRenderable(webgl, this.textureA, this.textureB); this.framebuffer = resources.framebuffer(); diff --git a/src/mol-geo/geometry/base.ts b/src/mol-geo/geometry/base.ts index ca2ba47d8f0a1a50b974cc56fffd31543e4568bd..d17a47077333819561ef57ceb5234b08b8bee45e 100644 --- a/src/mol-geo/geometry/base.ts +++ b/src/mol-geo/geometry/base.ts @@ -111,6 +111,7 @@ export namespace BaseGeometry { uRoughness: ValueCell.create(props.material.roughness), uBumpiness: ValueCell.create(props.material.bumpiness), dLightCount: ValueCell.create(1), + dColorMarker: ValueCell.create(true), dClipObjectCount: ValueCell.create(clip.objects.count), dClipVariant: ValueCell.create(clip.variant), diff --git a/src/mol-geo/geometry/mesh/mesh.ts b/src/mol-geo/geometry/mesh/mesh.ts index 29b08ba3a935cac0f9a95f70da6e7bcef23f1ad5..10b9c9b058105fa54e6fd2d95f743f36dec8d059 100644 --- a/src/mol-geo/geometry/mesh/mesh.ts +++ b/src/mol-geo/geometry/mesh/mesh.ts @@ -45,6 +45,8 @@ export interface Mesh { readonly normalBuffer: ValueCell<Float32Array>, /** Group buffer as array of group ids for each vertex wrapped in a value cell */ readonly groupBuffer: ValueCell<Float32Array>, + /** Indicates that group may vary within a triangle, wrapped in a value cell */ + readonly varyingGroup: ValueCell<boolean>, /** Bounding sphere of the mesh */ readonly boundingSphere: Sphere3D @@ -95,6 +97,7 @@ export namespace Mesh { indexBuffer: ValueCell.create(indices), normalBuffer: ValueCell.create(normals), groupBuffer: ValueCell.create(groups), + varyingGroup: ValueCell.create(false), get boundingSphere() { const newHash = hashCode(mesh); if (newHash !== currentHash) { @@ -686,6 +689,7 @@ export namespace Mesh { aNormal: mesh.normalBuffer, aGroup: mesh.groupBuffer, elements: mesh.indexBuffer, + dVaryingGroup: mesh.varyingGroup, boundingSphere: ValueCell.create(boundingSphere), invariantBoundingSphere: ValueCell.create(invariantBoundingSphere), uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)), diff --git a/src/mol-geo/geometry/text/font-atlas.ts b/src/mol-geo/geometry/text/font-atlas.ts index 609a6985a50ab450799a602ed1ae91caaa95beb3..834b8ecbd49a1f278644496d67946c9554a0501e 100644 --- a/src/mol-geo/geometry/text/font-atlas.ts +++ b/src/mol-geo/geometry/text/font-atlas.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -88,7 +88,7 @@ export class FontAtlas { this.scratchCanvas.width = this.maxWidth; this.scratchCanvas.height = this.lineHeight; - this.scratchContext = this.scratchCanvas.getContext('2d')!; + this.scratchContext = this.scratchCanvas.getContext('2d', { willReadFrequently: true })!; this.scratchContext.font = `${p.fontStyle} ${p.fontVariant} ${p.fontWeight} ${fontSize}px ${p.fontFamily}`; this.scratchContext.fillStyle = 'black'; this.scratchContext.textBaseline = 'middle'; diff --git a/src/mol-geo/geometry/texture-mesh/color-smoothing.ts b/src/mol-geo/geometry/texture-mesh/color-smoothing.ts index 7d28455cd9fc98871a2a88be6c6483fdf107b208..8633a1a0577876f532dc4d7c5ae0efe8a8cac1b2 100644 --- a/src/mol-geo/geometry/texture-mesh/color-smoothing.ts +++ b/src/mol-geo/geometry/texture-mesh/color-smoothing.ts @@ -27,20 +27,18 @@ export const ColorAccumulateSchema = { instanceCount: ValueSpec('number'), stride: ValueSpec('number'), - uTotalCount: UniformSpec('i'), - uInstanceCount: UniformSpec('i'), - uGroupCount: UniformSpec('i'), + uGroupCount: UniformSpec('i', 'material'), aTransform: AttributeSpec('float32', 16, 1), aInstance: AttributeSpec('float32', 1, 1), aSample: AttributeSpec('float32', 1, 0), - uGeoTexDim: UniformSpec('v2', 'buffered'), - tPosition: TextureSpec('texture', 'rgba', 'float', 'nearest'), - tGroup: TextureSpec('texture', 'rgba', 'float', 'nearest'), + uGeoTexDim: UniformSpec('v2', 'material'), + tPosition: TextureSpec('texture', 'rgba', 'float', 'nearest', 'material'), + tGroup: TextureSpec('texture', 'rgba', 'float', 'nearest', 'material'), - uColorTexDim: UniformSpec('v2'), - tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'), + uColorTexDim: UniformSpec('v2', 'material'), + tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest', 'material'), dColorType: DefineSpec('string', ['group', 'groupInstance', 'vertex', 'vertexInstance']), uCurrentSlice: UniformSpec('f'), @@ -88,8 +86,6 @@ function getAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, box: ValueCell.updateIfChanged(v.instanceCount, input.instanceCount); ValueCell.updateIfChanged(v.stride, stride); - ValueCell.updateIfChanged(v.uTotalCount, input.vertexCount); - ValueCell.updateIfChanged(v.uInstanceCount, input.instanceCount); ValueCell.updateIfChanged(v.uGroupCount, input.groupCount); ValueCell.update(v.aTransform, input.transformBuffer); @@ -126,8 +122,6 @@ function createAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, b instanceCount: ValueCell.create(input.instanceCount), stride: ValueCell.create(stride), - uTotalCount: ValueCell.create(input.vertexCount), - uInstanceCount: ValueCell.create(input.instanceCount), uGroupCount: ValueCell.create(input.groupCount), aTransform: ValueCell.create(input.transformBuffer), @@ -325,8 +319,8 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu if (isTimingMode) webgl.timer.mark('ColorAccumulate.render'); setAccumulateDefaults(webgl); - gl.viewport(0, 0, width, height); - gl.scissor(0, 0, width, height); + state.viewport(0, 0, width, height); + state.scissor(0, 0, width, height); gl.clear(gl.COLOR_BUFFER_BIT); ValueCell.update(uCurrentY, 0); let currCol = 0; @@ -342,8 +336,8 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu // console.log({ i, currX, currY }); ValueCell.update(uCurrentX, currX); ValueCell.update(uCurrentSlice, i); - gl.viewport(currX, currY, dx, dy); - gl.scissor(currX, currY, dx, dy); + state.viewport(currX, currY, dx, dy); + state.scissor(currX, currY, dx, dy); accumulateRenderable.render(); ++currCol; currX += dx; @@ -377,8 +371,8 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu setNormalizeDefaults(webgl); texture.attachFramebuffer(framebuffer, 0); - gl.viewport(0, 0, width, height); - gl.scissor(0, 0, width, height); + state.viewport(0, 0, width, height); + state.scissor(0, 0, width, height); gl.clear(gl.COLOR_BUFFER_BIT); normalizeRenderable.render(); if (isTimingMode) webgl.timer.markEnd('ColorNormalize.render'); @@ -393,6 +387,8 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu const type = isInstanceType ? 'volumeInstance' : 'volume'; if (isTimingMode) webgl.timer.markEnd('calcTextureMeshColorSmoothing'); + // printTextureImage(readTexture(webgl, texture), { scale: 0.75 }); + return { texture, gridDim, gridTexDim: Vec2.create(width, height), gridTransform, type }; } diff --git a/src/mol-geo/geometry/texture-mesh/texture-mesh.ts b/src/mol-geo/geometry/texture-mesh/texture-mesh.ts index 8c941543972963b7e949c500ee4c0d5c41ab347e..0a246b3e0e29a1b49e1dfec9ef18accccbf7c01b 100644 --- a/src/mol-geo/geometry/texture-mesh/texture-mesh.ts +++ b/src/mol-geo/geometry/texture-mesh/texture-mesh.ts @@ -38,6 +38,7 @@ export interface TextureMesh { readonly vertexTexture: ValueCell<Texture>, readonly groupTexture: ValueCell<Texture>, readonly normalTexture: ValueCell<Texture>, + readonly varyingGroup: ValueCell<boolean>, readonly doubleBuffer: TextureMesh.DoubleBuffer readonly boundingSphere: Sphere3D @@ -92,6 +93,7 @@ export namespace TextureMesh { vertexTexture: ValueCell.create(vertexTexture), groupTexture: ValueCell.create(groupTexture), normalTexture: ValueCell.create(normalTexture), + varyingGroup: ValueCell.create(false), doubleBuffer: new DoubleBuffer(), boundingSphere: Sphere3D.clone(boundingSphere), meta: {} @@ -157,6 +159,7 @@ export namespace TextureMesh { tPosition: textureMesh.vertexTexture, tGroup: textureMesh.groupTexture, tNormal: textureMesh.normalTexture, + dVaryingGroup: textureMesh.varyingGroup, boundingSphere: ValueCell.create(boundingSphere), invariantBoundingSphere: ValueCell.create(invariantBoundingSphere), diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index 7d39b63ae9ac46a24cb51bbd13666aaf001323c2..4842ca9c9407e97059239e3b167f945c3acbbd6f 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -53,17 +53,17 @@ describe('renderer', () => { scene.commit(); expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5); expect(ctx.stats.resourceCounts.texture).toBe(9); - expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 5 : 0); - expect(ctx.stats.resourceCounts.program).toBe(5); - expect(ctx.stats.resourceCounts.shader).toBe(10); + expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 6 : 0); + expect(ctx.stats.resourceCounts.program).toBe(6); + expect(ctx.stats.resourceCounts.shader).toBe(12); scene.remove(points); scene.commit(); expect(ctx.stats.resourceCounts.attribute).toBe(0); expect(ctx.stats.resourceCounts.texture).toBe(1); expect(ctx.stats.resourceCounts.vertexArray).toBe(0); - expect(ctx.stats.resourceCounts.program).toBe(5); - expect(ctx.stats.resourceCounts.shader).toBe(10); + expect(ctx.stats.resourceCounts.program).toBe(6); + expect(ctx.stats.resourceCounts.shader).toBe(12); ctx.resources.destroy(); expect(ctx.stats.resourceCounts.program).toBe(0); diff --git a/src/mol-gl/compute/grid3d.ts b/src/mol-gl/compute/grid3d.ts index e291661ccae12a921b5d127ce6ecdbbc22617ce5..6a4922e34a6dcc497fdb5f672d94318fe0afe601 100644 --- a/src/mol-gl/compute/grid3d.ts +++ b/src/mol-gl/compute/grid3d.ts @@ -225,8 +225,8 @@ export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS> function resetGl(webgl: WebGLContext, w: number) { const { gl, state } = webgl; - gl.viewport(0, 0, w, w); - gl.scissor(0, 0, w, w); + state.viewport(0, 0, w, w); + state.scissor(0, 0, w, w); state.disable(gl.SCISSOR_TEST); state.disable(gl.BLEND); state.disable(gl.DEPTH_TEST); diff --git a/src/mol-gl/compute/histogram-pyramid/reduction.ts b/src/mol-gl/compute/histogram-pyramid/reduction.ts index 8d162f5e99e8e69cf1f5fcb188a956336682d59f..43aaddf292b62a701c48f4cdb273beda069cc255 100644 --- a/src/mol-gl/compute/histogram-pyramid/reduction.ts +++ b/src/mol-gl/compute/histogram-pyramid/reduction.ts @@ -122,7 +122,7 @@ export interface HistogramPyramid { export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2, gridTexDim: Vec3): HistogramPyramid { if (isTimingMode) ctx.timer.mark('createHistogramPyramid'); - const { gl } = ctx; + const { gl, state } = ctx; const w = inputTexture.getWidth(); const h = inputTexture.getHeight(); @@ -146,7 +146,7 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, const framebuffer = getFramebuffer('pyramid', ctx); pyramidTex.attachFramebuffer(framebuffer, 0); - gl.viewport(0, 0, maxSizeX, maxSizeY); + state.viewport(0, 0, maxSizeX, maxSizeY); if (isWebGL2(gl)) { gl.clearBufferiv(gl.COLOR, 0, [0, 0, 0, 0]); } else { @@ -157,7 +157,7 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, for (let i = 0; i < levels; ++i) levelTexturesFramebuffers.push(getLevelTextureFramebuffer(ctx, i)); const renderable = getHistopyramidReductionRenderable(ctx, inputTexture, levelTexturesFramebuffers[0].texture); - ctx.state.currentRenderItemId = -1; + state.currentRenderItemId = -1; setRenderingDefaults(ctx); let offset = 0; @@ -176,15 +176,15 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, ValueCell.update(renderable.values.tPreviousLevel, levelTexturesFramebuffers[levels - i].texture); renderable.update(); } - ctx.state.currentRenderItemId = -1; - gl.viewport(0, 0, size, size); - gl.scissor(0, 0, size, size); + state.currentRenderItemId = -1; + state.viewport(0, 0, size, size); + state.scissor(0, 0, size, size); if (isWebGL2(gl)) { gl.clearBufferiv(gl.COLOR, 0, [0, 0, 0, 0]); } else { gl.clear(gl.COLOR_BUFFER_BIT); } - gl.scissor(0, 0, gridTexDim[0], gridTexDim[1]); + state.scissor(0, 0, gridTexDim[0], gridTexDim[1]); renderable.render(); pyramidTex.bind(0); @@ -197,7 +197,7 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, gl.finish(); if (isTimingMode) ctx.timer.markEnd('createHistogramPyramid'); - // printTexture(ctx, pyramidTex, 2) + // printTextureImage(readTexture(ctx, pyramidTex), { scale: 0.75 }); // diff --git a/src/mol-gl/compute/histogram-pyramid/sum.ts b/src/mol-gl/compute/histogram-pyramid/sum.ts index a1cd5919a7bf5632b87d6ca0c8ea274ecff1caf4..65c36515d80ac9d2f1f7e3e87118c90fe240bcaf 100644 --- a/src/mol-gl/compute/histogram-pyramid/sum.ts +++ b/src/mol-gl/compute/histogram-pyramid/sum.ts @@ -68,7 +68,7 @@ const sumInts = new Int32Array(4); export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture) { if (isTimingMode) ctx.timer.mark('getHistopyramidSum'); - const { gl, resources } = ctx; + const { gl, state, resources } = ctx; const renderable = getHistopyramidSumRenderable(ctx, pyramidTopTexture); ctx.state.currentRenderItemId = -1; @@ -89,7 +89,7 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture setRenderingDefaults(ctx); - gl.viewport(0, 0, 1, 1); + state.viewport(0, 0, 1, 1); renderable.render(); gl.finish(); diff --git a/src/mol-gl/compute/marching-cubes/active-voxels.ts b/src/mol-gl/compute/marching-cubes/active-voxels.ts index b16014c011b8eef48a74c800b69c595b463bdee7..e4fe0173a728020bf95d0a2642a7c36ce9ba35b3 100644 --- a/src/mol-gl/compute/marching-cubes/active-voxels.ts +++ b/src/mol-gl/compute/marching-cubes/active-voxels.ts @@ -85,7 +85,7 @@ function setRenderingDefaults(ctx: WebGLContext) { export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, gridScale: Vec2) { if (isTimingMode) ctx.timer.mark('calcActiveVoxels'); - const { gl, resources } = ctx; + const { gl, state, resources } = ctx; const width = volumeData.getWidth(); const height = volumeData.getHeight(); @@ -106,15 +106,16 @@ export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim activeVoxelsTex.attachFramebuffer(framebuffer, 0); setRenderingDefaults(ctx); - gl.viewport(0, 0, width, height); - gl.scissor(0, 0, width, height); + state.viewport(0, 0, width, height); + state.scissor(0, 0, width, height); gl.clear(gl.COLOR_BUFFER_BIT); - gl.scissor(0, 0, gridTexDim[0], gridTexDim[1]); + state.scissor(0, 0, gridTexDim[0], gridTexDim[1]); renderable.render(); // console.log('gridScale', gridScale, 'gridTexDim', gridTexDim, 'gridDim', gridDim); // console.log('volumeData', volumeData); // console.log('at', readTexture(ctx, activeVoxelsTex)); + // printTextureImage(readTexture(ctx, activeVoxelsTex), { scale: 0.75 }); gl.finish(); if (isTimingMode) ctx.timer.markEnd('calcActiveVoxels'); diff --git a/src/mol-gl/compute/marching-cubes/isosurface.ts b/src/mol-gl/compute/marching-cubes/isosurface.ts index 93263e22713b409f2e343f85bd84d648e5e20085..62af9563c3315e72f2ed814250a2d4cb007ff6a7 100644 --- a/src/mol-gl/compute/marching-cubes/isosurface.ts +++ b/src/mol-gl/compute/marching-cubes/isosurface.ts @@ -42,12 +42,13 @@ const IsosurfaceSchema = { dPackedGroup: DefineSpec('boolean'), dAxisOrder: DefineSpec('string', ['012', '021', '102', '120', '201', '210']), + dConstantGroup: DefineSpec('boolean'), }; type IsosurfaceValues = Values<typeof IsosurfaceSchema> const IsosurfaceName = 'isosurface'; -function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3): ComputeRenderable<IsosurfaceValues> { +function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean): ComputeRenderable<IsosurfaceValues> { if (ctx.namedComputeRenderables[IsosurfaceName]) { const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues; @@ -66,17 +67,18 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture ValueCell.update(v.uGridTransform, transform); ValueCell.update(v.uScale, scale); - ValueCell.update(v.dPackedGroup, packedGroup); + ValueCell.updateIfChanged(v.dPackedGroup, packedGroup); ValueCell.updateIfChanged(v.dAxisOrder, axisOrder.join('')); + ValueCell.updateIfChanged(v.dConstantGroup, constantGroup); ctx.namedComputeRenderables[IsosurfaceName].update(); } else { - ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder); + ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup); } return ctx.namedComputeRenderables[IsosurfaceName]; } -function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3) { +function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean) { // console.log('uSize', Math.pow(2, levels)) const values: IsosurfaceValues = { ...QuadValues, @@ -99,6 +101,7 @@ function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Text dPackedGroup: ValueCell.create(packedGroup), dAxisOrder: ValueCell.create(axisOrder.join('')), + dConstantGroup: ValueCell.create(constantGroup), }; const schema = { ...IsosurfaceSchema }; @@ -119,12 +122,12 @@ function setRenderingDefaults(ctx: WebGLContext) { state.clearColor(0, 0, 0, 0); } -export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) { +export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) { const { drawBuffers } = ctx.extensions; if (!drawBuffers) throw new Error('need WebGL draw buffers'); if (isTimingMode) ctx.timer.mark('createIsosurfaceBuffers'); - const { gl, resources, extensions } = ctx; + const { gl, state, resources, extensions } = ctx; const { pyramidTex, height, levels, scale, count } = histogramPyramid; const width = pyramidTex.getWidth(); @@ -178,7 +181,7 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex groupTexture.attachFramebuffer(framebuffer, 1); normalTexture.attachFramebuffer(framebuffer, 2); - const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder); + const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, invert, packedGroup, axisOrder, constantGroup); ctx.state.currentRenderItemId = -1; framebuffer.bind(); @@ -189,13 +192,17 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex ]); setRenderingDefaults(ctx); - gl.viewport(0, 0, width, height); + state.viewport(0, 0, width, height); gl.clear(gl.COLOR_BUFFER_BIT); renderable.render(); gl.finish(); if (isTimingMode) ctx.timer.markEnd('createIsosurfaceBuffers'); + // printTextureImage(readTexture(ctx, vertexTexture, new Float32Array(width * height * 4)), { scale: 0.75 }); + // printTextureImage(readTexture(ctx, groupTexture, new Uint8Array(width * height * 4)), { scale: 0.75 }); + // printTextureImage(readTexture(ctx, normalTexture, new Float32Array(width * height * 4)), { scale: 0.75 }); + return { vertexTexture, groupTexture, normalTexture, vertexCount: count }; } @@ -210,11 +217,11 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex * * Implementation based on http://www.miaumiau.cat/2016/10/stream-compaction-in-webgl/ */ -export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) { +export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) { if (isTimingMode) ctx.timer.mark('extractIsosurface'); const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale); const compacted = createHistogramPyramid(ctx, activeVoxelsTex, gridTexScale, gridTexDim); - const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, vertexTexture, groupTexture, normalTexture); + const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, invert, packedGroup, axisOrder, constantGroup, vertexTexture, groupTexture, normalTexture); if (isTimingMode) ctx.timer.markEnd('extractIsosurface'); return gv; diff --git a/src/mol-gl/compute/util.ts b/src/mol-gl/compute/util.ts index 2e759efbb3def192fe627db0c448a0b81ba39bc5..044826ae0210215a3cdb0672ce5bfb5b134c5888 100644 --- a/src/mol-gl/compute/util.ts +++ b/src/mol-gl/compute/util.ts @@ -75,9 +75,9 @@ export function getSharedCopyRenderable(ctx: WebGLContext, texture: Texture) { const ReadTextureName = 'read-texture'; const ReadAlphaTextureName = 'read-alpha-texture'; -export function readTexture(ctx: WebGLContext, texture: Texture) { +export function readTexture<T extends Uint8Array | Float32Array | Int32Array = Uint8Array>(ctx: WebGLContext, texture: Texture, array?: T) { const { gl, resources } = ctx; - if (texture.type !== gl.UNSIGNED_BYTE) throw new Error('unsupported texture type'); + if (!array && texture.type !== gl.UNSIGNED_BYTE) throw new Error('unsupported texture type'); if (!ctx.namedFramebuffers[ReadTextureName]) { ctx.namedFramebuffers[ReadTextureName] = resources.framebuffer(); @@ -86,7 +86,7 @@ export function readTexture(ctx: WebGLContext, texture: Texture) { const width = texture.getWidth(); const height = texture.getHeight(); - const array = new Uint8Array(width * height * 4); + if (!array) array = new Uint8Array(width * height * 4) as T; framebuffer.bind(); texture.attachFramebuffer(framebuffer, 0); ctx.readPixels(0, 0, width, height, array); @@ -125,8 +125,8 @@ export function readAlphaTexture(ctx: WebGLContext, texture: Texture) { state.clearColor(0, 0, 0, 0); state.blendFunc(gl.ONE, gl.ONE); state.blendEquation(gl.FUNC_ADD); - gl.viewport(0, 0, width, height); - gl.scissor(0, 0, width, height); + state.viewport(0, 0, width, height); + state.scissor(0, 0, width, height); gl.clear(gl.COLOR_BUFFER_BIT); copy.render(); diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts index e0c4db03086fba4c7e3750fc225569651b02b397..c8758d53cdac056fab72ce22a698b1ee70f6cd37 100644 --- a/src/mol-gl/renderable.ts +++ b/src/mol-gl/renderable.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -10,7 +10,6 @@ import { GraphicsRenderItem, ComputeRenderItem, GraphicsRenderVariant } from './ import { ValueCell } from '../mol-util'; import { idFactory } from '../mol-util/id-factory'; import { clamp } from '../mol-math/interpolate'; -import { Textures } from './webgl/texture'; const getNextRenderableId = idFactory(); @@ -30,7 +29,7 @@ export interface Renderable<T extends RenderableValues> { readonly values: T readonly state: RenderableState - render: (variant: GraphicsRenderVariant, sharedTexturesList?: Textures) => void + render: (variant: GraphicsRenderVariant, sharedTexturesCount: number) => void getProgram: (variant: GraphicsRenderVariant) => Program update: () => void dispose: () => void @@ -43,11 +42,11 @@ export function createRenderable<T extends Values<RenderableSchema>>(renderItem: values, state, - render: (variant: GraphicsRenderVariant, sharedTexturesList?: Textures) => { + render: (variant: GraphicsRenderVariant, sharedTexturesCount: number) => { if (values.uAlpha && values.alpha) { ValueCell.updateIfChanged(values.uAlpha, clamp(values.alpha.ref.value * state.alphaFactor, 0, 1)); } - renderItem.render(variant, sharedTexturesList); + renderItem.render(variant, sharedTexturesCount); }, getProgram: (variant: GraphicsRenderVariant) => renderItem.getProgram(variant), update: () => renderItem.update(), @@ -73,7 +72,7 @@ export function createComputeRenderable<T extends Values<RenderableSchema>>(rend id: getNextRenderableId(), values, - render: () => renderItem.render('compute'), + render: () => renderItem.render('compute', 0), update: () => renderItem.update(), dispose: () => renderItem.destroy() }; diff --git a/src/mol-gl/renderable/cylinders.ts b/src/mol-gl/renderable/cylinders.ts index 5366aaae108f4f98d631edb538c53b9b8151cd45..db145d3edcf2eeff50f53b50d2eb07134fd8e5c4 100644 --- a/src/mol-gl/renderable/cylinders.ts +++ b/src/mol-gl/renderable/cylinders.ts @@ -23,12 +23,12 @@ export const CylindersSchema = { elements: ElementsSpec('uint32'), padding: ValueSpec('number'), - uDoubleSided: UniformSpec('b'), + uDoubleSided: UniformSpec('b', 'material'), dIgnoreLight: DefineSpec('boolean'), dXrayShaded: DefineSpec('boolean'), dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']), - uBumpFrequency: UniformSpec('f'), - uBumpAmplitude: UniformSpec('f'), + uBumpFrequency: UniformSpec('f', 'material'), + uBumpAmplitude: UniformSpec('f', 'material'), }; export type CylindersSchema = typeof CylindersSchema export type CylindersValues = Values<CylindersSchema> diff --git a/src/mol-gl/renderable/direct-volume.ts b/src/mol-gl/renderable/direct-volume.ts index 533235e9cdcb45b3bf36d94f95916785c6ffdd21..2b449fb262ed3a865e18634dc6b58ad338f28c72 100644 --- a/src/mol-gl/renderable/direct-volume.ts +++ b/src/mol-gl/renderable/direct-volume.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -26,7 +26,7 @@ export const DirectVolumeSchema = { uTransform: UniformSpec('m4'), uGridDim: UniformSpec('v3'), tTransferTex: TextureSpec('image-uint8', 'alpha', 'ubyte', 'linear'), - uTransferScale: UniformSpec('f'), + uTransferScale: UniformSpec('f', 'material'), dGridTexType: DefineSpec('string', ['2d', '3d']), uGridTexDim: UniformSpec('v3'), diff --git a/src/mol-gl/renderable/lines.ts b/src/mol-gl/renderable/lines.ts index a385e1922c585c3690e5612ed421129319209716..021ebba99d6fffad2d420da91de73128970c9ea1 100644 --- a/src/mol-gl/renderable/lines.ts +++ b/src/mol-gl/renderable/lines.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -20,7 +20,7 @@ export const LinesSchema = { aEnd: AttributeSpec('float32', 3, 0), elements: ElementsSpec('uint32'), dLineSizeAttenuation: DefineSpec('boolean'), - uDoubleSided: UniformSpec('b'), + uDoubleSided: UniformSpec('b', 'material'), dFlipSided: DefineSpec('boolean'), }; export type LinesSchema = typeof LinesSchema diff --git a/src/mol-gl/renderable/mesh.ts b/src/mol-gl/renderable/mesh.ts index c3da8f71d52720e5123d31f40419a0506946e241..fa5244d23e7e8aa87a87febf33619dee44861a5c 100644 --- a/src/mol-gl/renderable/mesh.ts +++ b/src/mol-gl/renderable/mesh.ts @@ -17,14 +17,15 @@ export const MeshSchema = { aPosition: AttributeSpec('float32', 3, 0), aNormal: AttributeSpec('float32', 3, 0), elements: ElementsSpec('uint32'), + dVaryingGroup: DefineSpec('boolean'), dFlatShaded: DefineSpec('boolean'), - uDoubleSided: UniformSpec('b'), + uDoubleSided: UniformSpec('b', 'material'), dFlipSided: DefineSpec('boolean'), dIgnoreLight: DefineSpec('boolean'), dXrayShaded: DefineSpec('boolean'), dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']), - uBumpFrequency: UniformSpec('f'), - uBumpAmplitude: UniformSpec('f'), + uBumpFrequency: UniformSpec('f', 'material'), + uBumpAmplitude: UniformSpec('f', 'material'), meta: ValueSpec('unknown') } as const; export type MeshSchema = typeof MeshSchema diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index 6880287cbf44de7927555000c09b8a04b38487cf..89b0752120f434019d180a3fab7f4773ab31dd77 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -2,6 +2,7 @@ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Gianluca Tomasello <giagitom@gmail.com> */ import { ValueCell } from '../../mol-util'; @@ -36,6 +37,7 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues) const attributeValues: AttributeValues = {}; const defineValues: DefineValues = {}; const textureValues: TextureValues = {}; + const materialTextureValues: TextureValues = {}; const uniformValues: UniformValues = {}; const materialUniformValues: UniformValues = {}; const bufferedUniformValues: UniformValues = {}; @@ -44,7 +46,10 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues) if (spec.type === 'attribute') attributeValues[k] = values[k]; if (spec.type === 'define') defineValues[k] = values[k]; // check if k exists in values to exclude global textures - if (spec.type === 'texture' && values[k] !== undefined) textureValues[k] = values[k]; + if (spec.type === 'texture' && values[k] !== undefined) { + if (spec.variant === 'material') materialTextureValues[k] = values[k]; + else textureValues[k] = values[k]; + } // check if k exists in values to exclude global uniforms if (spec.type === 'uniform' && values[k] !== undefined) { if (spec.variant === 'material') materialUniformValues[k] = values[k]; @@ -52,7 +57,7 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues) else uniformValues[k] = values[k]; } }); - return { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues, bufferedUniformValues }; + return { attributeValues, defineValues, textureValues, materialTextureValues, uniformValues, materialUniformValues, bufferedUniformValues }; } export type Versions<T extends RenderableValues> = { [k in keyof T]: number } @@ -76,9 +81,9 @@ export function UniformSpec<K extends UniformKind>(kind: K, variant?: 'material' return { type: 'uniform', kind, variant }; } -export type TextureSpec<K extends TextureKind> = { type: 'texture', kind: K, format: TextureFormat, dataType: TextureType, filter: TextureFilter } -export function TextureSpec<K extends TextureKind>(kind: K, format: TextureFormat, dataType: TextureType, filter: TextureFilter): TextureSpec<K> { - return { type: 'texture', kind, format, dataType, filter }; +export type TextureSpec<K extends TextureKind> = { type: 'texture', kind: K, format: TextureFormat, dataType: TextureType, filter: TextureFilter, variant?: 'material' } +export function TextureSpec<K extends TextureKind>(kind: K, format: TextureFormat, dataType: TextureType, filter: TextureFilter, variant?: 'material'): TextureSpec<K> { + return { type: 'texture', kind, format, dataType, filter, variant }; } export type ElementsSpec<K extends ElementsKind> = { type: 'elements', kind: K } @@ -163,6 +168,11 @@ export type GlobalUniformValues = Values<GlobalUniformSchema> export const GlobalTextureSchema = { tDepth: TextureSpec('texture', 'depth', 'ushort', 'nearest'), + + // dpoit + tDpoitDepth: TextureSpec('texture', 'rg', 'float', 'nearest'), + tDpoitFrontColor: TextureSpec('texture', 'rgba', 'float', 'nearest'), + tDpoitBackColor: TextureSpec('texture', 'rgba', 'float', 'nearest') } as const; export type GlobalTextureSchema = typeof GlobalTextureSchema export type GlobalTextureValues = Values<GlobalTextureSchema> @@ -194,7 +204,7 @@ export const SizeSchema = { uSizeTexDim: UniformSpec('v2'), tSize: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'), dSizeType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance']), - uSizeFactor: UniformSpec('f'), + uSizeFactor: UniformSpec('f', 'material'), } as const; export type SizeSchema = typeof SizeSchema export type SizeValues = Values<SizeSchema> @@ -232,7 +242,7 @@ export const TransparencySchema = { uTransparencyGridDim: UniformSpec('v3'), uTransparencyGridTransform: UniformSpec('v4'), tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'), - dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']), + dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']) } as const; export type TransparencySchema = typeof TransparencySchema export type TransparencyValues = Values<TransparencySchema> @@ -270,14 +280,15 @@ export const BaseSchema = { ...ClippingSchema, dLightCount: DefineSpec('number'), + dColorMarker: DefineSpec('boolean'), dClipObjectCount: DefineSpec('number'), dClipVariant: DefineSpec('string', ['instance', 'pixel']), - uClipObjectType: UniformSpec('i[]'), - uClipObjectInvert: UniformSpec('b[]'), - uClipObjectPosition: UniformSpec('v3[]'), - uClipObjectRotation: UniformSpec('v4[]'), - uClipObjectScale: UniformSpec('v3[]'), + uClipObjectType: UniformSpec('i[]', 'material'), + uClipObjectInvert: UniformSpec('b[]', 'material'), + uClipObjectPosition: UniformSpec('v3[]', 'material'), + uClipObjectRotation: UniformSpec('v4[]', 'material'), + uClipObjectScale: UniformSpec('v3[]', 'material'), aInstance: AttributeSpec('float32', 1, 1), /** @@ -322,4 +333,4 @@ export const BaseSchema = { invariantBoundingSphere: ValueSpec('sphere'), } as const; export type BaseSchema = typeof BaseSchema -export type BaseValues = Values<BaseSchema> \ No newline at end of file +export type BaseValues = Values<BaseSchema> diff --git a/src/mol-gl/renderable/spheres.ts b/src/mol-gl/renderable/spheres.ts index 60e316d81fefdb9d947c5dd961c451708b616a14..73c0e750754cb7617e34d23d32b2c9c6bf261012 100644 --- a/src/mol-gl/renderable/spheres.ts +++ b/src/mol-gl/renderable/spheres.ts @@ -20,12 +20,12 @@ export const SpheresSchema = { elements: ElementsSpec('uint32'), padding: ValueSpec('number'), - uDoubleSided: UniformSpec('b'), + uDoubleSided: UniformSpec('b', 'material'), dIgnoreLight: DefineSpec('boolean'), dXrayShaded: DefineSpec('boolean'), dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']), - uBumpFrequency: UniformSpec('f'), - uBumpAmplitude: UniformSpec('f'), + uBumpFrequency: UniformSpec('f', 'material'), + uBumpAmplitude: UniformSpec('f', 'material'), }; export type SpheresSchema = typeof SpheresSchema export type SpheresValues = Values<SpheresSchema> diff --git a/src/mol-gl/renderable/text.ts b/src/mol-gl/renderable/text.ts index 8f6e145e659b8197c8e2ae17069546afb4b0e6c9..364d07c5b07017aa419eefb668e8ae4601942686 100644 --- a/src/mol-gl/renderable/text.ts +++ b/src/mol-gl/renderable/text.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -24,13 +24,13 @@ export const TextSchema = { tFont: TextureSpec('image-uint8', 'alpha', 'ubyte', 'linear'), padding: ValueSpec('number'), - uBorderWidth: UniformSpec('f'), - uBorderColor: UniformSpec('v3'), - uOffsetX: UniformSpec('f'), - uOffsetY: UniformSpec('f'), - uOffsetZ: UniformSpec('f'), - uBackgroundColor: UniformSpec('v3'), - uBackgroundOpacity: UniformSpec('f'), + uBorderWidth: UniformSpec('f', 'material'), + uBorderColor: UniformSpec('v3', 'material'), + uOffsetX: UniformSpec('f', 'material'), + uOffsetY: UniformSpec('f', 'material'), + uOffsetZ: UniformSpec('f', 'material'), + uBackgroundColor: UniformSpec('v3', 'material'), + uBackgroundOpacity: UniformSpec('f', 'material'), }; export type TextSchema = typeof TextSchema export type TextValues = Values<TextSchema> diff --git a/src/mol-gl/renderable/texture-mesh.ts b/src/mol-gl/renderable/texture-mesh.ts index e997a9bb7c5c0e390d77f6e80d77d730c376533a..2436be94e4d17229f4ae060007d6c4b3b80c1d2d 100644 --- a/src/mol-gl/renderable/texture-mesh.ts +++ b/src/mol-gl/renderable/texture-mesh.ts @@ -17,15 +17,15 @@ export const TextureMeshSchema = { tPosition: TextureSpec('texture', 'rgb', 'float', 'nearest'), tGroup: TextureSpec('texture', 'alpha', 'float', 'nearest'), tNormal: TextureSpec('texture', 'rgb', 'float', 'nearest'), - + dVaryingGroup: DefineSpec('boolean'), dFlatShaded: DefineSpec('boolean'), - uDoubleSided: UniformSpec('b'), + uDoubleSided: UniformSpec('b', 'material'), dFlipSided: DefineSpec('boolean'), dIgnoreLight: DefineSpec('boolean'), dXrayShaded: DefineSpec('boolean'), dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']), - uBumpFrequency: UniformSpec('f'), - uBumpAmplitude: UniformSpec('f'), + uBumpFrequency: UniformSpec('f', 'material'), + uBumpAmplitude: UniformSpec('f', 'material'), meta: ValueSpec('unknown') }; export type TextureMeshSchema = typeof TextureMeshSchema diff --git a/src/mol-gl/renderable/util.ts b/src/mol-gl/renderable/util.ts index 87601da6a1dd366dada2eb728ad87ef75a3fb3aa..cb54c9a737dd471d6b3cf3e6b3571111618abbf6 100644 --- a/src/mol-gl/renderable/util.ts +++ b/src/mol-gl/renderable/util.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -8,6 +8,7 @@ import { Sphere3D } from '../../mol-math/geometry'; import { Vec3, Mat4 } from '../../mol-math/linear-algebra'; import { BoundaryHelper } from '../../mol-math/geometry/boundary-helper'; import { TextureFilter } from '../webgl/texture'; +import { arrayMinMax } from '../../mol-util/array'; export function calculateTextureInfo(n: number, itemSize: number) { n = Math.max(n, 2); // observed issues with 1 pixel textures @@ -42,7 +43,8 @@ export function createTextureImage<T extends Uint8Array | Float32Array>(n: numbe const DefaultPrintImageOptions = { scale: 1, pixelated: false, - id: 'molstar.debug.image' + id: 'molstar.debug.image', + normalize: false, }; export type PrintImageOptions = typeof DefaultPrintImageOptions @@ -58,7 +60,17 @@ export function printTextureImage(textureImage: TextureImage<any>, options: Part } } } else if (itemSize === 4) { - data.set(array); + if (options.normalize) { + const [min, max] = arrayMinMax(array); + for (let i = 0, il = width * height * 4; i < il; i += 4) { + data[i] = ((array[i] - min) / (max - min)) * 255; + data[i + 1] = ((array[i + 1] - min) / (max - min)) * 255; + data[i + 2] = ((array[i + 2] - min) / (max - min)) * 255; + data[i + 3] = 255; + } + } else { + data.set(array); + } } else { console.warn(`itemSize '${itemSize}' not supported`); } diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 93d7e73fd85dfb257d129112ba83102e93557052..086f6db4086bed82767ccb56c8d8bb7bec60f58a 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -2,6 +2,7 @@ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Gianluca Tomasello <giagitom@gmail.com> */ import { Viewport } from '../mol-canvas3d/camera/util'; @@ -64,12 +65,15 @@ interface Renderer { renderDepthTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderMarkingDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderMarkingMask: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void - renderBlended: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void + renderBlended: (group: Scene, camera: ICamera) => void renderBlendedOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderBlendedTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderBlendedVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderWboitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void renderWboitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void + renderDpoitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void + renderDpoitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, dpoitTextures: { depth: Texture, frontColor: Texture, backColor: Texture }) => void + renderDpoitVolume: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void setProps: (props: Partial<RendererProps>) => void setViewport: (x: number, y: number, width: number, height: number) => void @@ -89,6 +93,7 @@ export const RendererParams = { interiorColorFlag: PD.Boolean(true, { label: 'Use Interior Color' }), interiorColor: PD.Color(Color.fromNormalizedRgb(0.3, 0.3, 0.3)), + colorMarker: PD.Boolean(true, { description: 'Enable color marker' }), highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)), selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)), highlightStrength: PD.Numeric(0.3, { min: 0.0, max: 1.0, step: 0.1 }), @@ -140,7 +145,7 @@ namespace Renderer { const enum Flag { None = 0, BlendedFront = 1, - BlendedBack = 2 + BlendedBack = 2, } const enum Mask { @@ -246,6 +251,10 @@ namespace Renderer { ValueCell.update(r.values.dLightCount, light.count); definesNeedUpdate = true; } + if (r.values.dColorMarker.ref.value !== p.colorMarker) { + ValueCell.update(r.values.dColorMarker, p.colorMarker); + definesNeedUpdate = true; + } if (definesNeedUpdate) r.update(); const program = r.getProgram(variant); @@ -258,11 +267,12 @@ namespace Renderer { if (globalUniformsNeedUpdate) { // console.log('globalUniformsNeedUpdate') program.setUniforms(globalUniformList); + program.bindTextures(sharedTexturesList, 0); globalUniformsNeedUpdate = false; } if (r.values.dGeometryType.ref.value === 'directVolume') { - if (variant !== 'colorWboit' && variant !== 'colorBlended') { + if (variant !== 'colorDpoit' && variant !== 'colorWboit' && variant !== 'colorBlended') { return; // only color supported } @@ -315,7 +325,7 @@ namespace Renderer { } } - r.render(variant, sharedTexturesList); + r.render(variant, sharedTexturesList.length); }; const update = (camera: ICamera) => { @@ -353,8 +363,8 @@ namespace Renderer { state.colorMask(true, true, true, true); const { x, y, width, height } = viewport; - gl.viewport(x, y, width, height); - gl.scissor(x, y, width, height); + state.viewport(x, y, width, height); + state.scissor(x, y, width, height); globalUniformsNeedUpdate = true; state.currentRenderItemId = -1; @@ -469,9 +479,13 @@ namespace Renderer { if (isTimingMode) ctx.timer.markEnd('Renderer.renderMarkingMask'); }; - const renderBlended = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { - renderBlendedOpaque(group, camera, depthTexture); - renderBlendedTransparent(group, camera, depthTexture); + const renderBlended = (scene: Scene, camera: ICamera) => { + if (scene.hasOpaque) { + renderBlendedOpaque(scene, camera, null); + } + if (scene.opacityAverage < 1) { + renderBlendedTransparent(scene, camera, null); + } }; const renderBlendedOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { @@ -585,13 +599,78 @@ namespace Renderer { // TODO: simplify, handle in renderable.state??? // uAlpha is updated in "render" so we need to recompute it here const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); - if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dGeometryType.ref.value === 'directVolume' || r.values.dPointStyle?.ref.value === 'fuzzy' || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) { + if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dGeometryType.ref.value === 'directVolume' || r.values.dPointStyle?.ref.value === 'fuzzy' || r.values.dGeometryType.ref.value === 'text' || r.values.dXrayShaded?.ref.value) { renderObject(r, 'colorWboit', Flag.None); } } if (isTimingMode) ctx.timer.markEnd('Renderer.renderWboitTransparent'); }; + const renderDpoitOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { + if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitOpaque'); + state.disable(gl.BLEND); + state.enable(gl.DEPTH_TEST); + state.depthMask(true); + + updateInternal(group, camera, depthTexture, Mask.Opaque, false); + + const { renderables } = group; + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i]; + + // TODO: simplify, handle in renderable.state??? + // uAlpha is updated in "render" so we need to recompute it here + const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); + if ((alpha === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values.dPointStyle?.ref.value !== 'fuzzy' && !r.values.dXrayShaded?.ref.value) || r.values.dTransparentBackfaces?.ref.value === 'opaque') { + renderObject(r, 'colorDpoit', Flag.None); + } + } + if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitOpaque'); + }; + + const renderDpoitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, dpoitTextures: { depth: Texture, frontColor: Texture, backColor: Texture }) => { + if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitTransparent'); + + state.enable(gl.BLEND); + + arrayMapUpsert(sharedTexturesList, 'tDpoitDepth', dpoitTextures.depth); + arrayMapUpsert(sharedTexturesList, 'tDpoitFrontColor', dpoitTextures.frontColor); + arrayMapUpsert(sharedTexturesList, 'tDpoitBackColor', dpoitTextures.backColor); + + updateInternal(group, camera, depthTexture, Mask.Transparent, false); + + const { renderables } = group; + + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i]; + + // TODO: simplify, handle in renderable.state??? + // uAlpha is updated in "render" so we need to recompute it here + const alpha = clamp(r.values.alpha.ref.value * r.state.alphaFactor, 0, 1); + if (alpha < 1 || r.values.transparencyAverage.ref.value > 0 || r.values.dPointStyle?.ref.value === 'fuzzy' || !!r.values.uBackgroundColor || r.values.dXrayShaded?.ref.value) { + renderObject(r, 'colorDpoit', Flag.None); + } + } + if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitTransparent'); + }; + + const renderDpoitVolume = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { + if (isTimingMode) ctx.timer.mark('Renderer.renderDpoitVolume'); + state.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + state.enable(gl.BLEND); + + updateInternal(group, camera, depthTexture, Mask.Transparent, false); + + const { renderables } = group; + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i]; + if (r.values.dGeometryType.ref.value === 'directVolume') { + renderObject(r, 'colorDpoit', Flag.None); + } + } + if (isTimingMode) ctx.timer.markEnd('Renderer.renderDpoitVolume'); + }; + return { clear: (toBackgroundColor: boolean, ignoreTransparentBackground?: boolean) => { state.enable(gl.SCISSOR_TEST); @@ -635,6 +714,9 @@ namespace Renderer { renderBlendedVolume, renderWboitOpaque, renderWboitTransparent, + renderDpoitOpaque, + renderDpoitTransparent, + renderDpoitVolume, setProps: (props: Partial<RendererProps>) => { if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) { @@ -661,6 +743,9 @@ namespace Renderer { ValueCell.update(globalUniforms.uInteriorColor, Color.toVec3Normalized(globalUniforms.uInteriorColor.ref.value, p.interiorColor)); } + if (props.colorMarker !== undefined && props.colorMarker !== p.colorMarker) { + p.colorMarker = props.colorMarker; + } if (props.highlightColor !== undefined && props.highlightColor !== p.highlightColor) { p.highlightColor = props.highlightColor; ValueCell.update(globalUniforms.uHighlightColor, Color.toVec3Normalized(globalUniforms.uHighlightColor.ref.value, p.highlightColor)); @@ -705,8 +790,8 @@ namespace Renderer { } }, setViewport: (x: number, y: number, width: number, height: number) => { - gl.viewport(x, y, width, height); - gl.scissor(x, y, width, height); + state.viewport(x, y, width, height); + state.scissor(x, y, width, height); if (x !== viewport.x || y !== viewport.y || width !== viewport.width || height !== viewport.height) { Viewport.set(viewport, x, y, width, height); ValueCell.update(globalUniforms.uViewport, Vec4.set(globalUniforms.uViewport.ref.value, x, y, width, height)); @@ -749,4 +834,4 @@ namespace Renderer { } } -export { Renderer }; \ No newline at end of file +export { Renderer }; diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index 21f8cd529798cfaf739f8ae3249e5b14ab289cf7..b8a8252b941c13baff32c3951cd8b1193c1f664f 100644 --- a/src/mol-gl/scene.ts +++ b/src/mol-gl/scene.ts @@ -45,8 +45,8 @@ function calculateBoundingSphere(renderables: GraphicsRenderable[], boundingSphe } function renderableSort(a: GraphicsRenderable, b: GraphicsRenderable) { - const drawProgramIdA = (a.getProgram('colorBlended') || a.getProgram('colorWboit')).id; - const drawProgramIdB = (b.getProgram('colorBlended') || a.getProgram('colorWboit')).id; + const drawProgramIdA = (a.getProgram('colorBlended') || a.getProgram('colorWboit') || a.getProgram('colorDpoit')).id; + const drawProgramIdB = (b.getProgram('colorBlended') || b.getProgram('colorWboit') || b.getProgram('colorDpoit')).id; const materialIdA = a.materialId; const materialIdB = b.materialId; @@ -80,8 +80,12 @@ interface Scene extends Object3D { has: (o: GraphicsRenderObject) => boolean clear: () => void forEach: (callbackFn: (value: GraphicsRenderable, key: GraphicsRenderObject) => void) => void + /** Marker average of primitive renderables */ readonly markerAverage: number + /** Opacity average of primitive renderables */ readonly opacityAverage: number + /** Is `true` if any primitive renderable (possibly) has any opaque part */ + readonly hasOpaque: boolean } namespace Scene { @@ -103,6 +107,7 @@ namespace Scene { let markerAverage = 0; let opacityAverage = 0; + let hasOpaque = false; const object3d = Object3D.create(); const { view, position, direction, up } = object3d; @@ -160,7 +165,9 @@ namespace Scene { } renderables.sort(renderableSort); + markerAverage = calculateMarkerAverage(); opacityAverage = calculateOpacityAverage(); + hasOpaque = calculateHasOpaque(); return true; } @@ -182,7 +189,10 @@ namespace Scene { const newVisibleHash = computeVisibleHash(); if (newVisibleHash !== visibleHash) { boundingSphereVisibleDirty = true; + markerAverage = calculateMarkerAverage(); opacityAverage = calculateOpacityAverage(); + hasOpaque = calculateHasOpaque(); + visibleHash = newVisibleHash; return true; } else { return false; @@ -212,12 +222,27 @@ namespace Scene { // uAlpha is updated in "render" so we need to recompute it here const alpha = clamp(p.values.alpha.ref.value * p.state.alphaFactor, 0, 1); const xray = p.values.dXrayShaded?.ref.value ? 0.5 : 1; - opacityAverage += (1 - p.values.transparencyAverage.ref.value) * alpha * xray; + const fuzzy = p.values.dPointStyle?.ref.value === 'fuzzy' ? 0.5 : 1; + const text = p.values.dGeometryType.ref.value === 'text' ? 0.5 : 1; + opacityAverage += (1 - p.values.transparencyAverage.ref.value) * alpha * xray * fuzzy * text; count += 1; } return count > 0 ? opacityAverage / count : 0; } + function calculateHasOpaque() { + if (primitives.length === 0) return false; + for (let i = 0, il = primitives.length; i < il; ++i) { + const p = primitives[i]; + if (!p.state.visible) continue; + + if (p.state.opaque) return true; + if (p.state.alphaFactor === 1 && p.values.alpha.ref.value === 1 && p.values.transparencyAverage.ref.value !== 1) return true; + if (p.values.dTransparentBackfaces?.ref.value === 'opaque') return true; + } + return false; + } + return { view, position, direction, up, @@ -245,6 +270,7 @@ namespace Scene { } markerAverage = calculateMarkerAverage(); opacityAverage = calculateOpacityAverage(); + hasOpaque = calculateHasOpaque(); }, add: (o: GraphicsRenderObject) => commitQueue.add(o), remove: (o: GraphicsRenderObject) => commitQueue.remove(o), @@ -281,7 +307,6 @@ namespace Scene { if (boundingSphereVisibleDirty) { calculateBoundingSphere(renderables, boundingSphereVisible, true); boundingSphereVisibleDirty = false; - visibleHash = computeVisibleHash(); } return boundingSphereVisible; }, @@ -291,6 +316,9 @@ namespace Scene { get opacityAverage() { return opacityAverage; }, + get hasOpaque() { + return hasOpaque; + }, }; } } diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index c65f6df5dd080bcee9075dfabf0275b990f479db..baeb787af1ad4243ccc46f129181c885a9cb407d 100644 --- a/src/mol-gl/shader-code.ts +++ b/src/mol-gl/shader-code.ts @@ -67,6 +67,7 @@ import { texture3d_from_1d_trilinear } from './shader/chunks/texture3d-from-1d-t import { texture3d_from_2d_linear } from './shader/chunks/texture3d-from-2d-linear.glsl'; import { texture3d_from_2d_nearest } from './shader/chunks/texture3d-from-2d-nearest.glsl'; import { wboit_write } from './shader/chunks/wboit-write.glsl'; +import { dpoit_write } from './shader/chunks/dpoit-write.glsl'; const ShaderChunks: { [k: string]: string } = { apply_fog, @@ -99,7 +100,8 @@ const ShaderChunks: { [k: string]: string } = { texture3d_from_1d_trilinear, texture3d_from_2d_linear, texture3d_from_2d_nearest, - wboit_write + wboit_write, + dpoit_write }; const reInclude = /^(?!\/\/)\s*#include\s+(\S+)/gm; @@ -292,7 +294,9 @@ const glsl300VertPrefixCommon = ` const glsl300FragPrefixCommon = ` #define varying in #define texture2D texture +#define textureCube texture #define texture2DLodEXT textureLod +#define textureCubeLodEXT textureLod #define gl_FragColor out_FragData0 #define gl_FragDepthEXT gl_FragDepth diff --git a/src/mol-gl/shader/background.frag.ts b/src/mol-gl/shader/background.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..835314ff237890841d3d5c57ffccd73eed72911b --- /dev/null +++ b/src/mol-gl/shader/background.frag.ts @@ -0,0 +1,90 @@ +export const background_frag = ` +precision mediump float; +precision mediump samplerCube; +precision mediump sampler2D; + +#if defined(dVariant_skybox) + uniform samplerCube tSkybox; + uniform mat4 uViewDirectionProjectionInverse; + uniform float uBlur; + uniform float uOpacity; + uniform float uSaturation; + uniform float uLightness; +#elif defined(dVariant_image) + uniform sampler2D tImage; + uniform vec2 uImageScale; + uniform vec2 uImageOffset; + uniform float uOpacity; + uniform float uSaturation; + uniform float uLightness; +#elif defined(dVariant_horizontalGradient) || defined(dVariant_radialGradient) + uniform vec3 uGradientColorA; + uniform vec3 uGradientColorB; + uniform float uGradientRatio; +#endif + +uniform vec2 uTexSize; +uniform vec4 uViewport; +uniform bool uViewportAdjusted; +varying vec4 vPosition; + +// TODO: add as general pp option to remove banding? +// Iestyn's RGB dither from http://alex.vlachos.com/graphics/Alex_Vlachos_Advanced_VR_Rendering_GDC2015.pdf +vec3 ScreenSpaceDither(vec2 vScreenPos) { + vec3 vDither = vec3(dot(vec2(171.0, 231.0), vScreenPos.xy)); + vDither.rgb = fract(vDither.rgb / vec3(103.0, 71.0, 97.0)); + return vDither.rgb / 255.0; +} + +vec3 saturateColor(vec3 c, float amount) { + // https://www.w3.org/TR/WCAG21/#dfn-relative-luminance + const vec3 W = vec3(0.2125, 0.7154, 0.0721); + vec3 intensity = vec3(dot(c, W)); + return mix(intensity, c, 1.0 + amount); +} + +vec3 lightenColor(vec3 c, float amount) { + return c + amount; +} + +void main() { + #if defined(dVariant_skybox) + vec4 t = uViewDirectionProjectionInverse * vPosition; + #ifdef enabledShaderTextureLod + gl_FragColor = textureCubeLodEXT(tSkybox, normalize(t.xyz / t.w), uBlur * 8.0); + #else + gl_FragColor = textureCube(tSkybox, normalize(t.xyz / t.w)); + #endif + gl_FragColor.a = uOpacity; + gl_FragColor.rgb = lightenColor(saturateColor(gl_FragColor.rgb, uSaturation), uLightness); + #elif defined(dVariant_image) + vec2 coords; + if (uViewportAdjusted) { + coords = ((gl_FragCoord.xy - uViewport.xy) * (uTexSize / uViewport.zw) / uImageScale) + uImageOffset; + } else { + coords = (gl_FragCoord.xy / uImageScale) + uImageOffset; + } + gl_FragColor = texture2D(tImage, vec2(coords.x, 1.0 - coords.y)); + gl_FragColor.a = uOpacity; + gl_FragColor.rgb = lightenColor(saturateColor(gl_FragColor.rgb, uSaturation), uLightness); + #elif defined(dVariant_horizontalGradient) + float d; + if (uViewportAdjusted) { + d = ((gl_FragCoord.y - uViewport.y) * (uTexSize.y / uViewport.w) / uTexSize.y) + 1.0 - (uGradientRatio * 2.0); + } else { + d = (gl_FragCoord.y / uTexSize.y) + 1.0 - (uGradientRatio * 2.0); + } + gl_FragColor = vec4(mix(uGradientColorB, uGradientColorA, clamp(d, 0.0, 1.0)), 1.0); + gl_FragColor.rgb += ScreenSpaceDither(gl_FragCoord.xy); + #elif defined(dVariant_radialGradient) + float d; + if (uViewportAdjusted) { + d = distance(vec2(0.5), (gl_FragCoord.xy - uViewport.xy) * (uTexSize / uViewport.zw) / uTexSize) + uGradientRatio - 0.5; + } else { + d = distance(vec2(0.5), gl_FragCoord.xy / uTexSize) + uGradientRatio - 0.5; + } + gl_FragColor = vec4(mix(uGradientColorB, uGradientColorA, 1.0 - clamp(d, 0.0, 1.0)), 1.0); + gl_FragColor.rgb += ScreenSpaceDither(gl_FragCoord.xy); + #endif +} +`; diff --git a/src/mol-gl/shader/background.vert.ts b/src/mol-gl/shader/background.vert.ts new file mode 100644 index 0000000000000000000000000000000000000000..3f1b86fbbf861b84d577cd73c6e5e1a32908c114 --- /dev/null +++ b/src/mol-gl/shader/background.vert.ts @@ -0,0 +1,12 @@ +export const background_vert = ` +precision mediump float; + +attribute vec2 aPosition; + +varying vec4 vPosition; + +void main() { + vPosition = vec4(aPosition, 1.0, 1.0); + gl_Position = vec4(aPosition, 1.0, 1.0); +} +`; diff --git a/src/mol-gl/shader/blend-back-dpoit.frag.ts b/src/mol-gl/shader/blend-back-dpoit.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..0ee166222e68a732f88b71bba6301ecc23151721 --- /dev/null +++ b/src/mol-gl/shader/blend-back-dpoit.frag.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Gianluca Tomasello <giagitom@gmail.com> + */ + +export const blendBackDpoit_frag = ` + precision highp float; + + uniform sampler2D tDpoitBackColor; + uniform vec2 uTexSize; + + void main() { + vec2 coords = gl_FragCoord.xy / uTexSize; + gl_FragColor = texture2D(tDpoitBackColor, coords); + if (gl_FragColor.a == 0.0) { + discard; + } + } +`; diff --git a/src/mol-gl/shader/chunks/apply-fog.glsl.ts b/src/mol-gl/shader/chunks/apply-fog.glsl.ts index de2f323eddddde0d96872e7453b5bb8ada0a00ac..f1700d95bee705e85ffe98c029a0ac8cde85325b 100644 --- a/src/mol-gl/shader/chunks/apply-fog.glsl.ts +++ b/src/mol-gl/shader/chunks/apply-fog.glsl.ts @@ -12,8 +12,19 @@ if (!uTransparentBackground) { gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor); } } else { - // pre-multiplied alpha expected for transparent background - gl_FragColor.rgb *= fogAlpha; - gl_FragColor.a = fogAlpha; + #if defined(dRenderVariant_colorDpoit) + if (gl_FragColor.a < 1.0) { + // transparent objects are blended with background color + gl_FragColor.a = fogAlpha; + } else { + // opaque objects need to be pre-multiplied alpha + gl_FragColor.rgb *= fogAlpha; + gl_FragColor.a = fogAlpha; + } + #else + // pre-multiplied alpha expected for transparent background + gl_FragColor.rgb *= fogAlpha; + gl_FragColor.a = fogAlpha; + #endif } `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/apply-light-color.glsl.ts b/src/mol-gl/shader/chunks/apply-light-color.glsl.ts index 9b138b9d4f1ac7ab74a0e5a1f785905f3ffc9cf8..bddbf634b013296a098601b85406adb334e5450e 100644 --- a/src/mol-gl/shader/chunks/apply-light-color.glsl.ts +++ b/src/mol-gl/shader/chunks/apply-light-color.glsl.ts @@ -13,14 +13,7 @@ export const apply_light_color = ` #else #ifdef bumpEnabled if (uBumpFrequency > 0.0 && uBumpAmplitude > 0.0) { - vec3 bumpNormal = perturbNormal(-vViewPosition, normal, fbm(vModelPosition * uBumpFrequency), (uBumpAmplitude * bumpiness) / uBumpFrequency); - #ifdef enabledFragDepth - if (!isNaN(bumpNormal.x) && !isNaN(bumpNormal.y) && !isNaN(bumpNormal.z)) { - normal = bumpNormal; - } - #else - normal = bumpNormal; - #endif + normal = perturbNormal(-vViewPosition, normal, fbm(vModelPosition * uBumpFrequency), (uBumpAmplitude * bumpiness) / uBumpFrequency); } #endif @@ -64,6 +57,7 @@ export const apply_light_color = ` RE_IndirectSpecular_Physical(radiance, iblIrradiance, clearcoatRadiance, geometry, physicalMaterial, reflectedLight); vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular; + outgoingLight = clamp(outgoingLight, 0.01, 0.99); // prevents black artifacts on specular highlight with transparent background gl_FragColor = vec4(outgoingLight, color.a); #endif diff --git a/src/mol-gl/shader/chunks/apply-marker-color.glsl.ts b/src/mol-gl/shader/chunks/apply-marker-color.glsl.ts index 6f09848c28ae456bf73cf2d39b8895e63fa46c43..2033ca6d93b0cb5aced2ad3ff6f6dd618eb6d3a4 100644 --- a/src/mol-gl/shader/chunks/apply-marker-color.glsl.ts +++ b/src/mol-gl/shader/chunks/apply-marker-color.glsl.ts @@ -1,11 +1,13 @@ export const apply_marker_color = ` -if (marker > 0.0) { - if ((uMarkerPriority == 1 && marker != 2.0) || (uMarkerPriority != 1 && marker == 1.0)) { - gl_FragColor.rgb = mix(gl_FragColor.rgb, uHighlightColor, uHighlightStrength); - gl_FragColor.a = max(gl_FragColor.a, uHighlightStrength * 0.002); // for direct-volume rendering - } else { - gl_FragColor.rgb = mix(gl_FragColor.rgb, uSelectColor, uSelectStrength); - gl_FragColor.a = max(gl_FragColor.a, uSelectStrength * 0.002); // for direct-volume rendering +#if defined(dColorMarker) + if (marker > 0.0) { + if ((uMarkerPriority == 1 && marker != 2.0) || (uMarkerPriority != 1 && marker == 1.0)) { + gl_FragColor.rgb = mix(gl_FragColor.rgb, uHighlightColor, uHighlightStrength); + gl_FragColor.a = max(gl_FragColor.a, uHighlightStrength * 0.002); // for direct-volume rendering + } else { + gl_FragColor.rgb = mix(gl_FragColor.rgb, uSelectColor, uSelectStrength); + gl_FragColor.a = max(gl_FragColor.a, uSelectStrength * 0.002); // for direct-volume rendering + } } -} +#endif `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts b/src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts index 361ff36decaa432001944ba7a796b11c99203e02..2548b262849459b8cf038c90e2c18da5f5c6f51b 100644 --- a/src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts @@ -1,5 +1,5 @@ export const assign_marker_varying = ` -#if defined(dRenderVariant_color) || defined(dRenderVariant_marking) +#if defined(dNeedsMarker) #if defined(dMarkerType_instance) vMarker = readFromTexture(tMarker, aInstance, uMarkerTexDim).a; #elif defined(dMarkerType_groupInstance) diff --git a/src/mol-gl/shader/chunks/assign-material-color.glsl.ts b/src/mol-gl/shader/chunks/assign-material-color.glsl.ts index 0f206270a5c071ce2a00b501341690acee35b7c3..14b87d16ebd09c59fbaeb372a51c5b74564f04fc 100644 --- a/src/mol-gl/shader/chunks/assign-material-color.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-material-color.glsl.ts @@ -1,5 +1,5 @@ export const assign_material_color = ` -#if defined(dRenderVariant_color) || defined(dRenderVariant_marking) +#if defined(dNeedsMarker) float marker = uMarker; if (uMarker == -1.0) { marker = floor(vMarker * 255.0 + 0.5); // rounding required to work on some cards on win @@ -86,7 +86,7 @@ export const assign_material_color = ` // apply per-group transparency #if defined(dTransparency) && (defined(dRenderVariant_pick) || defined(dRenderVariant_color)) float ta = 1.0 - vTransparency; - if (vTransparency < 0.2) ta = 1.0; // hard cutoff looks better + if (vTransparency < 0.09) ta = 1.0; // hard cutoff looks better #if defined(dRenderVariant_pick) if (ta < uPickingAlphaThreshold) diff --git a/src/mol-gl/shader/chunks/color-frag-params.glsl.ts b/src/mol-gl/shader/chunks/color-frag-params.glsl.ts index 82d1601d20f547069991ce1670db797f7b671ba7..b5802f3688be2243b93853b1556226235d1af0bb 100644 --- a/src/mol-gl/shader/chunks/color-frag-params.glsl.ts +++ b/src/mol-gl/shader/chunks/color-frag-params.glsl.ts @@ -27,7 +27,7 @@ uniform float uBumpiness; varying vec4 vSubstance; #endif #elif defined(dRenderVariant_pick) - #if __VERSION__ == 100 + #if __VERSION__ == 100 || !defined(dVaryingGroup) #ifdef requiredDrawBuffers varying vec4 vObject; varying vec4 vInstance; diff --git a/src/mol-gl/shader/chunks/color-vert-params.glsl.ts b/src/mol-gl/shader/chunks/color-vert-params.glsl.ts index 5459d2fc31658b1eb257bb67852009c2692bea8d..30a830ad73651982363871cbb3fe8b1fcc20d431 100644 --- a/src/mol-gl/shader/chunks/color-vert-params.glsl.ts +++ b/src/mol-gl/shader/chunks/color-vert-params.glsl.ts @@ -55,7 +55,7 @@ uniform float uBumpiness; #endif #endif #elif defined(dRenderVariant_pick) - #if __VERSION__ == 100 + #if __VERSION__ == 100 || !defined(dVaryingGroup) #ifdef requiredDrawBuffers varying vec4 vObject; varying vec4 vInstance; diff --git a/src/mol-gl/shader/chunks/common-frag-params.glsl.ts b/src/mol-gl/shader/chunks/common-frag-params.glsl.ts index 877d8663619268257dc392788c63f6beb539a102..79ef4237b771648b1ddd4bd04b53d428232aae8b 100644 --- a/src/mol-gl/shader/chunks/common-frag-params.glsl.ts +++ b/src/mol-gl/shader/chunks/common-frag-params.glsl.ts @@ -14,7 +14,7 @@ uniform int uMarkingType; uniform vec3 uClipObjectScale[dClipObjectCount]; #if defined(dClipping) - #if __VERSION__ == 100 + #if __VERSION__ == 100 || defined(dClippingType_instance) || !defined(dVaryingGroup) varying float vClipping; #else flat in float vClipping; @@ -22,21 +22,29 @@ uniform int uMarkingType; #endif #endif -uniform vec3 uHighlightColor; -uniform vec3 uSelectColor; -uniform float uHighlightStrength; -uniform float uSelectStrength; -uniform int uMarkerPriority; +#if defined(dColorMarker) + uniform vec3 uHighlightColor; + uniform vec3 uSelectColor; + uniform float uHighlightStrength; + uniform float uSelectStrength; + uniform int uMarkerPriority; +#endif -#if defined(dRenderVariant_color) || defined(dRenderVariant_marking) +#if defined(dNeedsMarker) uniform float uMarker; - #if __VERSION__ == 100 + #if __VERSION__ == 100 || defined(dMarkerType_instance) || !defined(dVaryingGroup) varying float vMarker; #else flat in float vMarker; #endif #endif +#if defined(dRenderVariant_colorDpoit) + #define MAX_DPOIT_DEPTH 99999.0 // NOTE constant also set in TypeScript + uniform sampler2D tDpoitDepth; + uniform sampler2D tDpoitFrontColor; +#endif + varying vec3 vModelPosition; varying vec3 vViewPosition; diff --git a/src/mol-gl/shader/chunks/common-vert-params.glsl.ts b/src/mol-gl/shader/chunks/common-vert-params.glsl.ts index b4e3e2c50c604e079ddf150da5d4a823f276580c..4589093a99daffa056212e269b990bda4e3b3665 100644 --- a/src/mol-gl/shader/chunks/common-vert-params.glsl.ts +++ b/src/mol-gl/shader/chunks/common-vert-params.glsl.ts @@ -21,7 +21,7 @@ uniform int uPickType; #if defined(dClipping) uniform vec2 uClippingTexDim; uniform sampler2D tClipping; - #if __VERSION__ == 100 + #if __VERSION__ == 100 || defined(dClippingType_instance) || !defined(dVaryingGroup) varying float vClipping; #else flat out float vClipping; @@ -29,11 +29,11 @@ uniform int uPickType; #endif #endif -#if defined(dRenderVariant_color) || defined(dRenderVariant_marking) +#if defined(dNeedsMarker) uniform float uMarker; uniform vec2 uMarkerTexDim; uniform sampler2D tMarker; - #if __VERSION__ == 100 + #if __VERSION__ == 100 || defined(dMarkerType_instance) || !defined(dVaryingGroup) varying float vMarker; #else flat out float vMarker; @@ -44,7 +44,9 @@ varying vec3 vModelPosition; varying vec3 vViewPosition; #if defined(noNonInstancedActiveAttribs) - #define VertexID gl_VertexID + // int() is needed for some Safari versions + // see https://bugs.webkit.org/show_bug.cgi?id=244152 + #define VertexID int(gl_VertexID) #else attribute float aVertex; #define VertexID int(aVertex) diff --git a/src/mol-gl/shader/chunks/common.glsl.ts b/src/mol-gl/shader/chunks/common.glsl.ts index 785decd348bc5782b60b354ff47f474d107b1081..49eb5b9dba4027faa2382e7cde8dabdc0757a6b0 100644 --- a/src/mol-gl/shader/chunks/common.glsl.ts +++ b/src/mol-gl/shader/chunks/common.glsl.ts @@ -1,7 +1,7 @@ export const common = ` // TODO find a better place for these convenience defines -#if defined(dRenderVariant_colorBlended) || defined(dRenderVariant_colorWboit) +#if defined(dRenderVariant_colorBlended) || defined(dRenderVariant_colorWboit) || defined(dRenderVariant_colorDpoit) #define dRenderVariant_color #endif @@ -17,6 +17,10 @@ export const common = ` #define dColorType_varying #endif +#if (defined(dRenderVariant_color) && defined(dColorMarker)) || defined(dRenderVariant_marking) + #define dNeedsMarker +#endif + #define MaskAll 0 #define MaskOpaque 1 #define MaskTransparent 2 diff --git a/src/mol-gl/shader/chunks/dpoit-write.glsl.ts b/src/mol-gl/shader/chunks/dpoit-write.glsl.ts new file mode 100644 index 0000000000000000000000000000000000000000..2475d16ce86559a441669bb9b26c127dbf986480 --- /dev/null +++ b/src/mol-gl/shader/chunks/dpoit-write.glsl.ts @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Gianluca Tomasello <giagitom@gmail.com> + */ + +export const dpoit_write = ` +#if defined(dRenderVariant_colorDpoit) + if (uRenderMask == MaskOpaque) { + if (preFogAlpha < 1.0) { + discard; + } + } else if (uRenderMask == MaskTransparent) { + // the 'fragmentDepth > 0.99' check is to handle precision issues with packed depth + vec2 coords = gl_FragCoord.xy / uDrawingBufferSize; + if (preFogAlpha != 1.0 && (fragmentDepth < getDepth(coords) || fragmentDepth > 0.99)) { + #ifdef dTransparentBackfaces_off + if (interior) discard; + #endif + + // adapted from https://github.com/tsherif/webgl2examples + // The MIT License, Copyright 2017 Tarek Sherif, Shuai Shao + + vec2 lastDepth = texture2D(tDpoitDepth, coords).rg; + vec4 lastFrontColor = texture2D(tDpoitFrontColor, coords); + + vec4 fragColor = gl_FragColor; + + // depth value always increases + // so we can use MAX blend equation + gl_FragData[2].rg = vec2(-MAX_DPOIT_DEPTH); + + // front color always increases + // so we can use MAX blend equation + gl_FragColor = lastFrontColor; + + // back color is separately blend afterwards each pass + gl_FragData[1] = vec4(0.0); + + float nearestDepth = -lastDepth.x; + float furthestDepth = lastDepth.y; + float alphaMultiplier = 1.0 - lastFrontColor.a; + + if (fragmentDepth < nearestDepth || fragmentDepth > furthestDepth) { + // Skip this depth since it's been peeled. + return; + } + + if (fragmentDepth > nearestDepth && fragmentDepth < furthestDepth) { + // This needs to be peeled. + // The ones remaining after MAX blended for + // all need-to-peel will be peeled next pass. + gl_FragData[2].rg = vec2(-fragmentDepth, fragmentDepth); + return; + } + + // write to back and front color buffer + if (fragmentDepth == nearestDepth) { + gl_FragColor.rgb += fragColor.rgb * fragColor.a * alphaMultiplier; + gl_FragColor.a = 1.0 - alphaMultiplier * (1.0 - fragColor.a); + } else { + gl_FragData[1] += fragColor; + } + + } else { + discard; + } + } +#endif +`; diff --git a/src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts b/src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts index e6c36a65853f3fca7f9f983d2c03ceccf0444179..bec192c1990360a07674dc2bb26d10e6711bd4f1 100644 --- a/src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts +++ b/src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -10,7 +10,6 @@ precision highp float; #include common #include read_from_texture -uniform int uTotalCount; uniform int uGroupCount; attribute float aSample; diff --git a/src/mol-gl/shader/cylinders.frag.ts b/src/mol-gl/shader/cylinders.frag.ts index 82c0f8ca738db0fbc2be2ef44cbb9b146e294173..ba17705d7c67d751e744a3f70be36695d6087269 100644 --- a/src/mol-gl/shader/cylinders.frag.ts +++ b/src/mol-gl/shader/cylinders.frag.ts @@ -109,14 +109,14 @@ void main() { vec3 vViewPosition = vModelPosition + intersection.x * rayDir; vViewPosition = (uView * vec4(vViewPosition, 1.0)).xyz; - gl_FragDepthEXT = calcDepth(vViewPosition); + float fragmentDepth = calcDepth(vViewPosition); - vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz; + if (fragmentDepth < 0.0) discard; + if (fragmentDepth > 1.0) discard; - if (gl_FragDepthEXT < 0.0) discard; - if (gl_FragDepthEXT > 1.0) discard; + gl_FragDepthEXT = fragmentDepth; - float fragmentDepth = gl_FragDepthEXT; + vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz; #include assign_material_color #if defined(dRenderVariant_pick) @@ -142,6 +142,7 @@ void main() { #include apply_marker_color #include apply_fog #include wboit_write + #include dpoit_write #endif } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/direct-volume.frag.ts b/src/mol-gl/shader/direct-volume.frag.ts index a6e73a7f3d958ff3b0c192e9444a8bcd14e7ebe5..1b15f590f999549bfda4c25efda6a566a891ccbb 100644 --- a/src/mol-gl/shader/direct-volume.frag.ts +++ b/src/mol-gl/shader/direct-volume.frag.ts @@ -50,15 +50,17 @@ uniform int uVertexCount; uniform int uInstanceCount; uniform int uGroupCount; -uniform vec3 uHighlightColor; -uniform vec3 uSelectColor; -uniform float uHighlightStrength; -uniform float uSelectStrength; -uniform int uMarkerPriority; - -uniform float uMarker; -uniform vec2 uMarkerTexDim; -uniform sampler2D tMarker; +#if defined(dColorMarker) + uniform vec3 uHighlightColor; + uniform vec3 uSelectColor; + uniform float uHighlightStrength; + uniform float uSelectStrength; + uniform int uMarkerPriority; + + uniform float uMarker; + uniform vec2 uMarkerTexDim; + uniform sampler2D tMarker; +#endif uniform float uMetalness; uniform float uRoughness; @@ -304,11 +306,13 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) { gl_FragColor.a = material.a * uAlpha * uTransferScale; - float marker = uMarker; - if (uMarker == -1.0) { - marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; - marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win - } + #if defined(dColorMarker) + float marker = uMarker; + if (uMarker == -1.0) { + marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; + marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win + } + #endif #include apply_marker_color preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended; @@ -352,4 +356,4 @@ void main() { float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0); #include wboit_write } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/evaluate-dpoit.frag.ts b/src/mol-gl/shader/evaluate-dpoit.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b6d6f30da9bcac3bc527b871f056f21f314c662 --- /dev/null +++ b/src/mol-gl/shader/evaluate-dpoit.frag.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Gianluca Tomasello <giagitom@gmail.com> + */ + +export const evaluateDpoit_frag = ` +precision highp float; + +uniform sampler2D tDpoitFrontColor; +uniform vec2 uTexSize; + +void main() { + vec2 coords = gl_FragCoord.xy / uTexSize; + gl_FragColor = texture2D(tDpoitFrontColor, coords); +} +`; diff --git a/src/mol-gl/shader/image.frag.ts b/src/mol-gl/shader/image.frag.ts index 39c8835d8c94aee896e484fdbc9149121939f974..b8a147fc13219f7c346f138fd4c9b15d4bd2da99 100644 --- a/src/mol-gl/shader/image.frag.ts +++ b/src/mol-gl/shader/image.frag.ts @@ -159,6 +159,7 @@ void main() { #include apply_marker_color #include apply_fog #include wboit_write + #include dpoit_write #endif } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/lines.frag.ts b/src/mol-gl/shader/lines.frag.ts index fa62dbf5559b323f98e4e21e1e51d865e6ce900b..1639ae79df6e9d70a78b28a29515a77d7c00bfe5 100644 --- a/src/mol-gl/shader/lines.frag.ts +++ b/src/mol-gl/shader/lines.frag.ts @@ -39,6 +39,7 @@ void main(){ #include apply_marker_color #include apply_fog #include wboit_write + #include dpoit_write #endif } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/marching-cubes/isosurface.frag.ts b/src/mol-gl/shader/marching-cubes/isosurface.frag.ts index ac7f0cf3d8fc48321804bb1a4d99ea23eb4a46b7..964bfdf1753935172219deb41d2593b7479d5c00 100644 --- a/src/mol-gl/shader/marching-cubes/isosurface.frag.ts +++ b/src/mol-gl/shader/marching-cubes/isosurface.frag.ts @@ -268,9 +268,9 @@ void main(void) { gl_FragData[0].xyz = (uGridTransform * vec4(b0 + t * (b0 - b1), 1.0)).xyz; // group id - #if __VERSION__ == 100 + #if __VERSION__ == 100 || defined(dConstantGroup) // webgl1 does not support 'flat' interpolation (i.e. no interpolation) - // so we ensure a constant group id per triangle here + // ensure a constant group id per triangle as needed #ifdef dPackedGroup gl_FragData[1] = vec4(voxel(coord3d).rgb, 1.0); #else diff --git a/src/mol-gl/shader/mesh.frag.ts b/src/mol-gl/shader/mesh.frag.ts index e49049f60a8f4ddf845476d55321c3bc6aed8f83..98d0ca5445342517fd716e1030df8db3fc2c3960 100644 --- a/src/mol-gl/shader/mesh.frag.ts +++ b/src/mol-gl/shader/mesh.frag.ts @@ -62,6 +62,7 @@ void main() { #include apply_marker_color #include apply_fog #include wboit_write + #include dpoit_write #endif } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/points.frag.ts b/src/mol-gl/shader/points.frag.ts index 299779b299cbb4049a0b8452c9ae53049c4f9e0a..84001a3d76f59427e3811a227ad0a877e0eb13e4 100644 --- a/src/mol-gl/shader/points.frag.ts +++ b/src/mol-gl/shader/points.frag.ts @@ -55,6 +55,7 @@ void main(){ #include apply_marker_color #include apply_fog #include wboit_write + #include dpoit_write #endif } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/spheres.frag.ts b/src/mol-gl/shader/spheres.frag.ts index e4f2e39e81a8dc4c53bdc6b2d99d285360e936a5..9b3def951a34d227d9211ed14fa835b9aefc5fde 100644 --- a/src/mol-gl/shader/spheres.frag.ts +++ b/src/mol-gl/shader/spheres.frag.ts @@ -48,7 +48,6 @@ bool Impostor(out vec3 cameraPos, out vec3 cameraNormal){ cameraPos = rayDirection * negT + rayOrigin; - if (calcDepth(cameraPos) <= 0.0) { cameraPos = rayDirection * posT + rayOrigin; interior = true; @@ -71,17 +70,17 @@ void main(void){ } vec3 vViewPosition = cameraPos; - gl_FragDepthEXT = calcDepth(vViewPosition); - if (!flag && gl_FragDepthEXT >= 0.0) { - gl_FragDepthEXT = 0.0 + (0.0000001 / vRadius); + float fragmentDepth = calcDepth(vViewPosition); + if (!flag && fragmentDepth >= 0.0) { + fragmentDepth = 0.0 + (0.0000001 / vRadius); } - vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz; + if (fragmentDepth < 0.0) discard; + if (fragmentDepth > 1.0) discard; - if (gl_FragDepthEXT < 0.0) discard; - if (gl_FragDepthEXT > 1.0) discard; + gl_FragDepthEXT = fragmentDepth; - float fragmentDepth = gl_FragDepthEXT; + vec3 vModelPosition = (uInvView * vec4(vViewPosition, 1.0)).xyz; #include assign_material_color #if defined(dRenderVariant_pick) @@ -106,6 +105,7 @@ void main(void){ #include apply_marker_color #include apply_fog #include wboit_write + #include dpoit_write #endif } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/shader/text.frag.ts b/src/mol-gl/shader/text.frag.ts index ad46e8f3a62a4694cebf872d63f3cc16d27687ba..4e141f1293c1b9933299ac5a0b78d4deb4300546 100644 --- a/src/mol-gl/shader/text.frag.ts +++ b/src/mol-gl/shader/text.frag.ts @@ -83,6 +83,7 @@ void main(){ #include apply_marker_color #include apply_fog #include wboit_write + #include dpoit_write #endif } -`; \ No newline at end of file +`; diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index c3cd962a4f42716371db39688dcd4c66cd46ac22..f8d399ae8c71206870a3429fea2b790538f4d96a 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -142,12 +142,12 @@ export function readPixels(gl: GLRenderingContext, x: number, y: number, width: if (isDebugMode) checkError(gl); } -function getDrawingBufferPixelData(gl: GLRenderingContext) { +function getDrawingBufferPixelData(gl: GLRenderingContext, state: WebGLState) { const w = gl.drawingBufferWidth; const h = gl.drawingBufferHeight; const buffer = new Uint8Array(w * h * 4); unbindFramebuffer(gl); - gl.viewport(0, 0, w, h); + state.viewport(0, 0, w, h); readPixels(gl, 0, 0, w, h, buffer); return PixelData.flipY(PixelData.create(buffer, w, h)); } @@ -164,6 +164,7 @@ function createStats() { renderbuffer: 0, shader: 0, texture: 0, + cubeTexture: 0, vertexArray: 0, }, @@ -345,15 +346,15 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal readPixelsAsync, waitForGpuCommandsComplete: () => waitForGpuCommandsComplete(gl), waitForGpuCommandsCompleteSync: () => waitForGpuCommandsCompleteSync(gl), - getDrawingBufferPixelData: () => getDrawingBufferPixelData(gl), + getDrawingBufferPixelData: () => getDrawingBufferPixelData(gl, state), clear: (red: number, green: number, blue: number, alpha: number) => { unbindFramebuffer(gl); state.enable(gl.SCISSOR_TEST); state.depthMask(true); state.colorMask(true, true, true, true); state.clearColor(red, green, blue, alpha); - gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); - gl.scissor(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + state.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); + state.scissor(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); }, diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts index f25090291d9a891331e313133817586396949d7a..47d62df93bc773b8bdae262181e4254eac6c505e 100644 --- a/src/mol-gl/webgl/render-item.ts +++ b/src/mol-gl/webgl/render-item.ts @@ -1,11 +1,12 @@ /** - * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Gianluca Tomasello <giagitom@gmail.com> */ import { createAttributeBuffers, ElementsBuffer, AttributeKind } from './buffer'; -import { createTextures, Texture, Textures } from './texture'; +import { createTextures, Texture } from './texture'; import { WebGLContext, checkError } from './context'; import { ShaderCode, DefineValues } from '../shader-code'; import { Program } from './program'; @@ -42,18 +43,19 @@ export interface RenderItem<T extends string> { readonly materialId: number getProgram: (variant: T) => Program - render: (variant: T, sharedTexturesList?: Textures) => void + render: (variant: T, sharedTexturesCount: number) => void update: () => Readonly<ValueChanges> destroy: () => void } // -const GraphicsRenderVariant = { colorBlended: '', colorWboit: '', pick: '', depth: '', marking: '' }; +const GraphicsRenderVariant = { colorBlended: '', colorWboit: '', colorDpoit: '', pick: '', depth: '', marking: '' }; export type GraphicsRenderVariant = keyof typeof GraphicsRenderVariant export const GraphicsRenderVariants = Object.keys(GraphicsRenderVariant) as GraphicsRenderVariant[]; -export const GraphicsRenderVariantsBlended = GraphicsRenderVariants.filter(v => v !== 'colorWboit'); -export const GraphicsRenderVariantsWboit = GraphicsRenderVariants.filter(v => v !== 'colorBlended'); +export const GraphicsRenderVariantsBlended = GraphicsRenderVariants.filter(v => !['colorWboit', 'colorDpoit'].includes(v)); +export const GraphicsRenderVariantsWboit = GraphicsRenderVariants.filter(v => !['colorBlended', 'colorDpoit'].includes(v)); +export const GraphicsRenderVariantsDpoit = GraphicsRenderVariants.filter(v => !['colorWboit', 'colorBlended'].includes(v)); const ComputeRenderVariant = { compute: '' }; export type ComputeRenderVariant = keyof typeof ComputeRenderVariant @@ -118,7 +120,7 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode: (schema as any).aVertex = AttributeSpec('float32', 1, 0); } - const { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues, bufferedUniformValues } = splitValues(schema, values); + const { attributeValues, defineValues, textureValues, materialTextureValues, uniformValues, materialUniformValues, bufferedUniformValues } = splitValues(schema, values); const uniformValueEntries = Object.entries(uniformValues); const materialUniformValueEntries = Object.entries(materialUniformValues); @@ -136,6 +138,7 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode: } const textures = createTextures(ctx, schema, textureValues); + const materialTextures = createTextures(ctx, schema, materialTextureValues); const attributeBuffers = createAttributeBuffers(ctx, schema, attributeValues); let elementsBuffer: ElementsBuffer | undefined; @@ -149,8 +152,8 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode: vertexArrays[k] = vertexArrayObject ? resources.vertexArray(programs[k], attributeBuffers, elementsBuffer) : null; } - let drawCount = values.drawCount.ref.value; - let instanceCount = values.instanceCount.ref.value; + let drawCount: number = values.drawCount.ref.value; + let instanceCount: number = values.instanceCount.ref.value; stats.drawCount += drawCount; stats.instanceCount += instanceCount; @@ -166,17 +169,12 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode: materialId, getProgram: (variant: T) => programs[variant], - render: (variant: T, sharedTexturesList?: Textures) => { - if (drawCount === 0 || instanceCount === 0 || ctx.isContextLost) return; + render: (variant: T, sharedTexturesCount: number) => { + if (drawCount === 0 || instanceCount === 0) return; const program = programs[variant]; if (program.id === currentProgramId && state.currentRenderItemId === id) { program.setUniforms(uniformValueEntries); - if (sharedTexturesList && sharedTexturesList.length > 0) { - program.bindTextures(sharedTexturesList, 0); - program.bindTextures(textures, sharedTexturesList.length); - } else { - program.bindTextures(textures, 0); - } + program.bindTextures(textures, sharedTexturesCount); } else { const vertexArray = vertexArrays[variant]; if (program.id !== state.currentProgramId || program.id !== currentProgramId || @@ -185,17 +183,13 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode: // console.log('program.id changed or materialId changed/-1', materialId) if (program.id !== state.currentProgramId) program.use(); program.setUniforms(materialUniformValueEntries); + program.bindTextures(materialTextures, sharedTexturesCount + textures.length); state.currentMaterialId = materialId; currentProgramId = program.id; } program.setUniforms(uniformValueEntries); program.setUniforms(frontBufferUniformValueEntries); - if (sharedTexturesList && sharedTexturesList.length > 0) { - program.bindTextures(sharedTexturesList, 0); - program.bindTextures(textures, sharedTexturesList.length); - } else { - program.bindTextures(textures, 0); - } + program.bindTextures(textures, sharedTexturesCount); if (vertexArray) { vertexArray.bind(); // need to bind elements buffer explicitly since it is not always recorded in the VAO @@ -324,6 +318,22 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode: } } + for (let i = 0, il = materialTextures.length; i < il; ++i) { + const [k, texture] = materialTextures[i]; + const value = materialTextureValues[k]; + if (value.ref.version !== versions[k]) { + // update of textures with kind 'texture' is done externally + if (schema[k].kind !== 'texture') { + // console.log('texture version changed, uploading image', k); + texture.load(value.ref.value as TextureImage<any> | TextureVolume<any>); + valueChanges.textures = true; + } else { + materialTextures[i][1] = value.ref.value as Texture; + } + versions[k] = value.ref.version; + } + } + for (let i = 0, il = backBufferUniformValueEntries.length; i < il; ++i) { const [k, uniform] = backBufferUniformValueEntries[i]; if (uniform.ref.version !== versions[k]) { @@ -359,4 +369,4 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode: } } }; -} \ No newline at end of file +} diff --git a/src/mol-gl/webgl/resources.ts b/src/mol-gl/webgl/resources.ts index 4dd175cc7788bfdffd80614b8a56add7d2f7a336..3e2e2d370bfc771499cf8b5b7641f5ab25b2ce52 100644 --- a/src/mol-gl/webgl/resources.ts +++ b/src/mol-gl/webgl/resources.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -17,7 +17,7 @@ import { hashString, hashFnv32a } from '../../mol-data/util'; import { DefineValues, ShaderCode } from '../shader-code'; import { RenderableSchema } from '../renderable/schema'; import { createRenderbuffer, Renderbuffer, RenderbufferAttachment, RenderbufferFormat } from './renderbuffer'; -import { Texture, TextureKind, TextureFormat, TextureType, TextureFilter, createTexture } from './texture'; +import { Texture, TextureKind, TextureFormat, TextureType, TextureFilter, createTexture, CubeFaces, createCubeTexture } from './texture'; import { VertexArray, createVertexArray } from './vertex-array'; function defineValueHash(v: boolean | number | string): number { @@ -59,6 +59,7 @@ export interface WebGLResources { renderbuffer: (format: RenderbufferFormat, attachment: RenderbufferAttachment, width: number, height: number) => Renderbuffer shader: (type: ShaderType, source: string) => Shader texture: (kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter) => Texture, + cubeTexture: (faces: CubeFaces, mipaps: boolean, onload?: () => void) => Texture, vertexArray: (program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer) => VertexArray, getByteCounts: () => ByteCounts @@ -76,6 +77,7 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats renderbuffer: new Set<Resource>(), shader: new Set<Resource>(), texture: new Set<Resource>(), + cubeTexture: new Set<Resource>(), vertexArray: new Set<Resource>(), }; @@ -137,6 +139,9 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats texture: (kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter) => { return wrap('texture', createTexture(gl, extensions, kind, format, type, filter)); }, + cubeTexture: (faces: CubeFaces, mipmaps: boolean, onload?: () => void) => { + return wrap('cubeTexture', createCubeTexture(gl, faces, mipmaps, onload)); + }, vertexArray: (program: Program, attributeBuffers: AttributeBuffers, elementsBuffer?: ElementsBuffer) => { return wrap('vertexArray', createVertexArray(gl, extensions, program, attributeBuffers, elementsBuffer)); }, @@ -146,6 +151,9 @@ export function createResources(gl: GLRenderingContext, state: WebGLState, stats sets.texture.forEach(r => { texture += (r as Texture).getByteCount(); }); + sets.cubeTexture.forEach(r => { + texture += (r as Texture).getByteCount(); + }); let attribute = 0; sets.attribute.forEach(r => { diff --git a/src/mol-gl/webgl/state.ts b/src/mol-gl/webgl/state.ts index d84c91bc8fd48ed129b996d74dfdada51f7b958c..dc6184d7e894b8d274a4c111a1fc8355436b1e88 100644 --- a/src/mol-gl/webgl/state.ts +++ b/src/mol-gl/webgl/state.ts @@ -69,6 +69,9 @@ export type WebGLState = { clearVertexAttribsState: () => void disableUnusedVertexAttribs: () => void + viewport: (x: number, y: number, width: number, height: number) => void + scissor: (x: number, y: number, width: number, height: number) => void + reset: () => void } @@ -95,6 +98,9 @@ export function createState(gl: GLRenderingContext): WebGLState { let maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); const vertexAttribsState: number[] = []; + let currentViewport: [number, number, number, number] = gl.getParameter(gl.VIEWPORT); + let currentScissor: [number, number, number, number] = gl.getParameter(gl.SCISSOR_BOX); + const clearVertexAttribsState = () => { for (let i = 0; i < maxVertexAttribs; ++i) { vertexAttribsState[i] = 0; @@ -222,6 +228,26 @@ export function createState(gl: GLRenderingContext): WebGLState { } }, + viewport: (x: number, y: number, width: number, height: number) => { + if (x !== currentViewport[0] || y !== currentViewport[1] || width !== currentViewport[2] || height !== currentViewport[3]) { + gl.viewport(x, y, width, height); + currentViewport[0] = x; + currentViewport[1] = y; + currentViewport[2] = width; + currentViewport[3] = height; + } + }, + + scissor: (x: number, y: number, width: number, height: number) => { + if (x !== currentScissor[0] || y !== currentScissor[1] || width !== currentScissor[2] || height !== currentScissor[3]) { + gl.scissor(x, y, width, height); + currentScissor[0] = x; + currentScissor[1] = y; + currentScissor[2] = width; + currentScissor[3] = height; + } + }, + reset: () => { enabledCapabilities = {}; @@ -247,6 +273,9 @@ export function createState(gl: GLRenderingContext): WebGLState { for (let i = 0; i < maxVertexAttribs; ++i) { vertexAttribsState[i] = 0; } + + currentViewport = gl.getParameter(gl.VIEWPORT); + currentScissor = gl.getParameter(gl.SCISSOR_BOX); } }; } \ No newline at end of file diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index 3966a268d5d2d96cc7abbc94c18013523b86e617..d12c95d1c5ad65d52202c3ca14470c8931ac8603 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/src/mol-gl/webgl/texture.ts @@ -2,6 +2,7 @@ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Gianluca Tomasello <giagitom@gmail.com> */ import { WebGLContext } from './context'; @@ -11,8 +12,9 @@ import { RenderableSchema } from '../renderable/schema'; import { idFactory } from '../../mol-util/id-factory'; import { Framebuffer } from './framebuffer'; import { isWebGL2, GLRenderingContext } from './compat'; -import { ValueOf } from '../../mol-util/type-helpers'; +import { isPromiseLike, ValueOf } from '../../mol-util/type-helpers'; import { WebGLExtensions } from './extensions'; +import { objectForEach } from '../../mol-util/object'; const getNextTextureId = idFactory(); @@ -30,7 +32,7 @@ export type TextureKindValue = { export type TextureValueType = ValueOf<TextureKindValue> export type TextureKind = keyof TextureKindValue export type TextureType = 'ubyte' | 'ushort' | 'float' | 'fp16' | 'int' -export type TextureFormat = 'alpha' | 'rgb' | 'rgba' | 'depth' +export type TextureFormat = 'alpha' | 'rg' | 'rgb' | 'rgba' | 'depth' /** Numbers are shortcuts for color attachment */ export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 export type TextureFilter = 'nearest' | 'linear' @@ -62,6 +64,10 @@ export function getFormat(gl: GLRenderingContext, format: TextureFormat, type: T case 'rgb': if (isWebGL2(gl) && type === 'int') return gl.RGB_INTEGER; return gl.RGB; + case 'rg': + if (isWebGL2(gl) && type === 'float') return gl.RG; + else if (isWebGL2(gl) && type === 'int') return gl.RG_INTEGER; + else throw new Error('texture format "rg" requires webgl2 and type "float" or int"'); case 'rgba': if (isWebGL2(gl) && type === 'int') return gl.RGBA_INTEGER; return gl.RGBA; @@ -79,6 +85,13 @@ export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat, case 'fp16': return gl.R16F; case 'int': return gl.R32I; } + case 'rg': + switch (type) { + case 'ubyte': return gl.RG; + case 'float': return gl.RG32F; + case 'fp16': return gl.RG16F; + case 'int': return gl.RG32I; + } case 'rgb': switch (type) { case 'ubyte': return gl.RGB; @@ -111,6 +124,7 @@ function getByteCount(format: TextureFormat, type: TextureType, width: number, h function getFormatSize(format: TextureFormat) { switch (format) { case 'alpha': return 1; + case 'rg': return 2; case 'rgb': return 3; case 'rgba': return 4; case 'depth': return 4; @@ -423,6 +437,123 @@ export function loadImageTexture(src: string, cell: ValueCell<Texture>, texture: // +export type CubeSide = 'nx' | 'ny' | 'nz' | 'px' | 'py' | 'pz'; + +export type CubeFaces = { + [k in CubeSide]: string | File | Promise<Blob>; +} + +export function getCubeTarget(gl: GLRenderingContext, side: CubeSide): number { + switch (side) { + case 'nx': return gl.TEXTURE_CUBE_MAP_NEGATIVE_X; + case 'ny': return gl.TEXTURE_CUBE_MAP_NEGATIVE_Y; + case 'nz': return gl.TEXTURE_CUBE_MAP_NEGATIVE_Z; + case 'px': return gl.TEXTURE_CUBE_MAP_POSITIVE_X; + case 'py': return gl.TEXTURE_CUBE_MAP_POSITIVE_Y; + case 'pz': return gl.TEXTURE_CUBE_MAP_POSITIVE_Z; + } +} + +export function createCubeTexture(gl: GLRenderingContext, faces: CubeFaces, mipmaps: boolean, onload?: (errored?: boolean) => void): Texture { + const target = gl.TEXTURE_CUBE_MAP; + const filter = gl.LINEAR; + const internalFormat = gl.RGBA; + const format = gl.RGBA; + const type = gl.UNSIGNED_BYTE; + + let size = 0; + + const texture = gl.createTexture(); + gl.bindTexture(target, texture); + + let loadedCount = 0; + objectForEach(faces, (source, side) => { + if (!source) return; + + const level = 0; + const cubeTarget = getCubeTarget(gl, side as CubeSide); + + const image = new Image(); + if (source instanceof File) { + image.src = URL.createObjectURL(source); + } else if (isPromiseLike(source)) { + source.then(blob => { + image.src = URL.createObjectURL(blob); + }); + } else { + image.src = source; + } + image.addEventListener('load', () => { + if (size === 0) size = image.width; + + gl.texImage2D(cubeTarget, level, internalFormat, size, size, 0, format, type, null); + gl.pixelStorei(gl.UNPACK_ALIGNMENT, 4); + gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE); + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + gl.bindTexture(target, texture); + gl.texImage2D(cubeTarget, level, internalFormat, format, type, image); + + loadedCount += 1; + if (loadedCount === 6) { + if (!destroyed) { + if (mipmaps) { + gl.generateMipmap(target); + gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR); + } else { + gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, filter); + } + gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, filter); + } + onload?.(destroyed); + } + }); + image.addEventListener('error', () => { + onload?.(true); + }); + }); + + let destroyed = false; + + return { + id: getNextTextureId(), + target, + format, + internalFormat, + type, + filter, + + getWidth: () => size, + getHeight: () => size, + getDepth: () => 0, + getByteCount: () => { + return getByteCount('rgba', 'ubyte', size, size, 0) * 6 * (mipmaps ? 2 : 1); + }, + + define: () => {}, + load: () => {}, + bind: (id: TextureId) => { + gl.activeTexture(gl.TEXTURE0 + id); + gl.bindTexture(target, texture); + }, + unbind: (id: TextureId) => { + gl.activeTexture(gl.TEXTURE0 + id); + gl.bindTexture(target, null); + }, + attachFramebuffer: () => {}, + detachFramebuffer: () => {}, + + reset: () => {}, + destroy: () => { + if (destroyed) return; + gl.deleteTexture(texture); + destroyed = true; + }, + }; +} + +// + export function createNullTexture(gl?: GLRenderingContext): Texture { const target = gl?.TEXTURE_2D ?? 3553; return { diff --git a/src/mol-io/common/binary-cif/array-encoder.ts b/src/mol-io/common/binary-cif/array-encoder.ts index 08dfee8f6d315a0c995cdc0939f7d76648714365..7ca14219f7777ca448e5b5ae04615e7f07205023 100644 --- a/src/mol-io/common/binary-cif/array-encoder.ts +++ b/src/mol-io/common/binary-cif/array-encoder.ts @@ -264,28 +264,35 @@ export namespace ArrayEncoding { return false; } - function packingSize(data: Int32Array, upperLimit: number) { + function packingSizeUnsigned(data: Int32Array, upperLimit: number) { + let size = 0; + for (let i = 0, n = data.length; i < n; i++) { + size += (data[i] / upperLimit) | 0; + } + size += data.length; + return size; + } + + + function packingSizeSigned(data: Int32Array, upperLimit: number) { const lowerLimit = -upperLimit - 1; let size = 0; for (let i = 0, n = data.length; i < n; i++) { const value = data[i]; - if (value === 0) { - size += 1; - } else if (value > 0) { - size += Math.ceil(value / upperLimit); - if (value % upperLimit === 0) size += 1; + if (value >= 0) { + size += (value / upperLimit) | 0; } else { - size += Math.ceil(value / lowerLimit); - if (value % lowerLimit === 0) size += 1; + size += (value / lowerLimit) | 0; } } + size += data.length; return size; } function determinePacking(data: Int32Array): { isSigned: boolean, size: number, bytesPerElement: number } { const signed = isSigned(data); - const size8 = signed ? packingSize(data, 0x7F) : packingSize(data, 0xFF); - const size16 = signed ? packingSize(data, 0x7FFF) : packingSize(data, 0xFFFF); + const size8 = signed ? packingSizeSigned(data, 0x7F) : packingSizeUnsigned(data, 0xFF); + const size16 = signed ? packingSizeSigned(data, 0x7FFF) : packingSizeUnsigned(data, 0xFFFF); if (data.length * 4 < size16 * 2) { // 4 byte packing is the most effective diff --git a/src/mol-io/reader/cif/schema/bird.ts b/src/mol-io/reader/cif/schema/bird.ts index 5d88c181affb0c14c762e23046174d3a1078b3ae..882751de4acff8ebed74b3e426dbc4f59ee924d0 100644 --- a/src/mol-io/reader/cif/schema/bird.ts +++ b/src/mol-io/reader/cif/schema/bird.ts @@ -1,7 +1,7 @@ /** * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * - * Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.359, IHM 1.17, MA 1.4.1. + * Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.362, IHM 1.17, MA 1.4.3. * * @author molstar/ciftools package */ diff --git a/src/mol-io/reader/cif/schema/ccd.ts b/src/mol-io/reader/cif/schema/ccd.ts index 86ea246065cc2bb1cbdf248cb1f815311648be8f..abc49e51dc51aaf171cb31f71ae68ca4197c3d2b 100644 --- a/src/mol-io/reader/cif/schema/ccd.ts +++ b/src/mol-io/reader/cif/schema/ccd.ts @@ -1,7 +1,7 @@ /** * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * - * Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.359, IHM 1.17, MA 1.4.1. + * Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.362, IHM 1.17, MA 1.4.3. * * @author molstar/ciftools package */ diff --git a/src/mol-io/reader/cif/schema/mmcif.ts b/src/mol-io/reader/cif/schema/mmcif.ts index 51d060ddc0fd8cf5076fdcaac37712256978edbd..e39a178b6efb373b97f356c6f95dd134a97f6367 100644 --- a/src/mol-io/reader/cif/schema/mmcif.ts +++ b/src/mol-io/reader/cif/schema/mmcif.ts @@ -1,7 +1,7 @@ /** * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * - * Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.359, IHM 1.17, MA 1.4.1. + * Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.362, IHM 1.17, MA 1.4.3. * * @author molstar/ciftools package */ diff --git a/src/mol-math/geometry/_spec/lookup3d.spec.ts b/src/mol-math/geometry/_spec/lookup3d.spec.ts index 1641a2c6e85a431a265c4d8708baf453976b872a..9b0fe2d35f617048544926f20f6e4e61416aa308 100644 --- a/src/mol-math/geometry/_spec/lookup3d.spec.ts +++ b/src/mol-math/geometry/_spec/lookup3d.spec.ts @@ -2,6 +2,7 @@ * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Gianluca Tomasello <giagitom@gmail.com> */ import { GridLookup3D } from '../../geometry'; @@ -24,9 +25,17 @@ describe('GridLookup3d', () => { expect(r.count).toBe(1); expect(r.indices[0]).toBe(0); + r = grid.nearest(0, 0, 0, 1); + expect(r.count).toBe(1); + expect(r.indices[0]).toBe(0); + r = grid.find(0, 0, 0, 1); expect(r.count).toBe(3); expect(sortArray(r.indices)).toEqual([0, 1, 2]); + + r = grid.nearest(0, 0, 0, 3); + expect(r.count).toBe(3); + expect(sortArray(r.indices)).toEqual([0, 1, 2]); }); it('radius', () => { @@ -38,9 +47,17 @@ describe('GridLookup3d', () => { expect(r.count).toBe(1); expect(r.indices[0]).toBe(0); + r = grid.nearest(0, 0, 0, 1); + expect(r.count).toBe(1); + expect(r.indices[0]).toBe(0); + r = grid.find(0, 0, 0, 0.5); expect(r.count).toBe(2); expect(sortArray(r.indices)).toEqual([0, 1]); + + r = grid.nearest(0, 0, 0, 3); + expect(r.count).toBe(3); + expect(sortArray(r.indices)).toEqual([0, 1, 2]); }); it('indexed', () => { @@ -51,8 +68,15 @@ describe('GridLookup3d', () => { let r = grid.find(0, 0, 0, 0); expect(r.count).toBe(0); + r = grid.nearest(0, 0, 0, 1); + expect(r.count).toBe(1); + r = grid.find(0, 0, 0, 0.5); expect(r.count).toBe(1); expect(sortArray(r.indices)).toEqual([0]); + + r = grid.nearest(0, 0, 0, 3); + expect(r.count).toBe(1); + expect(sortArray(r.indices)).toEqual([0]); }); -}); \ No newline at end of file +}); diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts index 23f7612d6b62f0ff35760f54b093e03ab706a828..a9621f6e21d8c74371fec6a6a7d03d28009315ae 100644 --- a/src/mol-math/geometry/gaussian-density/gpu.ts +++ b/src/mol-math/geometry/gaussian-density/gpu.ts @@ -42,7 +42,7 @@ const GaussianDensitySchema = { uAlpha: UniformSpec('f', 'material'), uResolution: UniformSpec('f', 'material'), uRadiusFactorInv: UniformSpec('f', 'material'), - tMinDistanceTex: TextureSpec('texture', 'rgba', 'float', 'nearest'), + tMinDistanceTex: TextureSpec('texture', 'rgba', 'float', 'nearest', 'material'), dGridTexType: DefineSpec('string', ['2d', '3d']), dCalcType: DefineSpec('string', ['density', 'minDistance', 'groupId']), @@ -166,8 +166,8 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat state.currentRenderItemId = -1; fbTex.attachFramebuffer(framebuffer, 0); if (clear) { - gl.viewport(0, 0, width, height); - gl.scissor(0, 0, width, height); + state.viewport(0, 0, width, height); + state.scissor(0, 0, width, height); gl.clear(gl.COLOR_BUFFER_BIT); } ValueCell.update(uCurrentY, 0); @@ -184,8 +184,8 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat // console.log({ i, currX, currY }); ValueCell.update(uCurrentX, currX); ValueCell.update(uCurrentSlice, i); - gl.viewport(currX, currY, dx, dy); - gl.scissor(currX, currY, dx, dy); + state.viewport(currX, currY, dx, dy); + state.scissor(currX, currY, dx, dy); renderable.render(); ++currCol; currX += dx; @@ -204,7 +204,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat render(texture, false); } - // printTexture(webgl, minDistTex, 0.75); + // printTextureImage(readTexture(webgl, minDistTex), { scale: 0.75 }); return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale, radiusFactor, resolution, maxRadius }; } @@ -232,8 +232,8 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat const framebuffer = getFramebuffer(webgl); framebuffer.bind(); setRenderingDefaults(webgl); - gl.viewport(0, 0, dx, dy); - gl.scissor(0, 0, dx, dy); + state.viewport(0, 0, dx, dy); + state.scissor(0, 0, dx, dy); if (!texture) texture = colorBufferHalfFloat && textureHalfFloat ? resources.texture('volume-float16', 'rgba', 'fp16', 'linear') diff --git a/src/mol-math/geometry/lookup3d/common.ts b/src/mol-math/geometry/lookup3d/common.ts index 24b457d77d5207956cf997c1bd346e5dc8af6ade..2c6c76cc2635963617d02ac10d6f5fa47e9ca0f3 100644 --- a/src/mol-math/geometry/lookup3d/common.ts +++ b/src/mol-math/geometry/lookup3d/common.ts @@ -41,8 +41,9 @@ export namespace Result { export interface Lookup3D<T = number> { // The result is mutated with each call to find. find(x: number, y: number, z: number, radius: number, result?: Result<T>): Result<T>, + nearest(x: number, y: number, z: number, k: number, stopIf?: Function, result?: Result<T>): Result<T>, check(x: number, y: number, z: number, radius: number): boolean, readonly boundary: { readonly box: Box3D, readonly sphere: Sphere3D } /** transient result */ readonly result: Result<T> -} \ No newline at end of file +} diff --git a/src/mol-math/geometry/lookup3d/grid.ts b/src/mol-math/geometry/lookup3d/grid.ts index 970cce5881913c9d5447ca613dba7b26619b5378..b4f96dd0f9aee2c8bc1c4aaec740ed3e5fbbde03 100644 --- a/src/mol-math/geometry/lookup3d/grid.ts +++ b/src/mol-math/geometry/lookup3d/grid.ts @@ -3,6 +3,7 @@ * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Gianluca Tomasello <giagitom@gmail.com> */ import { Result, Lookup3D } from './common'; @@ -12,6 +13,7 @@ import { PositionData } from '../common'; import { Vec3 } from '../../linear-algebra'; import { OrderedSet } from '../../../mol-data/int'; import { Boundary } from '../boundary'; +import { FibonacciHeap } from '../../../mol-util/fibonacci-heap'; interface GridLookup3D<T = number> extends Lookup3D<T> { readonly buckets: { readonly offset: ArrayLike<number>, readonly count: ArrayLike<number>, readonly array: ArrayLike<number> } @@ -40,6 +42,17 @@ class GridLookup3DImpl<T extends number = number> implements GridLookup3D<T> { return ret; } + nearest(x: number, y: number, z: number, k: number = 1, stopIf?: Function, result?: Result<T>): Result<T> { + this.ctx.x = x; + this.ctx.y = y; + this.ctx.z = z; + this.ctx.k = k; + this.ctx.stopIf = stopIf; + const ret = result ?? this.result; + queryNearest(this.ctx, ret); + return ret; + } + check(x: number, y: number, z: number, radius: number): boolean { this.ctx.x = x; this.ctx.y = y; @@ -221,12 +234,14 @@ interface QueryContext { x: number, y: number, z: number, + k: number, + stopIf?: Function, radius: number, isCheck: boolean } function createContext(grid: Grid3D): QueryContext { - return { grid, x: 0.1, y: 0.1, z: 0.1, radius: 0.1, isCheck: false }; + return { grid, x: 0.1, y: 0.1, z: 0.1, k: 1, stopIf: undefined, radius: 0.1, isCheck: false }; } function query<T extends number = number>(ctx: QueryContext, result: Result<T>): boolean { @@ -277,4 +292,152 @@ function query<T extends number = number>(ctx: QueryContext, result: Result<T>): } } return result.count > 0; -} \ No newline at end of file +} + +const tmpDirVec = Vec3(); +const tmpVec = Vec3(); +const tmpSetG = new Set<number>(); +const tmpSetG2 = new Set<number>(); +const tmpArrG1 = [0.1]; +const tmpArrG2 = [0.1]; +const tmpArrG3 = [0.1]; +const tmpHeapG = new FibonacciHeap(); +function queryNearest<T extends number = number>(ctx: QueryContext, result: Result<T>): boolean { + const { min, expandedBox: box, boundingSphere: { center }, size: [sX, sY, sZ], bucketOffset, bucketCounts, bucketArray, grid, data: { x: px, y: py, z: pz, indices, radius }, delta, maxRadius } = ctx.grid; + const { x, y, z, k, stopIf } = ctx; + const indicesCount = OrderedSet.size(indices); + Result.reset(result); + if (indicesCount === 0 || k <= 0) return false; + let gX, gY, gZ, stop = false, gCount = 1, expandGrid = true, nextGCount = 0, arrG = tmpArrG1, nextArrG = tmpArrG2, maxRange = 0, expandRange = true, gridId: number, gridPointsFinished = false; + const expandedArrG = tmpArrG3, sqMaxRadius = maxRadius * maxRadius; + arrG.length = 0; + expandedArrG.length = 0; + tmpSetG.clear(); + tmpHeapG.clear(); + Vec3.set(tmpVec, x, y, z); + if (!Box3D.containsVec3(box, tmpVec)) { + // intersect ray pointing to box center + Box3D.nearestIntersectionWithRay(tmpVec, box, tmpVec, Vec3.normalize(tmpDirVec, Vec3.sub(tmpDirVec, center, tmpVec))); + gX = Math.max(0, Math.min(sX - 1, Math.floor((tmpVec[0] - min[0]) / delta[0]))); + gY = Math.max(0, Math.min(sY - 1, Math.floor((tmpVec[1] - min[1]) / delta[1]))); + gZ = Math.max(0, Math.min(sZ - 1, Math.floor((tmpVec[2] - min[2]) / delta[2]))); + } else { + gX = Math.floor((x - min[0]) / delta[0]); + gY = Math.floor((y - min[1]) / delta[1]); + gZ = Math.floor((z - min[2]) / delta[2]); + } + const dX = maxRadius !== 0 ? Math.max(1, Math.min(sX - 1, Math.ceil(maxRadius / delta[0]))) : 1; + const dY = maxRadius !== 0 ? Math.max(1, Math.min(sY - 1, Math.ceil(maxRadius / delta[1]))) : 1; + const dZ = maxRadius !== 0 ? Math.max(1, Math.min(sZ - 1, Math.ceil(maxRadius / delta[2]))) : 1; + arrG.push(gX, gY, gZ, (((gX * sY) + gY) * sZ) + gZ); + while (result.count < indicesCount) { + const arrGLen = gCount * 4; + for (let ig = 0; ig < arrGLen; ig += 4) { + gridId = arrG[ig + 3]; + if (!tmpSetG.has(gridId)) { + tmpSetG.add(gridId); + gridPointsFinished = tmpSetG.size >= grid.length; + const bucketIdx = grid[gridId]; + if (bucketIdx !== 0) { + const _maxRange = maxRange; + const ki = bucketIdx - 1; + const offset = bucketOffset[ki]; + const count = bucketCounts[ki]; + const end = offset + count; + for (let i = offset; i < end; i++) { + const bIdx = bucketArray[i]; + const idx = OrderedSet.getAt(indices, bIdx); + const dx = px[idx] - x; + const dy = py[idx] - y; + const dz = pz[idx] - z; + let distSq = dx * dx + dy * dy + dz * dz; + if (maxRadius !== 0) { + const r = radius![idx]; + distSq -= r * r; + } + if (expandRange && distSq > maxRange) { + maxRange = distSq; + } + tmpHeapG.insert(distSq, bIdx); + } + if (_maxRange < maxRange) expandRange = false; + } + } + } + // find next grid points + nextArrG.length = 0; + nextGCount = 0; + tmpSetG2.clear(); + for (let ig = 0; ig < arrGLen; ig += 4) { + gX = arrG[ig]; + gY = arrG[ig + 1]; + gZ = arrG[ig + 2]; + // fill grid points array with valid adiacent positions + for (let ix = -dX; ix <= dX; ix++) { + const xPos = gX + ix; + if (xPos < 0 || xPos >= sX) continue; + for (let iy = -dY; iy <= dY; iy++) { + const yPos = gY + iy; + if (yPos < 0 || yPos >= sY) continue; + for (let iz = -dZ; iz <= dZ; iz++) { + const zPos = gZ + iz; + if (zPos < 0 || zPos >= sZ) continue; + gridId = (((xPos * sY) + yPos) * sZ) + zPos; + if (tmpSetG2.has(gridId)) continue; // already scanned + tmpSetG2.add(gridId); + if (tmpSetG.has(gridId)) continue; // already visited + if (!expandGrid) { + const xP = min[0] + xPos * delta[0] - x; + const yP = min[1] + yPos * delta[1] - y; + const zP = min[2] + zPos * delta[2] - z; + const distSqG = (xP * xP) + (yP * yP) + (zP * zP) - sqMaxRadius; // is sqMaxRadius necessary? + if (distSqG > maxRange) { + expandedArrG.push(xPos, yPos, zPos, gridId); + continue; + } + } + nextArrG.push(xPos, yPos, zPos, gridId); + nextGCount++; + } + } + } + } + expandGrid = false; + if (nextGCount === 0) { + if (k === 1) { + const node = tmpHeapG.findMinimum(); + if (node) { + const { key: squaredDistance, value: index } = node!; + // const squaredDistance = node!.key, index = node!.value; + Result.add(result, index as number, squaredDistance as number); + return true; + } + } else { + while (!tmpHeapG.isEmpty() && (gridPointsFinished || tmpHeapG.findMinimum()!.key as number <= maxRange) && result.count < k) { + const node = tmpHeapG.extractMinimum(); + const squaredDistance = node!.key, index = node!.value; + Result.add(result, index as number, squaredDistance as number); + if (stopIf && !stop) { + stop = stopIf(index, squaredDistance); + } + } + } + if (result.count >= k || stop || result.count >= indicesCount) return result.count > 0; + expandGrid = true; + expandRange = true; + if (expandedArrG.length > 0) { + for (let i = 0, l = expandedArrG.length; i < l; i++) { + arrG.push(expandedArrG[i]); + } + expandedArrG.length = 0; + gCount = arrG.length; + } + } else { + const tmp = arrG; + arrG = nextArrG; + nextArrG = tmp; + gCount = nextGCount; + } + } + return result.count > 0; +} diff --git a/src/mol-math/geometry/primitives/box3d.ts b/src/mol-math/geometry/primitives/box3d.ts index d701ae18085f5b45feb9f29cb7cdd5682e9104f9..0176be2aed2a120beb44c398d851539a3d1c1f43 100644 --- a/src/mol-math/geometry/primitives/box3d.ts +++ b/src/mol-math/geometry/primitives/box3d.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -124,12 +124,62 @@ namespace Box3D { } export function containsVec3(box: Box3D, v: Vec3) { - return ( + return !( v[0] < box.min[0] || v[0] > box.max[0] || v[1] < box.min[1] || v[1] > box.max[1] || v[2] < box.min[2] || v[2] > box.max[2] - ) ? false : true; + ); + } + + export function overlaps(a: Box3D, b: Box3D) { + return !( + a.max[0] < b.min[0] || a.min[0] > b.max[0] || + a.max[1] < b.min[1] || a.min[1] > b.max[1] || + a.max[2] < b.min[2] || a.min[2] > b.max[2] + ); + } + + // const tmpTransformV = Vec3(); + export function nearestIntersectionWithRay(out: Vec3, box: Box3D, origin: Vec3, dir: Vec3): Vec3 { + const [minX, minY, minZ] = box.min; + const [maxX, maxY, maxZ] = box.max; + const [x, y, z] = origin; + const invDirX = 1.0 / dir[0]; + const invDirY = 1.0 / dir[1]; + const invDirZ = 1.0 / dir[2]; + let tmin, tmax, tymin, tymax, tzmin, tzmax; + if (invDirX >= 0) { + tmin = (minX - x) * invDirX; + tmax = (maxX - x) * invDirX; + } else { + tmin = (maxX - x) * invDirX; + tmax = (minX - x) * invDirX; + } + if (invDirY >= 0) { + tymin = (minY - y) * invDirY; + tymax = (maxY - y) * invDirY; + } else { + tymin = (maxY - y) * invDirY; + tymax = (minY - y) * invDirY; + } + if (invDirZ >= 0) { + tzmin = (minZ - z) * invDirZ; + tzmax = (maxZ - z) * invDirZ; + } else { + tzmin = (maxZ - z) * invDirZ; + tzmax = (minZ - z) * invDirZ; + } + if (tymin > tmin) + tmin = tymin; + if (tymax < tmax) + tmax = tymax; + if (tzmin > tmin) + tmin = tzmin; + if (tzmax < tmax) + tmax = tzmax; + Vec3.scale(out, dir, tmin); + return Vec3.set(out, out[0] + x, out[1] + y, out[2] + z); } } -export { Box3D }; \ No newline at end of file +export { Box3D }; diff --git a/src/mol-math/geometry/primitives/sphere3d.ts b/src/mol-math/geometry/primitives/sphere3d.ts index 6f09cce9503363b2c5da245f0198bf8a478c63f7..69953f360f467ce8a3ae6200b4b2517c135a7fe4 100644 --- a/src/mol-math/geometry/primitives/sphere3d.ts +++ b/src/mol-math/geometry/primitives/sphere3d.ts @@ -277,6 +277,12 @@ namespace Sphere3D { export function distance(a: Sphere3D, b: Sphere3D) { return Vec3.distance(a.center, b.center) - a.radius + b.radius; } + + /** Get the distance of v from sphere. If negative, v is inside sphere */ + export function distanceToVec(sphere: Sphere3D, v: Vec3): number { + const { center, radius } = sphere; + return Vec3.distance(v, center) - radius; + } } -export { Sphere3D }; \ No newline at end of file +export { Sphere3D }; diff --git a/src/mol-math/linear-algebra/3d/vec3.ts b/src/mol-math/linear-algebra/3d/vec3.ts index 5c5e39f21ed821b16da7cbd614dcf38943468358..d55b4ee8a79c6abccef76f75969fffe2813ffe83 100644 --- a/src/mol-math/linear-algebra/3d/vec3.ts +++ b/src/mol-math/linear-algebra/3d/vec3.ts @@ -35,7 +35,7 @@ function Vec3() { namespace Vec3 { export function zero(): Vec3 { - const out = [0.1, 0.0, 0.0]; + const out = [0.1, 0.0, 0.0]; // ensure backing array of type double out[0] = 0; return out as any; } diff --git a/src/mol-math/linear-algebra/matrix/principal-axes.ts b/src/mol-math/linear-algebra/matrix/principal-axes.ts index 2f28473a06a85a72cbf6136c789c007a758a6126..1150702a448ad9341c3f844c9811f13b81995a1a 100644 --- a/src/mol-math/linear-algebra/matrix/principal-axes.ts +++ b/src/mol-math/linear-algebra/matrix/principal-axes.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -9,6 +9,7 @@ import { Vec3 } from '../3d/vec3'; import { svd } from './svd'; import { NumberArray } from '../../../mol-util/type-helpers'; import { Axes3D } from '../../geometry'; +import { EPSILON } from '../3d/common'; export { PrincipalAxes }; @@ -58,10 +59,15 @@ namespace PrincipalAxes { return Axes3D.create(origin, dirA, dirB, dirC); } + export function calculateNormalizedAxes(momentsAxes: Axes3D): Axes3D { + const a = Axes3D.clone(momentsAxes); + if (Vec3.magnitude(a.dirC) < EPSILON) { + Vec3.cross(a.dirC, a.dirA, a.dirB); + } + return Axes3D.normalize(a, a); + } + const tmpBoxVec = Vec3(); - const tmpBoxVecA = Vec3(); - const tmpBoxVecB = Vec3(); - const tmpBoxVecC = Vec3(); /** * Get the scale/length for each dimension for a box around the axes * to enclose the given positions @@ -82,13 +88,11 @@ namespace PrincipalAxes { const t = Vec3(); const center = momentsAxes.origin; - const normVecA = Vec3.normalize(tmpBoxVecA, momentsAxes.dirA); - const normVecB = Vec3.normalize(tmpBoxVecB, momentsAxes.dirB); - const normVecC = Vec3.normalize(tmpBoxVecC, momentsAxes.dirC); + const a = calculateNormalizedAxes(momentsAxes); for (let i = 0, il = positions.length; i < il; i += 3) { - Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), normVecA, center); - const dp1 = Vec3.dot(normVecA, Vec3.normalize(t, Vec3.sub(t, p, center))); + Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), a.dirA, center); + const dp1 = Vec3.dot(a.dirA, Vec3.normalize(t, Vec3.sub(t, p, center))); const dt1 = Vec3.distance(p, center); if (dp1 > 0) { if (dt1 > d1a) d1a = dt1; @@ -96,8 +100,8 @@ namespace PrincipalAxes { if (dt1 > d1b) d1b = dt1; } - Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), normVecB, center); - const dp2 = Vec3.dot(normVecB, Vec3.normalize(t, Vec3.sub(t, p, center))); + Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), a.dirB, center); + const dp2 = Vec3.dot(a.dirB, Vec3.normalize(t, Vec3.sub(t, p, center))); const dt2 = Vec3.distance(p, center); if (dp2 > 0) { if (dt2 > d2a) d2a = dt2; @@ -105,8 +109,8 @@ namespace PrincipalAxes { if (dt2 > d2b) d2b = dt2; } - Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), normVecC, center); - const dp3 = Vec3.dot(normVecC, Vec3.normalize(t, Vec3.sub(t, p, center))); + Vec3.projectPointOnVector(p, Vec3.fromArray(p, positions, i), a.dirC, center); + const dp3 = Vec3.dot(a.dirC, Vec3.normalize(t, Vec3.sub(t, p, center))); const dt3 = Vec3.distance(p, center); if (dp3 > 0) { if (dt3 > d3a) d3a = dt3; @@ -115,16 +119,16 @@ namespace PrincipalAxes { } } - const dirA = Vec3.setMagnitude(Vec3(), normVecA, (d1a + d1b) / 2); - const dirB = Vec3.setMagnitude(Vec3(), normVecB, (d2a + d2b) / 2); - const dirC = Vec3.setMagnitude(Vec3(), normVecC, (d3a + d3b) / 2); + const dirA = Vec3.setMagnitude(Vec3(), a.dirA, (d1a + d1b) / 2); + const dirB = Vec3.setMagnitude(Vec3(), a.dirB, (d2a + d2b) / 2); + const dirC = Vec3.setMagnitude(Vec3(), a.dirC, (d3a + d3b) / 2); const origin = Vec3(); const addCornerHelper = function (d1: number, d2: number, d3: number) { Vec3.copy(tmpBoxVec, center); - Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, normVecA, d1); - Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, normVecB, d2); - Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, normVecC, d3); + Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirA, d1); + Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirB, d2); + Vec3.scaleAndAdd(tmpBoxVec, tmpBoxVec, a.dirC, d3); Vec3.add(origin, origin, tmpBoxVec); }; addCornerHelper(d1a, d2a, d3a); diff --git a/src/mol-model-formats/shape/ply.ts b/src/mol-model-formats/shape/ply.ts index d94a8047f4b8777bcbca1fadb303206cd0da0863..3d488a8889a009b5e7286f41289669afe786931e 100644 --- a/src/mol-model-formats/shape/ply.ts +++ b/src/mol-model-formats/shape/ply.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Schäfer, Marco <marco.schaefer@uni-tuebingen.de> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -19,6 +19,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { ColorNames } from '../../mol-util/color/names'; import { deepClone } from '../../mol-util/object'; import { stringToWords } from '../../mol-util/string'; +import { ValueCell } from '../../mol-util/value-cell'; // TODO support 'edge' element, see https://www.mathworks.com/help/vision/ug/the-ply-format.html // TODO support missing face element @@ -170,6 +171,9 @@ async function getMesh(ctx: RuntimeContext, vertex: PlyTable, face: PlyList, gro const m = MeshBuilder.getMesh(builderState); if (!hasNormals) Mesh.computeNormals(m); + // TODO: check if needed + ValueCell.updateIfChanged(m.varyingGroup, true); + return m; } diff --git a/src/mol-model-formats/structure/common/component.ts b/src/mol-model-formats/structure/common/component.ts index 948a01fcb7df8a3b8942d5798a106a5c13588416..770a095cac67716d4bb64b82157b2a6a4a5af33b 100644 --- a/src/mol-model-formats/structure/common/component.ts +++ b/src/mol-model-formats/structure/common/component.ts @@ -32,6 +32,7 @@ const DnaAtomIdsList = [ /** Used to reduce false positives for atom name-based type guessing */ const NonPolymerNames = new Set([ 'FMN', 'NCN', 'FNS', 'FMA', 'ATP', 'ADP', 'AMP', 'GTP', 'GDP', 'GMP', // Mononucleotides + 'LIG' ]); const StandardComponents = (function () { diff --git a/src/mol-model-formats/structure/mol.ts b/src/mol-model-formats/structure/mol.ts index 942f24a597cd0c8bbb18c564d571536e9310f49f..f32849f738b1bb31f4a9aca641bbe2a55adf1e91 100644 --- a/src/mol-model-formats/structure/mol.ts +++ b/src/mol-model-formats/structure/mol.ts @@ -80,7 +80,10 @@ export async function getMolModels(mol: MolFile, format: ModelFormat<any> | unde const indexA = Column.ofIntArray(Column.mapToArray(bonds.atomIdxA, x => x - 1, Int32Array)); const indexB = Column.ofIntArray(Column.mapToArray(bonds.atomIdxB, x => x - 1, Int32Array)); const order = Column.asArrayColumn(bonds.order, Int32Array); - const pairBonds = IndexPairBonds.fromData({ pairs: { indexA, indexB, order }, count: atoms.count }); + const pairBonds = IndexPairBonds.fromData( + { pairs: { indexA, indexB, order }, count: atoms.count }, + { maxDistance: Infinity } + ); IndexPairBonds.Provider.set(models.representative, pairBonds); } diff --git a/src/mol-model-formats/structure/mol2.ts b/src/mol-model-formats/structure/mol2.ts index ac8b4e75c1119a06afa11a91d18709f888129418..19723a6fd1987e77bb382bafb2621f98e95bfbeb 100644 --- a/src/mol-model-formats/structure/mol2.ts +++ b/src/mol-model-formats/structure/mol2.ts @@ -113,7 +113,10 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) { return BondType.Flag.Covalent; } }, Int8Array)); - const pairBonds = IndexPairBonds.fromData({ pairs: { key, indexA, indexB, order, flag }, count: atoms.count }); + const pairBonds = IndexPairBonds.fromData( + { pairs: { key, indexA, indexB, order, flag }, count: atoms.count }, + { maxDistance: crysin ? -1 : Infinity } + ); const first = _models.representative; IndexPairBonds.Provider.set(first, pairBonds); diff --git a/src/mol-model-formats/structure/pdb/atom-site.ts b/src/mol-model-formats/structure/pdb/atom-site.ts index 7de7aeed37eb9c36cd14b717f05b7b6b02f6b3f9..c8b1c996077de39d522b6916f161b69de7fcb27b 100644 --- a/src/mol-model-formats/structure/pdb/atom-site.ts +++ b/src/mol-model-formats/structure/pdb/atom-site.ts @@ -39,7 +39,7 @@ export function getAtomSiteTemplate(data: string, count: number) { }; } -export function getAtomSite(sites: AtomSiteTemplate, hasTer: boolean): { [K in keyof mmCIF_Schema['atom_site'] | 'partial_charge']?: CifField } { +export function getAtomSite(sites: AtomSiteTemplate, terIndices: Set<number>): { [K in keyof mmCIF_Schema['atom_site'] | 'partial_charge']?: CifField } { const pdbx_PDB_model_num = CifField.ofStrings(sites.pdbx_PDB_model_num); const auth_asym_id = CifField.ofTokens(sites.auth_asym_id); const auth_seq_id = CifField.ofTokens(sites.auth_seq_id); @@ -67,21 +67,17 @@ export function getAtomSite(sites: AtomSiteTemplate, hasTer: boolean): { [K in k const seqId = auth_seq_id.int(i); let atomId = auth_atom_id.str(i); - let asymIdChanged = false; - if (modelNum !== currModelNum) { asymIdCounts.clear(); atomIdCounts.clear(); currModelNum = modelNum; currAsymId = asymId; currSeqId = seqId; - asymIdChanged = true; currLabelAsymId = asymId; } else if (currAsymId !== asymId) { atomIdCounts.clear(); currAsymId = asymId; currSeqId = seqId; - asymIdChanged = true; currLabelAsymId = asymId; } else if (currSeqId !== seqId) { atomIdCounts.clear(); @@ -91,7 +87,7 @@ export function getAtomSite(sites: AtomSiteTemplate, hasTer: boolean): { [K in k if (asymIdCounts.has(asymId)) { // only change the chains name if there are TER records // otherwise assume repeated chain name use is from interleaved chains - if (hasTer && asymIdChanged) { + if (terIndices.has(i)) { const asymIdCount = asymIdCounts.get(asymId)! + 1; asymIdCounts.set(asymId, asymIdCount); currLabelAsymId = `${asymId}_${asymIdCount}`; diff --git a/src/mol-model-formats/structure/pdb/to-cif.ts b/src/mol-model-formats/structure/pdb/to-cif.ts index 21a2a68f0593c6aeecf3d549600b0836290d167d..792d5a136d2c45388eba6ca8612803c890c7d32c 100644 --- a/src/mol-model-formats/structure/pdb/to-cif.ts +++ b/src/mol-model-formats/structure/pdb/to-cif.ts @@ -51,7 +51,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> { let modelNum = 0, modelStr = ''; let conectRange: [number, number] | undefined = undefined; - let hasTer = false; + const terIndices = new Set<number>(); for (let i = 0, _i = lines.count; i < _i; i++) { let s = indices[2 * i], e = indices[2 * i + 1]; @@ -164,7 +164,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> { break; case 'T': if (substringStartsWith(data, s, e, 'TER')) { - hasTer = true; + terIndices.add(atomSite.index); } } } @@ -183,7 +183,7 @@ export async function pdbToMmCif(pdb: PdbFile): Promise<CifFrame> { atomSite.label_entity_id[i] = entityBuilder.getEntityId(compId, moleculeType, asymIds.value(i)); } - const atom_site = getAtomSite(atomSite, hasTer); + const atom_site = getAtomSite(atomSite, terIndices); if (!isPdbqt) delete atom_site.partial_charge; if (conectRange) { diff --git a/src/mol-model-formats/structure/property/bonds/chem_comp.ts b/src/mol-model-formats/structure/property/bonds/chem_comp.ts index b3f64adcff90adfd48410aa33035248c7f740ada..6ba2bc374891a16fff526ee3e8b7b96a3db37b70 100644 --- a/src/mol-model-formats/structure/property/bonds/chem_comp.ts +++ b/src/mol-model-formats/structure/property/bonds/chem_comp.ts @@ -65,7 +65,7 @@ export namespace ComponentBond { return e; } - const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount } = data; + const { comp_id, atom_id_1, atom_id_2, value_order, pdbx_aromatic_flag, _rowCount, pdbx_ordinal } = data; let entry = addEntry(comp_id.value(0)!); for (let i = 0; i < _rowCount; i++) { @@ -74,6 +74,7 @@ export namespace ComponentBond { const nameB = atom_id_2.value(i)!; const order = value_order.value(i)!; const aromatic = pdbx_aromatic_flag.value(i) === 'y'; + const key = pdbx_ordinal.value(i); if (entry.id !== id) { entry = addEntry(id); @@ -89,29 +90,29 @@ export namespace ComponentBond { case 'quad': ord = 4; break; } - entry.add(nameA, nameB, ord, flags); + entry.add(nameA, nameB, ord, flags, key); } return entries; } export class Entry { - readonly map: Map<string, Map<string, { order: number, flags: number }>> = new Map(); + readonly map: Map<string, Map<string, { order: number, flags: number, key: number }>> = new Map(); - add(a: string, b: string, order: number, flags: number, swap = true) { + add(a: string, b: string, order: number, flags: number, key: number, swap = true) { const e = this.map.get(a); if (e !== void 0) { const f = e.get(b); if (f === void 0) { - e.set(b, { order, flags }); + e.set(b, { order, flags, key }); } } else { - const map = new Map<string, { order: number, flags: number }>(); - map.set(b, { order, flags }); + const map = new Map<string, { order: number, flags: number, key: number }>(); + map.set(b, { order, flags, key }); this.map.set(a, map); } - if (swap) this.add(b, a, order, flags, false); + if (swap) this.add(b, a, order, flags, key, false); } constructor(public readonly id: string) { } diff --git a/src/mol-model-formats/structure/property/bonds/struct_conn.ts b/src/mol-model-formats/structure/property/bonds/struct_conn.ts index d8b2ca61e29bccaecdd16a293ef519eceb144cb6..f5f38998ec4da81174f5787b5e84719dc4c52fbc 100644 --- a/src/mol-model-formats/structure/property/bonds/struct_conn.ts +++ b/src/mol-model-formats/structure/property/bonds/struct_conn.ts @@ -111,24 +111,28 @@ export namespace StructConn { symmetry: struct_conn.ptnr2_symmetry }; + const entityIds = Array.from(model.entities.data.id.toArray()); const _p = (row: number, ps: typeof p1) => { if (ps.label_asym_id.valueKind(row) !== Column.ValueKind.Present) return void 0; const asymId = ps.label_asym_id.value(row); - const entityIndex = model.atomicHierarchy.index.findEntity(asymId); - if (entityIndex < 0) return void 0; - const residueIndex = model.atomicHierarchy.index.findResidue( - model.entities.data.id.value(entityIndex), - asymId, - ps.auth_seq_id.value(row), - ps.ins_code.value(row) - ); - if (residueIndex < 0) return void 0; const atomName = ps.label_atom_id.value(row); // turns out "mismat" records might not have atom name value - if (!atomName) return void 0; - const atomIndex = model.atomicHierarchy.index.findAtomOnResidue(residueIndex, atomName, ps.label_alt_id.value(row)); - if (atomIndex < 0) return void 0; - return { residueIndex, atomIndex, symmetry: ps.symmetry.value(row) }; + if (!atomName) return undefined; + + const altId = ps.label_alt_id.value(row); + for (const eId of entityIds) { + const residueIndex = model.atomicHierarchy.index.findResidue( + eId, + asymId, + ps.auth_seq_id.value(row), + ps.ins_code.value(row) + ); + if (residueIndex < 0) continue; + const atomIndex = model.atomicHierarchy.index.findAtomOnResidue(residueIndex, atomName, altId); + if (atomIndex < 0) continue; + return { residueIndex, atomIndex, symmetry: ps.symmetry.value(row) }; + } + return void 0; }; const entries: StructConn.Entry[] = []; diff --git a/src/mol-model-props/common/custom-element-property.ts b/src/mol-model-props/common/custom-element-property.ts index 8f60da9d85a008d47d0120c60fdce7ceff08b03b..47580e1985a8d146db25eb5c2111234d59c6799e 100644 --- a/src/mol-model-props/common/custom-element-property.ts +++ b/src/mol-model-props/common/custom-element-property.ts @@ -106,7 +106,7 @@ namespace CustomElementProperty { factory: Coloring, getParams: () => ({}), defaultValues: {}, - isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && !!modelProperty.get(ctx.structure.models[0]).value, + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure, ensureCustomProperties: { attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? modelProperty.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(), detach: (data: ThemeDataContext) => data.structure && data.structure.models[0].customProperties.reference(modelProperty.descriptor, false) diff --git a/src/mol-model-props/common/custom-model-property.ts b/src/mol-model-props/common/custom-model-property.ts index 5d9b001ae9e6a15550e8177d057475656f0c6485..81004faf0939784f7e528319d2c3d17c4ac29889 100644 --- a/src/mol-model-props/common/custom-model-property.ts +++ b/src/mol-model-props/common/custom-model-property.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -71,12 +71,12 @@ namespace CustomModelProperty { }, ref: (data: Model, add: boolean) => data.customProperties.reference(builder.descriptor, add), get: (data: Model) => get(data)?.data, - set: (data: Model, props: Partial<PD.Values<Params>> = {}) => { + set: (data: Model, props: Partial<PD.Values<Params>> = {}, value?: Value) => { const property = get(data); const p = PD.merge(builder.defaultParams, property.props, props); if (!PD.areEqual(builder.defaultParams, property.props, p)) { // this invalidates property.value - set(data, p, undefined); + set(data, p, value); // dispose of assets data.customProperties.assets(builder.descriptor); } @@ -96,7 +96,7 @@ namespace CustomModelProperty { getParams: () => ({ value: PD.Value(defaultValue, { isHidden: true }) }), isApplicable: () => true, obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<PD.Values<typeof defaultParams>>) => { - return { value: props.value ?? defaultValue }; + return { ...PD.getDefaultValues(defaultParams), ...props }; } }); } diff --git a/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts b/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts index d07ee17b564f5f7cacac9fc97312946db51eecdf..9a0d9164212b5f844665d350b75a970d7621513c 100644 --- a/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts +++ b/src/mol-model-props/computed/representations/interactions-inter-unit-cylinder.ts @@ -22,6 +22,8 @@ import { LocationIterator } from '../../../mol-geo/util/location-iterator'; import { InteractionFlag } from '../interactions/common'; import { Unit } from '../../../mol-model/structure/structure'; import { Sphere3D } from '../../../mol-math/geometry'; +import { assertUnreachable } from '../../../mol-util/type-helpers'; +import { InteractionsSharedParams } from './shared'; function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InteractionsInterUnitParams>, mesh?: Mesh) { if (!structure.hasAtomic) return Mesh.createEmpty(mesh); @@ -31,7 +33,7 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S const { contacts, unitsFeatures } = interactions; const { edgeCount, edges } = contacts; - const { sizeFactor } = props; + const { sizeFactor, parentDisplay } = props; if (!edgeCount) return Mesh.createEmpty(mesh); @@ -70,14 +72,48 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S if (child) { const b = edges[edgeIndex]; - const childUnitA = child.unitMap.get(b.unitA); - if (!childUnitA) return true; - - const unitA = structure.unitMap.get(b.unitA); - const { offsets, members } = unitsFeatures.get(b.unitA); - for (let i = offsets[b.indexA], il = offsets[b.indexA + 1]; i < il; ++i) { - const eA = unitA.elements[members[i]]; - if (!SortedArray.has(childUnitA.elements, eA)) return true; + + if (parentDisplay === 'stub') { + const childUnitA = child.unitMap.get(b.unitA); + if (!childUnitA) return true; + + const unitA = structure.unitMap.get(b.unitA); + const { offsets, members } = unitsFeatures.get(b.unitA); + for (let i = offsets[b.indexA], il = offsets[b.indexA + 1]; i < il; ++i) { + const eA = unitA.elements[members[i]]; + if (!SortedArray.has(childUnitA.elements, eA)) return true; + } + } else if (parentDisplay === 'full' || parentDisplay === 'between') { + let flagA = false; + let flagB = false; + + const childUnitA = child.unitMap.get(b.unitA); + if (!childUnitA) { + flagA = true; + } else { + const unitA = structure.unitMap.get(b.unitA); + const { offsets, members } = unitsFeatures.get(b.unitA); + for (let i = offsets[b.indexA], il = offsets[b.indexA + 1]; i < il; ++i) { + const eA = unitA.elements[members[i]]; + if (!SortedArray.has(childUnitA.elements, eA)) flagA = true; + } + } + + const childUnitB = child.unitMap.get(b.unitB); + if (!childUnitB) { + flagB = true; + } else { + const unitB = structure.unitMap.get(b.unitB); + const { offsets, members } = unitsFeatures.get(b.unitB); + for (let i = offsets[b.indexB], il = offsets[b.indexB + 1]; i < il; ++i) { + const eB = unitB.elements[members[i]]; + if (!SortedArray.has(childUnitB.elements, eB)) flagB = true; + } + } + + return parentDisplay === 'full' ? flagA && flagB : flagA === flagB; + } else { + assertUnreachable(parentDisplay); } } @@ -101,10 +137,7 @@ function createInterUnitInteractionCylinderMesh(ctx: VisualContext, structure: S export const InteractionsInterUnitParams = { ...ComplexMeshParams, ...LinkCylinderParams, - sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }), - dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }), - dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }), - includeParent: PD.Boolean(false), + ...InteractionsSharedParams, }; export type InteractionsInterUnitParams = typeof InteractionsInterUnitParams @@ -121,7 +154,8 @@ export function InteractionsInterUnitVisual(materialId: number): ComplexVisual<I newProps.dashCount !== currentProps.dashCount || newProps.dashScale !== currentProps.dashScale || newProps.dashCap !== currentProps.dashCap || - newProps.radialSegments !== currentProps.radialSegments + newProps.radialSegments !== currentProps.radialSegments || + newProps.parentDisplay !== currentProps.parentDisplay ); const interactionsHash = InteractionsProvider.get(newStructure).version; diff --git a/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts b/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts index de3675163db17e5eb355e8c6a9f9c6e9ffee9863..e0289f7b75aeffbf37ba01dd2ff02a6194b75172 100644 --- a/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts +++ b/src/mol-model-props/computed/representations/interactions-intra-unit-cylinder.ts @@ -22,6 +22,8 @@ import { Interactions } from '../interactions/interactions'; import { InteractionFlag } from '../interactions/common'; import { Sphere3D } from '../../../mol-math/geometry'; import { StructureGroup } from '../../../mol-repr/structure/visual/util/common'; +import { assertUnreachable } from '../../../mol-util/type-helpers'; +import { InteractionsSharedParams } from './shared'; async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<InteractionsIntraUnitParams>, mesh?: Mesh) { if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh); @@ -38,7 +40,7 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: const { x, y, z, members, offsets } = features; const { edgeCount, a, b, edgeProps: { flag } } = contacts; - const { sizeFactor } = props; + const { sizeFactor, parentDisplay } = props; if (!edgeCount) return Mesh.createEmpty(mesh); @@ -60,10 +62,31 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: if (flag[edgeIndex] === InteractionFlag.Filtered) return true; if (childUnit) { - const f = a[edgeIndex]; - for (let i = offsets[f], jl = offsets[f + 1]; i < jl; ++i) { - const e = unit.elements[members[offsets[i]]]; - if (!SortedArray.has(childUnit.elements, e)) return true; + if (parentDisplay === 'stub') { + const f = a[edgeIndex]; + for (let i = offsets[f], il = offsets[f + 1]; i < il; ++i) { + const e = unit.elements[members[offsets[i]]]; + if (!SortedArray.has(childUnit.elements, e)) return true; + } + } else if (parentDisplay === 'full' || parentDisplay === 'between') { + let flagA = false; + let flagB = false; + + const fA = a[edgeIndex]; + for (let i = offsets[fA], il = offsets[fA + 1]; i < il; ++i) { + const eA = unit.elements[members[offsets[i]]]; + if (!SortedArray.has(childUnit.elements, eA)) flagA = true; + } + + const fB = b[edgeIndex]; + for (let i = offsets[fB], il = offsets[fB + 1]; i < il; ++i) { + const eB = unit.elements[members[offsets[i]]]; + if (!SortedArray.has(childUnit.elements, eB)) flagB = true; + } + + return parentDisplay === 'full' ? flagA && flagB : flagA === flagB; + } else { + assertUnreachable(parentDisplay); } } @@ -86,10 +109,7 @@ async function createIntraUnitInteractionsCylinderMesh(ctx: VisualContext, unit: export const InteractionsIntraUnitParams = { ...UnitsMeshParams, ...LinkCylinderParams, - sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }), - dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }), - dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }), - includeParent: PD.Boolean(false), + ...InteractionsSharedParams, }; export type InteractionsIntraUnitParams = typeof InteractionsIntraUnitParams @@ -106,7 +126,8 @@ export function InteractionsIntraUnitVisual(materialId: number): UnitsVisual<Int newProps.dashCount !== currentProps.dashCount || newProps.dashScale !== currentProps.dashScale || newProps.dashCap !== currentProps.dashCap || - newProps.radialSegments !== currentProps.radialSegments + newProps.radialSegments !== currentProps.radialSegments || + newProps.parentDisplay !== currentProps.parentDisplay ); const interactionsHash = InteractionsProvider.get(newStructureGroup.structure).version; diff --git a/src/mol-model-props/computed/representations/shared.ts b/src/mol-model-props/computed/representations/shared.ts new file mode 100644 index 0000000000000000000000000000000000000000..61d70bf0745baf00cd9de2ae558a3e19116e54c1 --- /dev/null +++ b/src/mol-model-props/computed/representations/shared.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ParamDefinition as PD } from '../../../mol-util/param-definition'; + +export const InteractionsSharedParams = { + sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }), + dashCount: PD.Numeric(6, { min: 2, max: 10, step: 2 }), + dashScale: PD.Numeric(0.4, { min: 0, max: 2, step: 0.1 }), + includeParent: PD.Boolean(false), + parentDisplay: PD.Select('stub', PD.arrayToOptions(['stub', 'full', 'between'] as const), { description: 'Only has an effect when "includeParent" is enabled. "Stub" shows just the child side of interactions to the parent. "Full" shows both sides of interactions to the parent. "Between" shows only interactions to the parent.' }), +}; +export type InteractionsSharedParams = typeof InteractionsSharedParams diff --git a/src/mol-model/loci.ts b/src/mol-model/loci.ts index a85152bc8fcfa6dab7b9a062adb8c25ad29736fa..7118d487fac8480b7d4dad5718182f37154b97bc 100644 --- a/src/mol-model/loci.ts +++ b/src/mol-model/loci.ts @@ -241,7 +241,9 @@ namespace Loci { ? Structure.toStructureElementLoci(loci.structure) : ShapeGroup.isLoci(loci) ? Shape.Loci(loci.shape) - : loci; + : Volume.Cell.isLoci(loci) + ? Volume.Loci(loci.volume) + : loci; }, 'elementInstances': (loci: Loci) => { return StructureElement.Loci.is(loci) diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index 0301ef681b83f36adeb52f6d247c8ede6f6a27ba..76e9c4077a8134dda1e41cac9ee2facd5f601eb5 100644 --- a/src/mol-model/structure/model/model.ts +++ b/src/mol-model/structure/model/model.ts @@ -213,6 +213,9 @@ export namespace Model { export type Index = number; export const Index = CustomModelProperty.createSimple<Index>('index', 'static'); + export type MaxIndex = number; + export const MaxIndex = CustomModelProperty.createSimple<MaxIndex>('max_index', 'static'); + export function getRoot(model: Model) { return model.parent || model; } diff --git a/src/mol-model/structure/query.ts b/src/mol-model/structure/query.ts index f89c0259f230eb8c779775350c70c56edd141e61..afc174ba7031d3c17a85bec4b5b5373c38135042 100644 --- a/src/mol-model/structure/query.ts +++ b/src/mol-model/structure/query.ts @@ -12,6 +12,7 @@ import * as modifiers from './query/queries/modifiers'; import * as filters from './query/queries/filters'; import * as combinators from './query/queries/combinators'; import * as internal from './query/queries/internal'; +import * as atomset from './query/queries/atom-set'; import { Predicates as pred } from './query/predicates'; export const Queries = { @@ -20,7 +21,8 @@ export const Queries = { modifiers, combinators, pred, - internal + internal, + atomset }; -export { StructureSelection, StructureQuery }; \ No newline at end of file +export { StructureSelection, StructureQuery }; diff --git a/src/mol-model/structure/query/context.ts b/src/mol-model/structure/query/context.ts index 43b2538b98ba727032ab231948c203283e123f61..c51b441a77da6cfffd623877b0734df8e87fc757 100644 --- a/src/mol-model/structure/query/context.ts +++ b/src/mol-model/structure/query/context.ts @@ -1,7 +1,8 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { Structure, StructureElement, Unit } from '../structure'; @@ -113,6 +114,7 @@ class QueryContextBondInfo<U extends Unit = Unit> { bIndex: StructureElement.UnitIndex = 0 as StructureElement.UnitIndex; type: BondType = BondType.Flag.None; order: number = 0; + key: number = -1; private testFn: QueryPredicate = defaultBondTest; diff --git a/src/mol-model/structure/query/queries/atom-set.ts b/src/mol-model/structure/query/queries/atom-set.ts new file mode 100644 index 0000000000000000000000000000000000000000..2043bb2bcfcc81bb6143c504904ba1312cb41c8c --- /dev/null +++ b/src/mol-model/structure/query/queries/atom-set.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Koya Sakuma + * Adapted from MolQL implemtation of atom-set.ts + * + * Copyright (c) 2017 MolQL contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { StructureQuery } from '../query'; +import { StructureSelection } from '../selection'; +import { getCurrentStructureProperties } from './filters'; +import { QueryContext, QueryFn } from '../context'; + + +export function atomCount(ctx: QueryContext) { + return ctx.currentStructure.elementCount; +} + + +export function countQuery(query: StructureQuery) { + return (ctx: QueryContext) => { + const sel = query(ctx); + return StructureSelection.structureCount(sel); + }; +} + +export function propertySet(prop: QueryFn<any>) { + return (ctx: QueryContext) => { + const set = new Set(); + return getCurrentStructureProperties(ctx, prop, set); + }; +} + diff --git a/src/mol-model/structure/query/queries/filters.ts b/src/mol-model/structure/query/queries/filters.ts index 128f9b4fa7d0b698d88a7f939140d5737c3f8e21..00653ccf181d8217391e28a1cbddeb04865ffa30 100644 --- a/src/mol-model/structure/query/queries/filters.ts +++ b/src/mol-model/structure/query/queries/filters.ts @@ -1,12 +1,13 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { SetUtils } from '../../../../mol-util/set'; import { Unit } from '../../structure'; -import { QueryContext, QueryFn, QueryPredicate } from '../context'; +import { QueryContext, QueryFn } from '../context'; import { StructureQuery } from '../query'; import { StructureSelection } from '../selection'; import { structureAreIntersecting } from '../utils/structure-set'; @@ -16,7 +17,7 @@ import { Structure } from '../../structure/structure'; import { StructureElement } from '../../structure/element'; import { SortedArray } from '../../../../mol-data/int'; -export function pick(query: StructureQuery, pred: QueryPredicate): StructureQuery { +export function pick(query: StructureQuery, pred: QueryFn<any>): StructureQuery { return ctx => { const sel = query(ctx); const ret = StructureSelection.LinearBuilder(ctx.inputStructure); @@ -50,9 +51,7 @@ export function first(query: StructureQuery): StructureQuery { }; } -export interface UnitTypeProperties { atomic?: QueryFn, coarse?: QueryFn } - -export function getCurrentStructureProperties(ctx: QueryContext, props: UnitTypeProperties, set: Set<any>) { +export function getCurrentStructureProperties(ctx: QueryContext, props: QueryFn<any>, set: Set<any>) { const { units } = ctx.currentStructure; const l = ctx.pushCurrentElement(); @@ -61,9 +60,9 @@ export function getCurrentStructureProperties(ctx: QueryContext, props: UnitType l.unit = unit; const elements = unit.elements; - let fn; - if (Unit.isAtomic(unit)) fn = props.atomic; - else fn = props.coarse; + const fn = props; + // if (Unit.isAtomic(unit)) fn = props.atomic; + // else fn = props.coarse; if (!fn) continue; for (let j = 0, _j = elements.length; j < _j; j++) { @@ -77,7 +76,7 @@ export function getCurrentStructureProperties(ctx: QueryContext, props: UnitType return set; } -function getSelectionProperties(ctx: QueryContext, query: StructureQuery, props: UnitTypeProperties) { +function getSelectionProperties(ctx: QueryContext, query: StructureQuery, props: QueryFn<any>) { const set = new Set(); const sel = query(ctx); @@ -92,7 +91,7 @@ function getSelectionProperties(ctx: QueryContext, query: StructureQuery, props: return set; } -export function withSameAtomProperties(query: StructureQuery, propertySource: StructureQuery, props: UnitTypeProperties): StructureQuery { +export function withSameAtomProperties(query: StructureQuery, propertySource: StructureQuery, props: QueryFn<any>): StructureQuery { return ctx => { const sel = query(ctx); const propSet = getSelectionProperties(ctx, propertySource, props); @@ -102,7 +101,7 @@ export function withSameAtomProperties(query: StructureQuery, propertySource: St StructureSelection.forEach(sel, (s, i) => { ctx.currentStructure = s; const currentProps = getCurrentStructureProperties(ctx, props, new Set()); - if (SetUtils.isSuperset(currentProps, propSet)) { + if (SetUtils.isSuperset(propSet, currentProps)) { ret.add(s); } @@ -248,7 +247,7 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) { const inputUnit = input.unitMap.get(unit.id) as Unit.Atomic; - const { offset, b, edgeProps: { flags, order } } = inputUnit.bonds; + const { offset, b, edgeProps: { flags, order, key } } = inputUnit.bonds; const bondedUnits = interBonds.getConnectedUnits(unit.id); const buCount = bondedUnits.length; @@ -273,6 +272,7 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) { atomicBond.bIndex = b[l] as StructureElement.UnitIndex; atomicBond.type = flags[l]; atomicBond.order = order[l]; + atomicBond.key = key[l]; if (atomicBond.test(queryCtx, true)) return true; } @@ -295,6 +295,7 @@ function checkConnected(ctx: IsConnectedToCtx, structure: Structure) { atomicBond.bIndex = bond.indexB; atomicBond.type = bond.props.flag; atomicBond.order = bond.props.order; + atomicBond.key = bond.props.key; if (atomicBond.test(queryCtx, true)) return true; } } @@ -342,4 +343,4 @@ export function isConnectedTo({ query, target, disjunct, invert, bondTest }: IsC return ret.getSelection(); }; -} \ No newline at end of file +} diff --git a/src/mol-model/structure/query/queries/generators.ts b/src/mol-model/structure/query/queries/generators.ts index e963dc2a5b2eb62f408ccde5f5bbfbf000c9510e..807114c5e2ccde81788992acb01c9719c19ec92e 100644 --- a/src/mol-model/structure/query/queries/generators.ts +++ b/src/mol-model/structure/query/queries/generators.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -322,7 +322,7 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery { for (const unit of structure.units) { if (unit.kind !== Unit.Kind.Atomic) continue; - const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order } } = unit.bonds; + const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order, key } } = unit.bonds; atomicBond.a.unit = unit; atomicBond.b.unit = unit; for (let i = 0 as StructureElement.UnitIndex, _i = unit.elements.length; i < _i; i++) { @@ -335,6 +335,7 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery { atomicBond.b.element = unit.elements[intraBondB[lI]]; atomicBond.type = flags[lI]; atomicBond.order = order[lI]; + atomicBond.key = key[lI]; // No need to "swap test" because each bond direction will be visited eventually. if (atomicBond.test(ctx, false)) { const b = structure.subsetBuilder(false); @@ -358,6 +359,7 @@ export function bondedAtomicPairs(bondTest?: QueryPredicate): StructureQuery { atomicBond.bIndex = bond.indexB; atomicBond.order = bond.props.order; atomicBond.type = bond.props.flag; + atomicBond.key = bond.props.key; // No need to "swap test" because each bond direction will be visited eventually. if (atomicBond.test(ctx, false)) { diff --git a/src/mol-model/structure/query/queries/modifiers.ts b/src/mol-model/structure/query/queries/modifiers.ts index c860695950b6b3430e8d6f8effa9843e4456b396..f10e9f7c7683243b6795c70e74c994d624678b5d 100644 --- a/src/mol-model/structure/query/queries/modifiers.ts +++ b/src/mol-model/structure/query/queries/modifiers.ts @@ -1,7 +1,8 @@ /** - * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { Segmentation, SortedArray } from '../../../../mol-data/int'; @@ -370,7 +371,7 @@ function expandConnected(ctx: QueryContext, structure: Structure) { } const inputUnitA = inputStructure.unitMap.get(unit.id) as Unit.Atomic; - const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order } } = inputUnitA.bonds; + const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order, key } } = inputUnitA.bonds; atomicBond.setStructure(inputStructure); @@ -397,6 +398,7 @@ function expandConnected(ctx: QueryContext, structure: Structure) { atomicBond.b.element = bElement; atomicBond.type = flags[lI]; atomicBond.order = order[lI]; + atomicBond.key = key[lI]; if (atomicBond.test(ctx, true)) { builder.addToUnit(unit.id, bElement); @@ -427,6 +429,7 @@ function expandConnected(ctx: QueryContext, structure: Structure) { atomicBond.b.element = bElement; atomicBond.type = bond.props.flag; atomicBond.order = bond.props.order; + atomicBond.key = bond.props.key; if (atomicBond.test(ctx, true)) { builder.addToUnit(bondedUnit.unitB, bElement); diff --git a/src/mol-model/structure/structure/carbohydrates/compute.ts b/src/mol-model/structure/structure/carbohydrates/compute.ts index 20949907ab2b3a92d602121b0b429571d27e049a..3211b795adbd29230d68a7304262cf0de3dbbf9f 100644 --- a/src/mol-model/structure/structure/carbohydrates/compute.ts +++ b/src/mol-model/structure/structure/carbohydrates/compute.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> @@ -141,7 +141,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { Vec3.normalize(elements[iA].geometry.direction, elements[iA].geometry.direction); } - const tmpV = Vec3.zero(); + const tmpV = Vec3(); function fixTerminalLinkDirection(iA: number, indexB: number, unitB: Unit.Atomic) { const pos = unitB.conformation.position, geo = elements[iA].geometry; Vec3.sub(geo.direction, pos(unitB.elements[indexB], tmpV), geo.center); @@ -189,9 +189,10 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { const anomericCarbon = getAnomericCarbon(unit, ringAtoms); const ma = PrincipalAxes.calculateMomentsAxes(getPositions(unit, ringAtoms)); - const center = Vec3.copy(Vec3.zero(), ma.origin); - const normal = Vec3.copy(Vec3.zero(), ma.dirC); - const direction = getDirection(Vec3.zero(), unit, anomericCarbon, center); + const a = PrincipalAxes.calculateNormalizedAxes(ma); + const center = Vec3.copy(Vec3(), a.origin); + const normal = Vec3.copy(Vec3(), a.dirC); + const direction = getDirection(Vec3(), unit, anomericCarbon, center); Vec3.orthogonalize(direction, normal, direction); const ringAltId = UnitRing.getAltId(unit, ringAtoms); diff --git a/src/mol-model/structure/structure/carbohydrates/constants.ts b/src/mol-model/structure/structure/carbohydrates/constants.ts index bc9e49e728e386db75c92dfd118ee3e875d3de9d..aeb5fd82a1c2515becfcf439750f9fb7ccb35d8d 100644 --- a/src/mol-model/structure/structure/carbohydrates/constants.ts +++ b/src/mol-model/structure/structure/carbohydrates/constants.ts @@ -386,16 +386,6 @@ const DefaultSaccharideCompIdMap = (function () { map.set(charmm[j], saccharide); } } - - const glycam = GlycamSaccharideNames[saccharide.abbr]; - if (glycam) { - for (let j = 0, jl = glycam.length; j < jl; ++j) { - // On collision, use PDB name as default. - if (!map.has(glycam[j])) { - map.set(glycam[j], saccharide); - } - } - } } SaccharideNames.forEach(name => { if (!map.has(name)) map.set(name, UnknownSaccharideComponent); diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index 81f84f447fa5c17191641c2d8c3b81594bcdc08d..afcec12747ba27d40354809ab07bb8cc603366b1 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -23,7 +23,7 @@ import { Carbohydrates } from './carbohydrates/data'; import { computeCarbohydrates } from './carbohydrates/compute'; import { Vec3, Mat4 } from '../../../mol-math/linear-algebra'; import { idFactory } from '../../../mol-util/id-factory'; -import { GridLookup3D } from '../../../mol-math/geometry'; +import { Box3D, GridLookup3D } from '../../../mol-math/geometry'; import { UUID } from '../../../mol-util'; import { CustomProperties } from '../../custom-property'; import { AtomicHierarchy } from '../model/properties/atomic'; @@ -43,6 +43,8 @@ type State = { lookup3d?: StructureLookup3D, interUnitBonds?: InterUnitBonds, dynamicBonds: boolean, + interBondsValidUnit?: (unit: Unit) => boolean, + interBondsValidUnitPair?: (structure: Structure, unitA: Unit, unitB: Unit) => boolean, unitSymmetryGroups?: ReadonlyArray<Unit.SymmetryGroup>, unitSymmetryGroupsIndexMap?: IntMap<number>, unitsSortedByVolume?: ReadonlyArray<Unit>; @@ -241,6 +243,8 @@ class Structure { this.state.interUnitBonds = computeInterUnitBonds(this, { ignoreWater: !this.dynamicBonds, ignoreIon: !this.dynamicBonds, + validUnit: this.state.interBondsValidUnit, + validUnitPair: this.state.interBondsValidUnitPair, }); } return this.state.interUnitBonds; @@ -250,6 +254,14 @@ class Structure { return this.state.dynamicBonds; } + get interBondsValidUnit() { + return this.state.interBondsValidUnit; + } + + get interBondsValidUnitPair() { + return this.state.interBondsValidUnitPair; + } + get unitSymmetryGroups(): ReadonlyArray<Unit.SymmetryGroup> { if (this.state.unitSymmetryGroups) return this.state.unitSymmetryGroups; this.state.unitSymmetryGroups = StructureSymmetry.computeTransformGroups(this); @@ -380,7 +392,12 @@ class Structure { parent: parent?.remapModel(m), label: this.label, interUnitBonds: dynamicBonds ? undefined : interUnitBonds, - dynamicBonds + dynamicBonds, + interBondsValidUnit: this.state.interBondsValidUnit, + interBondsValidUnitPair: this.state.interBondsValidUnitPair, + coordinateSystem: this.state.coordinateSystem, + masterModel: this.state.masterModel, + representativeModel: this.state.representativeModel, }); } @@ -428,7 +445,6 @@ class Structure { function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) { return units[i].id - units[j].id; - } function getModels(s: Structure) { @@ -634,6 +650,8 @@ namespace Structure { * Also enables calculation of inter-unit bonds in water molecules. */ dynamicBonds?: boolean, + interBondsValidUnit?: (unit: Unit) => boolean, + interBondsValidUnitPair?: (structure: Structure, unitA: Unit, unitB: Unit) => boolean, coordinateSystem?: SymmetryOperator label?: string /** Master model for structures of a protein model and multiple ligand models */ @@ -722,6 +740,12 @@ namespace Structure { if (props.parent) state.parent = props.parent.parent || props.parent; if (props.interUnitBonds) state.interUnitBonds = props.interUnitBonds; + if (props.interBondsValidUnit) state.interBondsValidUnit = props.interBondsValidUnit; + else if (props.parent) state.interBondsValidUnit = props.parent.interBondsValidUnit; + + if (props.interBondsValidUnitPair) state.interBondsValidUnitPair = props.interBondsValidUnitPair; + else if (props.parent) state.interBondsValidUnitPair = props.parent.interBondsValidUnitPair; + if (props.dynamicBonds) state.dynamicBonds = props.dynamicBonds; else if (props.parent) state.dynamicBonds = props.parent.dynamicBonds; @@ -1180,7 +1204,7 @@ namespace Structure { /** * Iterate over all unit pairs of a structure and invokes callback for valid units - * and unit pairs if within a max distance. + * and unit pairs if their boundaries are within a max distance. */ export function eachUnitPair(structure: Structure, callback: (unitA: Unit, unitB: Unit) => void, props: EachUnitPairProps) { const { maxRadius, validUnit, validUnitPair } = props; @@ -1188,15 +1212,19 @@ namespace Structure { const lookup = structure.lookup3d; const imageCenter = Vec3(); + const bbox = Box3D(); + const rvec = Vec3.create(maxRadius, maxRadius, maxRadius); for (const unit of structure.units) { if (!validUnit(unit)) continue; const bs = unit.boundary.sphere; + Box3D.expand(bbox, unit.boundary.box, rvec); Vec3.transformMat4(imageCenter, bs.center, unit.conformation.operator.matrix); const closeUnits = lookup.findUnitIndices(imageCenter[0], imageCenter[1], imageCenter[2], bs.radius + maxRadius); for (let i = 0; i < closeUnits.count; i++) { const other = structure.units[closeUnits.indices[i]]; + if (!Box3D.overlaps(bbox, other.boundary.box)) continue; if (!validUnit(other) || unit.id >= other.id || !validUnitPair(unit, other)) continue; if (other.elements.length >= unit.elements.length) callback(unit, other); @@ -1329,6 +1357,9 @@ namespace Structure { export type Index = number; export const Index = CustomStructureProperty.createSimple<Index>('index', 'root'); + export type MaxIndex = number; + export const MaxIndex = CustomStructureProperty.createSimple<MaxIndex>('max_index', 'root'); + const PrincipalAxesProp = '__PrincipalAxes__'; export function getPrincipalAxes(structure: Structure): PrincipalAxes { if (structure.currentPropertyData[PrincipalAxesProp]) return structure.currentPropertyData[PrincipalAxesProp]; diff --git a/src/mol-model/structure/structure/unit/bonds/data.ts b/src/mol-model/structure/structure/unit/bonds/data.ts index b05df8ed1d0e27a6a5e1c4720741bcc6ae11d356..f2ab500a86fbb09a31bc4f4d72dc6ecd99cf195d 100644 --- a/src/mol-model/structure/structure/unit/bonds/data.ts +++ b/src/mol-model/structure/structure/unit/bonds/data.ts @@ -15,16 +15,17 @@ import { InterUnitGraph } from '../../../../../mol-math/graph/inter-unit-graph'; type IntraUnitBonds = IntAdjacencyGraph<StructureElement.UnitIndex, { readonly order: ArrayLike<number>, readonly flags: ArrayLike<BondType.Flag> + readonly key: ArrayLike<number>, }, { /** can remap even with dynamicBonds on, e.g., for water molecules */ readonly canRemap?: boolean }> namespace IntraUnitBonds { - export const Empty: IntraUnitBonds = IntAdjacencyGraph.create([], [], [], 0, { flags: [], order: [] }); + export const Empty: IntraUnitBonds = IntAdjacencyGraph.create([], [], [], 0, { flags: [], order: [], key: [] }); } -type InterUnitEdgeProps = { readonly order: number, readonly flag: BondType.Flag } +type InterUnitEdgeProps = { readonly order: number, readonly flag: BondType.Flag, readonly key: number } class InterUnitBonds extends InterUnitGraph<number, StructureElement.UnitIndex, InterUnitEdgeProps> { /** Get inter-unit bond given a bond-location */ diff --git a/src/mol-model/structure/structure/unit/bonds/inter-compute.ts b/src/mol-model/structure/structure/unit/bonds/inter-compute.ts index 535a6d09bca596e00119faffd2cf2fcefafd9344..74f5127df14aabdc3ba3ec71e6ca40ba1c92804c 100644 --- a/src/mol-model/structure/structure/unit/bonds/inter-compute.ts +++ b/src/mol-model/structure/structure/unit/bonds/inter-compute.ts @@ -21,12 +21,18 @@ import { StructConn } from '../../../../../mol-model-formats/structure/property/ import { equalEps } from '../../../../../mol-math/linear-algebra/3d/common'; import { Model } from '../../../model'; +// avoiding namespace lookup improved performance in Chrome (Aug 2020) +const v3distance = Vec3.distance; +const v3set = Vec3.set; +const v3squaredDistance = Vec3.squaredDistance; +const v3transformMat4 = Vec3.transformMat4; + const tmpDistVecA = Vec3(); const tmpDistVecB = Vec3(); function getDistance(unitA: Unit.Atomic, indexA: ElementIndex, unitB: Unit.Atomic, indexB: ElementIndex) { unitA.conformation.position(indexA, tmpDistVecA); unitB.conformation.position(indexB, tmpDistVecB); - return Vec3.distance(tmpDistVecA, tmpDistVecB); + return v3distance(tmpDistVecA, tmpDistVecB); } const _imageTransform = Mat4(); @@ -68,22 +74,22 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) { const aI = atomsA[_aI]; - Vec3.set(_imageA, xA[aI], yA[aI], zA[aI]); - if (isNotIdentity) Vec3.transformMat4(_imageA, _imageA, imageTransform); - if (Vec3.squaredDistance(_imageA, bCenter) > testDistanceSq) continue; + v3set(_imageA, xA[aI], yA[aI], zA[aI]); + if (isNotIdentity) v3transformMat4(_imageA, _imageA, imageTransform); + if (v3squaredDistance(_imageA, bCenter) > testDistanceSq) continue; if (!props.forceCompute && indexPairs) { const { maxDistance } = indexPairs; - const { offset, b, edgeProps: { order, distance, flag } } = indexPairs.bonds; + const { offset, b, edgeProps: { order, distance, flag, key } } = indexPairs.bonds; const srcA = sourceIndex.value(aI); + const aeI = getElementIdx(type_symbolA.value(aI)); for (let i = offset[srcA], il = offset[srcA + 1]; i < il; ++i) { const bI = invertedIndex![b[i]]; const _bI = SortedArray.indexOf(unitB.elements, bI) as StructureElement.UnitIndex; if (_bI < 0) continue; - const aeI = getElementIdx(type_symbolA.value(aI)); const beI = getElementIdx(type_symbolA.value(bI)); const d = distance[i]; @@ -107,7 +113,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput } if (add) { - builder.add(_aI, _bI, { order: order[i], flag: flag[i] }); + builder.add(_aI, _bI, { order: order[i], flag: flag[i], key: key[i] }); } } continue; // assume `indexPairs` supplies all bonds @@ -125,7 +131,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput // check if the bond is within MAX_RADIUS for this pair of units if (getDistance(unitA, aI, unitB, p.atomIndex) > maxRadius) continue; - builder.add(_aI, _bI, { order: se.order, flag: se.flags }); + builder.add(_aI, _bI, { order: se.order, flag: se.flags, key: se.rowIndex }); added = true; } // assume, for an atom, that if any inter unit bond is given @@ -181,7 +187,8 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput const compIdB = label_comp_idB.value(residueIndexB[bI]); builder.add(_aI, _bI, { order: getInterBondOrderFromTable(compIdA, compIdB, atomIdA, atomIdB), - flag: (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed + flag: (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed, + key: -1 }); } } @@ -191,6 +198,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput } export interface InterBondComputationProps extends BondComputationProps { + validUnit: (unit: Unit) => boolean validUnitPair: (structure: Structure, unitA: Unit, unitB: Unit) => boolean ignoreWater: boolean ignoreIon: boolean @@ -215,7 +223,7 @@ function findBonds(structure: Structure, props: InterBondComputationProps) { findPairBonds(unitA as Unit.Atomic, unitB as Unit.Atomic, props, builder); }, { maxRadius: props.maxRadius, - validUnit: (unit: Unit) => Unit.isAtomic(unit), + validUnit: (unit: Unit) => props.validUnit(unit), validUnitPair: (unitA: Unit, unitB: Unit) => props.validUnitPair(structure, unitA, unitB) }); @@ -226,6 +234,7 @@ function computeInterUnitBonds(structure: Structure, props?: Partial<InterBondCo const p = { ...DefaultInterBondComputationProps, ...props }; return findBonds(structure, { ...p, + validUnit: (props && props.validUnit) || (u => Unit.isAtomic(u)), validUnitPair: (props && props.validUnitPair) || ((s, a, b) => { const mtA = a.model.atomicHierarchy.derived.residue.moleculeType; const mtB = b.model.atomicHierarchy.derived.residue.moleculeType; diff --git a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts index 105347895b27c9080d7e5daeca079c975cbb6261..29fd505381b21fd4791c49718f3900b8eeb001cb 100644 --- a/src/mol-model/structure/structure/unit/bonds/intra-compute.ts +++ b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts @@ -21,17 +21,22 @@ import { ElementIndex } from '../../../model/indexing'; import { equalEps } from '../../../../../mol-math/linear-algebra/3d/common'; import { Model } from '../../../model/model'; -function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], atomCount: number, canRemap: boolean): IntraUnitBonds { +// avoiding namespace lookup improved performance in Chrome (Aug 2020) +const v3distance = Vec3.distance; + +function getGraph(atomA: StructureElement.UnitIndex[], atomB: StructureElement.UnitIndex[], _order: number[], _flags: number[], _key: number[], atomCount: number, canRemap: boolean): IntraUnitBonds { const builder = new IntAdjacencyGraph.EdgeBuilder(atomCount, atomA, atomB); const flags = new Uint16Array(builder.slotCount); const order = new Int8Array(builder.slotCount); + const key = new Uint32Array(builder.slotCount); for (let i = 0, _i = builder.edgeCount; i < _i; i++) { builder.addNextEdge(); builder.assignProperty(flags, _flags[i]); builder.assignProperty(order, _order[i]); + builder.assignProperty(key, _key[i]); } - return builder.createGraph({ flags, order }, { canRemap }); + return builder.createGraph({ flags, order, key }, { canRemap }); } const tmpDistVecA = Vec3(); @@ -39,7 +44,7 @@ const tmpDistVecB = Vec3(); function getDistance(unit: Unit.Atomic, indexA: ElementIndex, indexB: ElementIndex) { unit.conformation.position(indexA, tmpDistVecA); unit.conformation.position(indexB, tmpDistVecB); - return Vec3.distance(tmpDistVecA, tmpDistVecB); + return v3distance(tmpDistVecA, tmpDistVecB); } const __structConnAdded = new Set<StructureElement.UnitIndex>(); @@ -50,7 +55,7 @@ function findIndexPairBonds(unit: Unit.Atomic) { const { type_symbol } = unit.model.atomicHierarchy.atoms; const atomCount = unit.elements.length; const { maxDistance } = indexPairs; - const { offset, b, edgeProps: { order, distance, flag } } = indexPairs.bonds; + const { offset, b, edgeProps: { order, distance, flag, key } } = indexPairs.bonds; const { atomSourceIndex: sourceIndex } = unit.model.atomicHierarchy; const { invertedIndex } = Model.getInvertedAtomSourceIndex(unit.model); @@ -59,6 +64,7 @@ function findIndexPairBonds(unit: Unit.Atomic) { const atomB: StructureElement.UnitIndex[] = []; const flags: number[] = []; const orders: number[] = []; + const keys: number[] = []; for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) { const aI = atoms[_aI]; @@ -101,11 +107,12 @@ function findIndexPairBonds(unit: Unit.Atomic) { atomB[atomB.length] = _bI; orders[orders.length] = order[i]; flags[flags.length] = flag[i]; + keys[keys.length] = key[i]; } } } - return getGraph(atomA, atomB, orders, flags, atomCount, false); + return getGraph(atomA, atomB, orders, flags, keys, atomCount, false); } function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBonds { @@ -129,9 +136,10 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon const atomB: StructureElement.UnitIndex[] = []; const flags: number[] = []; const order: number[] = []; + const key: number[] = []; let lastResidue = -1; - let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0; + let componentMap: Map<string, Map<string, { flags: number, order: number, key: number }>> | undefined = void 0; let isWatery = true, isDictionaryBased = true, isSequenced = true; @@ -159,6 +167,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon atomB[atomB.length] = _bI; flags[flags.length] = se.flags; order[order.length] = se.order; + key[key.length] = se.rowIndex; if (!hasStructConn) structConnAdded.clear(); hasStructConn = true; @@ -227,6 +236,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon flag |= BondType.Flag.MetallicCoordination; } flags[flags.length] = flag; + key[key.length] = e.key; } continue; } @@ -240,6 +250,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon atomB[atomB.length] = _bI; order[order.length] = getIntraBondOrderFromTable(compId, atomIdA, label_atom_id.value(bI)); flags[flags.length] = (isMetal ? BondType.Flag.MetallicCoordination : BondType.Flag.Covalent) | BondType.Flag.Computed; + key[key.length] = -1; const seqIdB = label_seq_id.value(rbI); @@ -250,7 +261,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon } const canRemap = isWatery || (isDictionaryBased && isSequenced); - return getGraph(atomA, atomB, order, flags, atomCount, canRemap); + return getGraph(atomA, atomB, order, flags, key, atomCount, canRemap); } function computeIntraUnitBonds(unit: Unit.Atomic, props?: Partial<BondComputationProps>) { diff --git a/src/mol-model/structure/structure/util/lookup3d.ts b/src/mol-model/structure/structure/util/lookup3d.ts index 49de19cbd20e0ffa6790767068e6f519aabc48fe..693d17aca13aaa4d607ab3826a3a6b834deaca40 100644 --- a/src/mol-model/structure/structure/util/lookup3d.ts +++ b/src/mol-model/structure/structure/util/lookup3d.ts @@ -3,6 +3,7 @@ * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Gianluca Tomasello <giagitom@gmail.com> */ import { Structure } from '../structure'; @@ -13,6 +14,7 @@ import { StructureUniqueSubsetBuilder } from './unique-subset-builder'; import { StructureElement } from '../element'; import { Unit } from '../unit'; import { UnitIndex } from '../element/util'; +import { FibonacciHeap } from '../../../../mol-util/fibonacci-heap'; export interface StructureResult extends Result<StructureElement.UnitIndex> { units: Unit[] @@ -54,6 +56,7 @@ export function StructureLookup3DResultContext(): StructureLookup3DResultContext export class StructureLookup3D { private unitLookup: Lookup3D; private pivot = Vec3(); + private heap = new FibonacciHeap(); findUnitIndices(x: number, y: number, z: number, radius: number): Result<number> { return this.unitLookup.find(x, y, z, radius); @@ -86,6 +89,54 @@ export class StructureLookup3D { return ctx.result; } + nearest(x: number, y: number, z: number, k: number = 1, ctx?: StructureLookup3DResultContext): StructureResult { + return this._nearest(x, y, z, k, ctx ?? this.findContext); + } + + _nearest(x: number, y: number, z: number, k: number, ctx: StructureLookup3DResultContext): StructureResult { + const result = ctx.result, heap = this.heap; + Result.reset(result); + heap.clear(); + const { units } = this.structure; + let elementsCount = 0; + const closeUnits = this.unitLookup.nearest(x, y, z, units.length, (uid: number) => (elementsCount += units[uid].elements.length) >= k, ctx.closeUnitsResult); // sort units based on distance to the point + if (closeUnits.count === 0) return result; + let totalCount = 0, maxDistResult = -Number.MAX_VALUE; + for (let t = 0, _t = closeUnits.count; t < _t; t++) { + const unitSqDist = closeUnits.squaredDistances[t]; + if (totalCount >= k && maxDistResult < unitSqDist) break; + Vec3.set(this.pivot, x, y, z); + const unit = units[closeUnits.indices[t]]; + if (!unit.conformation.operator.isIdentity) { + Vec3.transformMat4(this.pivot, this.pivot, unit.conformation.operator.inverse); + } + const unitLookup = unit.lookup3d; + const groupResult = unitLookup.nearest(this.pivot[0], this.pivot[1], this.pivot[2], k, void 0, ctx.unitGroupResult); + if (groupResult.count === 0) continue; + totalCount += groupResult.count; + maxDistResult = Math.max(maxDistResult, groupResult.squaredDistances[groupResult.count - 1]); + for (let j = 0, _j = groupResult.count; j < _j; j++) { + heap.insert(groupResult.squaredDistances[j], { index: groupResult.indices[j], unit: unit }); + } + } + if (k === 1) { + const node = heap.findMinimum(); + if (node) { + const { key: squaredDistance } = node; + const { unit, index } = node.value as { index: UnitIndex, unit: Unit }; + StructureResult.add(result, unit as Unit, index as UnitIndex, squaredDistance as number); + } + } else { + while (!heap.isEmpty() && result.count < k) { + const node = heap.extractMinimum(); + const { key: squaredDistance } = node!; + const { unit, index } = node!.value as { index: UnitIndex, unit: Unit }; + StructureResult.add(result, unit as Unit, index as UnitIndex, squaredDistance as number); + } + } + return result; + } + findIntoBuilder(x: number, y: number, z: number, radius: number, builder: StructureUniqueSubsetBuilder) { const { units } = this.structure; const closeUnits = this.unitLookup.find(x, y, z, radius); @@ -217,4 +268,4 @@ export class StructureLookup3D { const position = { x: xs, y: ys, z: zs, radius, indices: OrderedSet.ofBounds(0, unitCount) }; this.unitLookup = GridLookup3D(position, boundary); } -} \ No newline at end of file +} diff --git a/src/mol-model/structure/structure/util/superposition-sifts-mapping.ts b/src/mol-model/structure/structure/util/superposition-sifts-mapping.ts index 4abf51e51762533338aad2ce4fe2f73e41d63c52..48fec9d0bd6fa744c5a4e701b56d19a659cd39ed 100644 --- a/src/mol-model/structure/structure/util/superposition-sifts-mapping.ts +++ b/src/mol-model/structure/structure/util/superposition-sifts-mapping.ts @@ -8,7 +8,8 @@ import { Segmentation } from '../../../../mol-data/int'; import { MinimizeRmsd } from '../../../../mol-math/linear-algebra/3d/minimize-rmsd'; import { SIFTSMapping } from '../../../../mol-model-props/sequence/sifts-mapping'; -import { ElementIndex } from '../../model/indexing'; +import { ElementIndex, ResidueIndex } from '../../model/indexing'; +import { StructureElement } from '../element'; import { Structure } from '../structure'; import { Unit } from '../unit'; @@ -24,11 +25,16 @@ export interface AlignmentResult { failedPairs: [number, number][] } -export function alignAndSuperposeWithSIFTSMapping(structures: Structure[], options?: { traceOnly?: boolean }): AlignmentResult { +type IncludeResidueTest = (traceElementOrFirstAtom: StructureElement.Location<Unit.Atomic>, residueIndex: ResidueIndex, startIndex: ElementIndex, endIndex: ElementIndex) => boolean + +export function alignAndSuperposeWithSIFTSMapping( + structures: Structure[], + options?: { traceOnly?: boolean, includeResidueTest?: IncludeResidueTest } +): AlignmentResult { const indexMap = new Map<string, IndexEntry>(); for (let i = 0; i < structures.length; i++) { - buildIndex(structures[i], indexMap, i, options?.traceOnly ?? true); + buildIndex(structures[i], indexMap, i, options?.traceOnly ?? true, options?.includeResidueTest ?? _includeAllResidues); } const index = Array.from(indexMap.values()); @@ -137,11 +143,16 @@ interface IndexEntry { pivots: { [i: number]: [unit: Unit.Atomic, start: ElementIndex, end: ElementIndex] | undefined } } -function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: number, traceOnly: boolean) { +function _includeAllResidues() { return true; } + +function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: number, traceOnly: boolean, includeTest: IncludeResidueTest) { + const loc = StructureElement.Location.create<Unit.Atomic>(structure); + for (const unit of structure.units) { if (unit.kind !== Unit.Kind.Atomic) continue; const { elements, model } = unit; + loc.unit = unit; const map = SIFTSMapping.Provider.get(model).value; if (!map) return; @@ -161,9 +172,11 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu if (!dbName[rI]) continue; + const traceElement = traceElementIndex[rI]; + let start, end; if (traceOnly) { - start = traceElementIndex[rI]; + start = traceElement; if (start === -1) continue; end = start + 1 as ElementIndex; } else { @@ -171,6 +184,9 @@ function buildIndex(structure: Structure, index: Map<string, IndexEntry>, sI: nu end = elements[residueSegment.end - 1] + 1 as ElementIndex; } + loc.element = (traceElement >= 0 ? traceElement : start) as ElementIndex; + if (!includeTest(loc, rI, start, end)) continue; + const key = `${dbName[rI]}-${accession[rI]}-${num[rI]}`; if (!index.has(key)) { diff --git a/src/mol-model/volume/volume.ts b/src/mol-model/volume/volume.ts index 8a322563ae06817f174312c55f413357baded5a1..741f51864665d8225117d11fda6117cb9efb1fa0 100644 --- a/src/mol-model/volume/volume.ts +++ b/src/mol-model/volume/volume.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -126,12 +126,12 @@ export namespace Volume { 'absolute': PD.Converted( (v: Volume.IsoValue) => Volume.IsoValue.toAbsolute(v, Grid.One.stats).absoluteValue, (v: number) => Volume.IsoValue.absolute(v), - PD.Numeric(mean, { min, max, step: toPrecision(sigma / 100, 2) }) + PD.Numeric(mean, { min, max, step: toPrecision(sigma / 100, 2) }, { immediateUpdate: true }) ), 'relative': PD.Converted( (v: Volume.IsoValue) => Volume.IsoValue.toRelative(v, Grid.One.stats).relativeValue, (v: number) => Volume.IsoValue.relative(v), - PD.Numeric(Math.min(1, relMax), { min: relMin, max: relMax, step: toPrecision(Math.round(((max - min) / sigma)) / 100, 2) }) + PD.Numeric(Math.min(1, relMax), { min: relMin, max: relMax, step: toPrecision(Math.round(((max - min) / sigma)) / 100, 2) }, { immediateUpdate: true }) ) }, (v: Volume.IsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative', @@ -219,4 +219,14 @@ export namespace Volume { return Sphere3D.expand(bs, bs, Mat4.getMaxScaleOnAxis(transform) * 10); } } + + export type PickingGranularity = 'volume' | 'object' | 'voxel'; + export const PickingGranularity = { + set(volume: Volume, granularity: PickingGranularity) { + volume._propertyData['__picking_granularity__'] = granularity; + }, + get(volume: Volume): PickingGranularity { + return volume._propertyData['__picking_granularity__'] ?? 'voxel'; + } + }; } \ No newline at end of file diff --git a/src/mol-plugin-state/actions/file.ts b/src/mol-plugin-state/actions/file.ts index 0f92b915b180d8626daf8cab3a1e460bfc19965a..ee81b0793d6aae3808e1a467f75ab1fc00393dfe 100644 --- a/src/mol-plugin-state/actions/file.ts +++ b/src/mol-plugin-state/actions/file.ts @@ -83,7 +83,7 @@ export const DownloadFile = StateAction.build({ display: { name: 'Download File', description: 'Load one or more file from an URL' }, from: PluginStateObject.Root, params: (a, ctx: PluginContext) => { - const options = [...ctx.dataFormats.options, ['zip', 'Zip'] as const]; + const options = [...ctx.dataFormats.options, ['zip', 'Zip'] as const, ['gzip', 'Gzip'] as const]; return { url: PD.Url(''), format: PD.Select(options[0][0], options), @@ -96,17 +96,23 @@ export const DownloadFile = StateAction.build({ await state.transaction(async () => { try { - if (params.format === 'zip') { + if (params.format === 'zip' || params.format === 'gzip') { // TODO: add ReadZipFile transformer so this can be saved as a simple state snaphot, // would need support for extracting individual files from zip const data = await plugin.builders.data.download({ url: params.url, isBinary: true }); - const zippedFiles = await unzip(taskCtx, (data.obj?.data as Uint8Array).buffer); - for (const [fn, filedata] of Object.entries(zippedFiles)) { - if (!(filedata instanceof Uint8Array) || filedata.length === 0) continue; + if (params.format === 'zip') { + const zippedFiles = await unzip(taskCtx, (data.obj?.data as Uint8Array).buffer); + for (const [fn, filedata] of Object.entries(zippedFiles)) { + if (!(filedata instanceof Uint8Array) || filedata.length === 0) continue; - const asset = Asset.File(new File([filedata], fn)); + const asset = Asset.File(new File([filedata], fn)); - await processFile(asset, plugin, 'auto', params.visuals); + await processFile(asset, plugin, 'auto', params.visuals); + } + } else { + const url = Asset.getUrl(params.url); + const info = getFileInfo(url); + await processFile(Asset.File(new File([data.obj?.data as Uint8Array], info.name)), plugin, 'auto', params.visuals); } } else { const provider = plugin.dataFormats.get(params.format); diff --git a/src/mol-plugin-state/actions/structure.ts b/src/mol-plugin-state/actions/structure.ts index 630c5d0cdf9f706e3092dd585b217cbb5e29caaa..61765b5a22b2ebc3d3b3592e98c5f0a48357fd20 100644 --- a/src/mol-plugin-state/actions/structure.ts +++ b/src/mol-plugin-state/actions/structure.ts @@ -90,6 +90,7 @@ const DownloadStructure = StateAction.build({ url: PD.Url(''), format: PD.Select<BuiltInTrajectoryFormat>('mmcif', PD.arrayToOptions(BuiltInTrajectoryFormats.map(f => f[0]), f => f)), isBinary: PD.Boolean(false), + label: PD.Optional(PD.Text('')), options }, { isFlat: true, label: 'URL' }) }) @@ -104,7 +105,7 @@ const DownloadStructure = StateAction.build({ switch (src.name) { case 'url': - downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary }]; + downloadParams = [{ url: src.params.url, isBinary: src.params.isBinary, label: src.params.label || undefined }]; format = src.params.format; break; case 'pdb': diff --git a/src/mol-plugin-state/builder/structure/hierarchy-preset.ts b/src/mol-plugin-state/builder/structure/hierarchy-preset.ts index b4b531e1585bb4057ae7a544c4949893f8beee8c..320df11dfe925d31383e342a7b6b1215001d508c 100644 --- a/src/mol-plugin-state/builder/structure/hierarchy-preset.ts +++ b/src/mol-plugin-state/builder/structure/hierarchy-preset.ts @@ -86,7 +86,7 @@ const allModels = TrajectoryHierarchyPresetProvider({ id: 'preset-trajectory-all-models', display: { name: 'All Models', group: 'Preset', - description: 'Shows all models; colored by model-index.' + description: 'Shows all models; colored by trajectory-index.' }, isApplicable: o => { return o.data.frameCount > 1; @@ -115,7 +115,7 @@ const allModels = TrajectoryHierarchyPresetProvider({ const quality = structure.obj ? getStructureQuality(structure.obj.data, { elementCountFactor: tr.frameCount }) : 'medium'; const representationPreset = params.representationPreset || plugin.config.get(PluginConfig.Structure.DefaultRepresentationPreset) || PresetStructureRepresentations.auto.id; - await builder.representation.applyPreset(structureProperties, representationPreset, { theme: { globalName: 'model-index' }, quality }); + await builder.representation.applyPreset(structureProperties, representationPreset, { theme: { globalName: 'trajectory-index' }, quality }); } return { models, structures }; diff --git a/src/mol-plugin-state/builder/structure/representation-preset.ts b/src/mol-plugin-state/builder/structure/representation-preset.ts index b581666d5548291d6082590eca1fadae460ccd94..25f2d1f907f1772b52a9da01ff27d0d91df9bfad 100644 --- a/src/mol-plugin-state/builder/structure/representation-preset.ts +++ b/src/mol-plugin-state/builder/structure/representation-preset.ts @@ -41,8 +41,10 @@ export namespace StructureRepresentationPresetProvider { quality: PD.Optional(PD.Select<VisualQuality>('auto', VisualQualityOptions)), theme: PD.Optional(PD.Group({ globalName: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')), + globalColorParams: PD.Optional(PD.Value<any>({}, { isHidden: true })), carbonColor: PD.Optional(PD.Select('chain-id', PD.arrayToOptions(['chain-id', 'operator-name', 'element-symbol'] as const))), symmetryColor: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')), + symmetryColorParams: PD.Optional(PD.Value<any>({}, { isHidden: true })), focus: PD.Optional(PD.Group({ name: PD.Optional(PD.Text<ColorTheme.BuiltIn>('')), params: PD.Optional(PD.Value<ColorTheme.BuiltInParams<ColorTheme.BuiltIn>>({} as any)) @@ -76,13 +78,15 @@ export namespace StructureRepresentationPresetProvider { if (params.ignoreLight !== void 0) typeParams.ignoreLight = !!params.ignoreLight; const color: ColorTheme.BuiltIn | undefined = params.theme?.globalName ? params.theme?.globalName : void 0; const ballAndStickColor: ColorTheme.BuiltInParams<'element-symbol'> = params.theme?.carbonColor !== undefined - ? { carbonColor: getCarbonColorParams(params.theme?.carbonColor) } - : { }; + ? { carbonColor: getCarbonColorParams(params.theme?.carbonColor), ...params.theme?.globalColorParams } + : { ...params.theme?.globalColorParams }; const symmetryColor: ColorTheme.BuiltIn | undefined = structure && params.theme?.symmetryColor ? isSymmetry(structure) ? params.theme?.symmetryColor : color : color; + const symmetryColorParams = params.theme?.symmetryColorParams ? { ...params.theme?.globalColorParams, ...params.theme?.symmetryColorParams } : { ...params.theme?.globalColorParams }; + const globalColorParams = params.theme?.globalColorParams ? { ...params.theme?.globalColorParams } : undefined; - return { update, builder, color, symmetryColor, typeParams, ballAndStickColor }; + return { update, builder, color, symmetryColor, symmetryColorParams, globalColorParams, typeParams, ballAndStickColor }; } export function updateFocusRepr<T extends ColorTheme.BuiltIn>(plugin: PluginContext, structure: Structure, themeName: T | undefined, themeParams: ColorTheme.BuiltInParams<T> | undefined) { @@ -177,18 +181,18 @@ const polymerAndLigand = StructureRepresentationPresetProvider({ const waterType = (components.water?.obj?.data?.elementCount || 0) > 50_000 ? 'line' : 'ball-and-stick'; const lipidType = (components.lipid?.obj?.data?.elementCount || 0) > 20_000 ? 'line' : 'ball-and-stick'; - const { update, builder, typeParams, color, symmetryColor, ballAndStickColor } = reprBuilder(plugin, params, structure); + const { update, builder, typeParams, color, symmetryColor, symmetryColorParams, globalColorParams, ballAndStickColor } = reprBuilder(plugin, params, structure); const representations = { - polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'polymer' }), + polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'polymer' }), ligand: builder.buildRepresentation(update, components.ligand, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'ligand' }), nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'non-standard' }), branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.3 }, color, colorParams: ballAndStickColor }, { tag: 'branched-ball-and-stick' }), - branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }, { tag: 'branched-snfg-3d' }), - water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6, visuals: waterType === 'line' ? ['intra-bond', 'element-point'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'water' }), - ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ion' }), - lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6, visuals: lipidType === 'line' ? ['intra-bond'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'lipid' }), - coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id' }, { tag: 'coarse' }) + branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color, colorParams: globalColorParams }, { tag: 'branched-snfg-3d' }), + water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6, visuals: waterType === 'line' ? ['intra-bond', 'element-point'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams } }, { tag: 'water' }), + ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams } }, { tag: 'ion' }), + lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6, visuals: lipidType === 'line' ? ['intra-bond'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams } }, { tag: 'lipid' }), + coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id', colorParams: globalColorParams }, { tag: 'coarse' }) }; await update.commit({ revertOnError: false }); @@ -223,11 +227,11 @@ const proteinAndNucleic = StructureRepresentationPresetProvider({ smoothness: structure.isCoarseGrained ? 1.0 : 1.5, }; - const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure); + const { update, builder, typeParams, symmetryColor, symmetryColorParams } = reprBuilder(plugin, params, structure); const representations = { - protein: builder.buildRepresentation(update, components.protein, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'protein' }), - nucleic: builder.buildRepresentation(update, components.nucleic, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'nucleic' }) + protein: builder.buildRepresentation(update, components.protein, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'protein' }), + nucleic: builder.buildRepresentation(update, components.nucleic, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'nucleic' }) }; await update.commit({ revertOnError: true }); @@ -275,11 +279,11 @@ const coarseSurface = StructureRepresentationPresetProvider({ }); } - const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure); + const { update, builder, typeParams, symmetryColor, symmetryColorParams } = reprBuilder(plugin, params, structure); const representations = { - polymer: builder.buildRepresentation(update, components.polymer, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'polymer' }), - lipid: builder.buildRepresentation(update, components.lipid, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor }, { tag: 'lipid' }) + polymer: builder.buildRepresentation(update, components.polymer, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'polymer' }), + lipid: builder.buildRepresentation(update, components.lipid, { type: 'gaussian-surface', typeParams: { ...typeParams, ...gaussianProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'lipid' }) }; await update.commit({ revertOnError: true }); @@ -309,10 +313,10 @@ const polymerCartoon = StructureRepresentationPresetProvider({ sizeFactor: structure.isCoarseGrained ? 0.8 : 0.2 }; - const { update, builder, typeParams, symmetryColor } = reprBuilder(plugin, params, structure); + const { update, builder, typeParams, symmetryColor, symmetryColorParams } = reprBuilder(plugin, params, structure); const representations = { - polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor }, { tag: 'polymer' }) + polymer: builder.buildRepresentation(update, components.polymer, { type: 'cartoon', typeParams: { ...typeParams, ...cartoonProps }, color: symmetryColor, colorParams: symmetryColorParams }, { tag: 'polymer' }) }; await update.commit({ revertOnError: true }); @@ -367,9 +371,9 @@ const atomicDetail = StructureRepresentationPresetProvider({ }); } - const { update, builder, typeParams, color, ballAndStickColor } = reprBuilder(plugin, params, structure); + const { update, builder, typeParams, color, ballAndStickColor, globalColorParams } = reprBuilder(plugin, params, structure); const colorParams = lowResidueElementRatio && !bondsGiven - ? { carbonColor: { name: 'element-symbol', params: {} } } + ? { carbonColor: { name: 'element-symbol', params: {} }, ...globalColorParams } : ballAndStickColor; const representations = { @@ -377,7 +381,7 @@ const atomicDetail = StructureRepresentationPresetProvider({ }; if (showCarbohydrateSymbol) { Object.assign(representations, { - snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color }, { tag: 'snfg-3d' }), + snfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams: { ...typeParams, alpha: 0.4, visuals: ['carbohydrate-symbol'] }, color, colorParams: globalColorParams }, { tag: 'snfg-3d' }), }); } diff --git a/src/mol-plugin-state/builder/structure/representation.ts b/src/mol-plugin-state/builder/structure/representation.ts index fc2b05c029131622f3093d7c8de70b87e341cd2e..4bcbb686140f517c5cceda8f53f63ed0d14c5e6f 100644 --- a/src/mol-plugin-state/builder/structure/representation.ts +++ b/src/mol-plugin-state/builder/structure/representation.ts @@ -90,7 +90,7 @@ export class StructureRepresentationBuilder { } applyPreset<K extends keyof PresetStructureRepresentations>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, preset: K, params?: StructureRepresentationPresetProvider.Params<PresetStructureRepresentations[K]>): Promise<StructureRepresentationPresetProvider.State<PresetStructureRepresentations[K]>> | undefined - applyPreset<P = any, S = {}>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, provider: StructureRepresentationPresetProvider<P, S>, params?: P): Promise<S> | undefined + applyPreset<P = any, S extends {} = {}>(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, provider: StructureRepresentationPresetProvider<P, S>, params?: P): Promise<S> | undefined applyPreset(parent: StateObjectRef<PluginStateObject.Molecule.Structure>, providerId: string, params?: any): Promise<any> | undefined applyPreset(parent: StateObjectRef, providerRef: string | StructureRepresentationPresetProvider, params?: any): Promise<any> | undefined { const provider = this.resolveProvider(providerRef); diff --git a/src/mol-plugin-state/component.ts b/src/mol-plugin-state/component.ts index 8230b7f222e48a85b0b350bed83cd11db8ebb759..46b7c35077efc787d5bc7a96b507a10d7ffe0a09 100644 --- a/src/mol-plugin-state/component.ts +++ b/src/mol-plugin-state/component.ts @@ -42,7 +42,7 @@ export class PluginComponent { } } -export class StatefulPluginComponent<State> extends PluginComponent { +export class StatefulPluginComponent<State extends {}> extends PluginComponent { private _state: State; protected updateState(...states: Partial<State>[]): boolean { diff --git a/src/mol-plugin-state/formats/trajectory.ts b/src/mol-plugin-state/formats/trajectory.ts index c8eca7e90416533fce2aa6c0605d0016c2521c61..82eef87085d9389dc63800c0427822f8505b1b7a 100644 --- a/src/mol-plugin-state/formats/trajectory.ts +++ b/src/mol-plugin-state/formats/trajectory.ts @@ -75,7 +75,7 @@ export const CifCoreProvider: TrajectoryFormatProvider = { visuals: defaultVisuals }; -function directTrajectory<P>(transformer: StateTransformer<PluginStateObject.Data.String | PluginStateObject.Data.Binary, PluginStateObject.Molecule.Trajectory, P>, transformerParams?: P): TrajectoryFormatProvider['parse'] { +function directTrajectory<P extends {}>(transformer: StateTransformer<PluginStateObject.Data.String | PluginStateObject.Data.Binary, PluginStateObject.Molecule.Trajectory, P>, transformerParams?: P): TrajectoryFormatProvider['parse'] { return async (plugin, data, params) => { const state = plugin.state.data; const trajectory = await state.build().to(data) diff --git a/src/mol-plugin-state/transforms/model.ts b/src/mol-plugin-state/transforms/model.ts index e9b356ddcd6fca4c285926139203ce8411fb68b6..3f1fff5864b424c68583e6e96672cbeed7d7d3d8 100644 --- a/src/mol-plugin-state/transforms/model.ts +++ b/src/mol-plugin-state/transforms/model.ts @@ -1079,4 +1079,4 @@ const ShapeFromPly = PluginStateTransform.BuiltIn({ return new SO.Shape.Provider(shape, props); }); } -}); \ No newline at end of file +}); diff --git a/src/mol-plugin-ui/base.tsx b/src/mol-plugin-ui/base.tsx index 6311d351b19aefa6ff5a5e0ef6dd92a10a71c396..6d724fa66726db3ba8141f1e548e1bd217ec85be 100644 --- a/src/mol-plugin-ui/base.tsx +++ b/src/mol-plugin-ui/base.tsx @@ -13,7 +13,7 @@ import { Icon, ArrowRightSvg, ArrowDropDownSvg } from './controls/icons'; export const PluginReactContext = React.createContext(void 0 as any as PluginUIContext); -export abstract class PluginUIComponent<P = {}, S = {}, SS = {}> extends React.Component<P & { children?: any }, S, SS> { +export abstract class PluginUIComponent<P extends {} = {}, S = {}, SS = {}> extends React.Component<P & { children?: any }, S, SS> { static contextType = PluginReactContext; readonly plugin: PluginUIContext; diff --git a/src/mol-plugin-ui/controls/parameters.tsx b/src/mol-plugin-ui/controls/parameters.tsx index ac908aeccc687e434a9a4bd1306082b156633f19..4a59801972ee5f62b538f035d5bd5560cb676646 100644 --- a/src/mol-plugin-ui/controls/parameters.tsx +++ b/src/mol-plugin-ui/controls/parameters.tsx @@ -7,6 +7,7 @@ import * as React from 'react'; import { Mat4, Vec2, Vec3 } from '../../mol-math/linear-algebra'; +import { Script } from '../../mol-script/script'; import { Asset } from '../../mol-util/assets'; import { Color } from '../../mol-util/color'; import { ColorListEntry } from '../../mol-util/color/color'; @@ -22,7 +23,7 @@ import { PluginUIContext } from '../context'; import { ActionMenu } from './action-menu'; import { ColorOptions, ColorValueOption, CombinedColorControl } from './color'; import { Button, ControlGroup, ControlRow, ExpandGroup, IconButton, TextInput, ToggleButton } from './common'; -import { ArrowDownwardSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowUpwardSvg, BookmarksOutlinedSvg, CheckSvg, ClearSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, MoreHorizSvg } from './icons'; +import { ArrowDownwardSvg, ArrowDropDownSvg, ArrowRightSvg, ArrowUpwardSvg, BookmarksOutlinedSvg, CheckSvg, ClearSvg, DeleteOutlinedSvg, HelpOutlineSvg, Icon, MoreHorizSvg, WarningSvg } from './icons'; import { legendFor } from './legend'; import { LineGraphComponent } from './line-graph/line-graph-component'; import { Slider, Slider2 } from './slider'; @@ -1466,31 +1467,38 @@ export class ConvertedControl extends React.PureComponent<ParamProps<PD.Converte } } -export class ScriptControl extends SimpleParam<PD.Script> { - onChange = (e: React.ChangeEvent<HTMLInputElement>) => { - const value = e.target.value; - if (value !== this.props.value.expression) { - this.update({ language: this.props.value.language, expression: value }); - } - }; - - onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => { - if ((e.keyCode === 13 || e.charCode === 13 || e.key === 'Enter')) { - if (this.props.onEnter) this.props.onEnter(); +export class ScriptControl extends React.PureComponent<ParamProps<PD.Script>> { + onChange: ParamOnChange = ({ name, value }) => { + const k = name as 'language' | 'expression'; + if (value !== this.props.value[k]) { + this.props.onChange({ param: this.props.param, name: this.props.name, value: { ...this.props.value, [k]: value } }); } - e.stopPropagation(); }; - renderControl() { + render() { // TODO: improve! - const placeholder = this.props.param.label || camelCaseToWords(this.props.name); - return <input type='text' - value={this.props.value.expression || ''} - placeholder={placeholder} - onChange={this.onChange} - onKeyPress={this.props.onEnter ? this.onKeyPress : void 0} - disabled={this.props.isDisabled} - />; + const selectParam: PD.Select<PD.Script['defaultValue']['language']> = { + defaultValue: this.props.value.language, + options: PD.objectToOptions(Script.Info), + type: 'select', + }; + const select = <SelectControl param={selectParam} + isDisabled={this.props.isDisabled} onChange={this.onChange} onEnter={this.props.onEnter} + name='language' value={this.props.value.language} />; + + const textParam: PD.Text = { + defaultValue: this.props.value.language, + type: 'text', + }; + const text = <TextControl param={textParam} isDisabled={this.props.isDisabled} onChange={this.onChange} name='expression' value={this.props.value.expression} />; + + return <> + {select} + {this.props.value.language !== 'mol-script' && <div className='msp-help-text' style={{ padding: '10px' }}> + <Icon svg={WarningSvg} /> Support for PyMOL, VMD, and Jmol selections is an experimental feature and may not always work as intended. + </div>} + {text} + </>; } -} \ No newline at end of file +} diff --git a/src/mol-plugin-ui/controls/screenshot.tsx b/src/mol-plugin-ui/controls/screenshot.tsx index a90e0778da21ac8b193541c2f222d38275ebcdee..3d79a786f49152bb3ca050d02b244acb35dd9226 100644 --- a/src/mol-plugin-ui/controls/screenshot.tsx +++ b/src/mol-plugin-ui/controls/screenshot.tsx @@ -1,7 +1,8 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import * as React from 'react'; @@ -25,7 +26,7 @@ export interface ScreenshotPreviewProps { const _ScreenshotPreview = (props: ScreenshotPreviewProps) => { const { plugin, cropFrameColor } = props; - const helper = plugin.helpers.viewportScreenshot!; + const helper = plugin.helpers.viewportScreenshot; const [currentCanvas, setCurrentCanvas] = useState<HTMLCanvasElement | null>(null); const canvasRef = useRef<HTMLCanvasElement | null>(null); const propsRef = useRef(props); @@ -70,8 +71,8 @@ const _ScreenshotPreview = (props: ScreenshotPreviewProps) => { subscribe(plugin.state.data.behaviors.isUpdating, v => { if (!v) isDirty = true; }); - subscribe(helper.behaviors.values, () => isDirty = true); - subscribe(helper.behaviors.cropParams, () => isDirty = true); + subscribe(helper?.behaviors.values, () => isDirty = true); + subscribe(helper?.behaviors.cropParams, () => isDirty = true); let resizeObserver: any = void 0; if (typeof ResizeObserver !== 'undefined') { @@ -108,7 +109,9 @@ export const ScreenshotPreview = React.memo(_ScreenshotPreview, (prev, next) => declare const ResizeObserver: any; -function drawPreview(helper: ViewportScreenshotHelper, target: HTMLCanvasElement, customBackground?: string, borderColor?: string, borderWidth?: number) { +function drawPreview(helper: ViewportScreenshotHelper | undefined, target: HTMLCanvasElement, customBackground?: string, borderColor?: string, borderWidth?: number) { + if (!helper) return; + const { canvas, width, height } = helper.getPreview()!; const ctx = target.getContext('2d'); if (!ctx) return; @@ -151,9 +154,9 @@ function drawPreview(helper: ViewportScreenshotHelper, target: HTMLCanvasElement function ViewportFrame({ plugin, canvas, color = 'rgba(255, 87, 45, 0.75)' }: { plugin: PluginContext, canvas: HTMLCanvasElement | null, color?: string }) { const helper = plugin.helpers.viewportScreenshot; - const params = useBehavior(helper?.behaviors.values!); - const cropParams = useBehavior(helper?.behaviors.cropParams!); - const crop = useBehavior(helper?.behaviors.relativeCrop!); + const params = useBehavior(helper?.behaviors.values); + const cropParams = useBehavior(helper?.behaviors.cropParams); + const crop = useBehavior(helper?.behaviors.relativeCrop); const cropFrameRef = useRef<Viewport>({ x: 0, y: 0, width: 0, height: 0 }); useBehavior(params?.resolution.name === 'viewport' ? plugin.canvas3d?.resized : void 0); @@ -161,7 +164,7 @@ function ViewportFrame({ plugin, canvas, color = 'rgba(255, 87, 45, 0.75)' }: { const [start, setStart] = useState([0, 0]); const [current, setCurrent] = useState([0, 0]); - if (!helper || !canvas) return null; + if (!helper || !canvas || !crop) return null; const { width, height } = helper.getSizeAndViewport(); @@ -267,7 +270,7 @@ function ViewportFrame({ plugin, canvas, color = 'rgba(255, 87, 45, 0.75)' }: { function finish() { const cropFrame = cropFrameRef.current; - if (cropParams.auto) { + if (cropParams?.auto) { helper?.behaviors.cropParams.next({ ...cropParams, auto: false }); } helper?.behaviors.relativeCrop.next({ diff --git a/src/mol-plugin-ui/custom/volume.tsx b/src/mol-plugin-ui/custom/volume.tsx index 91e44ea89755e6e13d71a227bed5a64f14a79dd7..0a13a436e087795313b51dd9ac5999822b4df229 100644 --- a/src/mol-plugin-ui/custom/volume.tsx +++ b/src/mol-plugin-ui/custom/volume.tsx @@ -1,7 +1,8 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Adam Midlik <midlik@gmail.com> */ import { PluginUIComponent } from '../base'; @@ -104,7 +105,7 @@ class Channel extends PluginUIComponent<{ colorStripe={channel.color} pivot={<div className='msp-volume-channel-inline-controls'> <Slider value={value} min={ctrlMin} max={ctrlMax} step={step} - onChange={v => props.changeIso(props.name, v, isRelative)} disabled={props.params.isDisabled} onEnter={props.params.events.onEnter} /> + onChange={v => props.changeIso(props.name, v, isRelative)} onChangeImmediate={v => props.changeIso(props.name, v, isRelative)} disabled={props.params.isDisabled} onEnter={props.params.events.onEnter} /> <IconButton svg={this.getVisible() ? VisibilityOutlinedSvg : VisibilityOffOutlinedSvg} onClick={this.toggleVisible} toggleState={false} disabled={props.params.isDisabled} /> </div>} controls={<ParameterControls onChange={({ name, value }) => props.changeParams(props.name, name, value)} params={ChannelParams} values={channel} onEnter={props.params.events.onEnter} isDisabled={props.params.isDisabled} />} @@ -199,6 +200,9 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf const viewParams = { ...oldView }; if (value.name === 'selection-box') { viewParams.radius = value.params.radius; + } else if (value.name === 'camera-target') { + viewParams.radius = value.params.radius; + viewParams.dynamicDetailLevel = value.params.dynamicDetailLevel; } else if (value.name === 'box') { viewParams.bottomLeft = value.params.bottomLeft; viewParams.topRight = value.params.topRight; @@ -240,13 +244,23 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf const pivot = isEM ? 'em' : '2fo-fc'; const params = this.props.params as VolumeStreaming.Params; - const entry = ((this.props.info.params as VolumeStreaming.ParamDefinition) - .entry.map(params.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>); + const entry = (this.props.info.params as VolumeStreaming.ParamDefinition) + .entry.map(params.entry.name) as PD.Group<VolumeStreaming.EntryParamDefinition>; const detailLevel = entry.params.detailLevel; - const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as Volume.IsoValue).kind === 'relative'; + const dynamicDetailLevel = { + ...detailLevel, + label: 'Dynamic Detail', + defaultValue: (entry.params.view as any).map('camera-target').params.dynamicDetailLevel.defaultValue, + }; + const selectionDetailLevel = { + ...detailLevel, + label: 'Selection Detail', + defaultValue: (entry.params.view as any).map('auto').params.selectionDetailLevel.defaultValue, + }; const sampling = b.info.header.sampling[0]; + const isRelative = ((params.entry.params.channels as any)[pivot].isoValue as Volume.IsoValue).kind === 'relative'; const isRelativeParam = PD.Boolean(isRelative, { description: 'Use normalized or absolute isocontour scale.', label: 'Normalized' }); const isUnbounded = !!(params.entry.params.view.params as any).isUnbounded; @@ -274,6 +288,13 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf isRelative: isRelativeParam, isUnbounded: isUnboundedParam, }, { description: 'Box around focused element.' }), + 'camera-target': PD.Group({ + radius: PD.Numeric(0.5, { min: 0, max: 1, step: 0.05 }, { description: 'Radius within which the volume is shown (relative to the field of view).' }), + detailLevel: { ...detailLevel, isHidden: true }, + dynamicDetailLevel: dynamicDetailLevel, + isRelative: isRelativeParam, + isUnbounded: isUnboundedParam, + }, { description: 'Box around camera target.' }), 'cell': PD.Group({ detailLevel, isRelative: isRelativeParam, @@ -282,12 +303,11 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf 'auto': PD.Group({ radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }), detailLevel, - selectionDetailLevel: { ...detailLevel, label: 'Selection Detail' }, + selectionDetailLevel: selectionDetailLevel, isRelative: isRelativeParam, isUnbounded: isUnboundedParam, }, { description: 'Box around focused element.' }), - // 'auto': PD.Group({ }), // TODO based on camera distance/active selection/whatever, show whole structure or slice. - }, { options: VolumeStreaming.ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Focus" shows the volume around the element/atom last interacted with. "Whole Structure" shows the volume for the whole structure.' }) + }, { options: VolumeStreaming.ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Focus" shows the volume around the element/atom last interacted with. "Around Camera" shows the volume around the point the camera is targeting. "Whole Structure" shows the volume for the whole structure.' }) }; const options = { entry: params.entry.name, @@ -299,6 +319,7 @@ export class VolumeStreamingCustomControls extends PluginUIComponent<StateTransf bottomLeft: (params.entry.params.view.params as any).bottomLeft, topRight: (params.entry.params.view.params as any).topRight, selectionDetailLevel: (params.entry.params.view.params as any).selectionDetailLevel, + dynamicDetailLevel: (params.entry.params.view.params as any).dynamicDetailLevel, isRelative, isUnbounded } diff --git a/src/mol-plugin-ui/index.ts b/src/mol-plugin-ui/index.ts index 4b32f50fa698c9a20535fc663adb60d194cd0f8e..01c4f85c9f6c56766a511703e39a23de94f09fd3 100644 --- a/src/mol-plugin-ui/index.ts +++ b/src/mol-plugin-ui/index.ts @@ -18,5 +18,10 @@ export async function createPluginUI(target: HTMLElement, spec?: PluginUISpec, o await options.onBeforeUIRender(ctx); } ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target); + try { + await ctx.canvas3dInitialized; + } catch { + // Error reported in UI/console elsewhere. + } return ctx; } \ No newline at end of file diff --git a/src/mol-plugin-ui/left-panel.tsx b/src/mol-plugin-ui/left-panel.tsx index 32477e580739e3388dbf0763db542fb97eb3c09c..62db9be7c613c82d0d1e8974d3523437aca376cf 100644 --- a/src/mol-plugin-ui/left-panel.tsx +++ b/src/mol-plugin-ui/left-panel.tsx @@ -141,11 +141,13 @@ class FullSettings extends PluginUIComponent { this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate()); this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate()); - this.subscribe(this.plugin.canvas3d!.camera.stateChanged, state => { - if (state.radiusMax !== undefined || state.radius !== undefined) { - this.forceUpdate(); - } - }); + if (this.plugin.canvas3d) { + this.subscribe(this.plugin.canvas3d.camera.stateChanged, state => { + if (state.radiusMax !== undefined || state.radius !== undefined) { + this.forceUpdate(); + } + }); + } } render() { diff --git a/src/mol-plugin-ui/plugin.tsx b/src/mol-plugin-ui/plugin.tsx index a6b4da446e27ff4e7b15409d9c07d288cee5a568..fc492fe8caa0a02f8307886137cd4d1ce88cd983 100644 --- a/src/mol-plugin-ui/plugin.tsx +++ b/src/mol-plugin-ui/plugin.tsx @@ -24,14 +24,6 @@ import { BehaviorSubject } from 'rxjs'; import { useBehavior } from './hooks/use-behavior'; export class Plugin extends React.Component<{ plugin: PluginUIContext, children?: any }, {}> { - region(kind: 'left' | 'right' | 'bottom' | 'main', element: JSX.Element) { - return <div className={`msp-layout-region msp-layout-${kind}`}> - <div className='msp-layout-static'> - {element} - </div> - </div>; - } - render() { return <PluginReactContext.Provider value={this.props.plugin}> <Layout /> diff --git a/src/mol-plugin-ui/react18.ts b/src/mol-plugin-ui/react18.ts index 6309eebc1a7f0bd49aed99ac13662ac0a8a02bb4..fd9113ba83a1257c533bbbf7e3fb5d8f52aae94f 100644 --- a/src/mol-plugin-ui/react18.ts +++ b/src/mol-plugin-ui/react18.ts @@ -18,5 +18,10 @@ export async function createPluginUI(target: HTMLElement, spec?: PluginUISpec, o await options.onBeforeUIRender(ctx); } createRoot(target).render(createElement(Plugin, { plugin: ctx })); + try { + await ctx.canvas3dInitialized; + } catch { + // Error reported in UI/console elsewhere. + } return ctx; } \ No newline at end of file diff --git a/src/mol-plugin-ui/structure/components.tsx b/src/mol-plugin-ui/structure/components.tsx index 797286a746d2ec8ae5d86c44ef49a12e380f43d2..0454a5772864732cb86e42c7ee48a5380e2a60c6 100644 --- a/src/mol-plugin-ui/structure/components.tsx +++ b/src/mol-plugin-ui/structure/components.tsx @@ -280,7 +280,6 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo selectAction: ActionMenu.OnSelect = item => { if (!item) return; - this.setState({ action: void 0 }); (item?.value as any)(); }; diff --git a/src/mol-plugin-ui/structure/measurements.tsx b/src/mol-plugin-ui/structure/measurements.tsx index cd26069728850817262781ff669b27c229f9c573..f60269f9056abd696e2f4378d0f4313539f1966e 100644 --- a/src/mol-plugin-ui/structure/measurements.tsx +++ b/src/mol-plugin-ui/structure/measurements.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> @@ -288,7 +288,12 @@ class MeasurementEntry extends PurePluginUIComponent<{ cell: StructureMeasuremen for (const loci of this.lociArray) { this.plugin.managers.interactivity.lociHighlights.highlight({ loci }, false); } - this.plugin.managers.interactivity.lociHighlights.highlight({ loci: this.props.cell.obj?.data.repr.getLoci()! }, false); + const reprLocis = this.props.cell.obj?.data.repr.getAllLoci(); + if (reprLocis) { + for (const loci of reprLocis) { + this.plugin.managers.interactivity.lociHighlights.highlight({ loci }, false); + } + } }; clearHighlight = () => { diff --git a/src/mol-plugin-ui/viewport/canvas.tsx b/src/mol-plugin-ui/viewport/canvas.tsx index 616c145d1bb5033e6ae956f125fe780863ff6f21..c75331955ad7f77ba1c285d5d2edbac84708c2b1 100644 --- a/src/mol-plugin-ui/viewport/canvas.tsx +++ b/src/mol-plugin-ui/viewport/canvas.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -19,13 +19,14 @@ export interface ViewportCanvasParams { parentClassName?: string, parentStyle?: React.CSSProperties, + // NOTE: hostClassName/hostStyle no longer in use + // TODO: remove in 4.0 hostClassName?: string, hostStyle?: React.CSSProperties, } export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, ViewportCanvasState> { private container = React.createRef<HTMLDivElement>(); - private canvas = React.createRef<HTMLCanvasElement>(); state: ViewportCanvasState = { noWebGl: false, @@ -37,7 +38,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View }; componentDidMount() { - if (!this.canvas.current || !this.container.current || !this.plugin.initViewer(this.canvas.current!, this.container.current!)) { + if (!this.container.current || !this.plugin.mount(this.container.current!, { checkeredCanvasBackground: true })) { this.setState({ noWebGl: true }); return; } @@ -47,7 +48,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View componentWillUnmount() { super.componentWillUnmount(); - // TODO viewer cleanup + this.plugin.unmount(); } renderMissing() { @@ -59,7 +60,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View return <div className='msp-no-webgl'> <div> <p><b>WebGL does not seem to be available.</b></p> - <p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps.</p> + <p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps. Also, make sure hardware acceleration is enabled in your browser.</p> <p>For a list of supported browsers, refer to <a href='http://caniuse.com/#feat=webgl' target='_blank'>http://caniuse.com/#feat=webgl</a>.</p> </div> </div>; @@ -70,10 +71,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View const Logo = this.props.logo; - return <div className={this.props.parentClassName || 'msp-viewport'} style={this.props.parentStyle}> - <div className={this.props.hostClassName || 'msp-viewport-host3d'} style={this.props.hostStyle} ref={this.container}> - <canvas ref={this.canvas} /> - </div> + return <div className={this.props.parentClassName || 'msp-viewport'} style={this.props.parentStyle} ref={this.container}> {(this.state.showLogo && Logo) && <Logo />} </div>; } diff --git a/src/mol-plugin-ui/viewport/help.tsx b/src/mol-plugin-ui/viewport/help.tsx index 468e63959c0d35cb68ddc733079f6b1306f451b2..9146fb6a7e461ccfc2d9be0f3b1ee310723b3c53 100644 --- a/src/mol-plugin-ui/viewport/help.tsx +++ b/src/mol-plugin-ui/viewport/help.tsx @@ -7,14 +7,15 @@ import * as React from 'react'; import { Binding } from '../../mol-util/binding'; import { PluginUIComponent } from '../base'; -import { StateTransformer, StateSelection } from '../../mol-state'; +import { StateTransformer, StateSelection, State } from '../../mol-state'; import { SelectLoci } from '../../mol-plugin/behavior/dynamic/representation'; import { FocusLoci } from '../../mol-plugin/behavior/dynamic/representation'; import { Icon, ArrowDropDownSvg, ArrowRightSvg, CameraSvg } from '../controls/icons'; import { Button } from '../controls/common'; +import { memoizeLatest } from '../../mol-util/memoize'; function getBindingsList(bindings: { [k: string]: Binding }) { - return Object.keys(bindings).map(k => [k, bindings[k]] as [string, Binding]); + return Object.keys(bindings).map(k => [k, bindings[k]] as [string, Binding]).filter(b => Binding.isBinding(b[1])); } export class BindingsHelp extends React.PureComponent<{ bindings: { [k: string]: Binding } }> { @@ -77,19 +78,30 @@ export class ViewportHelpContent extends PluginUIComponent<{ selectOnly?: boolea this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate()); } - render() { - const interactionBindings: { [k: string]: Binding } = {}; - this.plugin.spec.behaviors.forEach(b => { - const { bindings } = b.defaultParams; - if (bindings) Object.assign(interactionBindings, bindings); + getInteractionBindings = memoizeLatest((cells: State.Cells) => { + let interactionBindings: { [k: string]: Binding } | undefined = void 0; + + cells.forEach(c => { + const params = c.params?.values; + if (params?.bindings && Object.keys(params.bindings).length > 0) { + if (!interactionBindings) interactionBindings = { }; + Object.assign(interactionBindings, params.bindings); + } }); + + return interactionBindings; + }); + + render() { + const interactionBindings = this.getInteractionBindings(this.plugin.state.behaviors.cells); + return <> {(!this.props.selectOnly && this.plugin.canvas3d) && <HelpGroup key='trackball' header='Moving in 3D'> <BindingsHelp bindings={this.plugin.canvas3d.props.trackball.bindings} /> </HelpGroup>} - <HelpGroup key='interactions' header='Mouse Controls'> + {!!interactionBindings && <HelpGroup key='interactions' header='Mouse Controls'> <BindingsHelp bindings={interactionBindings} /> - </HelpGroup> + </HelpGroup>} </>; } } diff --git a/src/mol-plugin-ui/viewport/screenshot.tsx b/src/mol-plugin-ui/viewport/screenshot.tsx index 135beee19ae88e1204d2cda6eaa926e91b65eb8d..9158ef2b58961341f817a5bca89c5b663f28c477 100644 --- a/src/mol-plugin-ui/viewport/screenshot.tsx +++ b/src/mol-plugin-ui/viewport/screenshot.tsx @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> @@ -96,18 +96,21 @@ export class DownloadScreenshotControls extends PluginUIComponent<{ close: () => } function ScreenshotParams({ plugin, isDisabled }: { plugin: PluginContext, isDisabled: boolean }) { - const helper = plugin.helpers.viewportScreenshot!; - const values = useBehavior(helper.behaviors.values); + const helper = plugin.helpers.viewportScreenshot; + + const values = useBehavior(helper?.behaviors.values); + if (!helper) return null; return <ParameterControls params={helper.params} values={values} onChangeValues={v => helper.behaviors.values.next(v)} isDisabled={isDisabled} />; } function CropControls({ plugin }: { plugin: PluginContext }) { const helper = plugin.helpers.viewportScreenshot; - const cropParams = useBehavior(helper?.behaviors.cropParams!); + + const cropParams = useBehavior(helper?.behaviors.cropParams); useBehavior(helper?.behaviors.relativeCrop); - if (!helper) return null; + if (!helper || !cropParams) return null; return <div style={{ width: '100%', height: '24px', marginTop: '8px' }}> <ToggleButton icon={CropOrginalSvg} title='Auto-crop' inline isSelected={cropParams.auto} diff --git a/src/mol-plugin-ui/viewport/simple-settings.tsx b/src/mol-plugin-ui/viewport/simple-settings.tsx index 73f819331fbc1188e0d594da430e1fb3ec6af0be..a3f7eb6d939a26fb0db47839521823779989c94f 100644 --- a/src/mol-plugin-ui/viewport/simple-settings.tsx +++ b/src/mol-plugin-ui/viewport/simple-settings.tsx @@ -8,8 +8,10 @@ import { produce } from 'immer'; import { Canvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d'; import { PluginCommands } from '../../mol-plugin/commands'; +import { PluginConfig } from '../../mol-plugin/config'; import { StateTransform } from '../../mol-state'; import { Color } from '../../mol-util/color'; +import { deepClone } from '../../mol-util/object'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { ParamMapping } from '../../mol-util/param-mapping'; import { Mutable } from '../../mol-util/type-helpers'; @@ -20,6 +22,8 @@ import { ViewportHelpContent } from './help'; export class SimpleSettingsControl extends PluginUIComponent { componentDidMount() { + if (!this.plugin.canvas3d) return; + this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate()); this.subscribe(this.plugin.canvas3d!.camera.stateChanged, state => { @@ -50,7 +54,8 @@ const SimpleSettingsParams = { camera: Canvas3DParams.camera, background: PD.Group({ color: PD.Color(Color(0xFCFBF9), { label: 'Background', description: 'Custom background color' }), - transparent: PD.Boolean(false) + transparent: PD.Boolean(false), + style: Canvas3DParams.postprocessing.params.background, }, { pivot: 'color' }), lighting: PD.Group({ occlusion: Canvas3DParams.postprocessing.params.occlusion, @@ -75,6 +80,13 @@ const SimpleSettingsMapping = ParamMapping({ if (controls.left !== 'none') options.push(['left', LayoutOptions.left]); params.layout.options = options; } + const bgStyles = ctx.config.get(PluginConfig.Background.Styles) || []; + if (bgStyles.length > 0) { + Object.assign(params.background.params.style, { + presets: deepClone(bgStyles), + isFlat: false, // so the presets menu is shown + }); + } return params; }, target(ctx: PluginUIContext) { @@ -97,7 +109,8 @@ const SimpleSettingsMapping = ParamMapping({ camera: canvas.camera, background: { color: renderer.backgroundColor, - transparent: canvas.transparentBackground + transparent: canvas.transparentBackground, + style: canvas.postprocessing.background, }, lighting: { occlusion: canvas.postprocessing.occlusion, @@ -117,10 +130,12 @@ const SimpleSettingsMapping = ParamMapping({ canvas.renderer.backgroundColor = s.background.color; canvas.postprocessing.occlusion = s.lighting.occlusion; canvas.postprocessing.outline = s.lighting.outline; + canvas.postprocessing.background = s.background.style; canvas.cameraFog = s.lighting.fog; canvas.cameraClipping = { radius: s.clipping.radius, far: s.clipping.far, + minNear: s.clipping.minNear, }; props.layout = s.layout; diff --git a/src/mol-plugin/behavior/behavior.ts b/src/mol-plugin/behavior/behavior.ts index 86b809fb40e0742a052977e9d4b6abf3f25e7521..96ada468ba35d0960ae2481f20545c2884ab229d 100644 --- a/src/mol-plugin/behavior/behavior.ts +++ b/src/mol-plugin/behavior/behavior.ts @@ -1,7 +1,8 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Adam Midlik <midlik@gmail.com> */ import { PluginStateTransform, PluginStateObject } from '../../mol-plugin-state/objects'; @@ -17,7 +18,8 @@ export { PluginBehavior }; interface PluginBehavior<P = unknown> { register(ref: StateTransform.Ref): void, - unregister(): void, + unregister?(): void, + dispose?(): void, /** Update params in place. Optionally return a promise if it depends on an async action. */ update?(params: P): boolean | Promise<boolean> @@ -38,7 +40,7 @@ namespace PluginBehavior { 'misc': 'Miscellaneous' }; - export interface CreateParams<P> { + export interface CreateParams<P extends {}> { name: string, category: keyof typeof Categories, ctor: Ctor<P>, @@ -71,7 +73,7 @@ namespace PluginBehavior { return categoryMap.get(t.id)!; } - export function create<P>(params: CreateParams<P>) { + export function create<P extends {}>(params: CreateParams<P>) { const t = PluginStateTransform.CreateBuiltIn<Category, Behavior, P>({ name: params.name, display: params.display, @@ -102,7 +104,7 @@ namespace PluginBehavior { register(): void { this.sub = cmd.subscribe(this.ctx, data => action(data, this.ctx)); } - unregister(): void { + dispose(): void { if (this.sub) this.sub.unsubscribe(); this.sub = void 0; } @@ -111,7 +113,7 @@ namespace PluginBehavior { }; } - export abstract class Handler<P = { }> implements PluginBehavior<P> { + export abstract class Handler<P extends {} = {}> implements PluginBehavior<P> { private subs: PluginCommand.Subscription[] = []; protected subscribeCommand<T>(cmd: PluginCommand<T>, action: PluginCommand.Action<T>) { this.subs.push(cmd.subscribe(this.ctx, action)); @@ -123,7 +125,7 @@ namespace PluginBehavior { this.subs.push(sub); } abstract register(): void; - unregister() { + dispose(): void { for (const s of this.subs) s.unsubscribe(); this.subs = []; } @@ -143,11 +145,20 @@ namespace PluginBehavior { protected subscribeCommand<T>(cmd: PluginCommand<T>, action: PluginCommand.Action<T>) { this.subs.push(cmd.subscribe(this.plugin, action)); } - protected subscribeObservable<T>(o: Observable<T>, action: (v: T) => void) { - this.subs.push(o.subscribe(action)); + protected subscribeObservable<T>(o: Observable<T>, action: (v: T) => void): PluginCommand.Subscription { + const sub = o.subscribe(action); + this.subs.push(sub); + return { + unsubscribe: () => { + const idx = this.subs.indexOf(sub); + if (idx >= 0) { + this.subs.splice(idx, 1); + sub.unsubscribe(); + } + } + }; } - - unregister() { + dispose(): void { for (const s of this.subs) s.unsubscribe(); this.subs = []; } diff --git a/src/mol-plugin/behavior/dynamic/custom-props/structure-info.ts b/src/mol-plugin/behavior/dynamic/custom-props/structure-info.ts index 469fca3463ec3e26164cb77ffec055deb2c0510e..e2484b9ce1c95f8cb62b071636425f4ffa9ed1f1 100644 --- a/src/mol-plugin/behavior/dynamic/custom-props/structure-info.ts +++ b/src/mol-plugin/behavior/dynamic/custom-props/structure-info.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -52,17 +52,43 @@ export const StructureInfo = PluginBehavior.create({ return { auth, label }; } + private setModelMaxIndex() { + const value = this.maxModelIndex; + const cells = this.ctx.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Model)); + for (const c of cells) { + const m = c.obj?.data; + if (m) { + if (Model.MaxIndex.get(m).value !== value) { + Model.MaxIndex.set(m, { value }, value); + } + } + } + } + + private setStructureMaxIndex() { + const value = this.maxModelIndex; + const cells = this.ctx.state.data.select(StateSelection.Generators.rootsOfType(PluginStateObject.Molecule.Structure)); + for (const c of cells) { + const s = c.obj?.data; + if (s) { + if (Structure.MaxIndex.get(s).value !== value) { + Structure.MaxIndex.set(s, { value }, value); + } + } + } + } + private handleModel(model: Model, oldModel?: Model) { if (Model.Index.get(model).value === undefined) { const oldIndex = oldModel && Model.Index.get(oldModel).value; const value = oldIndex ?? (this.maxModelIndex + 1); - Model.Index.set(model, { value }); + Model.Index.set(model, { value }, value); } if (Model.AsymIdOffset.get(model).value === undefined) { const oldOffset = oldModel && Model.AsymIdOffset.get(oldModel).value; const value = oldOffset ?? { ...this.asymIdOffset }; - Model.AsymIdOffset.set(model, { value }); + Model.AsymIdOffset.set(model, { value }, value); } } @@ -72,7 +98,7 @@ export const StructureInfo = PluginBehavior.create({ const oldIndex = oldStructure && Structure.Index.get(oldStructure).value; const value = oldIndex ?? (this.maxStructureIndex + 1); - Structure.Index.set(structure, { value }); + Structure.Index.set(structure, { value }, value); } private handle(ref: string, obj: StateObject<any, StateObject.Type<any>>, oldObj?: StateObject<any, StateObject.Type<any>>) { @@ -92,10 +118,14 @@ export const StructureInfo = PluginBehavior.create({ register(): void { this.ctx.customModelProperties.register(Model.AsymIdOffset, true); this.ctx.customModelProperties.register(Model.Index, true); + this.ctx.customModelProperties.register(Model.MaxIndex, true); this.ctx.customStructureProperties.register(Structure.Index, true); + this.ctx.customStructureProperties.register(Structure.MaxIndex, true); this.subscribeObservable(this.ctx.state.data.events.object.created, o => { this.handle(o.ref, o.obj); + this.setModelMaxIndex(); + this.setStructureMaxIndex(); }); this.subscribeObservable(this.ctx.state.data.events.object.updated, o => { @@ -106,7 +136,9 @@ export const StructureInfo = PluginBehavior.create({ unregister() { this.ctx.customModelProperties.unregister(Model.AsymIdOffset.descriptor.name); this.ctx.customModelProperties.unregister(Model.Index.descriptor.name); + this.ctx.customModelProperties.unregister(Model.MaxIndex.descriptor.name); this.ctx.customStructureProperties.unregister(Structure.Index.descriptor.name); + this.ctx.customStructureProperties.unregister(Structure.MaxIndex.descriptor.name); } } }); \ No newline at end of file diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts index 071e4544ac1f234044caa0d222ad3953025c44bf..1050783faf4dac3ea5437eb5c619f754c2f97dd3 100644 --- a/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts +++ b/src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts @@ -1,8 +1,9 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Adam Midlik <midlik@gmail.com> */ import { ParamDefinition as PD } from '../../../../mol-util/param-definition'; @@ -24,6 +25,10 @@ import { PluginContext } from '../../../context'; import { EmptyLoci, Loci, isEmptyLoci } from '../../../../mol-model/loci'; import { Asset } from '../../../../mol-util/assets'; import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform'; +import { distinctUntilChanged, filter, map, Observable, throttleTime } from 'rxjs'; +import { Camera } from '../../../../mol-canvas3d/camera'; +import { PluginCommand } from '../../../command'; +import { SingleAsyncQueue } from '../../../../mol-util/single-async-queue'; export class VolumeStreaming extends PluginStateObject.CreateBehavior<VolumeStreaming.Behavior>({ name: 'Volume Streaming' }) { } @@ -53,7 +58,7 @@ export namespace VolumeStreaming { valuesInfo: [{ mean: 0, min: -1, max: 1, sigma: 0.1 }, { mean: 0, min: -1, max: 1, sigma: 0.1 }] }; - export function createParams(options: { data?: VolumeServerInfo.Data, defaultView?: ViewTypes, channelParams?: DefaultChannelParams } = { }) { + export function createParams(options: { data?: VolumeServerInfo.Data, defaultView?: ViewTypes, channelParams?: DefaultChannelParams } = {}) { const { data, defaultView, channelParams } = options; const map = new Map<string, VolumeServerInfo.EntryData>(); if (data) data.entries.forEach(d => map.set(d.dataId, d)); @@ -68,14 +73,14 @@ export namespace VolumeStreaming { export type EntryParams = PD.Values<EntryParamDefinition> export function createEntryParams(options: { entryData?: VolumeServerInfo.EntryData, defaultView?: ViewTypes, structure?: Structure, channelParams?: DefaultChannelParams }) { - const { entryData, defaultView, structure, channelParams = { } } = options; + const { entryData, defaultView, structure, channelParams = {} } = options; // fake the info const info = entryData || { kind: 'em', header: { sampling: [fakeSampling], availablePrecisions: [{ precision: 0, maxVoxels: 0 }] }, emDefaultContourLevel: Volume.IsoValue.relative(0) }; const box = (structure && structure.boundary.box) || Box3D(); return { - view: PD.MappedStatic(defaultView || (info.kind === 'em' ? 'cell' : 'selection-box'), { + view: PD.MappedStatic(defaultView || (info.kind === 'em' ? 'auto' : 'selection-box'), { 'off': PD.Group<{}>({}), 'box': PD.Group({ bottomLeft: PD.Vec3(box.min), @@ -86,19 +91,24 @@ export namespace VolumeStreaming { bottomLeft: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }), topRight: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }), }, { description: 'Box around focused element.', isFlat: true }), + 'camera-target': PD.Group({ + radius: PD.Numeric(0.5, { min: 0, max: 1, step: 0.05 }, { description: 'Radius within which the volume is shown (relative to the field of view).' }), + // Minimal detail level for the inside of the zoomed region (real detail can be higher, depending on the region size) + dynamicDetailLevel: createDetailParams(info.header.availablePrecisions, 0, { label: 'Dynamic Detail' }), + bottomLeft: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }), + topRight: PD.Vec3(Vec3.create(0, 0, 0), {}, { isHidden: true }), + }, { description: 'Box around camera target.', isFlat: true }), 'cell': PD.Group<{}>({}), // Show selection-box if available and cell otherwise. 'auto': PD.Group({ radius: PD.Numeric(5, { min: 0, max: 50, step: 0.5 }, { description: 'Radius in \u212B within which the volume is shown.' }), - selectionDetailLevel: PD.Select<number>(Math.min(6, info.header.availablePrecisions.length - 1), - info.header.availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]), { label: 'Selection Detail', description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 0 (0.52M voxels) to 6 (25.17M voxels).' }), + selectionDetailLevel: createDetailParams(info.header.availablePrecisions, 6, { label: 'Selection Detail' }), isSelection: PD.Boolean(false, { isHidden: true }), bottomLeft: PD.Vec3(box.min, {}, { isHidden: true }), topRight: PD.Vec3(box.max, {}, { isHidden: true }), }, { description: 'Box around focused element.', isFlat: true }) }, { options: ViewTypeOptions, description: 'Controls what of the volume is displayed. "Off" hides the volume alltogether. "Bounded box" shows the volume inside the given box. "Around Interaction" shows the volume around the focused element/atom. "Whole Structure" shows the volume for the whole structure.' }), - detailLevel: PD.Select<number>(Math.min(3, info.header.availablePrecisions.length - 1), - info.header.availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]), { description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 0 (0.52M voxels) to 6 (25.17M voxels).' }), + detailLevel: createDetailParams(info.header.availablePrecisions, 3), channels: info.kind === 'em' ? PD.Group({ 'em': channelParam('EM', Color(0x638F8F), info.emDefaultContourLevel || Volume.IsoValue.relative(1), info.header.sampling[0].valuesInfo[0], channelParams['em']) @@ -111,13 +121,40 @@ export namespace VolumeStreaming { }; } - export const ViewTypeOptions = [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Around Focus'], ['cell', 'Whole Structure'], ['auto', 'Auto']] as [ViewTypes, string][]; + function createDetailParams(availablePrecisions: VolumeServerHeader.DetailLevel[], preferredPrecision: number, info?: PD.Info) { + return PD.Select<number>(Math.min(preferredPrecision, availablePrecisions.length - 1), + availablePrecisions.map((p, i) => [i, `${i + 1} [ ${Math.pow(p.maxVoxels, 1 / 3) | 0}^3 cells ]`] as [number, string]), + { + description: 'Determines the maximum number of voxels. Depending on the size of the volume options are in the range from 1 (0.52M voxels) to 7 (25.17M voxels).', + ...info + } + ); + } - export type ViewTypes = 'off' | 'box' | 'selection-box' | 'cell' | 'auto' + export function copyParams(origParams: Params): Params { + return { + entry: { + name: origParams.entry.name, + params: { + detailLevel: origParams.entry.params.detailLevel, + channels: origParams.entry.params.channels, + view: { + name: origParams.entry.params.view.name, + params: { ...origParams.entry.params.view.params } as any, + } + } + } + }; + } + + export const ViewTypeOptions = [['off', 'Off'], ['box', 'Bounded Box'], ['selection-box', 'Around Focus'], ['camera-target', 'Around Camera'], ['cell', 'Whole Structure'], ['auto', 'Auto']] as [ViewTypes, string][]; + + export type ViewTypes = 'off' | 'box' | 'selection-box' | 'camera-target' | 'cell' | 'auto' export type ParamDefinition = ReturnType<typeof createParams> export type Params = PD.Values<ParamDefinition> + type ChannelsInfo = { [name in ChannelType]?: { isoValue: Volume.IsoValue, color: Color, wireframe: boolean, opacity: number } } type ChannelsData = { [name in 'EM' | '2FO-FC' | 'FO-FC']?: Volume } @@ -140,6 +177,14 @@ export namespace VolumeStreaming { private lastLoci: StructureElement.Loci | EmptyLoci = EmptyLoci; private ref: string = ''; public infoMap: Map<string, VolumeServerInfo.EntryData>; + private updateQueue: SingleAsyncQueue; + private cameraTargetObservable = this.plugin.canvas3d!.didDraw!.pipe( + throttleTime(500, undefined, { 'leading': true, 'trailing': true }), + map(() => this.plugin.canvas3d?.camera.getSnapshot()), + distinctUntilChanged((a, b) => this.isCameraTargetSame(a, b)), + filter(a => a !== undefined), + ) as Observable<Camera.Snapshot>; + private cameraTargetSubscription?: PluginCommand.Subscription = undefined; channels: Channels = {}; @@ -163,6 +208,9 @@ export namespace VolumeStreaming { if (this.params.entry.params.view.name === 'auto' && this.params.entry.params.view.params.isSelection) { detail = this.params.entry.params.view.params.selectionDetailLevel; } + if (this.params.entry.params.view.name === 'camera-target' && box) { + detail = this.decideDetail(box, this.params.entry.params.view.params.dynamicDetailLevel); + } url += `?detail=${detail}`; @@ -201,58 +249,21 @@ export namespace VolumeStreaming { return ret; } - private updateSelectionBoxParams(box: Box3D) { - if (this.params.entry.params.view.name !== 'selection-box') return; - - const state = this.plugin.state.data; - const newParams: Params = { - ...this.params, - entry: { - name: this.params.entry.name, - params: { - ...this.params.entry.params, - view: { - name: 'selection-box' as const, - params: { - radius: this.params.entry.params.view.params.radius, - bottomLeft: box.min, - topRight: box.max - } - } - } - } - }; - const update = state.build().to(this.ref).update(newParams); - - PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } }); - } - - private updateAutoParams(box: Box3D | undefined, isSelection: boolean) { - if (this.params.entry.params.view.name !== 'auto') return; + private async updateParams(box: Box3D | undefined, autoIsSelection: boolean = false) { + const newParams = copyParams(this.params); + const viewType = newParams.entry.params.view.name; + if (viewType !== 'off' && viewType !== 'cell') { + newParams.entry.params.view.params.bottomLeft = box?.min || Vec3.zero(); + newParams.entry.params.view.params.topRight = box?.max || Vec3.zero(); + } + if (viewType === 'auto') { + newParams.entry.params.view.params.isSelection = autoIsSelection; + } const state = this.plugin.state.data; - const newParams: Params = { - ...this.params, - entry: { - name: this.params.entry.name, - params: { - ...this.params.entry.params, - view: { - name: 'auto' as const, - params: { - radius: this.params.entry.params.view.params.radius, - selectionDetailLevel: this.params.entry.params.view.params.selectionDetailLevel, - isSelection, - bottomLeft: box?.min || Vec3.zero(), - topRight: box?.max || Vec3.zero() - } - } - } - } - }; const update = state.build().to(this.ref).update(newParams); - PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } }); + await PluginCommands.State.Update(this.plugin, { state, tree: update, options: { doNotUpdateCurrent: true } }); } private getStructureRoot() { @@ -303,6 +314,18 @@ export namespace VolumeStreaming { } } + private isCameraTargetSame(a?: Camera.Snapshot, b?: Camera.Snapshot): boolean { + if (!a || !b) return false; + const targetSame = Vec3.equals(a.target, b.target); + const sqDistA = Vec3.squaredDistance(a.target, a.position); + const sqDistB = Vec3.squaredDistance(b.target, b.position); + const distanceSame = Math.abs(sqDistA - sqDistB) / sqDistA < 1e-3; + return targetSame && distanceSame; + } + private cameraTargetDistance(snapshot: Camera.Snapshot): number { + return Vec3.distance(snapshot.target, snapshot.position); + } + private _invTransform: Mat4 = Mat4(); private getBoxFromLoci(loci: StructureElement.Loci | EmptyLoci): Box3D { if (Loci.isEmpty(loci) || isEmptyLoci(loci)) { @@ -328,39 +351,82 @@ export namespace VolumeStreaming { } private updateAuto(loci: StructureElement.Loci | EmptyLoci) { - // if (Loci.areEqual(this.lastLoci, loci)) { - // this.lastLoci = EmptyLoci; - // this.updateSelectionBoxParams(Box3D.empty()); - // return; - // } - - this.lastLoci = loci; - - if (isEmptyLoci(loci)) { - this.updateAutoParams(this.info.kind === 'x-ray' ? this.data.structure.boundary.box : void 0, false); - return; - } - - const box = this.getBoxFromLoci(loci); - this.updateAutoParams(box, true); + this.updateQueue.enqueue(async () => { + this.lastLoci = loci; + if (isEmptyLoci(loci)) { + await this.updateParams(this.info.kind === 'x-ray' ? this.data.structure.boundary.box : void 0, false); + } else { + await this.updateParams(this.getBoxFromLoci(loci), true); + } + }); } private updateSelectionBox(loci: StructureElement.Loci | EmptyLoci) { - if (Loci.areEqual(this.lastLoci, loci)) { - this.lastLoci = EmptyLoci; - this.updateSelectionBoxParams(Box3D()); - return; - } + this.updateQueue.enqueue(async () => { + if (Loci.areEqual(this.lastLoci, loci)) { + this.lastLoci = EmptyLoci; + } else { + this.lastLoci = loci; + } + const box = this.getBoxFromLoci(this.lastLoci); + await this.updateParams(box); + }); + } - this.lastLoci = loci; + private updateCameraTarget(snapshot: Camera.Snapshot) { + this.updateQueue.enqueue(async () => { + const origManualReset = this.plugin.canvas3d?.props.camera.manualReset; + try { + if (!origManualReset) this.plugin.canvas3d?.setProps({ camera: { manualReset: true } }); + const box = this.boxFromCameraTarget(snapshot, true); + await this.updateParams(box); + } finally { + if (!origManualReset) this.plugin.canvas3d?.setProps({ camera: { manualReset: origManualReset } }); + } + }); + } - if (isEmptyLoci(loci)) { - this.updateSelectionBoxParams(Box3D()); - return; + private boxFromCameraTarget(snapshot: Camera.Snapshot, boundByBoundarySize: boolean): Box3D { + const target = snapshot.target; + const distance = this.cameraTargetDistance(snapshot); + const top = Math.tan(0.5 * snapshot.fov) * distance; + let radius = top; + const viewport = this.plugin.canvas3d?.camera.viewport; + if (viewport && viewport.width > viewport.height) { + radius *= viewport.width / viewport.height; + } + const relativeRadius = this.params.entry.params.view.name === 'camera-target' ? this.params.entry.params.view.params.radius : 0.5; + radius *= relativeRadius; + let radiusX, radiusY, radiusZ; + if (boundByBoundarySize) { + const bBoxSize = Vec3.zero(); + Box3D.size(bBoxSize, this.data.structure.boundary.box); + radiusX = Math.min(radius, 0.5 * bBoxSize[0]); + radiusY = Math.min(radius, 0.5 * bBoxSize[1]); + radiusZ = Math.min(radius, 0.5 * bBoxSize[2]); + } else { + radiusX = radiusY = radiusZ = radius; } + return Box3D.create( + Vec3.create(target[0] - radiusX, target[1] - radiusY, target[2] - radiusZ), + Vec3.create(target[0] + radiusX, target[1] + radiusY, target[2] + radiusZ) + ); + } - const box = this.getBoxFromLoci(loci); - this.updateSelectionBoxParams(box); + private decideDetail(box: Box3D, baseDetail: number): number { + const cellVolume = this.info.kind === 'x-ray' + ? Box3D.volume(this.data.structure.boundary.box) + : this.info.header.spacegroup.size.reduce((a, b) => a * b, 1); + const boxVolume = Box3D.volume(box); + let ratio = boxVolume / cellVolume; + const maxDetail = this.info.header.availablePrecisions.length - 1; + let detail = baseDetail; + while (ratio <= 0.5 && detail < maxDetail) { + ratio *= 2; + detail += 1; + } + // console.log(`Decided dynamic detail: ${detail}, (base detail: ${baseDetail}, box/cell volume ratio: ${boxVolume / cellVolume})`); + return detail; } async update(params: Params) { @@ -369,6 +435,11 @@ export namespace VolumeStreaming { this.params = params; let box: Box3D | undefined = void 0, emptyData = false; + if (params.entry.params.view.name !== 'camera-target' && this.cameraTargetSubscription) { + this.cameraTargetSubscription.unsubscribe(); + this.cameraTargetSubscription = undefined; + } + switch (params.entry.params.view.name) { case 'off': emptyData = true; @@ -388,6 +459,12 @@ export namespace VolumeStreaming { Box3D.expand(box, box, Vec3.create(r, r, r)); break; } + case 'camera-target': + if (!this.cameraTargetSubscription) { + this.cameraTargetSubscription = this.subscribeObservable(this.cameraTargetObservable, (e) => this.updateCameraTarget(e)); + } + box = this.boxFromCameraTarget(this.plugin.canvas3d!.camera.getSnapshot(), true); + break; case 'cell': box = this.info.kind === 'x-ray' ? this.data.structure.boundary.box @@ -439,6 +516,7 @@ export namespace VolumeStreaming { getDescription() { if (this.params.entry.params.view.name === 'selection-box') return 'Selection'; + if (this.params.entry.params.view.name === 'camera-target') return 'Camera'; if (this.params.entry.params.view.name === 'box') return 'Static Box'; if (this.params.entry.params.view.name === 'cell') return 'Cell'; return ''; @@ -449,6 +527,7 @@ export namespace VolumeStreaming { this.infoMap = new Map<string, VolumeServerInfo.EntryData>(); this.data.entries.forEach(info => this.infoMap.set(info.dataId, info)); + this.updateQueue = new SingleAsyncQueue(); } } -} \ No newline at end of file +} diff --git a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts index af1c49d98d59c0e2adf0d4ffd9c035f441866688..b58b00605022fd16192276152d8a8c5582cdd0ad 100644 --- a/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts +++ b/src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts @@ -1,8 +1,9 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Adam Midlik <midlik@gmail.com> */ import { PluginStateObject as SO, PluginStateTransform } from '../../../../mol-plugin-state/objects'; @@ -42,7 +43,7 @@ export const InitVolumeStreaming = StateAction.build({ return { method: PD.Select<VolumeServerInfo.Kind>(method, [['em', 'EM'], ['x-ray', 'X-Ray']]), entries: PD.ObjectList({ id: PD.Text(ids[0] || '') }, ({ id }) => id, { defaultValue: ids.map(id => ({ id })) }), - defaultView: PD.Select<VolumeStreaming.ViewTypes>(method === 'em' ? 'cell' : 'selection-box', VolumeStreaming.ViewTypeOptions as any), + defaultView: PD.Select<VolumeStreaming.ViewTypes>(method === 'em' ? 'auto' : 'selection-box', VolumeStreaming.ViewTypeOptions as any), options: PD.Group({ serverUrl: PD.Text(plugin.config.get(PluginConfig.VolumeStreaming.DefaultServer) || 'https://ds.litemol.org'), behaviorRef: PD.Text('', { isHidden: true }), @@ -219,6 +220,7 @@ const CreateVolumeStreamingBehavior = PluginStateTransform.BuiltIn({ canAutoUpdate: ({ oldParams, newParams }) => { return oldParams.entry.params.view === newParams.entry.params.view || newParams.entry.params.view.name === 'selection-box' + || newParams.entry.params.view.name === 'camera-target' || newParams.entry.params.view.name === 'off'; }, apply: ({ a, params }, plugin: PluginContext) => Task.create('Volume streaming', async _ => { diff --git a/src/mol-plugin/behavior/static/state.ts b/src/mol-plugin/behavior/static/state.ts index 989fe3337ffac3170c51a3c1cfbfa778debc8f56..e4270736da5816cc6b6b5ce7287a6878169b5b24 100644 --- a/src/mol-plugin/behavior/static/state.ts +++ b/src/mol-plugin/behavior/static/state.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -37,12 +37,16 @@ export function SyncBehaviors(ctx: PluginContext) { ctx.state.events.object.removed.subscribe(o => { if (!SO.isBehavior(o.obj)) return; - o.obj.data.unregister(); + o.obj.data.unregister?.(); + o.obj.data.dispose?.(); }); ctx.state.events.object.updated.subscribe(o => { if (o.action === 'recreate') { - if (o.oldObj && SO.isBehavior(o.oldObj)) o.oldObj.data.unregister(); + if (o.oldObj && SO.isBehavior(o.oldObj)) { + o.oldObj.data.unregister?.(); + o.oldObj.data.dispose?.(); + } if (o.obj && SO.isBehavior(o.obj)) o.obj.data.register(o.ref); } }); @@ -115,7 +119,9 @@ export function Highlight(ctx: PluginContext) { ctx.managers.interactivity.lociHighlights.highlight({ loci: Structure.Loci(cell.obj.data) }, false); } else if (cell && SO.isRepresentation3D(cell.obj)) { const { repr } = cell.obj.data; - ctx.managers.interactivity.lociHighlights.highlight({ loci: repr.getLoci(), repr }, false); + for (const loci of repr.getAllLoci()) { + ctx.managers.interactivity.lociHighlights.highlight({ loci, repr }, false); + } } else if (SO.Molecule.Structure.Selections.is(cell.obj)) { for (const entry of cell.obj.data) { ctx.managers.interactivity.lociHighlights.highlight({ loci: entry.loci }, false); diff --git a/src/mol-plugin/config.ts b/src/mol-plugin/config.ts index b70305fb7f6773cc470aca8bc9733a15db101d87..8d097a20047244701bc05f203d6764ecc7ef274c 100644 --- a/src/mol-plugin/config.ts +++ b/src/mol-plugin/config.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -12,6 +12,7 @@ import { EmdbDownloadProvider } from '../mol-plugin-state/actions/volume'; import { StructureRepresentationPresetProvider } from '../mol-plugin-state/builder/structure/representation-preset'; import { PluginFeatureDetection } from './features'; import { SaccharideCompIdMapType } from '../mol-model/structure/structure/carbohydrates/constants'; +import { BackgroundProps } from '../mol-canvas3d/passes/background'; export class PluginConfigItem<T = any> { toString() { return this.key; } @@ -30,10 +31,12 @@ export const PluginConfig = { PixelScale: item('plugin-config.pixel-scale', 1), PickScale: item('plugin-config.pick-scale', 0.25), PickPadding: item('plugin-config.pick-padding', 3), - EnableWboit: item('plugin-config.enable-wboit', PluginFeatureDetection.wboit), + EnableWboit: item('plugin-config.enable-wboit', true), + EnableDpoit: item('plugin-config.enable-dpoit', false), // as of Oct 1 2021, WebGL 2 doesn't work on iOS 15. // TODO: check back in a few weeks to see if it was fixed PreferWebGl1: item('plugin-config.prefer-webgl1', PluginFeatureDetection.preferWebGl1), + AllowMajorPerformanceCaveat: item('plugin-config.allow-major-performance-caveat', false), }, State: { DefaultServer: item('plugin-state.server', 'https://webchem.ncbr.muni.cz/molstar-state'), @@ -65,6 +68,9 @@ export const PluginConfig = { DefaultRepresentationPreset: item<string>('structure.default-representation-preset', 'auto'), DefaultRepresentationPresetParams: item<StructureRepresentationPresetProvider.CommonParams>('structure.default-representation-preset-params', { }), SaccharideCompIdMapType: item<SaccharideCompIdMapType>('structure.saccharide-comp-id-map-type', 'default'), + }, + Background: { + Styles: item<[BackgroundProps, string][]>('background.styles', []), } }; @@ -88,4 +94,4 @@ export class PluginConfigManager { if (!initial) return; initial.forEach(([k, v]) => this._config.set(k, v)); } -} \ No newline at end of file +} diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 29ec1c03d65a88bbff9c669fd86e3eed0a5901b4..fd315a57e306cd784e6d3286fafd8fcf2e26a1a6 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -35,7 +35,7 @@ import { Representation } from '../mol-repr/representation'; import { StructureRepresentationRegistry } from '../mol-repr/structure/registry'; import { VolumeRepresentationRegistry } from '../mol-repr/volume/registry'; import { StateTransform } from '../mol-state'; -import { RuntimeContext, Task } from '../mol-task'; +import { RuntimeContext, Scheduler, Task } from '../mol-task'; import { ColorTheme } from '../mol-theme/color'; import { SizeTheme } from '../mol-theme/size'; import { ThemeRegistryContext } from '../mol-theme/theme'; @@ -71,8 +71,10 @@ export class PluginContext { }; protected subs: Subscription[] = []; + private initCanvas3dPromiseCallbacks: [res: () => void, rej: (err: any) => void] = [() => {}, () => {}]; private disposed = false; + private canvasContainer: HTMLDivElement | undefined = void 0; private ev = RxEventHelper.create(); readonly config = new PluginConfigManager(this.spec.config); // needed to init state @@ -102,10 +104,15 @@ export class PluginContext { leftPanelTabName: this.ev.behavior<LeftPanelTabName>('root') }, canvas3d: { + // TODO: remove in 4.0? initialized: this.canvas3dInit.pipe(filter(v => !!v), take(1)) } } as const; + readonly canvas3dInitialized = new Promise<void>((res, rej) => { + this.initCanvas3dPromiseCallbacks = [res, rej]; + }); + readonly canvas3dContext: Canvas3DContext | undefined; readonly canvas3d: Canvas3D | undefined; readonly layout = new PluginLayout(this); @@ -186,6 +193,63 @@ export class PluginContext { */ readonly customState: unknown = Object.create(null); + initContainer(options?: { canvas3dContext?: Canvas3DContext, checkeredCanvasBackground?: boolean }) { + if (this.canvasContainer) return true; + + const container = document.createElement('div'); + Object.assign(container.style, { + position: 'absolute', + left: 0, + top: 0, + right: 0, + bottom: 0, + '-webkit-user-select': 'none', + 'user-select': 'none', + '-webkit-tap-highlight-color': 'rgba(0,0,0,0)', + '-webkit-touch-callout': 'none', + 'touch-action': 'manipulation', + }); + let canvas = options?.canvas3dContext?.canvas; + if (!canvas) { + canvas = document.createElement('canvas'); + if (options?.checkeredCanvasBackground) { + Object.assign(canvas.style, { + 'background-image': 'linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey), linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey)', + 'background-size': '60px 60px', + 'background-position': '0 0, 30px 30px' + }); + } + container.appendChild(canvas); + } + if (!this.initViewer(canvas, container, options?.canvas3dContext)) { + return false; + } + this.canvasContainer = container; + return true; + } + + /** + * Mount the plugin into the target element (assumes the target has "relative"-like positioninig). + * If initContainer wasn't called separately before, initOptions will be passed to it. + */ + mount(target: HTMLElement, initOptions?: { canvas3dContext?: Canvas3DContext, checkeredCanvasBackground?: boolean }) { + if (this.disposed) throw new Error('Cannot mount a disposed context'); + + if (!this.initContainer(initOptions)) return false; + + if (this.canvasContainer!.parentElement !== target) { + this.canvasContainer!.parentElement?.removeChild(this.canvasContainer!); + } + + target.appendChild(this.canvasContainer!); + this.handleResize(); + return true; + } + + unmount() { + this.canvasContainer?.parentElement?.removeChild(this.canvasContainer); + } + initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement, canvas3dContext?: Canvas3DContext) { try { this.layout.setRoot(container); @@ -200,8 +264,10 @@ export class PluginContext { const pickScale = this.config.get(PluginConfig.General.PickScale) || 0.25; const pickPadding = this.config.get(PluginConfig.General.PickPadding) ?? 1; const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false; + const enableDpoit = this.config.get(PluginConfig.General.EnableDpoit) || false; const preferWebGl1 = this.config.get(PluginConfig.General.PreferWebGl1) || false; - (this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, preferWebGl1 }); + const failIfMajorPerformanceCaveat = !(this.config.get(PluginConfig.General.AllowMajorPerformanceCaveat) ?? false); + (this.canvas3dContext as Canvas3DContext) = Canvas3DContext.fromCanvas(canvas, this.managers.asset, { antialias, preserveDrawingBuffer, pixelScale, pickScale, pickPadding, enableWboit, enableDpoit, preferWebGl1, failIfMajorPerformanceCaveat }); } (this.canvas3d as Canvas3D) = Canvas3D.create(this.canvas3dContext!); this.canvas3dInit.next(true); @@ -230,10 +296,12 @@ export class PluginContext { this.handleResize(); + Scheduler.setImmediate(() => this.initCanvas3dPromiseCallbacks[0]()); return true; } catch (e) { this.log.error('' + e); console.error(e); + Scheduler.setImmediate(() => this.initCanvas3dPromiseCallbacks[1](e)); return false; } } @@ -304,6 +372,9 @@ export class PluginContext { objectForEach(this.managers, m => (m as any)?.dispose?.()); objectForEach(this.managers.structure, m => (m as any)?.dispose?.()); + this.unmount(); + this.canvasContainer = undefined; + this.disposed = true; } diff --git a/src/mol-plugin/features.ts b/src/mol-plugin/features.ts index ad04c71c6287bbae2ca4f8439632503403ea3b71..6a1c06d2206b2329e8f883acb0a8aec9377f08b7 100644 --- a/src/mol-plugin/features.ts +++ b/src/mol-plugin/features.ts @@ -1,7 +1,8 @@ /** - * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ export const PluginFeatureDetection = { @@ -13,7 +14,7 @@ export const PluginFeatureDetection = { const unpportedSafariVersions = [ 'Version/15.1 Safari', 'Version/15.2 Safari', - 'Version/15.3 Safari' + 'Version/15.3 Safari', ]; if (unpportedSafariVersions.some(v => navigator.userAgent.indexOf(v) > 0)) { return true; @@ -28,10 +29,4 @@ export const PluginFeatureDetection = { const isTouchScreen = navigator.maxTouchPoints >= 4; // true for iOS 13 (and hopefully beyond) return !(window as any).MSStream && (isIOS || (isAppleDevice && isTouchScreen)); }, - get wboit() { - if (typeof navigator === 'undefined' || typeof window === 'undefined') return true; - - // disable Wboit in Safari 15 - return !/Version\/15.\d Safari/.test(navigator.userAgent); - } }; \ No newline at end of file diff --git a/src/mol-plugin/state.ts b/src/mol-plugin/state.ts index cf0a05dc34b3f3439e5bde476228cacba2d0a7cd..dedad16d970299b0f5e4f59d538a1938eec4164d 100644 --- a/src/mol-plugin/state.ts +++ b/src/mol-plugin/state.ts @@ -124,7 +124,8 @@ class PluginState extends PluginComponent { dispose() { this.behaviors.cells.forEach(cell => { if (PluginBehavior.Behavior.is(cell.obj)) { - cell.obj.data.unregister(); + cell.obj.data.unregister?.(); + cell.obj.data.dispose?.(); } }); diff --git a/src/mol-plugin/util/viewport-screenshot.ts b/src/mol-plugin/util/viewport-screenshot.ts index 261ae3708d95a5c428b162b35ff69242d306c30c..97368fde642536abe397a736829216e82cbee236 100644 --- a/src/mol-plugin/util/viewport-screenshot.ts +++ b/src/mol-plugin/util/viewport-screenshot.ts @@ -309,7 +309,9 @@ class ViewportScreenshotHelper extends PluginComponent { if (width <= 0 || height <= 0) return; await ctx.update('Rendering image...'); - const imageData = this.imagePass.getImageData(width, height, viewport); + const pass = this.imagePass; + await pass.updateBackground(); + const imageData = pass.getImageData(width, height, viewport); await ctx.update('Encoding image...'); const canvas = this.canvas; diff --git a/src/mol-repr/representation.ts b/src/mol-repr/representation.ts index 246e34eb625d176da85f0221531742c4c93ca499..17da5156448dd9e75ff88ae586a1106c9386825d 100644 --- a/src/mol-repr/representation.ts +++ b/src/mol-repr/representation.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -154,8 +154,8 @@ interface Representation<D, P extends PD.Params = {}, S extends Representation.S createOrUpdate: (props?: Partial<PD.Values<P>>, data?: D) => Task<void> setState: (state: Partial<S>) => void setTheme: (theme: Theme) => void - /** If no pickingId is given, returns a Loci for the whole representation */ - getLoci: (pickingId?: PickingId) => ModelLoci + getLoci: (pickingId: PickingId) => ModelLoci + getAllLoci: () => ModelLoci[] mark: (loci: ModelLoci, action: MarkerAction) => boolean destroy: () => void } @@ -227,6 +227,7 @@ namespace Representation { setState: () => {}, setTheme: () => {}, getLoci: () => EmptyLoci, + getAllLoci: () => [], mark: () => false, destroy: () => {} }; @@ -327,7 +328,7 @@ namespace Representation { }, get state() { return currentState; }, get theme() { return currentTheme; }, - getLoci: (pickingId?: PickingId) => { + getLoci: (pickingId: PickingId) => { const { visuals } = currentProps; for (let i = 0, il = reprList.length; i < il; ++i) { if (!visuals || visuals.includes(reprMap[i])) { @@ -337,6 +338,16 @@ namespace Representation { } return EmptyLoci; }, + getAllLoci: () => { + const loci: ModelLoci[] = []; + const { visuals } = currentProps; + for (let i = 0, il = reprList.length; i < il; ++i) { + if (!visuals || visuals.includes(reprMap[i])) { + loci.push(...reprList[i].getAllLoci()); + } + } + return loci; + }, mark: (loci: ModelLoci, action: MarkerAction) => { let marked = false; for (let i = 0, il = reprList.length; i < il; ++i) { @@ -399,6 +410,10 @@ namespace Representation { // TODO return EmptyLoci; }, + getAllLoci: () => { + // TODO + return []; + }, mark: (loci: ModelLoci, action: MarkerAction) => { // TODO return false; diff --git a/src/mol-repr/shape/representation.ts b/src/mol-repr/shape/representation.ts index 803199b180ff825601d3c1c88b2d5c69eef1f6c7..45c4c68776db007b38a7e0568bd9c0f3c1581095 100644 --- a/src/mol-repr/shape/representation.ts +++ b/src/mol-repr/shape/representation.ts @@ -213,14 +213,16 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa get geometryVersion() { return geometryVersion; }, updated, createOrUpdate, - getLoci(pickingId?: PickingId) { - if (pickingId === undefined) return Shape.Loci(_shape); + getLoci(pickingId: PickingId) { const { objectId, groupId, instanceId } = pickingId; if (_renderObject && _renderObject.id === objectId) { return ShapeGroup.Loci(_shape, [{ ids: OrderedSet.ofSingleton(groupId), instance: instanceId }]); } return EmptyLoci; }, + getAllLoci() { + return [Shape.Loci(_shape)]; + }, mark(loci: Loci, action: MarkerAction) { if (!MarkerActions.is(_state.markerActions, action)) return false; if (ShapeGroup.isLoci(loci) || Shape.isLoci(loci)) { diff --git a/src/mol-repr/structure/complex-representation.ts b/src/mol-repr/structure/complex-representation.ts index f1edcbae4f58c5fe4938f2c4e2f2923ecf27b7a3..a50d04ae0c47bda3e592bfd93e2aa2b25dcc4de3 100644 --- a/src/mol-repr/structure/complex-representation.ts +++ b/src/mol-repr/structure/complex-representation.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> @@ -72,11 +72,14 @@ export function ComplexRepresentation<P extends StructureParams>(label: string, }); } - function getLoci(pickingId?: PickingId) { - if (pickingId === undefined) return Structure.Loci(_structure.target); + function getLoci(pickingId: PickingId) { return visual ? visual.getLoci(pickingId) : EmptyLoci; } + function getAllLoci() { + return [Structure.Loci(_structure.target)]; + } + function mark(loci: Loci, action: MarkerAction) { if (!_structure) return false; if (!MarkerActions.is(_state.markerActions, action)) return false; @@ -157,6 +160,7 @@ export function ComplexRepresentation<P extends StructureParams>(label: string, setState, setTheme, getLoci, + getAllLoci, mark, destroy }; diff --git a/src/mol-repr/structure/units-representation.ts b/src/mol-repr/structure/units-representation.ts index bc3130700ca2fe75673b471f9b4a3aa4ff634369..d7a2b6e0634e71156342aff34217ec67b23ac55b 100644 --- a/src/mol-repr/structure/units-representation.ts +++ b/src/mol-repr/structure/units-representation.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author David Sehnal <david.sehnal@gmail.com> @@ -185,8 +185,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct }); } - function getLoci(pickingId?: PickingId) { - if (pickingId === undefined) return Structure.Loci(_structure.target); + function getLoci(pickingId: PickingId) { let loci: Loci = EmptyLoci; visuals.forEach(({ visual }) => { const _loci = visual.getLoci(pickingId); @@ -195,6 +194,10 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct return loci; } + function getAllLoci() { + return [Structure.Loci(_structure.target)]; + } + function mark(loci: Loci, action: MarkerAction) { if (!_structure) return false; if (!MarkerActions.is(_state.markerActions, action)) return false; @@ -302,6 +305,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct setState, setTheme, getLoci, + getAllLoci, mark, destroy }; diff --git a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts index 884f7045262d152833701362bb9ae9b3d9632e50..85da9b6064cf3e6d59dd19a88ccc118a92fbb152 100644 --- a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts +++ b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts @@ -28,6 +28,7 @@ import { applyTextureMeshColorSmoothing } from '../../../mol-geo/geometry/textur import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base'; import { Vec3 } from '../../../mol-math/linear-algebra'; import { isTimingMode } from '../../../mol-util/debug'; +import { ValueCell } from '../../../mol-util/value-cell'; const SharedParams = { ...GaussianDensityParams, @@ -101,7 +102,12 @@ async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structu (surface.meta.resolution as GaussianSurfaceMeta['resolution']) = resolution; Mesh.transform(surface, transform); - if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface); + if (ctx.webgl && !ctx.webgl.isWebGL2) { + Mesh.uniformTriangleGroup(surface); + ValueCell.updateIfChanged(surface.varyingGroup, false); + } else { + ValueCell.updateIfChanged(surface.varyingGroup, true); + } const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, maxRadius); surface.setBoundingSphere(sphere); @@ -162,7 +168,12 @@ async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure: (surface.meta.resolution as GaussianSurfaceMeta['resolution']) = resolution; Mesh.transform(surface, transform); - if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface); + if (ctx.webgl && !ctx.webgl.isWebGL2) { + Mesh.uniformTriangleGroup(surface); + ValueCell.updateIfChanged(surface.varyingGroup, false); + } else { + ValueCell.updateIfChanged(surface.varyingGroup, true); + } const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, maxRadius); surface.setBoundingSphere(sphere); @@ -229,7 +240,7 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit, const axisOrder = Vec3.create(0, 1, 2); const buffer = textureMesh?.doubleBuffer.get(); - const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal); + const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal); if (isTimingMode) ctx.webgl.timer.markEnd('createGaussianSurfaceTextureMesh'); const groupCount = unit.elements.length; @@ -303,7 +314,7 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str const axisOrder = Vec3.create(0, 1, 2); const buffer = textureMesh?.doubleBuffer.get(); - const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal); + const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, false, true, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal); if (isTimingMode) ctx.webgl.timer.markEnd('createStructureGaussianSurfaceTextureMesh'); const groupCount = structure.elementCount; diff --git a/src/mol-repr/structure/visual/molecular-surface-mesh.ts b/src/mol-repr/structure/visual/molecular-surface-mesh.ts index 173a5181c1111e935b0ac9ab3ba3bcefccc3fb7b..72e1a972c6f5c04d0eac95cc39527cab2406c5a5 100644 --- a/src/mol-repr/structure/visual/molecular-surface-mesh.ts +++ b/src/mol-repr/structure/visual/molecular-surface-mesh.ts @@ -22,6 +22,7 @@ import { Texture } from '../../../mol-gl/webgl/texture'; import { WebGLContext } from '../../../mol-gl/webgl/context'; import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-smoothing'; import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base'; +import { ValueCell } from '../../../mol-util'; export const MolecularSurfaceMeshParams = { ...UnitsMeshParams, @@ -55,7 +56,12 @@ async function createMolecularSurfaceMesh(ctx: VisualContext, unit: Unit, struct } Mesh.transform(surface, transform); - if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface); + if (ctx.webgl && !ctx.webgl.isWebGL2) { + Mesh.uniformTriangleGroup(surface); + ValueCell.updateIfChanged(surface.varyingGroup, false); + } else { + ValueCell.updateIfChanged(surface.varyingGroup, true); + } const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, maxRadius); surface.setBoundingSphere(sphere); diff --git a/src/mol-repr/volume/isosurface.ts b/src/mol-repr/volume/isosurface.ts index 7f413cc883337afa8c69d84759dd710d2a853abd..e99c46d855e6991d2c794735e01916f9fa4192b9 100644 --- a/src/mol-repr/volume/isosurface.ts +++ b/src/mol-repr/volume/isosurface.ts @@ -29,6 +29,7 @@ import { WebGLContext } from '../../mol-gl/webgl/context'; import { CustomPropertyDescriptor } from '../../mol-model/custom-property'; import { Texture } from '../../mol-gl/webgl/texture'; import { BaseGeometry } from '../../mol-geo/geometry/base'; +import { ValueCell } from '../../mol-util/value-cell'; export const VolumeIsosurfaceParams = { isoValue: Volume.IsoValueParam @@ -65,8 +66,16 @@ function getLoci(volume: Volume, props: VolumeIsosurfaceProps) { function getIsosurfaceLoci(pickingId: PickingId, volume: Volume, props: VolumeIsosurfaceProps, id: number) { const { objectId, groupId } = pickingId; + if (id === objectId) { - return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex)); + const granularity = Volume.PickingGranularity.get(volume); + if (granularity === 'volume') { + return Volume.Loci(volume); + } else if (granularity === 'object') { + return Volume.Isosurface.Loci(volume, props.isoValue); + } else { + return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex)); + } } return EmptyLoci; } @@ -94,6 +103,9 @@ export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: Vol // 2nd arg means not to split triangles based on group id. Splitting triangles // is too expensive if each cell has its own group id as is the case here. Mesh.uniformTriangleGroup(surface, false); + ValueCell.updateIfChanged(surface.varyingGroup, false); + } else { + ValueCell.updateIfChanged(surface.varyingGroup, true); } surface.setBoundingSphere(Volume.getBoundingSphere(volume)); @@ -185,7 +197,7 @@ async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Vol const axisOrder = volume.grid.cells.space.axisOrderSlowToFast as Vec3; const buffer = textureMesh?.doubleBuffer.get(); - const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, value < 0, false, axisOrder, buffer?.vertex, buffer?.group, buffer?.normal); + const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, value < 0, false, axisOrder, true, buffer?.vertex, buffer?.group, buffer?.normal); const groupCount = volume.grid.cells.data.length; const surface = TextureMesh.create(gv.vertexCount, groupCount, gv.vertexTexture, gv.groupTexture, gv.normalTexture, Volume.getBoundingSphere(volume), textureMesh); diff --git a/src/mol-repr/volume/representation.ts b/src/mol-repr/volume/representation.ts index fae57da2554529ff0eee4d13a229d8cc6c956271..c6de383731b5f36a097cd321d19660c7e4502651 100644 --- a/src/mol-repr/volume/representation.ts +++ b/src/mol-repr/volume/representation.ts @@ -358,10 +358,12 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx: createOrUpdate, setState, setTheme, - getLoci: (pickingId?: PickingId): Loci => { - if (pickingId === undefined) return getLoci(_volume, _props); + getLoci: (pickingId: PickingId): Loci => { return visual ? visual.getLoci(pickingId) : EmptyLoci; }, + getAllLoci: (): Loci[] => { + return [getLoci(_volume, _props)]; + }, mark, destroy }; diff --git a/src/mol-repr/volume/slice.ts b/src/mol-repr/volume/slice.ts index 460f7881736f49bc119de7d554734cc96f2bd11e..d81bb7c513893aad48a17d3b94cf6f69e6cbb911 100644 --- a/src/mol-repr/volume/slice.ts +++ b/src/mol-repr/volume/slice.ts @@ -150,7 +150,14 @@ function getLoci(volume: Volume, props: PD.Values<SliceParams>) { function getSliceLoci(pickingId: PickingId, volume: Volume, props: PD.Values<SliceParams>, id: number) { const { objectId, groupId } = pickingId; if (id === objectId) { - return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex)); + const granularity = Volume.PickingGranularity.get(volume); + if (granularity === 'volume') { + return Volume.Loci(volume); + } if (granularity === 'object') { + return getLoci(volume, props); + } else { + return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex)); + } } return EmptyLoci; } diff --git a/src/mol-script/language/symbol-table/structure-query.ts b/src/mol-script/language/symbol-table/structure-query.ts index 9e765f41649288a054b01a69b4a6a7d06b1c2bf6..209da4376e81c413caada96b2a2b544146e3de48 100644 --- a/src/mol-script/language/symbol-table/structure-query.ts +++ b/src/mol-script/language/symbol-table/structure-query.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 Mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -333,6 +333,7 @@ const bondProperty = { flags: bondProp(Types.BondFlags), order: bondProp(Type.Num), + key: bondProp(Type.Num), length: bondProp(Type.Num), atomA: bondProp(Types.ElementReference), atomB: bondProp(Types.ElementReference) @@ -356,5 +357,5 @@ export const structureQuery = { combinator, atomSet, atomProperty, - bondProperty: bondProperty + bondProperty }; \ No newline at end of file diff --git a/src/mol-script/runtime/query/table.ts b/src/mol-script/runtime/query/table.ts index dc32f889628e0958a518ed074a31cbb8390d4780..deab2d4baa0c10ab7fc3641b258ef8a1c7a0cce2 100644 --- a/src/mol-script/runtime/query/table.ts +++ b/src/mol-script/runtime/query/table.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2022 Mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -211,21 +211,21 @@ const symbols = [ // ============= FILTERS ================ D(MolScript.structureQuery.filter.pick, (ctx, xs) => Queries.filters.pick(xs[0] as any, xs['test'])(ctx)), D(MolScript.structureQuery.filter.first, (ctx, xs) => Queries.filters.first(xs[0] as any)(ctx)), - D(MolScript.structureQuery.filter.withSameAtomProperties, (ctx, xs) => Queries.filters.withSameAtomProperties(xs[0] as any, xs['source'] as any, xs['property'] as any)(ctx)), + D(MolScript.structureQuery.filter.withSameAtomProperties, (ctx, xs) => Queries.filters.withSameAtomProperties(xs[0] as any, xs['source'] as any, xs['property'])(ctx)), D(MolScript.structureQuery.filter.intersectedBy, (ctx, xs) => Queries.filters.areIntersectedBy(xs[0] as any, xs['by'] as any)(ctx)), D(MolScript.structureQuery.filter.within, (ctx, xs) => Queries.filters.within({ query: xs[0] as any, target: xs['target'] as any, - minRadius: xs['min-radius'] as any, - maxRadius: xs['max-radius'] as any, + minRadius: xs['min-radius']?.(ctx) as any, + maxRadius: xs['max-radius']?.(ctx) as any, elementRadius: xs['atom-radius'] as any, - invert: xs['invert'] as any + invert: xs['invert']?.(ctx) as any })(ctx)), D(MolScript.structureQuery.filter.isConnectedTo, (ctx, xs) => Queries.filters.isConnectedTo({ query: xs[0] as any, target: xs['target'] as any, - disjunct: xs['disjunct'] as any, - invert: xs['invert'] as any, + disjunct: xs['disjunct']?.(ctx) as any, + invert: xs['invert']?.(ctx) as any, bondTest: xs['bond-test'] })(ctx)), @@ -248,6 +248,9 @@ const symbols = [ D(MolScript.structureQuery.generator.rings, function structureQuery_generator_rings(ctx, xs) { return Queries.generators.rings(xs?.['fingerprint']?.(ctx) as any, xs?.['only-aromatic']?.(ctx))(ctx); }), + D(MolScript.structureQuery.generator.queryInSelection, function structureQuery_generator_queryInSelection(ctx, xs) { + return Queries.generators.querySelection(xs[0] as any, xs['query'] as any, xs['in-complement']?.(ctx) as any)(ctx); + }), // ============= MODIFIERS ================ @@ -278,6 +281,7 @@ const symbols = [ fixedPoint: xs['fixed-point']?.(ctx) ?? false })(ctx); }), + D(MolScript.structureQuery.modifier.intersectBy, function structureQuery_modifier_intersectBy(ctx, xs) { return Queries.modifiers.intersectBy(xs[0] as any, xs['by'] as any)(ctx); }), // ============= COMBINATORS ================ @@ -353,9 +357,27 @@ const symbols = [ D(MolScript.structureQuery.atomProperty.macromolecular.secondaryStructureFlags, atomProp(StructureProperties.residue.secondary_structure_type)), D(MolScript.structureQuery.atomProperty.macromolecular.chemCompType, atomProp(StructureProperties.residue.chem_comp_type)), + // ============= ATOM SET ================ + + D(MolScript.structureQuery.atomSet.atomCount, + function structureQuery_atomset_atomCount(ctx, xs) { + return Queries.atomset.atomCount(ctx); + }), + + D(MolScript.structureQuery.atomSet.countQuery, + function structureQuery_atomset_countQuery(ctx, xs) { + return Queries.atomset.countQuery(xs[0] as any)(ctx); + }), + + D(MolScript.structureQuery.atomSet.propertySet, + function structureQuery_atomset_propertySet(ctx, xs) { + return Queries.atomset.propertySet(xs[0] as any)(ctx); + }), + // ============= BOND PROPERTIES ================ D(MolScript.structureQuery.bondProperty.order, (ctx, xs) => ctx.atomicBond.order), D(MolScript.structureQuery.bondProperty.flags, (ctx, xs) => ctx.atomicBond.type), + D(MolScript.structureQuery.bondProperty.key, (ctx, xs) => ctx.atomicBond.key), D(MolScript.structureQuery.bondProperty.atomA, (ctx, xs) => ctx.atomicBond.a), D(MolScript.structureQuery.bondProperty.atomB, (ctx, xs) => ctx.atomicBond.b), D(MolScript.structureQuery.bondProperty.length, (ctx, xs) => ctx.atomicBond.length), @@ -406,4 +428,4 @@ function getArray<T = any>(ctx: QueryContext, xs: any): T[] { for (const s of symbols) { DefaultQueryRuntimeTable.addSymbol(s); } -})(); \ No newline at end of file +})(); diff --git a/src/mol-script/script.ts b/src/mol-script/script.ts index 756861c6a62a118da82c2347023f3854f8480e8c..7497f49b82e83461416930d50d330aaf584eb911 100644 --- a/src/mol-script/script.ts +++ b/src/mol-script/script.ts @@ -6,10 +6,12 @@ import { transpileMolScript } from './script/mol-script/symbols'; import { parseMolScript } from './language/parser'; +import { parse } from './transpile'; import { Expression } from './language/expression'; import { StructureElement, QueryContext, StructureSelection, Structure, QueryFn, QueryContextOptions } from '../mol-model/structure'; import { compile } from './runtime/query/compiler'; import { MolScriptBuilder } from './language/builder'; +import { assertUnreachable } from '../mol-util/type-helpers'; export { Script }; @@ -20,7 +22,13 @@ function Script(expression: string, language: Script.Language): Script { } namespace Script { - export type Language = 'mol-script' + export const Info = { + 'mol-script': 'Mol-Script', + 'pymol': 'PyMOL', + 'vmd': 'VMD', + 'jmol': 'Jmol', + }; + export type Language = keyof typeof Info; export function is(x: any): x is Script { return !!x && typeof (x as Script).expression === 'string' && !!(x as Script).language; @@ -36,8 +44,13 @@ namespace Script { const parsed = parseMolScript(script.expression); if (parsed.length === 0) throw new Error('No query'); return transpileMolScript(parsed[0]); + case 'pymol': + case 'jmol': + case 'vmd': + return parse(script.language, script.expression); + default: + assertUnreachable(script.language); } - throw new Error('unsupported script language'); } export function toQuery(script: Script): QueryFn<StructureSelection> { @@ -56,4 +69,4 @@ namespace Script { const query = compile<StructureSelection>(e); return query(new QueryContext(structure, options)); } -} \ No newline at end of file +} diff --git a/src/mol-script/transpile.ts b/src/mol-script/transpile.ts new file mode 100644 index 0000000000000000000000000000000000000000..31395308bfe9e56fffbccf15b656403e47002fb5 --- /dev/null +++ b/src/mol-script/transpile.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + * + * Adapted from MolQL src/transpile.ts + */ + +import { Transpiler } from './transpilers/transpiler'; +import { _transpiler } from './transpilers/all'; +import { Expression } from './language/expression'; +import { Script } from './script'; +const transpiler: {[index: string]: Transpiler} = _transpiler; + +export function parse(lang: Script.Language, str: string): Expression { + try { + + const query = transpiler[lang](str); + return query; + + } catch (e) { + + console.error(e.message); + throw e; + + } +} diff --git a/src/mol-script/transpilers/_spec/examples.spec.ts b/src/mol-script/transpilers/_spec/examples.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..ca45ce7e2a519950e1a792da8aa81f646b21c87d --- /dev/null +++ b/src/mol-script/transpilers/_spec/examples.spec.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + * Adapted from MolQL project +**/ + +import { Transpiler } from '../transpiler'; +import { _transpiler as transpilers } from '../all'; + +function testTranspilerExamples(name: string, transpiler: Transpiler) { + describe(`${name} examples`, () => { + const examples = require(`../${name}/examples`).examples; + // console.log(examples); + for (const e of examples) { + + it(e.name, () => { + // check if it transpiles and compiles/typechecks. + transpiler(e.value); + }); + } + }); +} + +testTranspilerExamples('pymol', transpilers.pymol); +testTranspilerExamples('vmd', transpilers.vmd); +testTranspilerExamples('jmol', transpilers.jmol); diff --git a/src/mol-script/transpilers/_spec/jmol.spec.ts b/src/mol-script/transpilers/_spec/jmol.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..4dabd8383b60bde3259d074116043e9e0d2f8795 --- /dev/null +++ b/src/mol-script/transpilers/_spec/jmol.spec.ts @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * + * Adapted from MolQL project + */ + +import * as u from './utils'; +import { transpiler } from '../jmol/parser'; +import { keywords } from '../jmol/keywords'; +import { properties } from '../jmol/properties'; +import { operators } from '../jmol/operators'; + +const general = { + supported: [ + // atom expressions + '123', + '-42', + '_C', + '.CA', + 'ALA', + '%A', + '^B', + ':C', + '/2', + '10^A:F.CA%C/0', + '10^A:F.CA%C', + '10^A:F.CA', + '10^A:F', + '10^A', + '10:F.CA', + '10/0', + '32 or 42', + '.CA/0 OR 42:A', + '!23', + 'not ASP', + '(ASP or .CA)', + 'ASP and .CA', + '123.CA', + '(1 or 2) and .CA', + '(1 or 2) and (.CA or .N)', + '.CA and (2 or 3)', + '.CA and (2 or 3) and ^A', + '!32 or :A and .CA', + + // trimming + ' atomName = CA ', + 'atomName = CA ', + ' atomName = CA', + + // value comparison + 'resno > 10', + // atom expression + '[LEU]100:A.CA', + '[LEU]100:A', + '[LEU]100.CA', + '[LEU]:A.CA', + '[LEU].CA', + // comma as OR + '100, 42, ALA', + // residue numbering + '(1-10,15,21-30)', + // within + 'within(5,[HEM])', + // within with parentheses + '(within(5,[HEM])) and backbone', + '( within(5,[HEM]) ) and backbone', + // trimming + '[ALA] and [VAL] ', + ' [ALA] and [VAL] ', + ' [ALA] and [VAL]', + // within with whitespaces + 'within ( 5 , [HEM] ) ', + // un-braketed residue name + 'LEU and ILE', + // un-parenthesized residue index range + '100-120,220', + // un-parenthesized residue index + '20', + // within in the head or the middle of sentence + 'within ( 5 , [HEM] ) and backbone', + + // atom expressions with ranges + '19-32:A', + '-2-32:B', + '-10--2:C', + '[1FO]19-32:A', + ], + unsupported: [ + // values outside of comparisons + 'foobar', + 'protein or foobar', + ] +}; + +describe('jmol general', () => { + general.supported.forEach(str => { + it(str, () => { + transpiler(str); + }); + }); + general.unsupported.forEach(str => { + it(str, () => { + const transpileStr = () => transpiler(str); + expect(transpileStr).toThrow(); + expect(transpileStr).not.toThrowError(RangeError); + }); + }); +}); + +describe('jmol keywords', () => u.testKeywords(keywords, transpiler)); +describe('jmol properties', () => u.testProperties(properties, transpiler)); +describe('jmol operators', () => u.testOperators(operators, transpiler)); diff --git a/src/mol-script/transpilers/_spec/pymol.spec.ts b/src/mol-script/transpilers/_spec/pymol.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..3a463f2122ed7f21b9c63a6e7465cc34c3112db0 --- /dev/null +++ b/src/mol-script/transpilers/_spec/pymol.spec.ts @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + */ + +import * as u from './utils'; +import { transpiler } from '../pymol/parser'; +import { keywords } from '../pymol/keywords'; +import { properties } from '../pymol/properties'; +import { operators } from '../pymol/operators'; + +const general = { + supported: [ + // macros + '10/cb', + 'a/10-12/ca', + 'lig/b/6+8/c+o', + + // trimming + ' name CA ', + 'name CA ', + ' name CA', + ], + unsupported: [ + // macros + 'pept/enz/c/3/n', + 'pept/enz///n', + + '/pept/lig/', + '/pept/lig/a', + '/pept/lig/a/10', + '/pept/lig/a/10/ca', + '/pept//a/10', + + // object + 'foobar', + 'protein and bazbar', + ] +}; + +describe('pymol general', () => { + general.supported.forEach(str => { + it(str, () => { + transpiler(str); + // compile(expr); + }); + }); + general.unsupported.forEach(str => { + it(str, () => { + const transpileStr = () => transpiler(str); + expect(transpileStr).toThrow(); + expect(transpileStr).not.toThrowError(RangeError); + }); + }); +}); + +// check against builder output +// 'not (resi 42 or chain A)' +// '!resi 42 or chain A' +// 'b >= 0.3', +// 'b != 0.3', +// 'b>0.3', +// 'b <0.3', +// 'b <= 0.3', +// 'b = 1', +// 'fc.=.1', + +describe('pymol keywords', () => u.testKeywords(keywords, transpiler)); +describe('pymol operators', () => u.testOperators(operators, transpiler)); +describe('pymol properties', () => u.testProperties(properties, transpiler)); diff --git a/src/mol-script/transpilers/_spec/utils.ts b/src/mol-script/transpilers/_spec/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..9e0d6b59d269ccf1e3a28ddf64fa56db1c2d6a6f --- /dev/null +++ b/src/mol-script/transpilers/_spec/utils.ts @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panangiot_tourlov@hotmail.com> + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + */ + +import { Transpiler } from '../transpiler'; +import { KeywordDict, PropertyDict, OperatorList } from '../types'; + +export function testKeywords(keywords: KeywordDict, transpiler: Transpiler) { + for (const name in keywords) { + it(name, () => { + const k = keywords[name]; + if (k.map) { + const expr = transpiler(name); + expect(expr).toEqual(k.map()); + } else { + const transpile = () => transpiler(name); + expect(transpile).toThrow(); + expect(transpile).not.toThrowError(RangeError); + } + }); + } +} + +export function testProperties(properties: PropertyDict, transpiler: Transpiler) { + for (const name in properties) { + const p = properties[name]; + p['@examples'].forEach(example => { + it(name, () => { + if (!p.isUnsupported) { + transpiler(example); + } else { + const transpile = () => transpiler(example); + expect(transpile).toThrow(); + expect(transpile).not.toThrowError(RangeError); + } + }); + }); + it(name, () => { + if (!p['@examples'].length) { + throw Error(`'${name}' property has no example(s)`); + } + }); + } +} + +export function testOperators(operators: OperatorList, transpiler: Transpiler) { + operators.forEach(o => { + o['@examples'].forEach(example => { + it(o.name, () => { + if (!o.isUnsupported) { + transpiler(example); + } else { + const transpile = () => transpiler(example); + expect(transpile).toThrow(); + expect(transpile).not.toThrowError(RangeError); + } + }); + }); + it(o.name, () => { + if (!o['@examples'].length) { + throw Error(`'${o.name}' operator has no example(s)`); + } + }); + }); +} diff --git a/src/mol-script/transpilers/_spec/vmd.spec.ts b/src/mol-script/transpilers/_spec/vmd.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..1da4b9f58a742cec0fb9eed84f89cfad17f36a83 --- /dev/null +++ b/src/mol-script/transpilers/_spec/vmd.spec.ts @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + */ + +import * as u from './utils'; +import { transpiler } from '../vmd/parser'; +import { keywords } from '../vmd/keywords'; +import { properties } from '../vmd/properties'; +import { operators } from '../vmd/operators'; + +const general = { + supported: [ + // trimming + ' name CA ', + 'name CA ', + ' name CA', + ], + unsupported: [ + // variables + 'name $atomname', + 'protein and @myselection', + + // values outside of comparisons + 'foobar', + '34', + 'name', + 'abs(-42)', + 'abs(21+21)', + 'sqr(3)', + 'sqr(x)', + 'sqr(x+33)', + 'protein or foobar', + '34 and protein', + 'name or protein', + ] +}; + +describe('vmd general', () => { + general.supported.forEach(str => { + it(str, () => { + transpiler(str); + // compile(expr); + }); + }); + general.unsupported.forEach(str => { + it(str, () => { + const transpileStr = () => transpiler(str); + expect(transpileStr).toThrow(); + expect(transpileStr).not.toThrowError(RangeError); + }); + }); +}); + +describe('vmd keywords', () => u.testKeywords(keywords, transpiler)); +describe('vmd operators', () => u.testOperators(operators, transpiler)); +describe('vmd properties', () => u.testProperties(properties, transpiler)); diff --git a/src/mol-script/transpilers/all.ts b/src/mol-script/transpilers/all.ts new file mode 100644 index 0000000000000000000000000000000000000000..01f98d6643f9a8ae9d39460cb13d7092301121d9 --- /dev/null +++ b/src/mol-script/transpilers/all.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + * + * Adapted from MolQL project + */ + +import { transpiler as jmol } from './jmol/parser'; +import { transpiler as pymol } from './pymol/parser'; +import { transpiler as vmd } from './vmd/parser'; + +export const _transpiler = { + pymol, + vmd, + jmol, +}; diff --git a/src/mol-script/transpilers/helper.ts b/src/mol-script/transpilers/helper.ts new file mode 100644 index 0000000000000000000000000000000000000000..03b3d583aedf184e59595d37f5361b9b72c4167b --- /dev/null +++ b/src/mol-script/transpilers/helper.ts @@ -0,0 +1,385 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + * + * Adapted from MolQL project + */ + +import * as P from '../../mol-util/monadic-parser'; +import { MolScriptBuilder } from '../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { Expression } from '../language/expression'; +import { KeywordDict, PropertyDict, FunctionDict, OperatorList } from './types'; + +export function escapeRegExp(s: String) { + return String(s).replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'); +} + +// Takes a parser for the prefix operator, and a parser for the base thing being +// parsed, and parses as many occurrences as possible of the prefix operator. +// Note that the parser is created using `P.lazy` because it's recursive. It's +// valid for there to be zero occurrences of the prefix operator. +export function prefix(opParser: P.MonadicParser<any>, nextParser: P.MonadicParser<any>, mapFn: any) { + const parser: P.MonadicParser<any> = P.MonadicParser.lazy(() => { + return P.MonadicParser.seq(opParser, parser) + .map(x => mapFn(...x)) + .or(nextParser); + }); + return parser; +} + +// Ideally this function would be just like `PREFIX` but reordered like +// `P.seq(parser, opParser).or(nextParser)`, but that doesn't work. The +// reason for that is that Parsimmon will get stuck in infinite recursion, since +// the very first rule. Inside `parser` is to match parser again. Alternatively, +// you might think to try `nextParser.or(P.seq(parser, opParser))`, but +// that won't work either because in a call to `.or` (aka `P.alt`), Parsimmon +// takes the first possible match, even if subsequent matches are longer, so the +// parser will never actually look far enough ahead to see the postfix +// operators. +export function postfix(opParser: P.MonadicParser<any>, nextParser: P.MonadicParser<any>, mapFn: any) { + // Because we can't use recursion like stated above, we just match a flat list + // of as many occurrences of the postfix operator as possible, then use + // `.reduce` to manually nest the list. + // + // Example: + // + // INPUT :: "4!!!" + // PARSE :: [4, "factorial", "factorial", "factorial"] + // REDUCE :: ["factorial", ["factorial", ["factorial", 4]]] + return P.MonadicParser.seqMap( + nextParser, + opParser.many(), + (x: any, suffixes: any) => + suffixes.reduce((acc: any, x: any) => { + return mapFn(x, acc); + }, x) + ); +} + +// Takes a parser for all the operators at this precedence level, and a parser +// that parsers everything at the next precedence level, and returns a parser +// that parses as many binary operations as possible, associating them to the +// right. (e.g. 1^2^3 is 1^(2^3) not (1^2)^3) +export function binaryRight(opParser: P.MonadicParser<any>, nextParser: P.MonadicParser<any>, mapFn: any) { + const parser: P.MonadicParser<any> = P.MonadicParser.lazy(() => + nextParser.chain(next => + P.MonadicParser.seq( + opParser, + P.MonadicParser.of(next), + parser + ).map((x) => { + return x; + }).or(P.MonadicParser.of(next)) + ) + ); + return parser; +} + +// Takes a parser for all the operators at this precedence level, and a parser +// that parsers everything at the next precedence level, and returns a parser +// that parses as many binary operations as possible, associating them to the +// left. (e.g. 1-2-3 is (1-2)-3 not 1-(2-3)) +export function binaryLeft(opParser: P.MonadicParser<any>, nextParser: P.MonadicParser<any>, mapFn: any) { + // We run into a similar problem as with the `POSTFIX` parser above where we + // can't recurse in the direction we want, so we have to resort to parsing an + // entire list of operator chunks and then using `.reduce` to manually nest + // them again. + // + // Example: + // + // INPUT :: "1+2+3" + // PARSE :: [1, ["+", 2], ["+", 3]] + // REDUCE :: ["+", ["+", 1, 2], 3] + return P.MonadicParser.seqMap( + nextParser, + P.MonadicParser.seq(opParser, nextParser).many(), + (first: any, rest: any) => { + return rest.reduce((acc: any, ch: any) => { + const [op, another] = ch; + return mapFn(op, acc, another); + }, first); + } + ); +} + +/** + * combine operators of decreasing binding strength + */ +export function combineOperators(opList: any[], rule: P.MonadicParser<any>) { + const x = opList.reduce( + (acc, level) => { + const map = level.isUnsupported ? makeError(`operator '${level.name}' not supported`) : level.map; + return level.type(level.rule, acc, map); + }, + rule + ); + return x; +} + +export function infixOp(re: RegExp, group: number = 0) { + return P.MonadicParser.optWhitespace.then(P.MonadicParser.regexp(re, group).skip(P.MonadicParser.optWhitespace)); +} + +export function prefixOp(re: RegExp, group: number = 0) { + return P.MonadicParser.regexp(re, group).skip(P.MonadicParser.optWhitespace); +} + +export function postfixOp(re: RegExp, group: number = 0) { + return P.MonadicParser.optWhitespace.then(P.MonadicParser.regexp(re, group)); +} + +export function ofOp(name: string, short?: string) { + const op = short ? `${name}|${escapeRegExp(short)}` : name; + const re = RegExp(`(${op})\\s+([-+]?[0-9]*\\.?[0-9]+)\\s+OF`, 'i'); + return infixOp(re, 2).map(parseFloat); +} + +export function makeError(msg: string) { + return function () { + throw new Error(msg); + }; +} + +export function andExpr(selections: any[]) { + if (selections.length === 1) { + return selections[0]; + } else if (selections.length > 1) { + return B.core.logic.and(selections); + } else { + return undefined; + } +} + +export function orExpr(selections: any[]) { + if (selections.length === 1) { + return selections[0]; + } else if (selections.length > 1) { + return B.core.logic.or(selections); + } else { + return undefined; + } +} + +export function testExpr(property: any, args: any) { + if (args && args.op !== undefined && args.val !== undefined) { + const opArgs = [property, args.val]; + switch (args.op) { + case '=': return B.core.rel.eq(opArgs); + case '!=': return B.core.rel.neq(opArgs); + case '>': return B.core.rel.gr(opArgs); + case '<': return B.core.rel.lt(opArgs); + case '>=': return B.core.rel.gre(opArgs); + case '<=': return B.core.rel.lte(opArgs); + default: throw new Error(`operator '${args.op}' not supported`); + } + } else if (args && args.flags !== undefined) { + return B.core.flags.hasAny([property, args.flags]); + } else if (args && args.min !== undefined && args.max !== undefined) { + return B.core.rel.inRange([property, args.min, args.max]); + } else if (!Array.isArray(args)) { + return B.core.rel.eq([property, args]); + } else if (args.length > 1) { + return B.core.set.has([B.core.type.set(args), property]); + } else { + return B.core.rel.eq([property, args[0]]); + } +} + +export function invertExpr(selection: Expression) { + return B.struct.generator.queryInSelection({ + 0: selection, query: B.struct.generator.all(), 'in-complement': true } + ); +} + +export function strLenSortFn(a: string, b: string) { + return a.length < b.length ? 1 : -1; +} + +function getNamesRegex(name: string, abbr?: string[]) { + const names = (abbr ? [name].concat(abbr) : [name]) + .sort(strLenSortFn).map(escapeRegExp).join('|'); + return RegExp(`${names}`, 'i'); +} + +export function getPropertyRules(properties: PropertyDict) { + // in keyof typeof properties + const propertiesDict: { [name: string]: P.MonadicParser<any> } = {}; + + Object.keys(properties).sort(strLenSortFn).forEach(name => { + const ps = properties[name]; + const errorFn = makeError(`property '${name}' not supported`); + const rule = P.MonadicParser.regexp(ps.regex).map((x: any) => { + if (ps.isUnsupported) errorFn(); + return testExpr(ps.property, ps.map(x)); + }); + + if (!ps.isNumeric) { + propertiesDict[name] = rule; + } + }); + + return propertiesDict; +} + +export function getNamedPropertyRules(properties: PropertyDict) { + const namedPropertiesList: P.MonadicParser<any>[] = []; + + Object.keys(properties).sort(strLenSortFn).forEach(name => { + const ps = properties[name]; + const errorFn = makeError(`property '${name}' not supported`); + const rule = P.MonadicParser.regexp(ps.regex).map((x: any) => { + if (ps.isUnsupported) errorFn(); + return testExpr(ps.property, ps.map(x)); + }); + const nameRule = P.MonadicParser.regexp(getNamesRegex(name, ps.abbr)).trim(P.MonadicParser.optWhitespace); + const groupMap = (x: any) => B.struct.generator.atomGroups({ [ps.level]: x }); + + if (ps.isNumeric) { + namedPropertiesList.push( + nameRule.then(P.MonadicParser.seq( + P.MonadicParser.regexp(/>=|<=|=|!=|>|</).trim(P.MonadicParser.optWhitespace), + P.MonadicParser.regexp(ps.regex).map(ps.map) + )).map((x: any) => { + if (ps.isUnsupported) errorFn(); + return testExpr(ps.property, { op: x[0], val: x[1] }); + }).map(groupMap) + ); + } else { + namedPropertiesList.push(nameRule.then(rule).map(groupMap)); + } + }); + + return namedPropertiesList; +} + +export function getKeywordRules(keywords: KeywordDict) { + const keywordsList: P.MonadicParser<any>[] = []; + + Object.keys(keywords).sort(strLenSortFn).forEach(name => { + const ks = keywords[name]; + const mapFn = ks.map ? ks.map : makeError(`keyword '${name}' not supported`); + const rule = P.MonadicParser.regexp(getNamesRegex(name, ks.abbr)).map(mapFn); + keywordsList.push(rule); + }); + + return keywordsList; +} + +export function getFunctionRules(functions: FunctionDict, argRule: P.MonadicParser<any>) { + const functionsList: P.MonadicParser<any>[] = []; + const begRule = P.MonadicParser.regexp(/\(\s*/); + const endRule = P.MonadicParser.regexp(/\s*\)/); + + Object.keys(functions).sort(strLenSortFn).forEach(name => { + const fs = functions[name]; + const mapFn = fs.map ? fs.map : makeError(`function '${name}' not supported`); + const rule = P.MonadicParser.regexp(new RegExp(name, 'i')).skip(begRule).then(argRule).skip(endRule).map(mapFn); + functionsList.push(rule); + }); + + return functionsList; +} + +export function getPropertyNameRules(properties: PropertyDict, lookahead: RegExp) { + const list: P.MonadicParser<any>[] = []; + Object.keys(properties).sort(strLenSortFn).forEach(name => { + const ps = properties[name]; + const errorFn = makeError(`property '${name}' not supported`); + const rule = (P.MonadicParser as any).regexp(getNamesRegex(name, ps.abbr)).lookahead(lookahead).map(() => { + if (ps.isUnsupported) errorFn(); + return ps.property; + }); + list.push(rule); + }); + + return list; +} + +export function getReservedWords(properties: PropertyDict, keywords: KeywordDict, operators: OperatorList, functions?: FunctionDict) { + const w: string[] = []; + for (const name in properties) { + w.push(name); + if (properties[name].abbr) w.push(...properties[name].abbr!); + } + for (const name in keywords) { + w.push(name); + if (keywords[name].abbr) w.push(...keywords[name].abbr!); + } + operators.forEach(o => { + w.push(o.name); + if (o.abbr) w.push(...o.abbr); + }); + return w; +} + +export function atomNameSet(ids: string[]) { + return B.core.type.set(ids.map(B.atomName)); +} + +export function asAtoms(e: Expression) { + return B.struct.generator.queryInSelection({ + 0: e, + query: B.struct.generator.all() + }); +} + +export function wrapValue(property: any, value: any, sstrucDict?: any) { + switch (property.head.name) { + case 'structure-query.atom-property.macromolecular.label_atom_id': + return B.atomName(value); + case 'structure-query.atom-property.core.element-symbol': + return B.es(value); + case 'structure-query.atom-property.macromolecular.secondary-structure-flags': + if (sstrucDict) { + value = [sstrucDict[value.toUpperCase()] || 'none']; + } + return B.struct.type.secondaryStructureFlags([value]); + default: + return value; + } +} + +const propPrefix = 'structure-query.atom-property.macromolecular.'; +const entityProps = ['entityKey', 'label_entity_id', 'entityType']; +const chainProps = ['chainKey', 'label_asym_id', 'label_entity_id', 'auth_asym_id', 'entityType']; +const residueProps = ['residueKey', 'label_comp_id', 'label_seq_id', 'auth_comp_id', 'auth_seq_id', 'pdbx_formal_charge', 'secondaryStructureKey', 'secondaryStructureFlags', 'isModified', 'modifiedParentName']; +export function testLevel(property: any) { + if (property.head.name.startsWith(propPrefix)) { + const name = property.head.name.substr(propPrefix.length); + if (entityProps.includes(name)) return 'entity-test'; + if (chainProps.includes(name)) return 'chain-test'; + if (residueProps.includes(name)) return 'residue-test'; + } + return 'atom-test'; +} + +const flagProps = [ + 'structure-query.atom-property.macromolecular.secondary-structure-flags' +]; +export function valuesTest(property: any, values: any[]) { + if (flagProps.includes(property.head.name)) { + const name = values[0].head; + const flags: any[] = []; + values.forEach(v => flags.push(...v.args[0])); + return B.core.flags.hasAny([property, { head: name, args: flags }]); + } else { + if (values.length === 1) { + return B.core.rel.eq([property, values[0]]); + } else if (values.length > 1) { + return B.core.set.has([B.core.type.set(values), property]); + } + } +} + +export function resnameExpr(resnameList: string[]) { + return B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(resnameList), + B.ammp('label_comp_id') + ]) + }); +} diff --git a/src/mol-script/transpilers/jmol/examples.ts b/src/mol-script/transpilers/jmol/examples.ts new file mode 100644 index 0000000000000000000000000000000000000000..e1fcac2892dc120472a9cea45197593ee3ff35af --- /dev/null +++ b/src/mol-script/transpilers/jmol/examples.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author David Sehnal <david.sehnal@gmail.com> + * + * Adapted from MolQL project + */ + +export const examples = [{ + name: 'Residue 50 or 135', + value: '50 or 135' +}, { + name: 'Atoms with no covalent bonds', + value: 'bondcount = 0' +}, { + name: 'All 3-10 helices', + value: 'substructure = "helix310"' +}, { + name: 'Metal atoms', + value: 'metal' +}, { + name: 'Atoms invloved in aromatic bonds', + value: 'isAromatic' +}, { + name: 'Pyrimidine residues', + value: 'pyrimidine' +}]; diff --git a/src/mol-script/transpilers/jmol/keywords.ts b/src/mol-script/transpilers/jmol/keywords.ts new file mode 100644 index 0000000000000000000000000000000000000000..1dc545721b73a88691b89e1a84dc89011df8fab3 --- /dev/null +++ b/src/mol-script/transpilers/jmol/keywords.ts @@ -0,0 +1,571 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * + * Adapted from MolQL project + */ + + +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import * as h from '../helper'; +import { KeywordDict } from '../types'; + +const ResDict = { + acidic: ['ASP', 'GLU'], + aliphatic: ['ALA', 'GLY', 'ILE', 'LEU', 'VAL'], + amino: ['ALA', 'ARG', 'ASN', 'ASP', 'CYS', 'GLN', 'GLU', 'GLY', 'HIS', 'ILE', 'LEU', 'LYS', 'MET', 'PHE', 'PRO', 'SER', 'THR', 'TRP', 'TYR', 'VAL', 'ASX', 'GLX', 'UNK'], + aromatic: ['HIS', 'PHE', 'TRP', 'TYR'], + basic: ['ARG', 'HIS', 'LYS'], + buried: ['ALA', 'CYS', 'ILE', 'LEU', 'MET', 'PHE', 'TRP', 'VAL'], + cg: ['CYT', 'C', 'GUA', 'G'], + cyclic: ['HIS', 'PHE', 'PRO', 'TRP', 'TYR'], + hydrophobic: ['ALA', 'GLY', 'ILE', 'LEU', 'MET', 'PHE', 'PRO', 'TRP', 'TYR', 'VAL'], + large: ['ARG', 'GLU', 'GLN', 'HIS', 'ILE', 'LEU', 'LYS', 'MET', 'PHE', 'TRP', 'TYR'], + medium: ['ASN', 'ASP', 'CYS', 'PRO', 'THR', 'VAL'], + small: ['ALA', 'GLY', 'SER'], + + nucleic: ['G', 'C', 'A', 'T', 'U', 'I', 'DG', 'DC', 'DA', 'DT', 'DU', 'DI', '+G', '+C', '+A', '+T', '+U', '+I'] +}; + +const Backbone = { + nucleic: ['P', "O3'", "O5'", "C5'", "C4'", "C3'", 'OP1', 'OP2', 'O3*', 'O5*', 'C5*', 'C4*', 'C3*', + "C2'", "C1'", "O4'", "O2'"], + protein: ['C', 'N', 'CA'] +}; + +function nucleicExpr() { + return B.struct.combinator.merge([ + B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.set(...ResDict.nucleic), + B.ammp('label_comp_id') + ]) + }), + B.struct.filter.pick({ + 0: B.struct.generator.atomGroups({ + 'group-by': B.ammp('residueKey') + }), + test: B.core.logic.and([ + B.core.rel.eq([B.struct.atomSet.atomCount(), 1]), + B.core.rel.eq([B.ammp('label_atom_id'), B.atomName('P')]), + ]) + }), + B.struct.filter.pick({ + 0: B.struct.generator.atomGroups({ + 'group-by': B.ammp('residueKey') + }), + test: B.core.logic.or([ + B.core.set.isSubset([ + h.atomNameSet(["C1'", "C2'", "O3'", "C3'", "C4'", "C5'", "O5'"]), + B.ammpSet('label_atom_id') + ]), + B.core.set.isSubset([ + h.atomNameSet(['C1*', 'C2*', 'O3*', 'C3*', 'C4*', 'C5*', 'O5*']), + B.ammpSet('label_atom_id') + ]) + ]) + }) + ]); +} + +// TODO: improve, see keywords.protein['@desc'] below +function proteinExpr() { + return B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.set(...ResDict.amino), + B.ammp('label_comp_id') + ]) + }); +} + +// TODO: improve, see keywords.backbone['@desc'] below +function backboneExpr() { + return B.struct.combinator.merge([ + B.struct.modifier.intersectBy({ + 0: B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.amino), + B.ammp('label_comp_id') + ]) + }), + by: B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + B.core.type.set(Backbone.protein), + B.ammp('label_atom_id') + ]) + }) + }), + B.struct.modifier.intersectBy({ + 0: B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.nucleic), + B.ammp('label_comp_id') + ]) + }), + by: B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + B.core.type.set(Backbone.nucleic), + B.ammp('label_atom_id') + ]) + }) + }), + ]); +} + +export const keywords: KeywordDict = { + // general terms + all: { + '@desc': 'all atoms; same as *', + abbr: ['*'], + map: () => B.struct.generator.all() + }, + bonded: { + '@desc': 'covalently bonded', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.gr([B.struct.atomProperty.core.bondCount({ + flags: B.struct.type.bondFlags(['covalent', 'metallic', 'sulfide']) + }), 0]) + }) + }, + clickable: { + '@desc': 'actually visible -- having some visible aspect such as wireframe, spacefill, or a label showing, or the alpha-carbon or phosphorus atom in a biomolecule that is rendered with only cartoon, rocket, or other biomolecule-specific shape.' + }, + connected: { + '@desc': 'bonded in any way, including hydrogen bonds', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.gr([B.struct.atomProperty.core.bondCount({ + flags: B.struct.type.bondFlags() + }), 0]) + }) + }, + displayed: { + '@desc': 'displayed using the display or hide command; not necessarily visible' + }, + hidden: { + '@desc': 'hidden using the display or hide command' + }, + none: { + '@desc': 'no atoms', + map: () => B.struct.generator.empty() + }, + selected: { + '@desc': 'atoms that have been selected; defaults to all when a file is first loaded' + }, + thisModel: { + '@desc': 'atoms in the current frame set, as defined by frame, model, or animation commands. If more than one model is in this set, "thisModel" refers to all of them, regardless of atom displayed/hidden status.' + }, + visible: { + '@desc': 'visible in any way, including PDB residue atoms for which a cartoon or other such rendering makes their group visible, even if they themselves are not visible.' + }, + subset: { + '@desc': 'the currently defined subset. Note that if a subset is currently defined, then select/display all is the same as select/display subset, restrict none is the same as restrict not subset. In addition, select not subset selects nothing.' + }, + specialPosition: { + '@desc': 'atoms in crystal structures that are at special positions - that is, for which there is more than one operator that leads to them.' + }, + unitcell: { + '@desc': 'atoms within the current unitcell, which may be offset. This includes atoms on the faces and at the vertices of the unitcell.' + }, + polyhedra: { + '@desc': 'all central atoms for which polyhedra have been created. See also polyhera(n), below. (Jmol 14.4)' + }, + nonmetal: { + '@desc': '_H,_He,_B,_C,_N,_O,_F,_Ne,_Si,_P,_S,_Cl,_Ar,_As,_Se,_Br,_Kr,_Te,_I,_Xe,_At,_Rn', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + B.set(...['H', 'He', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Si', 'P', 'S', 'Cl', 'Ar', 'As', 'Se', 'Br', 'Kr', 'Te', 'I', 'Xe', 'At', 'Rn'].map(B.es)), + B.acp('elementSymbol') + ]) + }) + }, + metal: { + '@desc': '!nonmetal', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.logic.not([ + B.core.set.has([ + B.set(...['H', 'He', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Si', 'P', 'S', 'Cl', 'Ar', 'As', 'Se', 'Br', 'Kr', 'Te', 'I', 'Xe', 'At', 'Rn'].map(B.es)), + B.acp('elementSymbol') + ]) + ]) + }) + }, + alkaliMetal: { + '@desc': '_Li,_Na,_K,_Rb,_Cs,_Fr', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + B.set(...['Li', 'Na', 'K', 'Rb', 'Cs', 'Fr'].map(B.es)), + B.acp('elementSymbol') + ]) + }) + }, + alkalineEarth: { + '@desc': '_Be,_Mg,_Ca,_Sr,_Ba,_Ra', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + B.set(...['Be', 'Mg', 'Ca', 'Sr', 'Ba', 'Ra'].map(B.es)), + B.acp('elementSymbol') + ]) + }) + }, + nobleGas: { + '@desc': '_He,_Ne,_Ar,_Kr,_Xe,_Rn', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + B.set(...['He', 'Ne', 'Ar', 'Kr', 'Xe', 'Rn'].map(B.es)), + B.acp('elementSymbol') + ]) + }) + }, + metalloid: { + '@desc': '_B,_Si,_Ge,_As,_Sb,_Te', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + B.set(...['B', 'Si', 'Ge', 'As', 'Sb', 'Te'].map(B.es)), + B.acp('elementSymbol') + ]) + }) + }, + transitionMetal: { + '@desc': '(includes La and Ac) elemno>=21 and elemno<=30, elemno=57, elemno=89, elemno>=39 and elemno<=48, elemno>=72 and elemno<=80, elemno>=104 and elemno<=112', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.logic.or([ + B.core.rel.inRange([B.acp('atomicNumber'), 21, 30]), + B.core.rel.inRange([B.acp('atomicNumber'), 39, 48]), + B.core.rel.inRange([B.acp('atomicNumber'), 72, 80]), + B.core.rel.inRange([B.acp('atomicNumber'), 104, 112]), + B.core.set.has([B.set(57, 89), B.acp('atomicNumber')]) + ]) + }) + }, + lanthanide: { + '@desc': '(does not include La) elemno>57 and elemno<=71', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.inRange([B.acp('atomicNumber'), 57, 71]) + }) + }, + actinide: { + '@desc': '(does not include Ac) elemno>89 and elemno<=103', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.inRange([B.acp('atomicNumber'), 89, 103]) + }) + }, + isaromatic: { + '@desc': 'atoms connected with the AROMATIC, AROMATICSINGLE, or AROMATICDOUBLE bond types', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.gr([ + B.struct.atomProperty.core.bondCount({ + flags: B.struct.type.bondFlags(['aromatic']) + }), + 0 + ]) + }) + }, + + carbohydrate: { + '@desc': '' + }, + ions: { + '@desc': '(specifically the PDB designations "PO4" and "SO4")' + }, + ligand: { + '@desc': '(originally "hetero and not solvent"; changed to "!(protein,nucleic,water,UREA)" for Jmol 12.2)' + }, + nucleic: { + '@desc': 'any group that (a) has one of the following group names: G, C, A, T, U, I, DG, DC, DA, DT, DU, DI, +G, +C, +A, +T, +U, +I; or (b) can be identified as a group that is only one atom, with name "P"; or (c) has all of the following atoms (prime, \', can replace * here): C1*, C2*, C3*, O3*, C4*, C5*, and O5*.', + map: () => nucleicExpr() + }, + purine: { + '@desc': 'any nucleic group that (a) has one of the following group names: A, G, I, DA, DG, DI, +A, +G, or +I; or (b) also has atoms N7, C8, and N9.', + map: () => B.struct.modifier.intersectBy({ + 0: nucleicExpr(), + by: B.struct.combinator.merge([ + B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.set(...['A', 'G', 'I', 'DA', 'DG', 'DI', '+A', '+G', '+I']), + B.ammp('label_comp_id') + ]) + }), + B.struct.filter.pick({ + 0: B.struct.generator.atomGroups({ + 'group-by': B.ammp('residueKey') + }), + test: B.core.set.isSubset([ + h.atomNameSet(['N7', 'C8', 'N9']), + B.ammpSet('label_atom_id') + ]) + }) + ]) + }) + }, + pyrimidine: { + '@desc': 'any nucleic group that (a) has one of the following group names: C, T, U, DC, DT, DU, +C, +T, +U; or (b) also has atom O2.', + map: () => B.struct.modifier.intersectBy({ + 0: nucleicExpr(), + by: B.struct.combinator.merge([ + B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.set(...['C', 'T', 'U', 'DC', 'DT', 'DU', '+C', '+T', '+U']), + B.ammp('label_comp_id') + ]) + }), + B.struct.filter.pick({ + 0: B.struct.generator.atomGroups({ + 'group-by': B.ammp('residueKey') + }), + test: B.core.logic.or([ + B.core.set.has([ + B.ammpSet('label_atom_id'), + B.atomName('O2*') + ]), + B.core.set.has([ + B.ammpSet('label_atom_id'), + B.atomName("O2'") + ]) + ]) + }) + ]) + }) + }, + dna: { + '@desc': 'any nucleic group that (a) has one of the following group names: DG, DC, DA, DT, DU, DI, T, +G, +C, +A, +T; or (b) has neither atom O2* or O2\'.', + map: () => B.struct.modifier.intersectBy({ + 0: nucleicExpr(), + by: B.struct.combinator.merge([ + B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.set(...['DG', 'DC', 'DA', 'DT', 'DU', 'DI', 'T', '+G', '+C', '+A', '+T']), + B.ammp('label_comp_id') + ]) + }), + B.struct.filter.pick({ + 0: B.struct.generator.atomGroups({ + 'group-by': B.ammp('residueKey') + }), + test: B.core.logic.not([ + B.core.logic.or([ + B.core.set.has([ + B.ammpSet('label_atom_id'), + B.atomName('O2*') + ]), + B.core.set.has([ + B.ammpSet('label_atom_id'), + B.atomName("O2'") + ]) + ]) + ]) + }) + ]) + }) + }, + rna: { + '@desc': 'any nucleic group that (a) has one of the following group names: G, C, A, U, I, +U, +I; or (b) has atom O2* or O2\'.', + map: () => B.struct.modifier.intersectBy({ + 0: nucleicExpr(), + by: B.struct.combinator.merge([ + B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.set(...['G', 'C', 'A', 'U', 'I', '+U', '+I']), + B.ammp('label_comp_id') + ]) + }), + B.struct.filter.pick({ + 0: B.struct.generator.atomGroups({ + 'group-by': B.ammp('residueKey') + }), + test: B.core.logic.or([ + B.core.set.has([ + B.ammpSet('label_atom_id'), + B.atomName('O2*') + ]), + B.core.set.has([ + B.ammpSet('label_atom_id'), + B.atomName("O2'") + ]) + ]) + }) + ]) + }) + }, + protein: { + '@desc': 'defined as a group that (a) has one of the following group names: ALA, ARG, ASN, ASP, CYS, GLN, GLU, GLY, HIS, ILE, LEU, LYS, MET, PHE, PRO, SER, THR, TRP, TYR, VAL, ASX, GLX, or UNK; or (b) contains PDB atom designations [C, O, CA, and N] bonded correctly; or (c) does not contain "O" but contains [C, CA, and N] bonded correctly; or (d) has only one atom, which has name CA and does not have the group name CA (indicating a calcium atom).', + map: () => proteinExpr() + }, + acidic: { + '@desc': 'ASP GLU', + map: () => h.resnameExpr(ResDict.acidic) + }, + acyclic: { + '@desc': 'amino and not cyclic', + map: () => B.struct.modifier.intersectBy({ + 0: h.resnameExpr(ResDict.amino), + by: h.invertExpr(h.resnameExpr(ResDict.cyclic)) + }) + }, + aliphatic: { + '@desc': 'ALA GLY ILE LEU VAL', + map: () => h.resnameExpr(ResDict.aliphatic) + }, + amino: { + '@desc': 'all twenty standard amino acids, plus ASX, GLX, UNK', + map: () => h.resnameExpr(ResDict.amino) + }, + aromatic: { + '@desc': 'HIS PHE TRP TYR (see also "isaromatic" for aromatic bonds)', + map: () => h.resnameExpr(ResDict.aromatic) + }, + basic: { + '@desc': 'ARG HIS LYS', + map: () => h.resnameExpr(ResDict.basic) + }, + buried: { + '@desc': 'ALA CYS ILE LEU MET PHE TRP VAL', + map: () => h.resnameExpr(ResDict.buried) + }, + charged: { + '@desc': 'same as acidic or basic -- ASP GLU, ARG HIS LYS', + map: () => h.resnameExpr(ResDict.acidic.concat(ResDict.basic)) + }, + cyclic: { + '@desc': 'HIS PHE PRO TRP TYR', + map: () => h.resnameExpr(ResDict.cyclic) + }, + helix: { + '@desc': 'secondary structure-related.', + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.flags.hasAny([ + B.struct.type.secondaryStructureFlags(['helix']), + B.ammp('secondaryStructureFlags') + ]) + }) + }, + helixalpha: { + '@desc': 'secondary structure-related.', + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.flags.hasAny([ + B.struct.type.secondaryStructureFlags(['alpha']), + B.ammp('secondaryStructureFlags') + ]) + }) + }, + helix310: { + '@desc': 'secondary structure-related.', + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.flags.hasAny([ + B.struct.type.secondaryStructureFlags(['3-10']), + B.ammp('secondaryStructureFlags') + ]) + }) + }, + helixpi: { + '@desc': 'secondary structure-related.', + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.flags.hasAny([ + B.struct.type.secondaryStructureFlags(['pi']), + B.ammp('secondaryStructureFlags') + ]) + }) + }, + hetero: { + '@desc': 'PDB atoms designated as HETATM', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.ammp('isHet') + }) + }, + hydrophobic: { + '@desc': 'ALA GLY ILE LEU MET PHE PRO TRP TYR VAL', + map: () => h.resnameExpr(ResDict.hydrophobic) + }, + large: { + '@desc': 'ARG GLU GLN HIS ILE LEU LYS MET PHE TRP TYR', + map: () => h.resnameExpr(ResDict.large) + }, + medium: { + '@desc': 'ASN ASP CYS PRO THR VAL', + map: () => h.resnameExpr(ResDict.medium) + }, + negative: { + '@desc': 'same as acidic -- ASP GLU', + map: () => h.resnameExpr(ResDict.acidic) + }, + neutral: { + '@desc': 'amino and not (acidic or basic)', + map: () => B.struct.modifier.intersectBy({ + 0: h.resnameExpr(ResDict.amino), + by: h.invertExpr(h.resnameExpr(ResDict.acidic.concat(ResDict.basic))) + }) + }, + polar: { + '@desc': 'amino and not hydrophobic', + map: () => B.struct.modifier.intersectBy({ + 0: h.resnameExpr(ResDict.amino), + by: h.invertExpr(h.resnameExpr(ResDict.hydrophobic)) + }) + }, + positive: { + '@desc': 'same as basic -- ARG HIS LYS', + map: () => h.resnameExpr(ResDict.basic) + }, + sheet: { + '@desc': 'secondary structure-related', + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.flags.hasAny([ + B.struct.type.secondaryStructureFlags(['sheet']), + B.ammp('secondaryStructureFlags') + ]) + }) + }, + small: { + '@desc': 'ALA GLY SER', + map: () => h.resnameExpr(ResDict.small) + }, + surface: { + '@desc': 'amino and not buried', + map: () => B.struct.modifier.intersectBy({ + 0: h.resnameExpr(ResDict.amino), + by: h.invertExpr(h.resnameExpr(ResDict.buried)) + }) + }, + turn: { + '@desc': 'secondary structure-related', + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.flags.hasAny([ + B.struct.type.secondaryStructureFlags(['turn']), + B.ammp('secondaryStructureFlags') + ]) + }) + }, + alpha: { + '@desc': '(*.CA)', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.eq([ + B.atomName('CA'), + B.ammp('label_atom_id') + ]) + }) + }, + base: { + '@desc': '(nucleic bases)' + }, + backbone: { + '@desc': '(*.C, *.CA, *.N, and all nucleic other than the bases themselves)', + abbr: ['mainchain'], + map: () => backboneExpr() + }, + sidechain: { + '@desc': '((protein or nucleic) and not backbone)' + }, + spine: { + '@desc': '(*.CA, *.N, *.C for proteins; *.P, *.O3\', *.O5\', *.C3\', *.C4\', *.C5 for nucleic acids)' + }, + leadatom: { + '@desc': '(*.CA, *.P, and terminal *.O5\')' + }, + solvent: { + '@desc': 'PDB "HOH", water, also the connected set of H-O-H in any model' + }, +}; + + diff --git a/src/mol-script/transpilers/jmol/markdown-docs.ts b/src/mol-script/transpilers/jmol/markdown-docs.ts new file mode 100644 index 0000000000000000000000000000000000000000..15fd62c6de50ec23cd28b54c3ad629e5a2850377 --- /dev/null +++ b/src/mol-script/transpilers/jmol/markdown-docs.ts @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + * + * Adapted from MolQL project + */ + +import { properties } from './properties'; +import { operators } from './operators'; +import { keywords } from './keywords'; + + +const _docs: string[] = [ + 'Jmol', + '============', + '--------------------------------', + '' +]; + +_docs.push(`## Properties\n\n`); +_docs.push('--------------------------------\n'); +for (const name in properties) { + if (properties[name].isUnsupported) continue; + + const names = [name]; + if (properties[name].abbr) names.push(...properties[name].abbr!); + _docs.push(`\`\`\`\n${names.join(', ')}\n\`\`\`\n`); + + if (properties[name]['@desc']) { + _docs.push(`*${properties[name]['@desc']}*\n`); + } +} + +_docs.push(`## Operators\n\n`); +_docs.push('--------------------------------\n'); +operators.forEach(o => { + if (o.isUnsupported) return; + + const names = [o.name]; + if (o.abbr) names.push(...o.abbr!); + _docs.push(`\`\`\`\n${names.join(', ')}\n\`\`\`\n`); + + if (o['@desc']) { + _docs.push(`*${o['@desc']}*\n`); + } +}); + +_docs.push(`## Keywords\n\n`); +_docs.push('--------------------------------\n'); +for (const name in keywords) { + if (!keywords[name].map) continue; + + const names = [name]; + if (keywords[name].abbr) names.push(...keywords[name].abbr!); + _docs.push(`\`\`\`\n${names.join(', ')}\n\`\`\`\n`); + + if (keywords[name]['@desc']) { + _docs.push(`*${keywords[name]['@desc']}*\n`); + } +} + +export const docs = _docs.join('\n'); diff --git a/src/mol-script/transpilers/jmol/operators.ts b/src/mol-script/transpilers/jmol/operators.ts new file mode 100644 index 0000000000000000000000000000000000000000..ac9db6f007303ed416989fb0d243a30c555cc23e --- /dev/null +++ b/src/mol-script/transpilers/jmol/operators.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * + * Adapted from MolQL project + */ + +import * as P from '../../../mol-util/monadic-parser'; +import * as h from '../helper'; +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { OperatorList } from '../types'; + +export const operators: OperatorList = [ + { + '@desc': 'Selects atoms that are not included in s1.', + '@examples': ['not ARG'], + name: 'not', + type: h.prefix, + rule: P.MonadicParser.alt(P.MonadicParser.regex(/NOT/i).skip(P.MonadicParser.whitespace), P.MonadicParser.string('!').skip(P.MonadicParser.optWhitespace)), + map: (op, selection) => h.invertExpr(selection), + }, + { + '@desc': 'Selects atoms included in both s1 and s2.', + '@examples': ['ASP and .CA'], + name: 'and', + type: h.binaryLeft, + rule: h.infixOp(/AND|&/i), + map: (op, selection, by) => B.struct.modifier.intersectBy({ 0: selection, by }) + }, + { + '@desc': 'Selects atoms included in either s1 or s2.', + '@examples': ['ASP or GLU'], + name: 'or', + type: h.binaryLeft, + rule: h.infixOp(/OR|\||,/i), + map: (op, s1, s2) => B.struct.combinator.merge([s1, s2]) + } +]; + diff --git a/src/mol-script/transpilers/jmol/parser.ts b/src/mol-script/transpilers/jmol/parser.ts new file mode 100644 index 0000000000000000000000000000000000000000..8c9d0afc89b517b72f2c7260015b13d74bb95e65 --- /dev/null +++ b/src/mol-script/transpilers/jmol/parser.ts @@ -0,0 +1,267 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Koya Sakuma < koya.sakuma.work@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * + * Adapted from MolQL project + */ + +import * as P from '../../../mol-util/monadic-parser'; +import * as h from '../helper'; +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { properties, structureMap } from './properties'; +import { operators } from './operators'; +import { keywords } from './keywords'; +import { AtomGroupArgs } from '../types'; +import { Transpiler } from '../transpiler'; +import { OperatorList } from '../types'; +import { Expression } from '../../language/expression'; + +// <, <=, =, >=, >, !=, and LIKE +const valueOperators: OperatorList = [ + { + '@desc': 'value comparisons', + '@examples': [], + name: '=', + abbr: ['=='], + type: h.binaryLeft, + rule: P.MonadicParser.regexp(/\s*(LIKE|>=|<=|=|!=|>|<)\s*/i, 1), + map: (op, e1, e2) => { + let expr; + if (e1 === 'structure') { + expr = B.core.flags.hasAny([B.ammp('secondaryStructureFlags'), structureMap(e2)]); + } else if (e2 === 'structure') { + expr = B.core.flags.hasAny([B.ammp('secondaryStructureFlags'), structureMap(e1)]); + } else if (e1.head !== undefined) { + if (e1.head.name === 'core.type.regex') { + expr = B.core.str.match([e1, B.core.type.str([e2])]); + } + } else if (e2.head !== undefined) { + if (e2.head.name === 'core.type.regex') { + expr = B.core.str.match([e2, B.core.type.str([e1])]); + } + } else if (op.toUpperCase() === 'LIKE') { + if (e1.head) { + expr = B.core.str.match([ + B.core.type.regex([`^${e2}$`, 'i']), + B.core.type.str([e1]) + ]); + } else { + expr = B.core.str.match([ + B.core.type.regex([`^${e1}$`, 'i']), + B.core.type.str([e2]) + ]); + } + } + if (!expr) { + if (e1.head) e2 = h.wrapValue(e1, e2); + if (e2.head) e1 = h.wrapValue(e2, e1); + switch (op) { + case '=': + expr = B.core.rel.eq([e1, e2]); + break; + case '!=': + expr = B.core.rel.neq([e1, e2]); + break; + case '>': + expr = B.core.rel.gr([e1, e2]); + break; + case '<': + expr = B.core.rel.lt([e1, e2]); + break; + case '>=': + expr = B.core.rel.gre([e1, e2]); + break; + case '<=': + expr = B.core.rel.lte([e1, e2]); + break; + default: throw new Error(`value operator '${op}' not supported`); + } + } + return B.struct.generator.atomGroups({ 'atom-test': expr }); + } + } +]; + +function atomExpressionQuery(x: any[]) { + const [resname, resnoRange, resno, inscode, chainname, atomname, altloc] = x[1]; + const tests: AtomGroupArgs = {}; + + if (chainname) { + // TODO: should be configurable, there is an option in Jmol to use auth or label + tests['chain-test'] = B.core.rel.eq([B.ammp('auth_asym_id'), chainname]); + } + + const resProps = []; + if (resname) resProps.push(B.core.rel.eq([B.ammp('label_comp_id'), resname])); + if (resnoRange) resProps.push(B.core.logic.and([ + B.core.rel.gre([B.ammp('auth_seq_id'), resnoRange[0]]), + B.core.rel.lte([B.ammp('auth_seq_id'), resnoRange[1]]) + ])); + if (resno) resProps.push(B.core.rel.eq([B.ammp('auth_seq_id'), resno])); + if (inscode) resProps.push(B.core.rel.eq([B.ammp('pdbx_PDB_ins_code'), inscode])); + if (resProps.length) tests['residue-test'] = h.andExpr(resProps); + + const atomProps = []; + if (atomname) atomProps.push(B.core.rel.eq([B.ammp('auth_atom_id'), atomname])); + if (altloc) atomProps.push(B.core.rel.eq([B.ammp('label_alt_id'), altloc])); + if (atomProps.length) tests['atom-test'] = h.andExpr(atomProps); + + return B.struct.generator.atomGroups(tests); +} + +const lang = P.MonadicParser.createLanguage({ + Integer: () => P.MonadicParser.regexp(/-?[0-9]+/).map(Number).desc('integer'), + + Parens: function (r: any) { + return P.MonadicParser.alt( + r.Parens, + r.Operator, + r.Expression + ).wrap(P.MonadicParser.regexp(/\(\s*/), P.MonadicParser.regexp(/\s*\)/)); + }, + + Expression: function (r: any) { + return P.MonadicParser.alt( + r.Keywords, + + r.AtomExpression.map(atomExpressionQuery), + + r.Within.map((x: [number, Expression]) => B.struct.modifier.includeSurroundings({ 0: x[1], radius: x[0] })), + r.ValueQuery, + + r.Element.map((x: string) => B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.eq([B.acp('elementSymbol'), B.struct.type.elementSymbol(x)]) + })), + r.Resname.map((x: string) => B.struct.generator.atomGroups({ + 'residue-test': B.core.rel.eq([B.ammp('label_comp_id'), x]) + })), + ); + }, + + Operator: function (r: any) { + return h.combineOperators(operators, P.MonadicParser.alt(r.Parens, r.Expression)); + }, + + AtomExpression: function (r: any) { + return P.MonadicParser.seq( + P.MonadicParser.lookahead(r.AtomPrefix), + P.MonadicParser.seq( + r.BracketedResname.or(P.MonadicParser.of(null)), + r.ResnoRange.or(P.MonadicParser.of(null)), + r.Resno.or(P.MonadicParser.of(null)), + r.Inscode.or(P.MonadicParser.of(null)), + r.Chainname.or(P.MonadicParser.of(null)), + r.Atomname.or(P.MonadicParser.of(null)), + r.Altloc.or(P.MonadicParser.of(null)), + r.Model.or(P.MonadicParser.of(null)) + ) + ).desc('expression'); + }, + + AtomPrefix: () => P.MonadicParser.regexp(/[\[0-9:^%/.-]/).desc('atom-prefix'), + + Chainname: () => P.MonadicParser.regexp(/:([A-Za-z]{1,3})/, 1).desc('chainname'), + Model: () => P.MonadicParser.regexp(/\/([0-9]+)/, 1).map(Number).desc('model'), + Element: () => P.MonadicParser.regexp(/_([A-Za-z]{1,3})/, 1).desc('element'), + Atomname: () => P.MonadicParser.regexp(/\.([a-zA-Z0-9]{1,4})/, 1).map(B.atomName).desc('atomname'), + Resname: () => P.MonadicParser.regexp(/[a-zA-Z0-9]{1,4}/).desc('resname'), + Resno: (r: any) => r.Integer.desc('resno'), + Altloc: () => P.MonadicParser.regexp(/%([a-zA-Z0-9])/, 1).desc('altloc'), + Inscode: () => P.MonadicParser.regexp(/\^([a-zA-Z0-9])/, 1).desc('inscode'), + + BracketedResname: () => P.MonadicParser.regexp(/\[([a-zA-Z0-9]{1,4})\]/, 1).desc('bracketed-resname'), + ResnoRange: (r: any) => { + return P.MonadicParser.seq( + r.Integer.skip(P.MonadicParser.seq( + P.MonadicParser.optWhitespace, + P.MonadicParser.string('-'), + P.MonadicParser.optWhitespace + )), + r.Integer + ).desc('resno-range'); + }, + Within: (r: any) => { + return P.MonadicParser.regexp(/within/i) + .skip(P.MonadicParser.regexp(/\s*\(\s*/)) + .then(P.MonadicParser.seq( + r.Integer.skip(P.MonadicParser.regexp(/\s*,\s*/)), + r.Query + )) + .skip(P.MonadicParser.regexp(/\)/)); + }, + + Keywords: () => P.MonadicParser.alt(...h.getKeywordRules(keywords)).desc('keyword'), + + Query: function (r: any) { + return P.MonadicParser.alt( + r.Operator, + r.Parens, + r.Expression + ).trim(P.MonadicParser.optWhitespace); + }, + + Number: function () { + return P.MonadicParser.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/) + .map(Number) + .desc('number'); + }, + + String: function () { + const w = h.getReservedWords(properties, keywords, operators) + .sort(h.strLenSortFn).map(h.escapeRegExp).join('|'); + return P.MonadicParser.alt( + P.MonadicParser.regexp(new RegExp(`(?!(${w}))[A-Z0-9_]+`, 'i')), + P.MonadicParser.regexp(/'((?:[^"\\]|\\.)*)'/, 1), + P.MonadicParser.regexp(/"((?:[^"\\]|\\.)*)"/, 1).map(x => B.core.type.regex([`^${x}$`, 'i'])) + ).desc('string'); + }, + + Value: function (r: any) { + return P.MonadicParser.alt(r.Number, r.String); + }, + + ValueParens: function (r: any) { + return P.MonadicParser.alt( + r.ValueParens, + r.ValueOperator, + r.ValueExpressions + ).wrap(P.MonadicParser.string('('), P.MonadicParser.string(')')); + }, + + ValuePropertyNames: function () { + return P.MonadicParser.alt(...h.getPropertyNameRules(properties, /LIKE|>=|<=|=|!=|>|<|\)|\s/i)); + }, + + ValueOperator: function (r: any) { + return h.combineOperators(valueOperators, P.MonadicParser.alt(r.ValueParens, r.ValueExpressions)); + }, + + ValueExpressions: function (r: any) { + return P.MonadicParser.alt( + r.Value, + r.ValuePropertyNames + ); + }, + + ValueQuery: function (r: any) { + return P.MonadicParser.alt( + r.ValueOperator.map((x: any) => { + if (x.head) { + if (x.head.name.startsWith('structure-query.generator')) return x; + } else { + if (typeof x === 'string' && x.length <= 4) { + return B.struct.generator.atomGroups({ + 'residue-test': B.core.rel.eq([B.ammp('label_comp_id'), x]) + }); + } + } + throw new Error(`values must be part of an comparison, value '${x}'`); + }) + ); + } +}); + +export const transpiler: Transpiler = str => lang.Query.tryParse(str); diff --git a/src/mol-script/transpilers/jmol/properties.ts b/src/mol-script/transpilers/jmol/properties.ts new file mode 100644 index 0000000000000000000000000000000000000000..566e6780f9d016016a77c0d20c3bde6b21e80308 --- /dev/null +++ b/src/mol-script/transpilers/jmol/properties.ts @@ -0,0 +1,667 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + * + * Adapted from MolQL project + */ + +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { PropertyDict } from '../types'; + +const reFloat = /[-+]?[0-9]*\.?[0-9]+/; +const rePosInt = /[0-9]+/; + +function str(x: string) { return x; } + +const structureDict: { [key: string]: string } = { + none: 'none', + turn: 'turn', + sheet: 'beta', + helix: 'helix', + dna: 'dna', + rna: 'rna', + carbohydrate: 'carbohydrate', + helix310: '3-10', + helixalpha: 'alpha', + helixpi: 'pi', + + 0: 'none', + 1: 'turn', + 2: 'beta', + 3: 'helix', + 4: 'dna', + 5: 'rna', + 6: 'carbohydrate', + 7: '3-10', + 8: 'alpha', + 9: 'pi', +}; +export function structureMap(x: any) { + if (x.head) { + if (x.head.name && x.head.name === 'core.type.regex') x = x.args[0].replace(/^\^|\$$/g, ''); + x = structureDict[x.toString().toLowerCase()] || 'none'; + if (['dna', 'rna', 'carbohydrate'].indexOf(x) !== -1) { + throw new Error("values 'dna', 'rna', 'carbohydrate' not yet supported for 'structure' property"); + } else { + return B.struct.type.secondaryStructureFlags([x]); + } + } +} + +export const properties: PropertyDict = { + adpmax: { + '@desc': 'the maximum anisotropic displacement parameter for the selected atom', + '@examples': [''], + isUnsupported: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test' + }, + adpmin: { + '@desc': 'the minimum anisotropic displacement parameter for the selected atom', + '@examples': [''], + isUnsupported: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test' + }, + altloc: { + '@desc': 'PDB alternate location identifier', + '@examples': ['altloc = A'], + regex: /[a-zA-Z0-9]/, map: str, + level: 'atom-test', property: B.ammp('label_alt_id') + }, + altname: { + '@desc': 'an alternative name given to atoms by some file readers (for example, P2N)', + '@examples': [''], + isUnsupported: true, + regex: /[a-zA-Z0-9]/, map: str, + level: 'atom-test' + }, + atomID: { + '@desc': 'special atom IDs for PDB atoms assigned by Jmol', + '@examples': [''], + isUnsupported: true, + regex: rePosInt, map: x => parseInt(x), + level: 'atom-test' + }, + atomIndex: { + '@desc': 'atom 0-based index; a unique number for each atom regardless of the number of models loaded', + '@examples': [''], + isUnsupported: true, + regex: rePosInt, map: x => parseInt(x), + level: 'atom-test' + }, + atomName: { + '@desc': 'atom name', + '@examples': ['atomName = CA'], + regex: /[a-zA-Z0-9]+/, map: v => B.atomName(v), + level: 'atom-test', property: B.ammp('label_atom_id') + }, + atomno: { + '@desc': 'sequential number; you can use "@" instead of "atomno=" -- for example, select @33 or Var x = @33 or @35', + '@examples': [''], + isUnsupported: true, + regex: rePosInt, map: x => parseInt(x), + level: 'atom-test' + }, + atomType: { + '@desc': 'atom type (mol2, AMBER files) or atom name (other file types)', + '@examples': ['atomType = OH'], + regex: /[a-zA-Z0-9]+/, map: v => B.atomName(v), + level: 'atom-test', property: B.ammp('label_atom_id') + }, + atomX: { + '@desc': 'Cartesian X coordinate (or just X)', + '@examples': ['x = 4.2'], + abbr: ['X'], + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.acp('x') + }, + atomY: { + '@desc': 'Cartesian Y coordinate (or just Y)', + '@examples': ['y < 42'], + abbr: ['Y'], + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.acp('y') + }, + atomZ: { + '@desc': 'Cartesian Z coordinate (or just Z)', + '@examples': ['Z > 10'], + abbr: ['Z'], + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.acp('z') + }, + bondcount: { + '@desc': 'covalent bond count', + '@examples': ['bondcount = 0'], + isNumeric: true, + regex: rePosInt, map: x => parseInt(x), + level: 'atom-test', property: B.acp('bondCount') + }, + bondingRadius: { + '@desc': 'radius used for auto bonding; synonymous with ionic and ionicRadius', + '@examples': [''], + abbr: ['ionic', 'ionicRadius'], + isUnsupported: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test' + }, + cell: { + '@desc': 'crystallographic unit cell, expressed either in lattice integer notation (111-999) or as a coordinate in ijk space, where {1 1 1} is the same as 555. ANDing two cells, for example select cell=555 and cell=556, selects the atoms on the common face. (Note: in the specifc case of CELL, only "=" is allowed as a comparator.)', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + configuration: { + '@desc': 'Only in the context {configuration=n}, this option selects the set of atoms with either no ALTLOC specified or those atoms having this index into the array of altlocs within its model. So, for example, if the model has altloc "A" and "B", select configuration=1 is equivalent to select altloc="" or altloc="A", and print {configuration=2} is equivalent to print {altloc="" or altloc="B"}. Configuration 0 is "all atoms in a model having configurations", and an invalid configuration number gives no atoms. (Note: in the specifc case of CONFIGURATION, only "=" is allowed as a comparator.)', + '@examples': [''], + isUnsupported: true, + regex: rePosInt, map: x => parseInt(x), + level: 'atom-test' + }, + chain: { + '@desc': 'protein chain. For newer CIF files allowing multicharacter chain specifications, use quotations marks: select chain="AA". For these multicharacter desigations, case is not checked unless the CIF file has lower-case chain designations.', + '@examples': ['chain = A', 'chain = "AA"'], + regex: /[a-zA-Z0-9]+/, map: str, + level: 'chain-test', property: B.ammp('auth_asym_id') + }, + chainNo: { + '@desc': 'chain number; sequentially counted from 1 for each model; chainNo == 0 means"no chain" or PDB chain identifier indicated as a blank (Jmol 14.0).', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + color: { + '@desc': 'the atom color', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + covalentRadius: { + '@desc': 'covalent bonding radius, synonymous with covalent. Not used by Jmol, but could be used, for example, in {*}.spacefill={*}.covalentRadius.all.', + '@examples': [''], + abbr: ['covalent'], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + cs: { + '@desc': 'chemical shift calculated using computational results that include magnetic shielding tensors.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + element: { + '@desc': 'element symbol. The value of this parameter depends upon the context. Used with select structure=x, x can be either the quoted element symbol, "H", "He", "Li", etc. or atomic number. In all other contexts, the value is the element symbol. When the atom is a specific isotope, the string will contain the isotope number -- "13C", for example.', + '@examples': ['element=Fe'], + regex: /[a-zA-Z]+/, map: x => B.es(x), + level: 'atom-test', property: B.acp('elementSymbol') + }, + elemno: { + '@desc': 'atomic element number', + '@examples': ['elemno=8'], + regex: /[0-9\s{}-]+/, map: x => parseInt(x), + level: 'atom-test', property: B.acp('atomicNumber') + }, + eta: { + '@desc': 'Based on Carlos M. Duarte, Leven M. Wadley, and Anna Marie Pyle, RNA structure comparison, motif search and discovery using a reduced representation of RNA conformational space, Nucleic Acids Research, 2003, Vol. 31, No. 16 4755-4761. The parameter eta is the C4\'[i-1]-P[i]-C4\'[i]-P[i+1] dihedral angle; theta is the P[i]-C4\'[i]-P[i+1]-C4\'[i+1] dihedral angle. Both are measured on a 0-360 degree scale because they are commonly near 180 degrees. Using the commands plot PROPERTIES eta theta resno; select visible;wireframe only one can create these authors\' "RNA worm" graph.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + theta: { + '@desc': 'Based on Carlos M. Duarte, Leven M. Wadley, and Anna Marie Pyle, RNA structure comparison, motif search and discovery using a reduced representation of RNA conformational space, Nucleic Acids Research, 2003, Vol. 31, No. 16 4755-4761. The parameter eta is the C4\'[i-1]-P[i]-C4\'[i]-P[i+1] dihedral angle; theta is the P[i]-C4\'[i]-P[i+1]-C4\'[i+1] dihedral angle. Both are measured on a 0-360 degree scale because they are commonly near 180 degrees. Using the commands plot PROPERTIES eta theta resno; select visible;wireframe only one can create these authors\' "RNA worm" graph.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + file: { + '@desc': 'file number containing this atom', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + formalCharge: { + '@desc': 'formal charge', + '@examples': ['formalCharge=1'], + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.ammp('pdbx_formal_charge') + }, + format: { + '@desc': 'format (label) of the atom.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + fXyz: { + '@desc': 'fractional XYZ coordinates', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + fX: { + '@desc': 'fractional X coordinate', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + fY: { + '@desc': 'fractional Y coordinate', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + fZ: { + '@desc': 'fractional Z coordinate', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + fuxyz: { + '@desc': 'fractional XYZ coordinates in the unitcell coordinate system', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + fux: { + '@desc': 'fractional X coordinate in the unitcell coordinate system', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + fuy: { + '@desc': 'fractional Y coordinate in the unitcell coordinate system', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + fuz: { + '@desc': 'fractional Z coordinate in the unit cell coordinate system', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + group: { + '@desc': '3-letter residue code', + '@examples': ['group = ALA'], + regex: /[a-zA-Z0-9]{1,3}/, map: str, + level: 'residue-test', property: B.ammp('label_comp_id') + }, + group1: { + '@desc': 'single-letter residue code (amino acids only)', + '@examples': ['group1 = G'], + regex: /[a-zA-Z]/, map: str, + level: 'residue-test', property: B.ammp('label_comp_id') + }, + groupID: { + '@desc': 'group ID number: A unique ID for each amino acid or nucleic acid residue in a PDB file. 0 noGroup 1-5 ALA, ARG, ASN, ASP, CYS 6-10 GLN, GLU, GLY, HIS, ILE 11-15 LEU, LYS, MET, PHE, PRO 16-20 SER, THR, TRP, TYR, VAL 21-23 ASX, GLX, UNK 24-29 A, +A, G, +G, I, +I 30-35 C, +C, T, +T, U, +U Additional unique numbers are assigned arbitrarily by Jmol and cannot be used reproducibly.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + groupindex: { + '@desc': 'overall group index', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + hydrophobicity: { + '@desc': 'Aminoacid residue scale of hydrophobicity based on Rose, G. D., Geselowitz, A. R., Lesser, G. J., Lee, R. H., and Zehfus, M. H. (1985). Hydrophobicity of amino acid residues in globular proteins, Science, 229(4716):834-838.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + identify: { + '@desc': 'for a PDB/mmCIF file, a label such as [ILE]7^1:A.CD1%A/3 #47, which includes the group ([ILE]), residue number with optional insertion code (7^1), chain (:A), atom name (CD1), alternate location if present (%A), PDB model number (/3, for NMR models when one file is loaded; /file.model such as /2.3 if more than one file is loaded), and atom number (#47). For non-PDB data, the information is shorter -- for example, H15/2.1 #6, indicating atom name (H15), full file.model number (/2.1), and atom number (#6). If only a single model is loaded, %[identify] does not include the model number.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + insertion: { + '@desc': 'protein residue insertion code', + '@examples': ['insertion=A'], + regex: /[a-zA-Z0-9]/, map: str, + level: 'atom-test', property: B.ammp('pdbx_PDB_ins_code') + }, + label: { + '@desc': 'current atom label (same as format)', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + mass: { + '@desc': 'atomic mass -- especially useful with appended .max or .sum', + '@examples': ['mass > 13'], + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.acp('mass') + }, + model: { + '@desc': 'model number', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + modelindex: { + '@desc': 'a unique number for each model, starting with 0 and spanning all models in all files', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + modO: { + '@desc': 'currently calculated occupancy from modulation (0 to 100; NaN if atom has no occupancy modulation)', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + modXYZ: { + '@desc': 'currently calculated displacement modulation (for incommensurately modulated structures). Also modX, modY, modZ for individual components. For atoms without modultion, {xx}.modXYZ is -1 and {xx}.modX is NaN, and in a label %[modXYZ] and %[modX] are blank.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + molecule: { + '@desc': 'molecule number', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + monomer: { + '@desc': 'monomer number (group number) in a polymer (usually a chain), starting with 1, or 0 if not part of a biopolymer -- that is, not a connected carbohydrate, amino acid, or nucleic acid (Jmol 14.3.15)', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + ms: { + '@desc': 'magnetic shielding calculated from file-loaded tensors.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + occupancy: { + '@desc': 'CIF file site occupancy. In SELECT command comparisons ("select occupancy < 90"), an integer n implies measurement on a 0-100 scale; also, in the context %[occupancy] or %q for a label, the reported number is a percentage. In all other cases, such as when %Q is used in a label or when a decimal number is used in a comparison, the scale is 0.0 - 1.0.', + '@examples': ['occupancy < 1'], + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.ammp('occupancy') + }, + partialCharge: { + '@desc': 'partial charge', + '@examples': [''], + isUnsupported: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test' + }, + phi: { + '@desc': 'protein group PHI angle for atom\'s residue', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + polymer: { + '@desc': 'sequential polymer number in a model, starting with 1.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + polymerLength: { + '@desc': 'polymer length', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + property_xx: { + '@desc': 'a property created using the DATA command', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + psi: { + '@desc': 'protein group PSI angle for the atom\'s residue', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + radius: { + '@desc': 'currently displayed radius -- In SELECT command comparisons ("select radius=n"), integer n implies Rasmol units 1/250 Angstroms; in all other cases or when a decimal number is used, the units are Angstroms.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + resno: { + '@desc': 'PDB residue number, not including insertion code (see also seqcode, below)', + '@examples': ['resno = 100'], + regex: /-?[0-9]+/, map: x => parseInt(x), + level: 'residue-test', property: B.ammp('auth_seq_id') + }, + selected: { + '@desc': '1.0 if atom is selected; 0.0 if not', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + sequence: { + '@desc': 'PDB one-character sequence code, as a string of characters, with "?" indicated where single-character codes are not available', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + seqcode: { + '@desc': 'PDB residue number, including insertion code (for example, 234^2; "seqcode" option added in Jmol 14.3.16)', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + seqid: { + '@desc': '(mmCIF only) the value from _atom_site.label_seq_id; a pointer to _entity_poly_seq.num in the ENTITY_POLY_SEQ category specifying the sequence of monomers in a polymer. Allowance is made for the possibility of microheterogeneity in a sample by allowing a given sequence number to be correlated with more than one monomer id. (Jmol 14.2.3)', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + shape: { + '@desc': 'hybridization geometry such as "tetrahedral"', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + site: { + '@desc': 'crystallographic site number', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + spacefill: { + '@desc': 'currently displayed radius', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + straightness: { + '@desc': 'quaternion-derived straightness (second derivative of the quaternion describing the orientation of the residue. This quantity will have different values depending upon the setting of quaternionFrame as "A" (alpha-carbon/phosphorus atom only), "C" (alpha-carbon/pyrimidine or purine base based), "P" (carbonyl-carbon peptide plane/phosphorus tetrahedron based), or "N" (amide-nitrogen based). The default is alpha-carbon based, which corresponds closely to the following combination of Ramachandran angles involving three consecutive residues i-1, i, and i+1: -psii-1 - phii + psii + phii+1.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + strucno: { + '@desc': 'a unique number for each helix, sheet, or turn in a model, starting with 1.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + structure: { + '@desc': 'The value of this parameter depends upon the context. Used with select structure=x, x can be either the quoted keyword "none", "turn", "sheet", "helix", "dna", "rna", or "carbohydrate" or a respective number 0-6. In the context {*}.structure, the return value is a number; in the context label %[structure], the return is one of the six keywords.', + '@examples': ['structure="helix"', 'structure=3'], + regex: /none|turn|sheet|helix|dna|rna|carbohydrate|[0-6]/i, map: str, + level: 'residue-test', property: 'structure' + }, + substructure: { + '@desc': 'like structure, the value of this parameter depends upon the context. Used with select substructure=x, x can be either the quoted keyword "none", "turn", "sheet", "helix", "dna", "rna", "carbohydrate", "helix310", "helixalpha", or "helixpi", or the respective number 0-9. In the context {*}.substructure, the return value is a number; in the context label %[substructure], the return is one of the nine keywords.', + '@examples': ['substructure = "alphahelix"', 'substructure =9'], + regex: /none|turn|sheet|helix|dna|rna|carbohydrate|helix310|helixalpha|helixpi|[0-9]/i, map: str, + level: 'residue-test', property: 'structure' + }, + surfacedistance: { + '@desc': 'A value related to the distance of an atom to a nominal molecular surface. 0 indicates at the surface. Positive numbers are minimum distances in Angstroms from the given atom to the surface.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + symop: { + '@desc': 'the first symmetry operation code that generated this atom by Jmol; an integer starting with 1. See also symmetry, below. This operator is only present if the file contains space group information and the file was loaded using the {i, j, k} option so as to generate symmetry-based atoms. To select only the original atoms prior to application of symmetry, you can either use "SYMOP=n", where n is the symmetry operator corresponding to "x,y,z", or you can specify instead simply "NOT symmetry" the way you might specify "NOT hydrogen". Note that atoms in special positions will have multiple operator matches. These atoms can be selected using the keyword SPECIALPOSITION. The special form select SYMOP=nijk selects a specific translation of atoms from the given crystallographic symmetry operation. Comparators <, <=, >, >=, and != can be used and only apply to the ijk part of the designation. The ijk are relative, not absolute. Thus, symop=2555 selects for atoms that have been transformed by symop=2 but not subjected to any further translation. select symop=1555 is identical to select not symmetry. All other ijk are relative to these selections for 555. If the model was loaded using load "filename.cif" {444 666 1}, where the 1 indicates that all symmetry-generated atoms are to be packed within cell 555 and then translated to fill the other 26 specified cells, then select symop=3555 is nearly the same as select symop=3 and cell=555. (The difference being that cell=555 selects for all atoms that are on any edge of the cell, while symop=3555 does not.) However, the situation is different if instead the model was loaded using load "filename.cif" {444 666 0}, where the 0 indicates that symmetry-generated atoms are to be placed exactly where their symmetry operator would put them (x,-y,z being different then from x, 1-y, z). In that case, select symop=3555 is for all atoms that have been generated using symmetry operation 3 but have not had any additional translations applied to the x,y,z expression found in the CIF file. If, for example, symmetry operation 3 is -x,-y,-z, then load "filename.cif" {444 666 0} will place an atom originally at {1/2, 1/2, 1/2} at positions {-1/2, -1/2, -1/2} (symop=3555) and {-3/2, -3/2, -3/2} (symop=3444) and 24 other sites.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + symmetry: { + '@desc': 'as "symmetry" or in a label as lower-case "o" gives list of crystallographic symmetry operators generating this atom with lattice designations,such as 3555; upper-case "%O" in a label gives a list without the lattice designations. See also symop, above.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + temperature: { + '@desc': 'yes yes temperature factor (B-factor)', + '@examples': ['temperature >= 20'], + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.ammp('B_iso_or_equiv') + }, + unitXyz: { + '@desc': 'unit cell XYZ coordinates', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + uX: { + '@desc': 'unit cell X coordinate normalized to [0,1)', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + uY: { + '@desc': 'unit cell Y coordinate normalized to [0,1)', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + uZ: { + '@desc': 'unit cell Z coordinate normalized to [0,1)', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + valence: { + '@desc': 'the valence of an atom (sum of bonds, where double bond counts as 2 and triple bond counts as 3', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + vanderwaals: { + '@desc': 'van der Waals radius', + '@examples': ['vanderwaals >2'], + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.acp('vdw') + }, + vectorScale: { + '@desc': 'vibration vector scale', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + volume: { + '@desc': 'approximate van der Waals volume for this atom. Note, {*}.volume gives an average; use {*}.volume.sum to get total volume.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + vXyz: { + '@desc': 'vibration vector, or individual components as %vx %vy %vz. For atoms without vibration vectors, {xx}.vXyz is -1; in a label, %[vxyz] is blank.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + vX: { + '@desc': 'vibration vector X coordinate; for atoms without vibration vector, {xx}.vX is NaN (same for vY and vZ)', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + vY: { + '@desc': 'vibration vector Y coordinate', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + vZ: { + '@desc': 'vibration vector Z coordinate', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, + xyz: { + '@desc': 'Cartesian XYZ coordinates; select xyz > 1.0 selects atoms more than one Angstrom from the origin.', + '@examples': [''], + isUnsupported: true, + regex: /[0-9\s{}-]+/, map: str, + level: 'atom-test' + }, +}; + diff --git a/src/mol-script/transpilers/jmol/symbols.ts b/src/mol-script/transpilers/jmol/symbols.ts new file mode 100644 index 0000000000000000000000000000000000000000..6720acb588f1cf2d00efd09f8612e7b0d212e56e --- /dev/null +++ b/src/mol-script/transpilers/jmol/symbols.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + * + * Adapted from MolQL project + */ + +import { properties } from './properties'; +import { operators } from './operators'; +import { keywords } from './keywords'; + +export const Properties: string[] = []; +for (const name in properties) { + if (properties[name].isUnsupported) continue; + Properties.push(name); + if (properties[name].abbr) Properties.push(...properties[name].abbr!); +} + +export const Operators: string[] = []; +operators.forEach(o => { + if (o.isUnsupported) return; + Operators.push(o.name); + if (o.abbr) Operators.push(...o.abbr); +}); + +export const Keywords: string[] = []; +for (const name in keywords) { + if (!keywords[name].map) continue; + Keywords.push(name); + if (keywords[name].abbr) Keywords.push(...keywords[name].abbr!); +} + +export const _all = { Properties, Operators, Keywords }; diff --git a/src/mol-script/transpilers/pymol/examples.ts b/src/mol-script/transpilers/pymol/examples.ts new file mode 100644 index 0000000000000000000000000000000000000000..c92105904b054ebf99667cd8c6bb05f706bf412b --- /dev/null +++ b/src/mol-script/transpilers/pymol/examples.ts @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author David Sehnal <david.sehnal@gmail.com> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +export const examples = [{ + name: 'ALA residues', + value: 'resn ALA' +}, { + name: 'Atoms named "C", "O", "N", or "CA"', + value: 'name c+o+n+ca' +}, { + name: 'Residues with helix or sheet secondary structure', + value: 'ss h+s' +}, { + name: 'C-alpha atoms of residues 100 to 180 in chain A', + value: 'A/100-180/CA' +}, { + name: 'Residues 100 to 180', + value: 'resi 100-180' +}, { + name: 'Atoms that are 1 ang + vdw radius away from polymer', + value: 'polymer gap 1' +}, { + name: 'Residues within 4 ang of HEM', + value: 'byres resn HEM around 4' +}, { + name: 'HEM and residues within 4 ang', + value: 'byres resn HEM expand 4' +}, { + name: 'Solvent close (2.5 ang) to polymer', + value: 'solvent NEAR_TO 2.5 OF polymer' +}, { + name: 'Cystein residues within 3 ang of HEM', + value: 'byres resn CYS WITHIN 3 OF resn HEM' +}, { + name: 'Solvent atoms 4 ang away from oxygen', + value: 'solvent beyond 4 of (name O and not solvent)' +}, { + name: 'All rings in PHE', + value: 'byring resn PHE' +}, { + name: 'CYS and all bound residues', + value: 'byres BOUND_TO resn CYS' +}, { + name: 'HEM and atoms up to 7 bonds away', + value: 'resn HEM extend 7' +}, { + name: 'Atoms with alternate location A or none', + value: 'alt A+""' +}]; diff --git a/src/mol-script/transpilers/pymol/keywords.ts b/src/mol-script/transpilers/pymol/keywords.ts new file mode 100644 index 0000000000000000000000000000000000000000..1e3c95107e26a6dadfe340d0b97560b257907e3c --- /dev/null +++ b/src/mol-script/transpilers/pymol/keywords.ts @@ -0,0 +1,275 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import * as h from '../helper'; +import { KeywordDict } from '../types'; + +const ResDict = { + nucleic: ['A', 'C', 'T', 'G', 'U', 'DA', 'DC', 'DT', 'DG', 'DU'], + protein: ['ALA', 'ARG', 'ASN', 'ASP', 'CYS', 'CYX', 'GLN', 'GLU', 'GLY', 'HIS', 'HID', 'HIE', 'HIP', 'ILE', 'LEU', 'LYS', 'MET', 'MSE', 'PHE', 'PRO', 'SER', 'THR', 'TRP', 'TYR', 'VAL'], + solvent: ['HOH', 'WAT', 'H20', 'TIP', 'SOL'] +}; + +const Backbone = { + nucleic: ['P', "O3'", "O5'", "C5'", "C4'", "C3'", 'OP1', 'OP2', 'O3*', 'O5*', 'C5*', 'C4*', 'C3*', + "C2'", "C1'", "O4'", "O2'"], + protein: ['C', 'N', 'CA', 'O'] +}; + +function backboneExpr() { + return B.struct.combinator.merge([ + B.struct.modifier.intersectBy({ + 0: B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.protein), + B.ammp('label_comp_id') + ]) + }), + by: B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + B.core.type.set(Backbone.protein), + B.ammp('label_atom_id') + ]) + }) + }), + B.struct.modifier.intersectBy({ + 0: B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.nucleic), + B.ammp('label_comp_id') + ]) + }), + by: B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + B.core.type.set(Backbone.nucleic), + B.ammp('label_atom_id') + ]) + }) + }), + ]); +} + +export const keywords: KeywordDict = { + all: { + '@desc': 'All atoms currently loaded into PyMOL', + abbr: ['*'], + map: () => B.struct.generator.all() + }, + none: { + '@desc': 'No atoms (empty selection)', + map: () => B.struct.generator.empty() + }, + hydrogens: { + '@desc': 'All hydrogen atoms currently loaded into PyMOL', + abbr: ['hydro', 'h.'], + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.eq([ + B.acp('elementSymbol'), + B.es('H') + ]) + }) + }, + hetatm: { + '@desc': 'All atoms loaded from Protein Data Bank HETATM records', + abbr: ['het'], + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.eq([B.ammp('isHet'), true]) + }) + }, + visible: { + '@desc': 'All atoms in enabled objects with at least one visible representation', + abbr: ['v.'] + }, + polymer: { + '@desc': 'All atoms on the polymer (not het). Finds atoms with residue identifiers matching a known polymer, such a peptide and DNA.', + abbr: ['pol.'], + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.nucleic.concat(ResDict.protein)), + B.ammp('label_comp_id') + ]) + }) + }, + sidechain: { + '@desc': 'Polymer non-backbone atoms (new in PyMOL 1.6.1)', + abbr: ['sc.'], + map: () => { + return B.struct.modifier.exceptBy({ + '0': B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.nucleic.concat(ResDict.protein)), + B.ammp('label_comp_id') + ]) + }), + by: backboneExpr() + }); + }, + }, + present: { + '@desc': 'All atoms with defined coordinates in the current state (used in creating movies)', + abbr: ['pr.'] + }, + center: { + '@desc': 'Pseudo-atom at the center of the scene' + }, + origin: { + '@desc': 'Pseudo-atom at the origin of rotation', + }, + enabled: { + '@desc': 'All enabled objects or selections from the object list.', + }, + masked: { + '@desc': 'All masked atoms.', + abbr: ['msk.'] + }, + protected: { + '@desc': 'All protected atoms.', + abbr: ['pr.'] + }, + bonded: { + '@desc': 'All bonded atoms', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.gr([B.struct.atomProperty.core.bondCount({ + flags: B.struct.type.bondFlags(['covalent', 'metallic', 'sulfide']) + }), 0]) + }) + }, + donors: { + '@desc': 'All hydrogen bond donor atoms.', + abbr: ['don.'] + }, + acceptors: { + '@desc': 'All hydrogen bond acceptor atoms.', + abbr: ['acc.'] + }, + fixed: { + '@desc': 'All fixed atoms.', + abbr: ['fxd.'] + }, + restrained: { + '@desc': 'All restrained atoms.', + abbr: ['rst.'] + }, + organic: { + '@desc': 'All atoms in non-polymer organic compounds (e.g. ligands, buffers). Finds carbon-containing molecules that do not match known polymers.', + abbr: ['org.'], + map: () => h.asAtoms(B.struct.modifier.expandProperty({ + '0': B.struct.modifier.union([ + B.struct.generator.queryInSelection({ + '0': B.struct.generator.atomGroups({ + 'residue-test': B.core.logic.not([ + B.core.set.has([ + B.core.type.set(ResDict.nucleic.concat(ResDict.protein)), + B.ammp('label_comp_id') + ]) + ]) + }), + query: B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.eq([ + B.es('C'), + B.acp('elementSymbol') + ]) + }) + }) + ]), + property: B.ammp('residueKey') + })) + }, + inorganic: { + '@desc': 'All non-polymer inorganic atoms/ions. Finds atoms in molecules that do not contain carbon and do not match any known solvent residues.', + abbr: ['ino.'], + map: () => h.asAtoms(B.struct.modifier.expandProperty({ + '0': B.struct.modifier.union([ + B.struct.filter.pick({ + '0': B.struct.generator.atomGroups({ + 'residue-test': B.core.logic.not([ + B.core.set.has([ + B.core.type.set(ResDict.nucleic.concat(ResDict.protein).concat(ResDict.solvent)), + B.ammp('label_comp_id') + ]) + ]), + 'group-by': B.ammp('residueKey') + }), + test: B.core.logic.not([ + B.core.set.has([ + B.struct.atomSet.propertySet([B.acp('elementSymbol')]), + B.es('C') + ]) + ]) + }) + ]), + property: B.ammp('residueKey') + })) + }, + solvent: { + '@desc': 'All water molecules. The hardcoded solvent residue identifiers are currently: HOH, WAT, H20, TIP, SOL.', + abbr: ['sol.'], + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.solvent), + B.ammp('label_comp_id') + ]) + }) + }, + guide: { + '@desc': 'All protein CA and nucleic acid C4*/C4', + map: () => B.struct.combinator.merge([ + B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.eq([ + B.atomName('CA'), + B.ammp('label_atom_id') + ]), + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.protein), + B.ammp('label_comp_id') + ]) + }), + B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + h.atomNameSet(['C4*', 'C4\'']), + B.ammp('label_atom_id') + ]), + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.nucleic), + B.ammp('label_comp_id') + ]) + }) + ]), + }, + metals: { + '@desc': 'All metal atoms (new in PyMOL 1.6.1)' + }, + backbone: { + '@desc': 'Polymer backbone atoms (new in PyMOL 1.6.1)', + abbr: ['bb.'], + map: () => backboneExpr() + }, + 'polymer.protein': { + '@desc': 'Protein (New in PyMOL 2.1)', + abbr: ['polymer.protein'], + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.protein), + B.ammp('label_comp_id') + ]) + }) + }, + 'polymer.nucleic': { + '@desc': 'Nucleic Acid (New in PyMOL 2.1)', + abbr: ['polymer.nucleic'], + map: () => B.struct.generator.atomGroups({ + 'residue-test': B.core.set.has([ + B.core.type.set(ResDict.nucleic), + B.ammp('label_comp_id') + ]) + }) + } +}; diff --git a/src/mol-script/transpilers/pymol/markdown-docs.ts b/src/mol-script/transpilers/pymol/markdown-docs.ts new file mode 100644 index 0000000000000000000000000000000000000000..0740d545ea21ace645261db85b9922f5b76633fb --- /dev/null +++ b/src/mol-script/transpilers/pymol/markdown-docs.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +import { properties } from './properties'; +import { operators } from './operators'; +import { keywords } from './keywords'; + +const _docs: string[] = [ + 'PyMol', + '============', + '--------------------------------', + '' +]; + +_docs.push(`## Properties\n\n`); +_docs.push('--------------------------------\n'); +for (const name in properties) { + if (properties[name].isUnsupported) continue; + + const names = [name]; + if (properties[name].abbr) names.push(...properties[name].abbr!); + _docs.push(`\`\`\`\n${names.join(', ')}\n\`\`\`\n`); + + if (properties[name]['@desc']) { + _docs.push(`*${properties[name]['@desc']}*\n`); + } +} + +_docs.push(`## Operators\n\n`); +_docs.push('--------------------------------\n'); +operators.forEach(o => { + if (o.isUnsupported) return; + + const names = [o.name]; + if (o.abbr) names.push(...o.abbr!); + _docs.push(`\`\`\`\n${names.join(', ')}\n\`\`\`\n`); + + if (o['@desc']) { + _docs.push(`*${o['@desc']}*\n`); + } +}); + +_docs.push(`## Keywords\n\n`); +_docs.push('--------------------------------\n'); +for (const name in keywords) { + if (!keywords[name].map) continue; + + const names = [name]; + if (keywords[name].abbr) names.push(...keywords[name].abbr!); + _docs.push(`\`\`\`\n${names.join(', ')}\n\`\`\`\n`); + + if (keywords[name]['@desc']) { + _docs.push(`*${keywords[name]['@desc']}*\n`); + } +} + +export const docs = _docs.join('\n'); \ No newline at end of file diff --git a/src/mol-script/transpilers/pymol/operators.ts b/src/mol-script/transpilers/pymol/operators.ts new file mode 100644 index 0000000000000000000000000000000000000000..f1537e3e570788b9a38ecc2580a362ee1f849c3c --- /dev/null +++ b/src/mol-script/transpilers/pymol/operators.ts @@ -0,0 +1,371 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +import * as P from '../../../mol-util/monadic-parser'; +import * as h from '../helper'; +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { OperatorList } from '../types'; +import { Expression } from '../../language/expression'; + +export const operators: OperatorList = [ + { + '@desc': 'Selects atoms that are not included in s1.', + '@examples': [ + 'NOT resn ALA', + 'not (resi 42 or chain A)', + '!resi 42 or chain A', + ], + name: 'not', + type: h.prefix, + rule: P.MonadicParser.alt( + P.MonadicParser.regexp(/NOT/i).skip(P.MonadicParser.whitespace), + P.MonadicParser.string('!').skip(P.MonadicParser.optWhitespace) + ), + map: (op, selection) => h.invertExpr(selection), + }, + { + '@desc': 'Selects atoms included in both s1 and s2.', + '@examples': ['chain A AND name CA'], + name: 'and', + type: h.binaryLeft, + rule: h.infixOp(/AND|&/i), + map: (op, selection, by) => + B.struct.modifier.intersectBy({ 0: selection, by }), + }, + { + '@desc': 'Selects atoms included in either s1 or s2.', + '@examples': ['chain A OR chain B'], + name: 'or', + type: h.binaryLeft, + rule: h.infixOp(/OR|\|/i), + map: (op: string, s1: Expression, s2: Expression) => B.struct.combinator.merge([s1, s2]), + }, + { + '@desc': + 'Selects atoms in s1 whose identifiers name, resi, resn, chain and segi all match atoms in s2.', + '@examples': ['chain A IN chain B'], + name: 'in', + type: h.binaryLeft, + rule: h.infixOp(/IN/i), + map: (op: string, selection: Expression, source: Expression) => { + return B.struct.filter.withSameAtomProperties({ + 0: selection, + source, + property: B.core.type.compositeKey([ + B.ammp('label_atom_id'), + B.ammp('label_seq_id'), + B.ammp('label_comp_id'), + B.ammp('auth_asym_id'), + B.ammp('label_asym_id'), + ]), + }); + }, + }, + { + '@desc': + 'Selects atoms in s1 whose identifiers name and resi match atoms in s2.', + '@examples': ['chain A LIKE chain B'], + name: 'like', + type: h.binaryLeft, + rule: h.infixOp(/LIKE|l\./i), + map: (op: string, selection: Expression, source: Expression) => { + return B.struct.filter.withSameAtomProperties({ + 0: selection, + source, + property: B.core.type.compositeKey([ + B.ammp('label_atom_id'), + B.ammp('label_seq_id'), + ]), + }); + }, + }, + { + '@desc': + 'Selects all atoms whose van der Waals radii are separated from the van der Waals radii of s1 by a minimum of X Angstroms.', + '@examples': ['solvent GAP 2'], + name: 'gap', + type: h.postfix, + rule: h + .postfixOp(/GAP\s+([-+]?[0-9]*\.?[0-9]+)/i, 1) + .map((x: any) => parseFloat(x)), + map: (distance: number, target: Expression) => { + return B.struct.filter.within({ + '0': B.struct.generator.all(), + target, + 'atom-radius': B.acp('vdw'), + 'max-radius': distance, + invert: true, + }); + }, + }, + { + '@desc': + 'Selects atoms with centers within X Angstroms of the center of any atom in s1.', + '@examples': ['resname LIG AROUND 1'], + name: 'around', + abbr: ['a.'], + type: h.postfix, + rule: h + .postfixOp(/(AROUND|a\.)\s+([-+]?[0-9]*\.?[0-9]+)/i, 2) + .map((x: any) => parseFloat(x)), + map: (radius: number, target: Expression) => { + return B.struct.modifier.exceptBy({ + '0': B.struct.filter.within({ + '0': B.struct.generator.all(), + target, + 'max-radius': radius, + }), + by: target, + }); + }, + }, + { + '@desc': + 'Expands s1 by all atoms within X Angstroms of the center of any atom in s1.', + '@examples': ['chain A EXPAND 3'], + name: 'expand', + abbr: ['x.'], + type: h.postfix, + rule: h + .postfixOp(/(EXPAND|x\.)\s+([-+]?[0-9]*\.?[0-9]+)/i, 2) + .map((x: any) => parseFloat(x)), + map: (radius: number, selection: Expression) => { + return B.struct.modifier.includeSurroundings({ 0: selection, radius }); + }, + }, + { + '@desc': + 'Selects atoms in s1 that are within X Angstroms of any atom in s2.', + '@examples': ['chain A WITHIN 3 OF chain B'], + name: 'within', + abbr: ['w.'], + type: h.binaryLeft, + rule: h.ofOp('WITHIN', 'w.'), + map: (radius: number, selection: Expression, target: Expression) => { + return B.struct.filter.within({ + 0: selection, + target, + 'max-radius': radius, + }); + }, + }, + { + '@desc': + 'Same as within, but excludes s2 from the selection (and thus is identical to s1 and s2 around X).', + '@examples': ['chain A NEAR_TO 3 OF chain B'], + name: 'near_to', + abbr: ['nto.'], + type: h.binaryLeft, + rule: h.ofOp('NEAR_TO', 'nto.'), + map: (radius: number, selection: Expression, target: Expression) => { + return B.struct.modifier.exceptBy({ + '0': B.struct.filter.within({ + '0': selection, + target, + 'max-radius': radius, + }), + by: target, + }); + }, + }, + { + '@desc': 'Selects atoms in s1 that are at least X Anstroms away from s2.', + '@examples': ['solvent BEYOND 2 OF chain A'], + name: 'beyond', + abbr: ['be.'], + type: h.binaryLeft, + rule: h.ofOp('BEYOND', 'be.'), + map: (radius: number, selection: Expression, target: Expression) => { + return B.struct.modifier.exceptBy({ + '0': B.struct.filter.within({ + '0': selection, + target, + 'max-radius': radius, + invert: true, + }), + by: target, + }); + }, + }, + { + '@desc': 'Expands selection to complete residues.', + '@examples': ['BYRESIDUE name N'], + name: 'byresidue', + abbr: ['byresi', 'byres', 'br.'], + type: h.prefix, + rule: h.prefixOp(/BYRESIDUE|byresi|byres|br\./i), + map: (op: string, selection: Expression) => { + return h.asAtoms( + B.struct.modifier.expandProperty({ + '0': B.struct.modifier.union({ 0: selection }), + property: B.ammp('residueKey'), + }) + ); + }, + }, + { + '@desc': + 'Completely selects all alpha carbons in all residues covered by a selection.', + '@examples': ['BYCALPHA chain A'], + name: 'bycalpha', + abbr: ['bca.'], + type: h.prefix, + rule: h.prefixOp(/BYCALPHA|bca\./i), + map: (op: string, selection: Expression) => { + return B.struct.generator.queryInSelection({ + '0': B.struct.modifier.expandProperty({ + '0': B.struct.modifier.union({ 0: selection }), + property: B.ammp('residueKey'), + }), + query: B.struct.generator.atomGroups({ + 'atom-test': B.core.rel.eq([ + B.atomName('CA'), + B.ammp('label_atom_id'), + ]), + }), + }); + }, + }, + { + '@desc': 'Expands selection to complete molecules.', + '@examples': ['BYMOLECULE resi 20-30'], + name: 'bymolecule', + isUnsupported: true, // structure-query.atom-property.topology.connected-component-key' is not implemented + abbr: ['bymol', 'bm.'], + type: h.prefix, + rule: h.prefixOp(/BYMOLECULE|bymol|bm\./i), + map: (op: string, selection: Expression) => { + return h.asAtoms( + B.struct.modifier.expandProperty({ + '0': B.struct.modifier.union({ 0: selection }), + property: B.atp('connectedComponentKey'), + }) + ); + }, + }, + { + '@desc': 'Expands selection to complete fragments.', + '@examples': ['BYFRAGMENT resi 10'], + name: 'byfragment', + abbr: ['byfrag', 'bf.'], + isUnsupported: true, + type: h.prefix, + rule: h.prefixOp(/BYFRAGMENT|byfrag|bf\./i), + map: (op: string, selection: Expression) => [op, selection], + }, + { + '@desc': 'Expands selection to complete segments.', + '@examples': ['BYSEGMENT resn CYS'], + name: 'bysegment', + abbr: ['bysegi', 'byseg', 'bs.'], + type: h.prefix, + rule: h.prefixOp(/BYSEGMENT|bysegi|byseg|bs\./i), + map: (op: string, selection: Expression) => { + return h.asAtoms( + B.struct.modifier.expandProperty({ + '0': B.struct.modifier.union({ 0: selection }), + property: B.ammp('chainKey'), + }) + ); + }, + }, + { + '@desc': 'Expands selection to complete objects.', + '@examples': ['BYOBJECT chain A'], + name: 'byobject', + abbr: ['byobj', 'bo.'], + isUnsupported: true, + type: h.prefix, + rule: h.prefixOp(/BYOBJECT|byobj|bo\./i), + map: (op: string, selection: Expression) => [op, selection], + }, + { + '@desc': 'Expands selection to unit cell.', + '@examples': ['BYCELL chain A'], + name: 'bycell', + isUnsupported: true, + type: h.prefix, + rule: h.prefixOp(/BYCELL/i), + map: (op: string, selection: Expression) => [op, selection], + }, + { + '@desc': 'All rings of size ≤ 7 which have at least one atom in s1.', + '@examples': ['BYRING resn HEM'], + name: 'byring', + // isUnsupported: true, // structure-query.atom-set.atom-count' is not implemented. + type: h.prefix, + rule: h.prefixOp(/BYRING/i), + map: (op: string, selection: Expression) => { + return h.asAtoms( + B.struct.modifier.intersectBy({ + '0': B.struct.filter.pick({ + '0': B.struct.generator.rings(), + test: B.core.logic.and([ + B.core.rel.lte([B.struct.atomSet.atomCount(), 7]), + B.core.rel.gr([B.struct.atomSet.countQuery([selection]), 1]), + ]), + }), + by: selection, + }) + ); + }, + }, + { + '@desc': 'Selects atoms directly bonded to s1, excludes s1.', + '@examples': ['NEIGHBOR resn CYS'], + name: 'neighbor', + type: h.prefix, + abbr: ['nbr.'], + rule: h.prefixOp(/NEIGHBOR|nbr\./i), + map: (op: string, selection: Expression) => { + return B.struct.modifier.exceptBy({ + '0': h.asAtoms( + B.struct.modifier.includeConnected({ + '0': B.struct.modifier.union({ 0: selection }), + 'bond-test': true, + }) + ), + by: selection, + }); + }, + }, + { + '@desc': 'Selects atoms directly bonded to s1, may include s1.', + '@examples': ['BOUND_TO name CA'], + name: 'bound_to', + abbr: ['bto.'], + type: h.prefix, + rule: h.prefixOp(/BOUND_TO|bto\./i), + map: (op: string, selection: Expression) => { + return h.asAtoms( + B.struct.modifier.includeConnected({ + '0': B.struct.modifier.union({ 0: selection }), + }) + ); + }, + }, + { + '@desc': 'Extends s1 by X bonds connected to atoms in s1.', + '@examples': ['resname LIG EXTEND 3'], + name: 'extend', + abbr: ['xt.'], + type: h.postfix, + rule: h.postfixOp(/(EXTEND|xt\.)\s+([0-9]+)/i, 2).map((x: any) => parseInt(x)), + map: (count: number, selection: Expression) => { + return h.asAtoms( + B.struct.modifier.includeConnected({ + '0': B.struct.modifier.union({ 0: selection }), + 'bond-test': true, + 'layer-count': count, + }) + ); + }, + }, +]; diff --git a/src/mol-script/transpilers/pymol/parser.ts b/src/mol-script/transpilers/pymol/parser.ts new file mode 100644 index 0000000000000000000000000000000000000000..b730b2a3e5ed019a4f5ac1e074f397a5f53a6480 --- /dev/null +++ b/src/mol-script/transpilers/pymol/parser.ts @@ -0,0 +1,173 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +// https://pymol.org/dokuwiki/doku.php?id=selection:alpha +// https://pymolwiki.org/index.php/Selection_Algebra +// https://github.com/evonove/pymol/blob/master/pymol/layer3/Selector.cpp + +import * as P from '../../../mol-util/monadic-parser'; +import * as h from '../helper'; +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { properties } from './properties'; +import { operators } from './operators'; +import { keywords } from './keywords'; +import { AtomGroupArgs } from '../types'; +import { Transpiler } from '../transpiler'; + +const propertiesDict = h.getPropertyRules(properties); + +const slash = P.MonadicParser.string('/'); + +function orNull(rule: P.MonadicParser<any>) { + return rule.or(P.MonadicParser.of(null)); +} + +function atomSelectionQuery(x: any) { + const tests: AtomGroupArgs = {}; + const props: { [k: string]: any[] } = {}; + + for (const k in x) { + const ps = properties[k]; + if (!ps) { + throw new Error(`property '${k}' not supported, value '${x[k]}'`); + } + if (x[k] === null) continue; + if (!props[ps.level]) props[ps.level] = []; + props[ps.level].push(x[k]); + } + + for (const p in props) { + tests[p] = h.andExpr(props[p]); + } + + return B.struct.generator.atomGroups(tests); +} + +const lang = P.MonadicParser.createLanguage({ + Parens: function (r: any) { + return P.MonadicParser.alt( + r.Parens, + r.Operator, + r.Expression + ).wrap(P.MonadicParser.string('('), P.MonadicParser.string(')')); + }, + + Expression: function (r: any) { + return P.MonadicParser.alt( + r.Keywords, + r.AtomSelectionMacro.map(atomSelectionQuery), + r.NamedAtomProperties, + r.Pepseq, + r.Rep, + r.Object + ); + }, + + AtomSelectionMacro: function (r: any) { + return P.MonadicParser.alt( + slash.then(P.MonadicParser.alt( + P.MonadicParser.seq( + orNull(r.ObjectProperty).skip(slash), + orNull(propertiesDict.segi).skip(slash), + orNull(propertiesDict.chain).skip(slash), + orNull(propertiesDict.resi).skip(slash), + orNull(propertiesDict.name) + ).map(x => { return { object: x[0], segi: x[1], chain: x[2], resi: x[3], name: x[4] }; }), + P.MonadicParser.seq( + orNull(r.ObjectProperty).skip(slash), + orNull(propertiesDict.segi).skip(slash), + orNull(propertiesDict.chain).skip(slash), + orNull(propertiesDict.resi) + ).map(x => { return { object: x[0], segi: x[1], chain: x[2], resi: x[3] }; }), + P.MonadicParser.seq( + orNull(r.ObjectProperty).skip(slash), + orNull(propertiesDict.segi).skip(slash), + orNull(propertiesDict.chain) + ).map(x => { return { object: x[0], segi: x[1], chain: x[2] }; }), + P.MonadicParser.seq( + orNull(r.ObjectProperty).skip(slash), + orNull(propertiesDict.segi) + ).map(x => { return { object: x[0], segi: x[1] }; }), + P.MonadicParser.seq( + orNull(r.ObjectProperty) + ).map(x => { return { object: x[0] }; }), + )), + P.MonadicParser.alt( + P.MonadicParser.seq( + orNull(r.ObjectProperty).skip(slash), + orNull(propertiesDict.segi).skip(slash), + orNull(propertiesDict.chain).skip(slash), + orNull(propertiesDict.resi).skip(slash), + orNull(propertiesDict.name) + ).map(x => { return { object: x[0], segi: x[1], chain: x[2], resi: x[3], name: x[4] }; }), + P.MonadicParser.seq( + orNull(propertiesDict.segi).skip(slash), + orNull(propertiesDict.chain).skip(slash), + orNull(propertiesDict.resi).skip(slash), + orNull(propertiesDict.name) + ).map(x => { return { segi: x[0], chain: x[1], resi: x[2], name: x[3] }; }), + P.MonadicParser.seq( + orNull(propertiesDict.chain).skip(slash), + orNull(propertiesDict.resi).skip(slash), + orNull(propertiesDict.name) + ).map(x => { return { chain: x[0], resi: x[1], name: x[2] }; }), + P.MonadicParser.seq( + orNull(propertiesDict.resi).skip(slash), + orNull(propertiesDict.name) + ).map(x => { return { resi: x[0], name: x[1] }; }), + ) + ); + }, + + NamedAtomProperties: function () { + return P.MonadicParser.alt(...h.getNamedPropertyRules(properties)); + }, + + Keywords: () => P.MonadicParser.alt(...h.getKeywordRules(keywords)), + + ObjectProperty: () => { + const w = h.getReservedWords(properties, keywords, operators) + .sort(h.strLenSortFn).map(h.escapeRegExp).join('|'); + return P.MonadicParser.regexp(new RegExp(`(?!(${w}))[A-Z0-9_]+`, 'i')); + }, + Object: (r: any) => { + return r.ObjectProperty.notFollowedBy(slash) + .map((x: any) => { throw new Error(`property 'object' not supported, value '${x}'`); }); + }, + + // Selects peptide sequence matching upper-case one-letter + // sequence SEQ (see also FindSeq). + // PEPSEQ seq + Pepseq: () => { + return P.MonadicParser.regexp(/(PEPSEQ|ps\.)\s+([a-z]+)/i, 2) + .map(h.makeError(`operator 'pepseq' not supported`)); + }, + + // Selects atoms which show representation rep. + // REP rep + Rep: () => { + return P.MonadicParser.regexp(/REP\s+(lines|spheres|mesh|ribbon|cartoon|sticks|dots|surface|labels|extent|nonbonded|nb_spheres|slice|extent|slice|dashes|angles|dihedrals|cgo|cell|callback|everything)/i, 1) + .map(h.makeError(`operator 'rep' not supported`)); + }, + + Operator: function (r: any) { + return h.combineOperators(operators, P.MonadicParser.alt(r.Parens, r.Expression, r.Operator)); + }, + + Query: function (r: any) { + return P.MonadicParser.alt( + r.Operator, + r.Parens, + r.Expression + ).trim(P.MonadicParser.optWhitespace); + } +}); + +export const transpiler: Transpiler = str => lang.Query.tryParse(str); diff --git a/src/mol-script/transpilers/pymol/properties.ts b/src/mol-script/transpilers/pymol/properties.ts new file mode 100644 index 0000000000000000000000000000000000000000..04c4bb716d5168507a81f1e211e879552c041b31 --- /dev/null +++ b/src/mol-script/transpilers/pymol/properties.ts @@ -0,0 +1,215 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + * + * Adapted from MolQL project + */ + +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { PropertyDict } from '../types'; + +const reFloat = /[-+]?[0-9]*\.?[0-9]+/; + +function atomNameListMap(x: string) { return x.split('+').map(B.atomName); } +function listMap(x: string) { return x.split('+').map(x => x.replace(/^["']|["']$/g, '')); } + +function listOrRangeMap(x: string) { + // cases + if (x.includes('-') && x.includes('+')) { + const pSplit = x.split('+').map(x => x.replace(/^["']|["']$/g, '')); + const res: number[] = []; + pSplit.forEach(x => { + if (x.includes('-') && !x.startsWith('-')) { + const [min, max] = x.split('-').map(x => parseInt(x)); + for (let i = min; i <= max; i++) { + res.push(i); + } + } else if (x.includes('-') && x.startsWith('-') && x.match(/[0-9]+-[-0-9]+/)) { + const min = -parseInt(x.split('-')[1]); + let max; + if (x.includes('--')) { + max = -parseInt(x.split('-')[3]); + } else { + max = parseInt(x.split('-')[2]); + } + for (let i = min; i <= max; i++) { + res.push(i); + } + } else if (x.includes('-') && x.startsWith('-') && !x.match(/[0-9]+-[-0-9]+/)) { + res.push(parseInt(x)); + } else { + res.push(parseInt(x)); + } + }); + return res; + } else if (x.includes('-') && !x.includes('+')) { + const res: number[] = []; + if (!x.startsWith('-')) { + const [min, max] = x.split('-').map(x => parseInt(x)); + for (let i = min; i <= max; i++) { + res.push(i); + } + } else if (x.startsWith('-') && x.match(/[0-9]+-[-0-9]+/)) { + const min = -parseInt(x.split('-')[1]); + let max; + if (x.includes('--')) { + max = -parseInt(x.split('-')[3]); + } else { + max = parseInt(x.split('-')[2]); + } + for (let i = min; i <= max; i++) { + res.push(i); + } + } else if (x.startsWith('-') && !x.match(/[0-9]+-[-0-9]+/)) { + res.push(parseInt(x)); + } else { + res.push(parseInt(x)); + } + return res; + } else if (!x.includes('-') && x.includes('+')) { + return listMap(x).map(x => parseInt(x)); + } else { + return [parseInt(x)]; + } +} + +function elementListMap(x: string) { + return x.split('+').map(B.struct.type.elementSymbol); +} + +const sstrucDict: { [k: string]: string } = { + H: 'helix', + S: 'beta', + L: 'none' +}; +function sstrucListMap(x: string) { + return { + flags: B.struct.type.secondaryStructureFlags( + x.toUpperCase().split('+').map(ss => sstrucDict[ss] || 'none') + ) + }; +} + +export const properties: PropertyDict = { + symbol: { + '@desc': 'chemical-symbol-list: list of 1- or 2-letter chemical symbols from the periodic table', + '@examples': ['symbol O+N'], + abbr: ['e.'], regex: /[a-zA-Z'"+]+/, map: elementListMap, + level: 'atom-test', property: B.acp('elementSymbol') + }, + name: { + '@desc': 'atom-name-list: list of up to 4-letter codes for atoms in proteins or nucleic acids', + '@examples': ['name CA+CB+CG+CD'], + abbr: ['n.'], regex: /[a-zA-Z0-9'"+]+/, map: atomNameListMap, + level: 'atom-test', property: B.ammp('label_atom_id') + }, + resn: { + '@desc': 'residue-name-list: list of 3-letter codes for amino acids or list of up to 2-letter codes for nucleic acids', + '@examples': ['resn ASP+GLU+ASN+GLN', 'resn A+G'], + abbr: ['resname', 'r.'], regex: /[a-zA-Z0-9'"+]+/, map: listMap, + level: 'residue-test', property: B.ammp('label_comp_id') + }, + resi: { + '@desc': 'residue-identifier-list list of up to 4-digit residue numbers or residue-identifier-range', + '@examples': ['resi 1+10+100+1000', 'resi 1-10'], + abbr: ['resident', 'residue', 'resid', 'i.'], regex: /[0-9+-]+/, map: listOrRangeMap, + level: 'residue-test', property: B.ammp('auth_seq_id') + }, + alt: { + '@desc': 'alternate-conformation-identifier-list list of single letters', + '@examples': ['alt A+B', 'alt ""', 'alt ""+A'], + abbr: [], regex: /[a-zA-Z0-9'"+]+/, map: listMap, + level: 'atom-test', property: B.ammp('label_alt_id') + }, + chain: { + '@desc': 'chain-identifier-list list of single letters or sometimes numbers', + '@examples': ['chain A'], + abbr: ['c.'], regex: /[a-zA-Z0-9'"+]+/, map: listMap, + level: 'chain-test', property: B.ammp('auth_asym_id') + }, + segi: { + '@desc': 'segment-identifier-list list of up to 4 letter identifiers', + '@examples': ['segi lig'], + abbr: ['segid', 's.'], regex: /[a-zA-Z0-9'"+]+/, map: listMap, + level: 'chain-test', property: B.ammp('label_asym_id') + }, + flag: { + '@desc': 'flag-number a single integer from 0 to 31', + '@examples': ['flag 0'], + isUnsupported: true, + abbr: ['f.'], regex: /[0-9]+/, map: x => parseInt(x), + level: 'atom-test' + }, + numeric_type: { + '@desc': 'type-number a single integer', + '@examples': ['nt. 5'], + isUnsupported: true, + abbr: ['nt.'], regex: /[0-9]+/, map: x => parseInt(x), + level: 'atom-test' + }, + text_type: { + '@desc': 'type-string a list of up to 4 letter codes', + '@examples': ['text_type HA+HC'], + isUnsupported: true, + abbr: ['tt.'], regex: /[a-zA-Z0-9'"+]+/, map: listMap, + level: 'atom-test' + }, + id: { + '@desc': 'external-index-number a single integer', + '@examples': ['id 23'], + regex: /[0-9+-]+/, map: listOrRangeMap, + level: 'atom-test', property: B.ammp('id') + }, + index: { + '@desc': 'internal-index-number a single integer', + '@examples': ['index 11'], + regex: /[0-9+-]+/, map: listOrRangeMap, + level: 'atom-test', property: B.ammp('id') + }, + ss: { + '@desc': 'secondary-structure-type list of single letters. Helical regions should be assigned H and sheet regions S. Loop regions can either be assigned L or be blank.', + '@examples': ['ss H+S+L', 'ss S+""'], + abbr: [], regex: /[a-zA-Z'"+]+/, map: sstrucListMap, + level: 'residue-test', property: B.ammp('secondaryStructureFlags') + }, + + b: { + '@desc': 'comparison-operator b-factor-value a real number', + '@examples': ['b > 10'], + isNumeric: true, + abbr: [], regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.ammp('B_iso_or_equiv') + }, + q: { + '@desc': 'comparison-operator occupancy-value a real number', + '@examples': ['q <0.50'], + isNumeric: true, + abbr: [], regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.ammp('occupancy') + }, + formal_charge: { + '@desc': 'comparison-operator formal charge-value an integer', + '@examples': ['fc. = -1'], + isNumeric: true, + abbr: ['fc.'], regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.ammp('pdbx_formal_charge') + }, + partial_charge: { + '@desc': 'comparison-operator partial charge-value a real number', + '@examples': ['pc. > 1'], + isUnsupported: true, + isNumeric: true, + abbr: ['pc.'], regex: reFloat, map: x => parseFloat(x), + level: 'atom-test' + }, + elem: { + '@desc': 'str atomic element symbol string ("X" if undefined)', + '@examples': ['elem N'], + regex: /[a-zA-Z0-9]{1,3}/, map: x => B.es(x), + level: 'atom-test', property: B.acp('elementSymbol') + } +}; diff --git a/src/mol-script/transpilers/pymol/symbols.ts b/src/mol-script/transpilers/pymol/symbols.ts new file mode 100644 index 0000000000000000000000000000000000000000..7e1c89586e408cea1880a9d38766c0eaa100c646 --- /dev/null +++ b/src/mol-script/transpilers/pymol/symbols.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +import { properties } from './properties'; +import { operators } from './operators'; +import { keywords } from './keywords'; + +export const Properties: string[] = []; +for (const name in properties) { + if (properties[name].isUnsupported) continue; + Properties.push(name); + if (properties[name].abbr) Properties.push(...properties[name].abbr!); +} + +export const Operators: string[] = []; +operators.forEach(o => { + if (o.isUnsupported) return; + Operators.push(o.name); + if (o.abbr) Operators.push(...o.abbr); +}); + +export const Keywords: string[] = []; +for (const name in keywords) { + if (!keywords[name].map) continue; + Keywords.push(name); + if (keywords[name].abbr) Keywords.push(...keywords[name].abbr!); +} + +export const all = { Properties, Operators: [...Operators, 'of'], Keywords }; diff --git a/src/mol-script/transpilers/transpiler.ts b/src/mol-script/transpilers/transpiler.ts new file mode 100644 index 0000000000000000000000000000000000000000..1fe04bfc150a23330ec19e57870e5211f266c3ad --- /dev/null +++ b/src/mol-script/transpilers/transpiler.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +import { Expression } from '../language/expression'; + +export type Transpiler = (source: string) => Expression + +export const Transpiler = (source: string) => Expression; diff --git a/src/mol-script/transpilers/types.ts b/src/mol-script/transpilers/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..1589b804262f896fe5bb817df51c85ba3df4a8c1 --- /dev/null +++ b/src/mol-script/transpilers/types.ts @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +import * as P from '../../mol-util/monadic-parser'; +import { Expression } from '../language/expression'; + +export interface AtomGroupArgs { + [index: string]: any + 'entity-test'?: Expression + 'chain-test'?: Expression + 'residue-test'?: Expression + 'atom-test'?: Expression + 'groupBy'?: Expression +} + +export interface Keyword { + '@desc': string + abbr?: string[] + map?: () => Expression /* not given means the keyword is unsupported */ +} + +export type KeywordDict = { [name: string]: Keyword } + +export interface Property { + '@desc': string + '@examples': string[] + isUnsupported?: boolean + isNumeric?: boolean + abbr?: string[] + regex: RegExp + map: (s: string) => any + level: 'atom-test' | 'residue-test' | 'chain-test' | 'entity-test' + property?: Expression +} + +export type PropertyDict = { [name: string]: Property } + +export interface Operator { + '@desc': string + '@examples': string[] + name: string + abbr?: string[] + isUnsupported?: boolean + type: (p1: P.MonadicParser<any>, p2: P.MonadicParser<any>, fn: any) => P.MonadicParser<any> + rule: P.MonadicParser<any> + map: (x: any, y: any, z?: any) => Expression | Expression[] +} + +export type OperatorList = Operator[] + +export interface Function { + '@desc': string + '@examples': string[] + map?: (x: any) => Expression /* not given means the keyword is unsupported */ +} + +export type FunctionDict = { [name: string]: Function } diff --git a/src/mol-script/transpilers/vmd/examples.ts b/src/mol-script/transpilers/vmd/examples.ts new file mode 100644 index 0000000000000000000000000000000000000000..fbc65d3bb12e29f4ad5c3d2fad2365f4757d52b4 --- /dev/null +++ b/src/mol-script/transpilers/vmd/examples.ts @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +export const examples = [{ + name: 'All water residues', + value: 'water' +}, { + name: 'All C-alpha atoms', + value: 'name CA' +}, { + name: 'Residue 35', + value: 'resid 35' +}, { + name: 'C-alpha atoms of ALA', + value: 'name CA and resname ALA' +}, { + name: 'Backbone atoms', + value: 'backbone' +}, { + name: 'Non-protein atoms', + value: 'not protein' +}, { + name: 'Protein backbone or hydrogen atoms', + value: 'protein (backbone or name H)' +}, { + name: 'Atoms heavier than 20', + value: 'mass > 20' +}, { + name: 'Atoms with two bonds', + value: 'numbonds = 2' +}, { + name: 'Atoms with an absolute charge greater 1', + value: 'abs(charge) > 1' +}, { + name: 'Atoms with an x coordinate between -25 and -20', + value: 'x < -20 and x > -25' +}, { + name: 'Helices', + value: 'structure H' +}, { + name: 'Atoms with name "A 1"', + value: "name 'A 1'" +}, { + name: 'Atoms with name "A *"', + value: "name 'A *'" +}, { + name: 'Atoms with names starting with C', + value: 'name "C.*"' +}, { + name: 'Atoms within 10 ang of [25, 15, 10]', + value: 'sqr(x+25)+sqr(y+15)+sqr(z+10) <= sqr(10)' +}, { + name: 'Atoms within 5 ang of iron atoms', + value: 'within 5 of name FE' +}, { + name: 'Atoms around 10 ang of HEM residue', + value: 'exwithin 10 of resname HEM' +}, { + name: 'ALA residues within 15 ang of HEM', + value: 'resname ALA within 15 of resname HEM' +}, { + name: 'All groups that include an iron atom', + value: 'same resid as name FE' +}, { + name: 'Atoms with mass between 12 and 17.5', + value: 'mass 12 to 17.5' +}, { + name: 'Residues 60, 80, 90 and 142', + value: 'resid 60 80 90 142' +}/* , { + name: 'Residues ala, arg, asn, asp, cys, and tyr', + value: 'resname ALA to CYS TYR' +}*/]; diff --git a/src/mol-script/transpilers/vmd/functions.ts b/src/mol-script/transpilers/vmd/functions.ts new file mode 100644 index 0000000000000000000000000000000000000000..a2db414a922cf3001456cd8c180ac38474ccbe2e --- /dev/null +++ b/src/mol-script/transpilers/vmd/functions.ts @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { FunctionDict } from '../types'; + +export const functions: FunctionDict = { + 'sqr': { + '@desc': 'square of x', + '@examples': ['sqr(2)'], + map: x => B.core.math.pow([x, 2]), + }, + 'sqrt': { + '@desc': 'square root of x', + '@examples': ['sqrt(2)'], + map: x => B.core.math.sqrt([x]), + }, + 'abs': { + '@desc': 'absolute value of x', + '@examples': ['abs(2)'], + map: x => B.core.math.abs([x]), + }, + 'floor': { + '@desc': 'largest integer not greater than x', + '@examples': ['floor(2)'], + map: x => B.core.math.floor([x]), + }, + 'ceil': { + '@desc': 'smallest integer not less than x', + '@examples': ['ceil(2)'], + map: x => B.core.math.ceil([x]), + }, + 'sin': { + '@desc': 'sine of x', + '@examples': ['sin(2)'], + map: x => B.core.math.sin([x]), + }, + 'cos': { + '@desc': 'cosine of x', + '@examples': ['cos(2)'], + map: x => B.core.math.cos([x]), + }, + 'tan': { + '@desc': 'tangent of x', + '@examples': ['tan(2)'], + map: x => B.core.math.tan([x]), + }, + 'atan': { + '@desc': 'arctangent of x', + '@examples': ['atan(2)'], + map: x => B.core.math.atan([x]), + }, + 'asin': { + '@desc': 'arcsin of x', + '@examples': ['asin(2)'], + map: x => B.core.math.asin([x]), + }, + 'acos': { + '@desc': 'arccos of x', + '@examples': ['acos(2)'], + map: x => B.core.math.acos([x]), + }, + 'sinh': { + '@desc': 'hyperbolic sine of x', + '@examples': ['sinh(2)'], + map: x => B.core.math.sinh([x]), + }, + 'cosh': { + '@desc': 'hyperbolic cosine of x', + '@examples': ['cosh(2)'], + map: x => B.core.math.cosh([x]), + }, + 'tanh': { + '@desc': 'hyperbolic tangent of x', + '@examples': ['tanh(2)'], + map: x => B.core.math.tanh([x]), + }, + 'exp': { + '@desc': 'e to the power x', + '@examples': ['exp(2)'], + map: x => B.core.math.exp([x]), + }, + 'log': { + '@desc': 'natural log of x', + '@examples': ['log(2)'], + map: x => B.core.math.log([x]), + }, + 'log10': { + '@desc': 'log base 10 of x', + '@examples': ['log10(2)'], + map: x => B.core.math.log10([x]), + } +}; diff --git a/src/mol-script/transpilers/vmd/keywords.ts b/src/mol-script/transpilers/vmd/keywords.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d783bf627806f13bd27f42f9fca81043c578cfa --- /dev/null +++ b/src/mol-script/transpilers/vmd/keywords.ts @@ -0,0 +1,303 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +import * as h from '../helper'; +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { KeywordDict } from '../types'; + +function proteinExpr() { + return B.struct.filter.pick({ + 0: B.struct.generator.atomGroups({ + 'group-by': B.ammp('residueKey') + }), + test: B.core.set.isSubset([ + h.atomNameSet(['C', 'N', 'CA', 'O']), + B.ammpSet('label_atom_id') + ]) + }); +} + +function nucleicExpr() { + return B.struct.filter.pick({ + 0: B.struct.generator.atomGroups({ + 'group-by': B.ammp('residueKey') + }), + test: B.core.logic.and([ + B.core.set.isSubset([ + h.atomNameSet(['P']), + B.ammpSet('label_atom_id') + ]), + B.core.logic.or([ + B.core.set.isSubset([ + h.atomNameSet(["O3'", "C3'", "C4'", "C5'", "O5'"]), + B.ammpSet('label_atom_id') + ]), + B.core.set.isSubset([ + h.atomNameSet(['O3*', 'C3*', 'C4*', 'C5*', 'O5*']), + B.ammpSet('label_atom_id') + ]) + ]) + ]) + }); +} + +function backboneExpr() { + return B.struct.combinator.merge([ + B.struct.generator.queryInSelection({ + 0: proteinExpr(), + query: B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + h.atomNameSet(Backbone.protein), + B.ammp('label_atom_id') + ]) + }) + }), + B.struct.generator.queryInSelection({ + 0: nucleicExpr(), + query: B.struct.generator.atomGroups({ + 'atom-test': B.core.set.has([ + h.atomNameSet(Backbone.nucleic), + B.ammp('label_atom_id') + ]) + }) + }) + ]); +} + +function secStrucExpr(flags: string[]) { + return B.struct.generator.atomGroups({ + 'residue-test': B.core.flags.hasAll([ + B.ammp('secondaryStructureFlags'), + B.struct.type.secondaryStructureFlags(flags) + ]) + }); +} + +const Backbone = { + nucleic: ['P', "O3'", "O5'", "C5'", "C4'", "C3'", 'OP1', 'OP2', 'O3*', 'O5*', 'C5*', 'C4*', 'C3*'], + protein: ['C', 'N', 'CA', 'O'] +}; + +const ResDict = { + acidic: ['ASP', 'GLU'], + aliphatic: ['ALA', 'GLY', 'ILE', 'LEU', 'VAL'], + aromatic: ['HIS', 'PHE', 'TRP', 'TYR'], + at: ['ADA', 'A', 'THY', 'T'], + basic: ['ARG', 'HIS', 'LYS'], + buried: ['ALA', 'LEU', 'VAL', 'ILE', 'PHE', 'CYS', 'MET', 'TRP'], + cg: ['CYT', 'C', 'GUA', 'G'], + cyclic: ['HIS', 'PHE', 'PRO', 'TRP', 'TYR'], + hydrophobic: ['ALA', 'LEU', 'VAL', 'ILE', 'PRO', 'PHE', 'MET', 'TRP'], + medium: ['VAL', 'THR', 'ASP', 'ASN', 'PRO', 'CYS', 'ASX', 'PCA', 'HYP'], + neutral: ['VAL', 'PHE', 'GLN', 'TYR', 'HIS', 'CYS', 'MET', 'TRP', 'ASX', 'GLX', 'PCA', 'HYP'], + purine: ['ADE', 'A', 'GUA', 'G'], + pyrimidine: ['CYT', 'C', 'THY', 'T', 'URI', 'U'], + small: ['ALA', 'GLY', 'SER'], + water: ['H2O', 'HH0', 'OHH', 'HOH', 'OH2', 'SOL', 'WAT', 'TIP', 'TIP2', 'TIP3', 'TIP4'] +}; + +export const keywords: KeywordDict = { + all: { + '@desc': 'everything', + map: () => B.struct.generator.all() + }, + none: { + '@desc': 'nothing', + map: () => B.struct.generator.empty() + }, + protein: { + '@desc': 'a residue with atoms named C, N, CA, and O', + map: () => proteinExpr() + }, + nucleic: { + '@desc': "a residue with atoms named P, O1P, O2P and either O3', C3', C4', C5', O5' or O3*, C3*, C4*, C5*, O5*. This definition assumes that the base is phosphorylated, an assumption which will be corrected in the future.", + map: () => nucleicExpr() + }, + backbone: { + '@desc': 'the C, N, CA, and O atoms of a protein and the equivalent atoms in a nucleic acid.', + map: () => backboneExpr() + }, + sidechain: { + '@desc': 'non-backbone atoms and bonds', // TODO: what does 'bonds' mean here? + map: () => h.invertExpr(backboneExpr()) + }, + water: { + '@desc': 'all atoms with the resname H2O, HH0, OHH, HOH, OH2, SOL, WAT, TIP, TIP2, TIP3 or TIP4', + abbr: ['waters'], + map: () => h.resnameExpr(ResDict.water) + }, + at: { + '@desc': 'residues named ADA A THY T', + map: () => h.resnameExpr(ResDict.at) + }, + acidic: { + '@desc': 'residues named ASP GLU', + map: () => h.resnameExpr(ResDict.acidic) + }, + acyclic: { + '@desc': '"protein and not cyclic"', + map: () => B.struct.modifier.intersectBy({ + 0: proteinExpr(), + by: h.invertExpr(h.resnameExpr(ResDict.cyclic)) + }) + }, + aliphatic: { + '@desc': 'residues named ALA GLY ILE LEU VAL', + map: () => h.resnameExpr(ResDict.aliphatic) + }, + alpha: { + '@desc': "atom's residue is an alpha helix", + map: () => secStrucExpr(['alpha']) + }, + amino: { + '@desc': 'a residue with atoms named C, N, CA, and O', + map: () => proteinExpr() + }, + aromatic: { + '@desc': 'residues named HIS PHE TRP TYR', + map: () => h.resnameExpr(ResDict.aromatic) + }, + basic: { + '@desc': 'residues named ARG HIS LYS', + map: () => h.resnameExpr(ResDict.basic) + }, + bonded: { + '@desc': 'atoms for which numbonds > 0', + map: () => h.asAtoms(B.struct.filter.pick({ + '0': B.struct.modifier.includeConnected({ + '0': B.struct.generator.all(), + 'bond-test': B.core.flags.hasAny([ + B.struct.bondProperty.flags(), + B.struct.type.bondFlags(['covalent', 'metallic', 'sulfide']) + ]) + }), + test: B.core.rel.gr([ + B.struct.atomSet.atomCount(), 1 + ]) + })) + }, + buried: { + '@desc': 'residues named ALA LEU VAL ILE PHE CYS MET TRP', + map: () => h.resnameExpr(ResDict.buried) + }, + cg: { + '@desc': 'residues named CYT C GUA G', + map: () => h.resnameExpr(ResDict.cg) + }, + charged: { + '@desc': '"basic or acidic"', + map: () => h.resnameExpr(ResDict.basic.concat(ResDict.acidic)) + }, + cyclic: { + '@desc': 'residues named HIS PHE PRO TRP TYR', + map: () => h.resnameExpr(ResDict.cyclic) + }, + hetero: { + '@desc': '"not (protein or nucleic)"', + map: () => h.invertExpr( + B.struct.combinator.merge([proteinExpr(), nucleicExpr()]) + ) + }, + hydrogen: { + '@desc': 'name "[0-9]?H.*"', + map: () => B.struct.generator.atomGroups({ + 'atom-test': B.core.str.match([ + B.core.type.regex(['^[0-9]?[H].*$', 'i']), + B.core.type.str([B.ammp('label_atom_id')]) + ]) + }) + }, + large: { + '@desc': '"protein and not (small or medium)"', + map: () => B.struct.modifier.intersectBy({ + 0: proteinExpr(), + by: h.invertExpr( + h.resnameExpr(ResDict.small.concat(ResDict.medium)) + ) + }) + }, + medium: { + '@desc': 'residues named VAL THR ASP ASN PRO CYS ASX PCA HYP', + map: () => h.resnameExpr(ResDict.medium) + }, + neutral: { + '@desc': 'residues named VAL PHE GLN TYR HIS CYS MET TRP ASX GLX PCA HYP', + map: () => h.resnameExpr(ResDict.neutral) + }, + hydrophobic: { + '@desc': 'hydrophobic resname ALA LEU VAL ILE PRO PHE MET TRP', + map: () => h.resnameExpr(ResDict.hydrophobic) + }, + polar: { + '@desc': '"protein and not hydrophobic"', + map: () => B.struct.modifier.intersectBy({ + 0: proteinExpr(), + by: h.invertExpr(h.resnameExpr(ResDict.hydrophobic)) + }) + }, + purine: { + '@desc': 'residues named ADE A GUA G', + map: () => h.resnameExpr(ResDict.purine) + }, + pyrimidine: { + '@desc': 'residues named CYT C THY T URI U', + map: () => h.resnameExpr(ResDict.pyrimidine) + }, + small: { + '@desc': 'residues named ALA GLY SER', + map: () => h.resnameExpr(ResDict.small) + }, + surface: { + '@desc': '"protein and not buried"', + map: () => B.struct.modifier.intersectBy({ + 0: proteinExpr(), + by: h.invertExpr(h.resnameExpr(ResDict.buried)) + }) + }, + alpha_helix: { + '@desc': "atom's residue is in an alpha helix", + map: () => secStrucExpr(['alpha']) + }, + pi_helix: { + '@desc': "atom's residue is in a pi helix", + map: () => secStrucExpr(['pi']) + }, + helix_3_10: { + '@desc': "atom's residue is in a 3-10 helix", + map: () => secStrucExpr(['3-10']) + }, + helix: { + '@desc': "atom's residue is in an alpha or pi or 3-10 helix", + map: () => secStrucExpr(['helix']) + }, + extended_beta: { + '@desc': "atom's residue is a beta sheet", + map: () => secStrucExpr(['sheet']) + }, + bridge_beta: { + '@desc': "atom's residue is a beta sheet", + map: () => secStrucExpr(['strand']) + }, + sheet: { + '@desc': "atom's residue is a beta sheet", + map: () => secStrucExpr(['beta']) + }, + turn: { + '@desc': "atom's residue is in a turn conformation", + map: () => secStrucExpr(['turn']) + }, + coil: { + '@desc': "atom's residue is in a coil conformation", + map: () => B.struct.modifier.intersectBy({ + 0: proteinExpr(), + by: secStrucExpr(['none']) + }) + } +}; diff --git a/src/mol-script/transpilers/vmd/markdown-docs.ts b/src/mol-script/transpilers/vmd/markdown-docs.ts new file mode 100644 index 0000000000000000000000000000000000000000..63a44f9ce3dc120cf0205627380be29627f58d5a --- /dev/null +++ b/src/mol-script/transpilers/vmd/markdown-docs.ts @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +import { properties } from './properties'; +import { operators } from './operators'; +import { keywords } from './keywords'; +import { functions } from './functions'; + +const _docs: string[] = [ + 'VMD', + '============', + '--------------------------------', + '' +]; + +_docs.push(`## Properties\n\n`); +_docs.push('--------------------------------\n'); +for (const name in properties) { + if (properties[name].isUnsupported) continue; + + const names = [name]; + if (properties[name].abbr) names.push(...properties[name].abbr!); + _docs.push(`\`\`\`\n${names.join(', ')}\n\`\`\`\n`); + + if (properties[name]['@desc']) { + _docs.push(`*${properties[name]['@desc']}*\n`); + } +} + +_docs.push(`## Operators\n\n`); +_docs.push('--------------------------------\n'); +operators.forEach(o => { + if (o.isUnsupported) return; + + const names = [o.name]; + if (o.abbr) names.push(...o.abbr!); + _docs.push(`\`\`\`\n${names.join(', ')}\n\`\`\`\n`); + + if (o['@desc']) { + _docs.push(`*${o['@desc']}*\n`); + } +}); + +_docs.push(`## Keywords\n\n`); +_docs.push('--------------------------------\n'); +for (const name in keywords) { + if (!keywords[name].map) continue; + + const names = [name]; + if (keywords[name].abbr) names.push(...keywords[name].abbr!); + _docs.push(`\`\`\`\n${names.join(', ')}\n\`\`\`\n`); + + if (keywords[name]['@desc']) { + _docs.push(`*${keywords[name]['@desc']}*\n`); + } +} + +_docs.push(`## Functions\n\n`); +_docs.push('--------------------------------\n'); +for (const name in functions) { + if (!functions[name].map) continue; + + const names = [name]; + _docs.push(`\`\`\`\n${names.join(', ')}\n\`\`\`\n`); + + if (functions[name]['@desc']) { + _docs.push(`*${functions[name]['@desc']}*\n`); + } +} + +export const docs = _docs.join('\n'); \ No newline at end of file diff --git a/src/mol-script/transpilers/vmd/operators.ts b/src/mol-script/transpilers/vmd/operators.ts new file mode 100644 index 0000000000000000000000000000000000000000..e3cddd364691a4aafa7489e91a7e2eac2a7e826e --- /dev/null +++ b/src/mol-script/transpilers/vmd/operators.ts @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +import * as P from '../../../mol-util/monadic-parser'; +import * as h from '../helper'; +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { properties } from './properties'; +import { Expression } from '../../language/expression'; +import { OperatorList } from '../types'; + +const propNames = Object.keys(properties).sort(h.strLenSortFn) + .filter(name => !properties[name].isUnsupported).join('|'); + +export const operators: OperatorList = [ + { + '@desc': 'Selects atoms that are not included in s1.', + '@examples': ['not protein'], + name: 'not', + type: h.prefix, + rule: P.MonadicParser.regexp(/NOT/i).skip(P.MonadicParser.whitespace), + map: (op, selection) => h.invertExpr(selection), + }, + { + '@desc': 'Selects atoms within a specified distance of a selection', + '@examples': ['within 5 of name FE'], + name: 'within', + type: h.prefix, + rule: h.prefixOp(/WITHIN\s+([-+]?[0-9]*\.?[0-9]+)\s+OF/i, 1).map((x: any) => parseFloat(x)), + map: (radius: number, selection: Expression) => { + return B.struct.modifier.includeSurroundings({ 0: selection, radius }); + } + }, + { + '@desc': 'Exclusive within, equivalent to (within 3 of X) and not X', + '@examples': ['exwithin 10 of resname HEM'], + name: 'exwithin', + type: h.prefix, + rule: h.prefixOp(/EXWITHIN\s+([-+]?[0-9]*\.?[0-9]+)\s+OF/i, 1).map((x: any) => parseFloat(x)), + map: (radius: number, target: Expression) => { + return B.struct.modifier.exceptBy({ + '0': B.struct.modifier.includeSurroundings({ 0: target, radius }), + by: target + }); + } + }, + { + '@desc': 'Selects atoms which have the same keyword as the atoms in a given selection', + '@examples': ['same resid as name FE'], + name: 'same', + type: h.prefix, + rule: h.prefixOp(new RegExp(`SAME\\s+(${propNames})\\s+AS`, 'i'), 1).map((x: any) => properties[x].property), + map: (property: Expression, source: Expression) => { + return B.struct.filter.withSameAtomProperties({ + '0': B.struct.generator.all(), + source, + property + }); + } + }, + { + '@desc': 'Selects atoms included in both s1 and s2.', + '@examples': ['backbone and protein'], + name: 'and', + type: h.binaryLeft, + rule: P.MonadicParser.alt(h.infixOp(/AND/i), P.MonadicParser.whitespace), + map: (op, selection, by) => B.struct.modifier.intersectBy({ 0: selection, by }) + }, + { + '@desc': 'Selects atoms included in either s1 or s2.', + '@examples': ['water or protein'], + name: 'or', + type: h.binaryLeft, + rule: h.infixOp(/OR/i), + map: (op, s1, s2) => B.struct.combinator.merge([s1, s2]) + } +]; diff --git a/src/mol-script/transpilers/vmd/parser.ts b/src/mol-script/transpilers/vmd/parser.ts new file mode 100644 index 0000000000000000000000000000000000000000..dbe9b7bda9f1386bc3c4e54a8ab33e023d4e4f5e --- /dev/null +++ b/src/mol-script/transpilers/vmd/parser.ts @@ -0,0 +1,262 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + * + * Adapted from MolQL project + */ + +import * as P from '../../../mol-util/monadic-parser'; +import * as h from '../helper'; +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { sstrucMap, sstrucDict, properties } from './properties'; +import { operators } from './operators'; +import { keywords } from './keywords'; +import { functions } from './functions'; +import { OperatorList } from '../types'; +import { Transpiler } from '../transpiler'; + +// <, <=, = or ==, >=, >, and != +// lt, le, eq, ge, gt, and ne, =~ +const valueOperators: OperatorList = [ + { + '@desc': 'multiplication, division', + '@examples': [], + name: 'mul-div', + type: h.binaryLeft, + rule: P.MonadicParser.regexp(/\s*(\*|\/)\s*/, 1), + map: (op, e1, e2) => { + switch (op) { + case '*': return B.core.math.mult([e1, e2]); + case '/': return B.core.math.div([e1, e2]); + default: throw new Error(`value operator '${op}' not supported`); + } + } + }, + { + '@desc': 'addition, substraction', + '@examples': [], + name: 'add-sub', + type: h.binaryLeft, + rule: P.MonadicParser.regexp(/\s*(-|\+)\s*/, 1), + map: (op, e1, e2) => { + switch (op) { + case '-': return B.core.math.sub([e1, e2]); + case '+': return B.core.math.add([e1, e2]); + default: throw new Error(`value operator '${op}' not supported`); + } + } + }, + { + '@desc': 'value comparisons', + '@examples': [], + name: 'comparison', + type: h.binaryLeft, + rule: P.MonadicParser.alt(P.MonadicParser.regexp(/\s*(=~|==|>=|<=|=|!=|>|<)\s*/, 1), P.MonadicParser.whitespace.result('=')), + map: (op, e1, e2) => { + let expr; + if (e1.head !== undefined) { + if (e1.head.name === 'structure-query.atom-property.macromolecular.secondary-structure-flags') { + expr = B.core.flags.hasAny([e1, sstrucMap(e2)]); + } + if (e1.head.name === 'core.type.regex') { + expr = B.core.str.match([e1, B.core.type.str([e2])]); + } + } else if (e2.head !== undefined) { + if (e2.head.name === 'structure-query.atom-property.macromolecular.secondary-structure-flags') { + expr = B.core.flags.hasAny([e2, sstrucMap(e1)]); + } + if (e2.head.name === 'core.type.regex') { + expr = B.core.str.match([e2, B.core.type.str([e1])]); + } + } else if (op === '=~') { + if (e1.head) { + expr = B.core.str.match([ + B.core.type.regex([`^${e2}$`, 'i']), + B.core.type.str([e1]) + ]); + } else { + expr = B.core.str.match([ + B.core.type.regex([`^${e1}$`, 'i']), + B.core.type.str([e2]) + ]); + } + } + if (!expr) { + if (e1.head) e2 = h.wrapValue(e1, e2); + if (e2.head) e1 = h.wrapValue(e2, e1); + switch (op) { + case '=': + case '==': + expr = B.core.rel.eq([e1, e2]); + break; + case '!=': + expr = B.core.rel.neq([e1, e2]); + break; + case '>': + expr = B.core.rel.gr([e1, e2]); + break; + case '<': + expr = B.core.rel.lt([e1, e2]); + break; + case '>=': + expr = B.core.rel.gre([e1, e2]); + break; + case '<=': + expr = B.core.rel.lte([e1, e2]); + break; + default: throw new Error(`value operator '${op}' not supported`); + } + } + return B.struct.generator.atomGroups({ 'atom-test': expr }); + } + } +]; + +const lang = P.MonadicParser.createLanguage({ + Parens: function (r: any) { + return P.MonadicParser.alt( + r.Parens, + r.Operator, + r.Expression + ).wrap(P.MonadicParser.string('('), P.MonadicParser.string(')')); + }, + + Expression: function (r: any) { + return P.MonadicParser.alt( + r.RangeListProperty, + // r.NamedAtomProperties, + r.ValueQuery, + r.Keywords, + ); + }, + + NamedAtomProperties: function () { + return P.MonadicParser.alt(...h.getNamedPropertyRules(properties)); + }, + + Keywords: () => P.MonadicParser.alt(...h.getKeywordRules(keywords)), + + ValueRange: function (r: any) { + return P.MonadicParser.seq( + r.Value + .skip(P.MonadicParser.regexp(/\s+TO\s+/i)), + r.Value + ).map(x => ({ range: x })); + }, + + RangeListProperty: function (r: any) { + return P.MonadicParser.seq( + P.MonadicParser.alt(...h.getPropertyNameRules(properties, /\s/)) + .skip(P.MonadicParser.whitespace), + P.MonadicParser.alt( + r.ValueRange, + r.Value + ).sepBy1(P.MonadicParser.whitespace) + ).map(x => { + const [property, values] = x; + const listValues: (string | number)[] = []; + const rangeValues: any[] = []; + + values.forEach((v: any) => { + if (v.range) { + rangeValues.push( + B.core.rel.inRange([property, v.range[0], v.range[1]]) + ); + } else { + listValues.push(h.wrapValue(property, v, sstrucDict)); + } + }); + + const rangeTest = h.orExpr(rangeValues); + const listTest = h.valuesTest(property, listValues); + + let test; + if (rangeTest && listTest) { + test = B.core.logic.or([rangeTest, listTest]); + } else { + test = rangeTest ? rangeTest : listTest; + } + + return B.struct.generator.atomGroups({ [h.testLevel(property)]: test }); + }); + }, + + Operator: function (r: any) { + return h.combineOperators(operators, P.MonadicParser.alt(r.Parens, r.Expression, r.ValueQuery)); + }, + + Query: function (r: any) { + return P.MonadicParser.alt( + r.Operator, + r.Parens, + r.Expression + ).trim(P.MonadicParser.optWhitespace); + }, + + Number: function () { + return P.MonadicParser.regexp(/-?(0|[1-9][0-9]*)([.][0-9]+)?([eE][+-]?[0-9]+)?/) + .map(Number) + .desc('number'); + }, + + String: function () { + const w = h.getReservedWords(properties, keywords, operators) + .sort(h.strLenSortFn).map(h.escapeRegExp).join('|'); + return P.MonadicParser.alt( + P.MonadicParser.regexp(new RegExp(`(?!(${w}))[A-Z0-9_]+`, 'i')), + P.MonadicParser.regexp(/'((?:[^"\\]|\\.)*)'/, 1), + P.MonadicParser.regexp(/"((?:[^"\\]|\\.)*)"/, 1).map((x: any) => B.core.type.regex([`^${x}$`, 'i'])) + ).desc('string'); + }, + + Value: function (r: any) { + return P.MonadicParser.alt(r.Number, r.String); + }, + + ValueParens: function (r: any) { + return P.MonadicParser.alt( + r.ValueParens, + r.ValueOperator, + r.ValueExpressions + ).wrap(P.MonadicParser.string('('), P.MonadicParser.string(')')); + }, + + ValuePropertyNames: function () { + return P.MonadicParser.alt(...h.getPropertyNameRules(properties, /=~|==|>=|<=|=|!=|>|<|\)|\s|\+|-|\*|\//i)); + }, + + ValueOperator: function (r: any) { + return h.combineOperators(valueOperators, P.MonadicParser.alt(r.ValueParens, r.ValueExpressions)); + }, + + ValueExpressions: function (r: any) { + return P.MonadicParser.alt( + r.ValueFunctions, + r.Value, + r.ValuePropertyNames + ); + }, + + ValueFunctions: function (r: any) { + return P.MonadicParser.alt(...h.getFunctionRules(functions, r.ValueOperator)); + }, + + ValueQuery: function (r: any) { + return P.MonadicParser.alt( + r.ValueOperator.map((x: any) => { + // if (!x.head || x.head.startsWith('core.math') || x.head.startsWith('structure-query.atom-property')) { + if (!x.head.name || !x.head.name.startsWith('structure-query.generator')) { + throw new Error(`values must be part of an comparison, value '${x}'`); + } else { + return x as any; + } + }) + ); + } +}); + +export const transpiler: Transpiler = str => lang.Query.tryParse(str); diff --git a/src/mol-script/transpilers/vmd/properties.ts b/src/mol-script/transpilers/vmd/properties.ts new file mode 100644 index 0000000000000000000000000000000000000000..2cf506943f4965f3098b5ce794e3d201896ebd57 --- /dev/null +++ b/src/mol-script/transpilers/vmd/properties.ts @@ -0,0 +1,269 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +import { MolScriptBuilder } from '../../../mol-script/language/builder'; +const B = MolScriptBuilder; +import { PropertyDict } from '../types'; + +const reFloat = /[-+]?[0-9]*\.?[0-9]+/; +const rePosInt = /[+]?[0-9]+/; +const reInt = /[-+]?[0-9]+/; + +function str(x: string) { return x; } + +export const sstrucDict: { [key: string]: string } = { + T: 'turn', // Turn + E: 'sheet', // Extended conformation ($\beta$ sheets) + B: 'strand', // Isolated bridge + H: 'alpha', // Alpha helix + G: '3-10', // 3-10 helix + I: 'pi', // Pi helix + C: 'none', // Coil +}; +export function sstrucMap(x: string) { + return B.struct.type.secondaryStructureFlags( + [sstrucDict[x.toUpperCase()] || 'none'] + ); +} + +export const properties: PropertyDict = { + name: { + '@desc': 'str atom name', + '@examples': ['name CA'], + regex: /[a-zA-Z0-9]+/, map: B.atomName, + level: 'atom-test', property: B.ammp('label_atom_id') + }, + type: { + '@desc': 'str atom type', + '@examples': ['type C3'], + isUnsupported: true, + regex: /[a-zA-Z0-9]+/, map: str, + level: 'atom-test' + }, + index: { + '@desc': 'num the atom number, starting at 0', + '@examples': ['index 10'], + isNumeric: true, + regex: rePosInt, map: x => (parseInt(x) - 1), + level: 'atom-test', property: B.ammp('id') + }, + serial: { + '@desc': 'num the atom number, starting at 1', + '@examples': ['serial 11'], + isNumeric: true, + regex: rePosInt, map: x => parseInt(x), + level: 'atom-test', property: B.ammp('id') + }, + atomicnumber: { + '@desc': 'num atomic number (0 if undefined)', + '@examples': ['atomicnumber 13'], + isNumeric: true, + regex: rePosInt, map: x => parseInt(x), + level: 'atom-test', property: B.acp('atomicNumber') + }, + element: { + '@desc': 'str atomic element symbol string ("X" if undefined)', + '@examples': ['element N'], + regex: /[a-zA-Z0-9]{1,3}/, map: x => B.es(x), + level: 'atom-test', property: B.acp('elementSymbol') + }, + altloc: { + '@desc': 'str alternate location/conformation identifier', + '@examples': ['altloc C'], + regex: /[a-zA-Z0-9]+/, map: str, + level: 'atom-test', property: B.ammp('label_alt_id') + }, + chain: { + '@desc': 'str the one-character chain identifier', + '@examples': ['chain A'], + regex: /[a-zA-Z0-9]+/, map: str, + level: 'residue-test', property: B.ammp('auth_asym_id') + }, + residue: { + '@desc': 'num a set of connected atoms with the same residue number', + '@examples': ['residue < 11', 'residue 11'], + isNumeric: true, + regex: reInt, map: x => parseInt(x), + level: 'residue-test', property: B.ammp('auth_seq_id') + }, + fragment: { + '@desc': 'num a set of connected residues', + '@examples': ['fragment 42'], + isUnsupported: true, + isNumeric: true, + regex: reInt, map: x => parseInt(x), + level: 'residue-test' + }, + pfrag: { + '@desc': 'num a set of connected protein residues', + '@examples': ['pfrag 42'], + isUnsupported: true, + isNumeric: true, + regex: reInt, map: x => parseInt(x), + level: 'residue-test' + }, + nfrag: { + '@desc': 'num a set of connected nucleic residues', + '@examples': ['nfrag 42'], + isUnsupported: true, + isNumeric: true, + regex: reInt, map: x => parseInt(x), + level: 'residue-test' + }, + sequence: { + '@desc': 'str a sequence given by one letter names', + '@examples': ['sequence PGATTACA'], + isUnsupported: true, + regex: /[a-zA-Z0-9]+/, map: str, + level: 'residue-test' + }, + numbonds: { + '@desc': 'num number of bonds', + '@examples': ['numbonds = 2', 'numbonds >= 3'], + isNumeric: true, + regex: rePosInt, map: x => parseInt(x), + level: 'atom-test', property: B.acp('bondCount') + }, + resname: { + '@desc': 'str residue name', + '@examples': ['resname ALA'], + regex: /[a-zA-Z0-9]+/, map: str, + level: 'residue-test', property: B.ammp('auth_comp_id') + }, + resid: { + '@desc': 'num residue id', + '@examples': ['resid 42'], + isNumeric: true, + regex: reInt, map: x => parseInt(x), + level: 'residue-test', property: B.ammp('auth_seq_id') + }, + segname: { + '@desc': 'str segment name', + '@examples': ['segname B'], + regex: /[a-zA-Z0-9]+/, map: str, + level: 'residue-test', property: B.ammp('label_asym_id') + }, + x: { + '@desc': 'float x coordinate', + '@examples': ['x 42'], + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.acp('x') + }, + y: { + '@desc': 'float y coordinate', + '@examples': ['y > 1.7'], + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.acp('y') + }, + z: { + '@desc': 'float z coordinate', + '@examples': ['z < 11', 'z > -21'], + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.acp('z') + }, + radius: { + '@desc': 'float atomic radius', + '@examples': ['radius > 1.3'], + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.acp('vdw') + }, + mass: { + '@desc': 'float atomic mass', + '@examples': ['mass > 2'], + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.acp('mass') + }, + charge: { + '@desc': 'float atomic charge', + '@examples': ['charge > 0', 'charge 1'], + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.ammp('pdbx_formal_charge') + }, + beta: { + '@desc': 'float temperature factor', + '@examples': ['beta < 20', 'beta > 35'], + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.ammp('B_iso_or_equiv') + }, + occupancy: { + '@desc': 'float occupancy', + '@examples': ['occupancy 1', 'occupancy < 1'], + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test', property: B.ammp('occupancy') + }, + user: { + '@desc': 'float time-varying user-specified value', + '@examples': ['user < 0.1'], + isUnsupported: true, + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'atom-test' + }, + rasmol: { + '@desc': 'str translates Rasmol selection string to VMD', + '@examples': ["rasmol 'all'"], + isUnsupported: true, + regex: /[^']*/, map: str, + level: 'atom-test' + }, + structure: { + '@desc': 'str single letter name for the secondary structure', + '@examples': ['structure H', 'structure H E'], + regex: /T|E|B|H|G|I|C/i, map: sstrucMap, + level: 'atom-test', property: B.ammp('secondaryStructureFlags') + }, + phi: { + '@desc': 'float phi backbone conformational angles', + '@examples': ['phi < 160'], + isUnsupported: true, + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'residue-test' + }, + psi: { + '@desc': 'float psi backbone conformational angles', + '@examples': ['psi < 160'], + isUnsupported: true, + isNumeric: true, + regex: reFloat, map: x => parseFloat(x), + level: 'residue-test' + }, + ufx: { + '@desc': 'num force to apply in the x coordinate', + '@examples': ['ufx 1'], + isUnsupported: true, + isNumeric: true, + regex: reFloat, map: x => parseInt(x), + level: 'atom-test' + }, + ufy: { + '@desc': 'num force to apply in the y coordinate', + '@examples': ['ufy 1'], + isUnsupported: true, + isNumeric: true, + regex: reFloat, map: x => parseInt(x), + level: 'atom-test' + }, + ufz: { + '@desc': 'num force to apply in the z coordinate', + '@examples': ['ufz 1'], + isUnsupported: true, + isNumeric: true, + regex: reFloat, map: x => parseInt(x), + level: 'atom-test' + }, +}; diff --git a/src/mol-script/transpilers/vmd/symbols.ts b/src/mol-script/transpilers/vmd/symbols.ts new file mode 100644 index 0000000000000000000000000000000000000000..1740243d4e755c385cf264c915df58456d5d1838 --- /dev/null +++ b/src/mol-script/transpilers/vmd/symbols.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Panagiotis Tourlas <panagiot_tourlov@hotmail.com> + * + * Adapted from MolQL project + */ + +import { properties } from './properties'; +import { operators } from './operators'; +import { keywords } from './keywords'; +import { functions } from './functions'; + +export const Properties: string[] = []; +for (const name in properties) { + if (properties[name].isUnsupported) continue; + Properties.push(name); + if (properties[name].abbr) Properties.push(...properties[name].abbr!); +} + +export const Operators: string[] = []; +operators.forEach(o => { + if (o.isUnsupported) return; + Operators.push(o.name); + if (o.abbr) Operators.push(...o.abbr); +}); + +export const Keywords: string[] = []; +for (const name in keywords) { + if (!keywords[name].map) continue; + Keywords.push(name); + if (keywords[name].abbr) Keywords.push(...keywords[name].abbr!); +} + +export const Functions: string[] = []; +for (const name in functions) { + if (!functions[name].map) continue; + Functions.push(name); +} + +export const all = { Properties, Operators: [...Operators, ...Functions], Keywords }; diff --git a/src/mol-state/action.ts b/src/mol-state/action.ts index b6416e63f626fa7fd0ebea77692c99c55cd345cf..864a8cb9535468705a5b03ac099e2aa2c443a629 100644 --- a/src/mol-state/action.ts +++ b/src/mol-state/action.ts @@ -84,7 +84,7 @@ namespace StateAction { } export namespace Builder { - export interface Type<A extends StateObject.Ctor, P extends { }> { + export interface Type<A extends StateObject.Ctor, P extends {}> { from?: A | A[], params?: PD.For<P> | ((a: StateObject.From<A>, globalCtx: any) => PD.For<P>), display?: string | { name: string, description?: string }, @@ -95,7 +95,7 @@ namespace StateAction { <A extends StateObject.Ctor, P extends { }>(info: Type<A, P>): Define<StateObject.From<A>, PD.Normalize<P>> } - export interface Define<A extends StateObject, P> { + export interface Define<A extends StateObject, P extends {}> { <T>(def: DefinitionBase<A, T, P> | DefinitionBase<A, T, P>['run']): StateAction<A, T, P>, } diff --git a/src/mol-state/transformer.ts b/src/mol-state/transformer.ts index 0656babb8f3a526776075368a9ca22d45b6b2b41..94905d9af53e6b852a40a47dbe9e46606eb7fd69 100644 --- a/src/mol-state/transformer.ts +++ b/src/mol-state/transformer.ts @@ -196,7 +196,7 @@ namespace Transformer { } export namespace Builder { - export interface Type<A extends StateObject.Ctor, B extends StateObject.Ctor, P extends { }> { + export interface Type<A extends StateObject.Ctor, B extends StateObject.Ctor, P extends {}> { name: string, from: A | A[], to: B | B[], @@ -210,7 +210,7 @@ namespace Transformer { <A extends StateObject.Ctor, B extends StateObject.Ctor, P extends { }>(info: Type<A, B, P>): Define<StateObject.From<A>, StateObject.From<B>, PD.Normalize<P>> } - export interface Define<A extends StateObject, B extends StateObject, P> { + export interface Define<A extends StateObject, B extends StateObject, P extends {}> { (def: DefinitionBase<A, B, P>): Transformer<A, B, P> } diff --git a/src/mol-theme/color.ts b/src/mol-theme/color.ts index 928137db106fe1c31ef1b577715e149a9e1c5857..84af7dad2d2a34f23cfcb4f270fc068dda79f949 100644 --- a/src/mol-theme/color.ts +++ b/src/mol-theme/color.ts @@ -28,7 +28,7 @@ import { UncertaintyColorThemeProvider } from './color/uncertainty'; import { EntitySourceColorThemeProvider } from './color/entity-source'; import { IllustrativeColorThemeProvider } from './color/illustrative'; import { HydrophobicityColorThemeProvider } from './color/hydrophobicity'; -import { ModelIndexColorThemeProvider } from './color/model-index'; +import { TrajectoryIndexColorThemeProvider } from './color/trajectory-index'; import { OccupancyColorThemeProvider } from './color/occupancy'; import { OperatorNameColorThemeProvider } from './color/operator-name'; import { OperatorHklColorThemeProvider } from './color/operator-hkl'; @@ -38,6 +38,8 @@ import { EntityIdColorThemeProvider } from './color/entity-id'; import { Texture, TextureFilter } from '../mol-gl/webgl/texture'; import { VolumeValueColorThemeProvider } from './color/volume-value'; import { Vec3, Vec4 } from '../mol-math/linear-algebra'; +import { ModelIndexColorThemeProvider } from './color/model-index'; +import { StructureIndexColorThemeProvider } from './color/structure-index'; export type LocationColor = (location: Location, isSecondary: boolean) => Color @@ -144,6 +146,8 @@ namespace ColorTheme { 'secondary-structure': SecondaryStructureColorThemeProvider, 'sequence-id': SequenceIdColorThemeProvider, 'shape-group': ShapeGroupColorThemeProvider, + 'structure-index': StructureIndexColorThemeProvider, + 'trajectory-index': TrajectoryIndexColorThemeProvider, 'uncertainty': UncertaintyColorThemeProvider, 'unit-index': UnitIndexColorThemeProvider, 'uniform': UniformColorThemeProvider, diff --git a/src/mol-theme/color/element-symbol.ts b/src/mol-theme/color/element-symbol.ts index bb75aa34d3b42175020cb7dd2eb7a867ef1dc0a3..a466848df48e6d5b9b41e5ffc1d4a6e30954eed2 100644 --- a/src/mol-theme/color/element-symbol.ts +++ b/src/mol-theme/color/element-symbol.ts @@ -19,6 +19,8 @@ import { OperatorNameColorThemeParams, OperatorNameColorTheme } from './operator import { EntityIdColorTheme, EntityIdColorThemeParams } from './entity-id'; import { assertUnreachable } from '../../mol-util/type-helpers'; import { EntitySourceColorTheme, EntitySourceColorThemeParams } from './entity-source'; +import { ModelIndexColorTheme, ModelIndexColorThemeParams } from './model-index'; +import { StructureIndexColorTheme, StructureIndexColorThemeParams } from './structure-index'; // from Jmol http://jmol.sourceforge.net/jscolors/ (or 0xFFFFFF) export const ElementSymbolColors = ColorMap({ @@ -35,6 +37,8 @@ export const ElementSymbolColorThemeParams = { 'entity-id': PD.Group(EntityIdColorThemeParams), 'entity-source': PD.Group(EntitySourceColorThemeParams), 'operator-name': PD.Group(OperatorNameColorThemeParams), + 'model-index': PD.Group(ModelIndexColorThemeParams), + 'structure-index': PD.Group(StructureIndexColorThemeParams), 'element-symbol': PD.EmptyGroup() }, { description: 'Use chain-id coloring for carbon atoms.' }), saturation: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }), @@ -46,7 +50,7 @@ export const ElementSymbolColorThemeParams = { }; export type ElementSymbolColorThemeParams = typeof ElementSymbolColorThemeParams export function getElementSymbolColorThemeParams(ctx: ThemeDataContext) { - return ElementSymbolColorThemeParams; // TODO return copy + return PD.clone(ElementSymbolColorThemeParams); } export function elementSymbolColor(colorMap: ElementSymbolColors, element: ElementSymbol): Color { @@ -63,8 +67,10 @@ export function ElementSymbolColorTheme(ctx: ThemeDataContext, props: PD.Values< pcc.name === 'entity-id' ? EntityIdColorTheme(ctx, pcc.params).color : pcc.name === 'entity-source' ? EntitySourceColorTheme(ctx, pcc.params).color : pcc.name === 'operator-name' ? OperatorNameColorTheme(ctx, pcc.params).color : - pcc.name === 'element-symbol' ? undefined : - assertUnreachable(pcc); + pcc.name === 'model-index' ? ModelIndexColorTheme(ctx, pcc.params).color : + pcc.name === 'structure-index' ? StructureIndexColorTheme(ctx, pcc.params).color : + pcc.name === 'element-symbol' ? undefined : + assertUnreachable(pcc); function elementColor(element: ElementSymbol, location: Location) { return (carbonColor && element === 'C') diff --git a/src/mol-theme/color/illustrative.ts b/src/mol-theme/color/illustrative.ts index 7483f5d4b92d3426a59d879f8f0ccae142bb6360..18510ef68fdb85d0aa57c6d49ae08eefeae2009c 100644 --- a/src/mol-theme/color/illustrative.ts +++ b/src/mol-theme/color/illustrative.ts @@ -17,9 +17,11 @@ import { assertUnreachable } from '../../mol-util/type-helpers'; import { EntityIdColorTheme, EntityIdColorThemeParams } from './entity-id'; import { MoleculeTypeColorTheme, MoleculeTypeColorThemeParams } from './molecule-type'; import { EntitySourceColorTheme, EntitySourceColorThemeParams } from './entity-source'; +import { ModelIndexColorTheme, ModelIndexColorThemeParams } from './model-index'; +import { StructureIndexColorTheme, StructureIndexColorThemeParams } from './structure-index'; const DefaultIllustrativeColor = Color(0xEEEEEE); -const Description = `Assigns an illustrative color that gives every chain a color based on the choosen style but with lighter carbons (inspired by David Goodsell's Molecule of the Month style).`; +const Description = `Assigns an illustrative color that gives every chain a color based on the chosen style but with lighter carbons (inspired by David Goodsell's Molecule of the Month style).`; export const IllustrativeColorThemeParams = { style: PD.MappedStatic('entity-id', { @@ -28,6 +30,8 @@ export const IllustrativeColorThemeParams = { 'entity-id': PD.Group(EntityIdColorThemeParams), 'entity-source': PD.Group(EntitySourceColorThemeParams), 'molecule-type': PD.Group(MoleculeTypeColorThemeParams), + 'model-index': PD.Group(ModelIndexColorThemeParams), + 'structure-index': PD.Group(StructureIndexColorThemeParams), }), carbonLightness: PD.Numeric(0.8, { min: -6, max: 6, step: 0.1 }) }; @@ -44,7 +48,9 @@ export function IllustrativeColorTheme(ctx: ThemeDataContext, props: PD.Values<I props.style.name === 'entity-id' ? EntityIdColorTheme(ctx, props.style.params) : props.style.name === 'entity-source' ? EntitySourceColorTheme(ctx, props.style.params) : props.style.name === 'molecule-type' ? MoleculeTypeColorTheme(ctx, props.style.params) : - assertUnreachable(props.style); + props.style.name === 'model-index' ? ModelIndexColorTheme(ctx, props.style.params) : + props.style.name === 'structure-index' ? StructureIndexColorTheme(ctx, props.style.params) : + assertUnreachable(props.style); function illustrativeColor(location: Location, typeSymbol: ElementSymbol) { const baseColor = styleColor(location, false); diff --git a/src/mol-theme/color/model-index.ts b/src/mol-theme/color/model-index.ts index 51e2b4ea98ab4920a109dfad83e5f685394390dd..cbdfd683f10abebf07ceee1ba180b57331440ce0 100644 --- a/src/mol-theme/color/model-index.ts +++ b/src/mol-theme/color/model-index.ts @@ -1,6 +1,7 @@ /** - * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * + * @author Jason Pattle <jpattle@exscientia.co.uk> * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -14,14 +15,14 @@ import { getPaletteParams, getPalette } from '../../mol-util/color/palette'; import { TableLegend, ScaleLegend } from '../../mol-util/legend'; const DefaultColor = Color(0xCCCCCC); -const Description = 'Gives every model a unique color based on the position (index) of the model in the list of models in the structure.'; +const Description = 'Gives every model a unique color based on its index.'; export const ModelIndexColorThemeParams = { - ...getPaletteParams({ type: 'colors', colorList: 'purples' }), + ...getPaletteParams({ type: 'colors', colorList: 'many-distinct' }), }; export type ModelIndexColorThemeParams = typeof ModelIndexColorThemeParams export function getModelIndexColorThemeParams(ctx: ThemeDataContext) { - return ModelIndexColorThemeParams; // TODO return copy + return PD.clone(ModelIndexColorThemeParams); } export function ModelIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<ModelIndexColorThemeParams>): ColorTheme<ModelIndexColorThemeParams> { @@ -29,24 +30,17 @@ export function ModelIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<Mod let legend: ScaleLegend | TableLegend | undefined; if (ctx.structure) { - const { models } = ctx.structure.root; - - let size = 0; - for (const m of models) size = Math.max(size, Model.TrajectoryInfo.get(m)?.size || 0); + // max-index is the same for all models + const size = (Model.MaxIndex.get(ctx.structure.models[0]).value ?? -1) + 1; const palette = getPalette(size, props); legend = palette.legend; - const modelColor = new Map<number, Color>(); - for (let i = 0, il = models.length; i < il; ++i) { - const idx = Model.TrajectoryInfo.get(models[i])?.index || 0; - modelColor.set(idx, palette.color(idx)); - } color = (location: Location): Color => { if (StructureElement.Location.is(location)) { - return modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!; + return palette.color(Model.Index.get(location.unit.model).value || 0)!; } else if (Bond.isLocation(location)) { - return modelColor.get(Model.TrajectoryInfo.get(location.aUnit.model).index)!; + return palette.color(Model.Index.get(location.aUnit.model).value || 0)!; } return DefaultColor; }; @@ -71,5 +65,5 @@ export const ModelIndexColorThemeProvider: ColorTheme.Provider<ModelIndexColorTh factory: ModelIndexColorTheme, getParams: getModelIndexColorThemeParams, defaultValues: PD.getDefaultValues(ModelIndexColorThemeParams), - isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.elementCount > 0 && Model.TrajectoryInfo.get(ctx.structure.models[0]).size > 1 + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.elementCount > 0 }; \ No newline at end of file diff --git a/src/mol-theme/color/structure-index.ts b/src/mol-theme/color/structure-index.ts new file mode 100644 index 0000000000000000000000000000000000000000..28dda92802ae4db29fd67dc7f807fc3ec1988583 --- /dev/null +++ b/src/mol-theme/color/structure-index.ts @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Color } from '../../mol-util/color'; +import { Location } from '../../mol-model/location'; +import { StructureElement, Bond, Structure } from '../../mol-model/structure'; +import { ColorTheme, LocationColor } from '../color'; +import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { ThemeDataContext } from '../../mol-theme/theme'; +import { getPaletteParams, getPalette } from '../../mol-util/color/palette'; +import { TableLegend, ScaleLegend } from '../../mol-util/legend'; + +const DefaultColor = Color(0xCCCCCC); +const Description = 'Gives every structure a unique color based on its index.'; + +export const StructureIndexColorThemeParams = { + ...getPaletteParams({ type: 'colors', colorList: 'many-distinct' }), +}; +export type StructureIndexColorThemeParams = typeof StructureIndexColorThemeParams +export function getStructureIndexColorThemeParams(ctx: ThemeDataContext) { + return PD.clone(StructureIndexColorThemeParams); +} + +export function StructureIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<StructureIndexColorThemeParams>): ColorTheme<StructureIndexColorThemeParams> { + let color: LocationColor; + let legend: ScaleLegend | TableLegend | undefined; + + if (ctx.structure) { + const size = (Structure.MaxIndex.get(ctx.structure).value ?? -1) + 1; + + const palette = getPalette(size, props); + legend = palette.legend; + + color = (location: Location): Color => { + if (StructureElement.Location.is(location)) { + return palette.color(Structure.Index.get(location.structure).value || 0)!; + } else if (Bond.isLocation(location)) { + return palette.color(Structure.Index.get(location.aStructure).value || 0)!; + } + return DefaultColor; + }; + } else { + color = () => DefaultColor; + } + + return { + factory: StructureIndexColorTheme, + granularity: 'instance', + color, + props, + description: Description, + legend + }; +} + +export const StructureIndexColorThemeProvider: ColorTheme.Provider<StructureIndexColorThemeParams, 'structure-index'> = { + name: 'structure-index', + label: 'Structure Index', + category: ColorTheme.Category.Chain, + factory: StructureIndexColorTheme, + getParams: getStructureIndexColorThemeParams, + defaultValues: PD.getDefaultValues(StructureIndexColorThemeParams), + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.elementCount > 0 +}; \ No newline at end of file diff --git a/src/mol-theme/color/trajectory-index.ts b/src/mol-theme/color/trajectory-index.ts new file mode 100644 index 0000000000000000000000000000000000000000..87b1b995518805448bdffc7baab9d46447c3ef26 --- /dev/null +++ b/src/mol-theme/color/trajectory-index.ts @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Color } from '../../mol-util/color'; +import { Location } from '../../mol-model/location'; +import { StructureElement, Bond, Model } from '../../mol-model/structure'; +import { ColorTheme, LocationColor } from '../color'; +import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { ThemeDataContext } from '../theme'; +import { getPaletteParams, getPalette } from '../../mol-util/color/palette'; +import { TableLegend, ScaleLegend } from '../../mol-util/legend'; + +const DefaultColor = Color(0xCCCCCC); +const Description = 'Gives every model (frame) a unique color based on the index in its trajectory.'; + +export const TrajectoryIndexColorThemeParams = { + ...getPaletteParams({ type: 'colors', colorList: 'purples' }), +}; +export type TrajectoryIndexColorThemeParams = typeof TrajectoryIndexColorThemeParams +export function getTrajectoryIndexColorThemeParams(ctx: ThemeDataContext) { + return PD.clone(TrajectoryIndexColorThemeParams); +} + +export function TrajectoryIndexColorTheme(ctx: ThemeDataContext, props: PD.Values<TrajectoryIndexColorThemeParams>): ColorTheme<TrajectoryIndexColorThemeParams> { + let color: LocationColor; + let legend: ScaleLegend | TableLegend | undefined; + + if (ctx.structure) { + const { models } = ctx.structure.root; + + let size = 0; + for (const m of models) size = Math.max(size, Model.TrajectoryInfo.get(m)?.size || 0); + + const palette = getPalette(size, props); + legend = palette.legend; + const modelColor = new Map<number, Color>(); + for (let i = 0, il = models.length; i < il; ++i) { + const idx = Model.TrajectoryInfo.get(models[i])?.index || 0; + modelColor.set(idx, palette.color(idx)); + } + + color = (location: Location): Color => { + if (StructureElement.Location.is(location)) { + return modelColor.get(Model.TrajectoryInfo.get(location.unit.model).index)!; + } else if (Bond.isLocation(location)) { + return modelColor.get(Model.TrajectoryInfo.get(location.aUnit.model).index)!; + } + return DefaultColor; + }; + } else { + color = () => DefaultColor; + } + + return { + factory: TrajectoryIndexColorTheme, + granularity: 'instance', + color, + props, + description: Description, + legend + }; +} + +export const TrajectoryIndexColorThemeProvider: ColorTheme.Provider<TrajectoryIndexColorThemeParams, 'trajectory-index'> = { + name: 'trajectory-index', + label: 'Trajectory Index', + category: ColorTheme.Category.Chain, + factory: TrajectoryIndexColorTheme, + getParams: getTrajectoryIndexColorThemeParams, + defaultValues: PD.getDefaultValues(TrajectoryIndexColorThemeParams), + isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.elementCount > 0 && Model.TrajectoryInfo.get(ctx.structure.models[0]).size > 1 +}; \ No newline at end of file diff --git a/src/mol-util/_spec/fibonacci-heap.spec.ts b/src/mol-util/_spec/fibonacci-heap.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..4016e8faf9655ff72f6d95f01174fa0798cd55be --- /dev/null +++ b/src/mol-util/_spec/fibonacci-heap.spec.ts @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Gianluca Tomasello <giagitom@gmail.com> + */ + +import { FibonacciHeap } from '../fibonacci-heap'; + +describe('fibonacci-heap', () => { + it('basic', () => { + const heap = new FibonacciHeap(); + heap.insert(1, 2); + heap.insert(4); + heap.insert(2); + heap.insert(3); + expect(heap.size()).toBe(4); + const node = heap.extractMinimum(); + expect(node!.key).toBe(1); + expect(node!.value).toBe(2); + expect(heap.size()).toBe(3); + }); +}); diff --git a/src/mol-util/binding.ts b/src/mol-util/binding.ts index 3ca2e97da24ad2e4f94087fb2aef71ab9b8c5d80..252de33a3bdd563565fe216d7e704486c9740d51 100644 --- a/src/mol-util/binding.ts +++ b/src/mol-util/binding.ts @@ -24,6 +24,10 @@ namespace Binding { return { triggers, action, description }; } + export function isBinding(x: any): x is Binding { + return !!x && Array.isArray(x.triggers) && typeof x.action === 'string'; + } + export const Empty: Binding = { triggers: [], action: '', description: '' }; export function isEmpty(binding: Binding) { return binding.triggers.length === 0 || diff --git a/src/mol-util/color/distinct.ts b/src/mol-util/color/distinct.ts index c12a8c9e26b873171ba1c36b0e2c71c4eee62abd..e2e3c1507513798dab052a6e9d23a56828973d60 100644 --- a/src/mol-util/color/distinct.ts +++ b/src/mol-util/color/distinct.ts @@ -13,6 +13,7 @@ import { deepClone } from '../../mol-util/object'; import { deepEqual } from '../../mol-util'; import { arraySum } from '../../mol-util/array'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { ColorNames } from './names'; export const DistinctColorsParams = { hue: PD.Interval([1, 360], { min: 0, max: 360, step: 1 }), @@ -105,7 +106,8 @@ export function distinctColors(count: number, props: Partial<DistinctColorsProps const samples = getSamples(Math.max(p.minSampleCount, count * 5), p); if (samples.length < count) { - throw new Error('Not enough samples to generate distinct colors, increase sample count.'); + console.warn('Not enough samples to generate distinct colors, increase sample count.'); + return (new Array(count)).fill(ColorNames.lightgrey); } const colors: Lab[] = []; diff --git a/src/mol-util/fibonacci-heap.ts b/src/mol-util/fibonacci-heap.ts new file mode 100644 index 0000000000000000000000000000000000000000..5602bd4b837117db57bed611d2ae282fa2e8fb33 --- /dev/null +++ b/src/mol-util/fibonacci-heap.ts @@ -0,0 +1,407 @@ +/** + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Gianluca Tomasello <giagitom@gmail.com> + * + * Adapted from https://github.com/gwtw/ts-fibonacci-heap, Copyright (c) 2014 Daniel Imms, MIT + */ + +interface INode<K, V> { + key: K; + value?: V; +} + +type CompareFunction<K, V> = (a: INode<K, V>, b: INode<K, V>) => number; + +class Node<K, V> implements INode<K, V> { + public key: K; + public value: V | undefined; + public prev: Node<K, V>; + public next: Node<K, V>; + public parent: Node<K, V> | null = null; + public child: Node<K, V> | null = null; + + public degree: number = 0; + public isMarked: boolean = false; + + constructor(key: K, value?: V) { + this.key = key; + this.value = value; + this.prev = this; + this.next = this; + } +} + +class NodeListIterator<K, V> { + private _index: number; + private _items: Node<K, V>[]; + private _len: number; + /** + * Creates an Iterator used to simplify the consolidate() method. It works by + * making a shallow copy of the nodes in the root list and iterating over the + * shallow copy instead of the source as the source will be modified. + * @param start A node from the root list. + */ + constructor(start?: Node<K, V>) { + this._index = -1; + this._items = []; + this._len = 0; + if (start) { + let current = start, l = 0; + do { + this._items[l++] = current; + current = current.next; + } while (start !== current); + this._len = l; + } + } + + /** + * @return Whether there is a next node in the iterator. + */ + public hasNext(): boolean { + return this._index < this._len - 1; + } + + /** + * @return The next node. + */ + public next(): Node<K, V> { + return this._items[++this._index]; + } + + /** + * @return Resets iterator to reuse it. + */ + public reset(start: Node<K, V>) { + this._index = -1; + this._len = 0; + let current = start, l = 0; + do { + this._items[l++] = current; + current = current.next; + } while (start !== current); + this._len = l; + } +} + +const tmpIt = new NodeListIterator<any, any>(); +/** + * A Fibonacci heap data structure with a key and optional value. +*/ +export class FibonacciHeap<K, V> { + private _minNode: Node<K, V> | null = null; + private _nodeCount: number = 0; + private _compare: CompareFunction<K, V>; + + constructor( + compare?: CompareFunction<K, V> + ) { + this._compare = compare ? compare : this._defaultCompare; + } + + /** + * Clears the heap's data, making it an empty heap. + */ + public clear(): void { + this._minNode = null; + this._nodeCount = 0; + } + + /** + * Decreases a key of a node. + * @param node The node to decrease the key of. + * @param newKey The new key to assign to the node. + */ + public decreaseKey(node: Node<K, V>, newKey: K): void { + if (!node) { + throw new Error('Cannot decrease key of non-existent node'); + } + if (this._compare({ key: newKey }, { key: node.key }) > 0) { + throw new Error('New key is larger than old key'); + } + + node.key = newKey; + const parent = node.parent; + if (parent && this._compare(node, parent) < 0) { + this._cut(node, parent, <Node<K, V>> this._minNode); + this._cascadingCut(parent, <Node<K, V>> this._minNode); + } + if (this._compare(node, <Node<K, V>> this._minNode) < 0) { + this._minNode = node; + } + } + + /** + * Deletes a node. + * @param node The node to delete. + */ + public delete(node: Node<K, V>): void { + // This is a special implementation of decreaseKey that sets the argument to + // the minimum value. This is necessary to make generic keys work, since there + // is no MIN_VALUE constant for generic types. + const parent = node.parent; + if (parent) { + this._cut(node, parent, <Node<K, V>> this._minNode); + this._cascadingCut(parent, <Node<K, V>> this._minNode); + } + this._minNode = node; + + this.extractMinimum(); + } + + /** + * Extracts and returns the minimum node from the heap. + * @return The heap's minimum node or null if the heap is empty. + */ + public extractMinimum(): Node<K, V> | null { + const extractedMin = this._minNode; + if (extractedMin) { + // Set parent to null for the minimum's children + if (extractedMin.child) { + let child = extractedMin.child; + do { + child.parent = null; + child = child.next; + } while (child !== extractedMin.child); + } + + let nextInRootList = null; + if (extractedMin.next !== extractedMin) { + nextInRootList = extractedMin.next; + } + // Remove min from root list + this._removeNodeFromList(extractedMin); + this._nodeCount--; + + // Merge the children of the minimum node with the root list + this._minNode = this._mergeLists(nextInRootList, extractedMin.child); + if (this._minNode) { + this._minNode = this._consolidate(this._minNode); + } + } + return extractedMin; + } + + /** + * Returns the minimum node from the heap. + * @return The heap's minimum node or null if the heap is empty. + */ + public findMinimum(): Node<K, V> | null { + return this._minNode; + } + + /** + * Inserts a new key-value pair into the heap. + * @param key The key to insert. + * @param value The value to insert. + * @return node The inserted node. + */ + public insert(key: K, value?: V): Node<K, V> { + const node = new Node(key, value); + this._minNode = this._mergeLists(this._minNode, node); + this._nodeCount++; + return node; + } + + /** + * @return Whether the heap is empty. + */ + public isEmpty(): boolean { + return this._minNode === null; + } + + /** + * @return The size of the heap. + */ + public size(): number { + if (this._minNode === null) { + return 0; + } + return this._getNodeListSize(this._minNode); + } + + /** + * Joins another heap to this heap. + * @param other The other heap. + */ + public union(other: FibonacciHeap<K, V>): void { + this._minNode = this._mergeLists(this._minNode, other._minNode); + this._nodeCount += other._nodeCount; + } + + /** + * Compares two nodes with each other. + * @param a The first key to compare. + * @param b The second key to compare. + * @return -1, 0 or 1 if a < b, a == b or a > b respectively. + */ + private _defaultCompare(a: INode<K, V>, b: INode<K, V>): number { + if (a.key > b.key) { + return 1; + } + if (a.key < b.key) { + return -1; + } + return 0; + } + + /** + * Cut the link between a node and its parent, moving the node to the root list. + * @param node The node being cut. + * @param parent The parent of the node being cut. + * @param minNode The minimum node in the root list. + * @return The heap's new minimum node. + */ + private _cut(node: Node<K, V>, parent: Node<K, V>, minNode: Node<K, V>): Node<K, V> | null { + node.parent = null; + parent.degree--; + if (node.next === node) { + parent.child = null; + } else { + parent.child = node.next; + } + this._removeNodeFromList(node); + const newMinNode = this._mergeLists(minNode, node); + node.isMarked = false; + return newMinNode; + } + + /** + * Perform a cascading cut on a node; mark the node if it is not marked, + * otherwise cut the node and perform a cascading cut on its parent. + * @param node The node being considered to be cut. + * @param minNode The minimum node in the root list. + * @return The heap's new minimum node. + */ + private _cascadingCut(node: Node<K, V>, minNode: Node<K, V> | null): Node<K, V> | null { + const parent = node.parent; + if (parent) { + if (node.isMarked) { + minNode = this._cut(node, parent, <Node<K, V>>minNode); + minNode = this._cascadingCut(parent, minNode); + } else { + node.isMarked = true; + } + } + return minNode; + } + + /** + * Merge all trees of the same order together until there are no two trees of + * the same order. + * @param minNode The current minimum node. + * @return The new minimum node. + */ + private _consolidate(minNode: Node<K, V>): Node<K, V> | null { + + const aux = []; + tmpIt.reset(minNode); + while (tmpIt.hasNext()) { + let current = tmpIt.next(); + + // If there exists another node with the same degree, merge them + let auxCurrent = aux[current.degree]; + while (auxCurrent) { + if (this._compare(current, auxCurrent) > 0) { + const temp = current; + current = auxCurrent; + auxCurrent = temp; + } + this._linkHeaps(auxCurrent, current); + aux[current.degree] = null; + current.degree++; + auxCurrent = aux[current.degree]; + } + + aux[current.degree] = current; + } + + let newMinNode = null; + for (let i = 0; i < aux.length; i++) { + const node = aux[i]; + if (node) { + // Remove siblings before merging + node.next = node; + node.prev = node; + newMinNode = this._mergeLists(newMinNode, node); + } + } + return newMinNode; + } + + /** + * Removes a node from a node list. + * @param node The node to remove. + */ + private _removeNodeFromList(node: Node<K, V>): void { + const prev = node.prev; + const next = node.next; + prev.next = next; + next.prev = prev; + node.next = node; + node.prev = node; + } + + /** + * Links two heaps of the same order together. + * + * @private + * @param max The heap with the larger root. + * @param min The heap with the smaller root. + */ + private _linkHeaps(max: Node<K, V>, min: Node<K, V>): void { + this._removeNodeFromList(max); + min.child = this._mergeLists(max, min.child); + max.parent = min; + max.isMarked = false; + } + + /** + * Merge two lists of nodes together. + * + * @private + * @param a The first list to merge. + * @param b The second list to merge. + * @return The new minimum node from the two lists. + */ + private _mergeLists(a: Node<K, V> | null, b: Node<K, V> | null): Node<K, V> | null { + if (!a) { + if (!b) { + return null; + } + return b; + } + if (!b) { + return a; + } + + const temp = a.next; + a.next = b.next; + a.next.prev = a; + b.next = temp; + b.next.prev = b; + + return this._compare(a, b) < 0 ? a : b; + } + + /** + * Gets the size of a node list. + * @param node A node within the node list. + * @return The size of the node list. + */ + private _getNodeListSize(node: Node<K, V>): number { + let count = 0; + let current = node; + + do { + count++; + if (current.child) { + count += this._getNodeListSize(current.child); + } + current = current.next; + } while (current !== node); + + return count; + } +} diff --git a/src/mol-util/index.ts b/src/mol-util/index.ts index 4db05201cd2b0631b5534f3be008456b440a42bf..ba4d557c3552354033770c9b3fe0cebe29fc33fa 100644 --- a/src/mol-util/index.ts +++ b/src/mol-util/index.ts @@ -117,7 +117,7 @@ export function defaults<T>(value: T | undefined, defaultValue: T): T { return value !== undefined ? value : defaultValue; } -export function extend<S, T, U>(object: S, source: T, guard?: U): S & T & U { +export function extend<S extends {}, T extends {}, U extends {}>(object: S, source: T, guard?: U): S & T & U { let v: any; const s = <any>source; @@ -139,7 +139,7 @@ export function extend<S, T, U>(object: S, source: T, guard?: U): S & T & U { return <any>object; } -export function shallowClone<T>(o: T): T { +export function shallowClone<T extends {}>(o: T): T { return extend({}, o) as T; } @@ -158,7 +158,7 @@ function _assign<T>(target: T): T { export declare function _assignType<T>(o: T, ...from: any[]): T; export const assign: (<T>(o: T, ...from: any[]) => T) = (Object as any).assign || _assign; -function _shallowMerge1<T>(source: T, update: T) { +function _shallowMerge1<T extends {}>(source: T, update: T) { let changed = false; for (const k of Object.keys(update)) { if (!hasOwnProperty.call(update, k)) continue; diff --git a/src/mol-util/input/input-observer.ts b/src/mol-util/input/input-observer.ts index 9326d9f077e07a94f2444f0e90aebdb92f73dc0a..56154c53140305edf15cfe35d05c55ec492b2930 100644 --- a/src/mol-util/input/input-observer.ts +++ b/src/mol-util/input/input-observer.ts @@ -304,6 +304,7 @@ namespace InputObserver { let buttons = ButtonsType.create(ButtonsType.Flag.None); let button = ButtonsType.Flag.None; let isInside = false; + let hasMoved = false; const events = createEvents(); const { drag, interactionEnd, wheel, pinch, gesture, click, move, leave, enter, resize, modifiers, key } = events; @@ -577,12 +578,13 @@ namespace InputObserver { if (!mask(ev.clientX, ev.clientY)) return; eventOffset(pointerEnd, ev); - if (Vec2.distance(pointerEnd, pointerDown) < 4) { + if (!hasMoved && Vec2.distance(pointerEnd, pointerDown) < 4) { const { pageX, pageY } = ev; const [x, y] = pointerEnd; click.next({ x, y, pageX, pageY, buttons, button, modifiers: getModifierKeys() }); } + hasMoved = false; } function onPointerMove(ev: PointerEvent) { @@ -604,6 +606,10 @@ namespace InputObserver { const isStart = dragging === DraggingState.Started; if (isStart && !mask(ev.clientX, ev.clientY)) return; + if (Vec2.distance(pointerEnd, pointerDown) >= 4) { + hasMoved = true; + } + const [dx, dy] = pointerDelta; drag.next({ x, y, dx, dy, pageX, pageY, buttons, button, modifiers: getModifierKeys(), isStart }); diff --git a/src/mol-util/monadic-parser.ts b/src/mol-util/monadic-parser.ts index 0bf8004d303558b95bc2c707cf75e7c3b66763d0..ad96df44cd41b194f46ddba1993e30359ffea7d6 100644 --- a/src/mol-util/monadic-parser.ts +++ b/src/mol-util/monadic-parser.ts @@ -2,12 +2,11 @@ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> - */ - -/** + * @author Koya Sakuma <koya.sakuma.work@gmail.com> + ** * Adapted from Parsimmon (https://github.com/jneen/parsimmon) * Copyright (c) 2011-present J. Adkisson (http://jneen.net). - */ + **/ export class MonadicParser<A> { constructor(public _: MonadicParser.Action<A>) { } @@ -234,15 +233,27 @@ export namespace MonadicParser { export type Result<T> = Success<T> | Failure - // export function createLanguage(parsers: any) { - // const language: any = {}; - // for (const key of Object.keys(parsers)) { - // (function (key) { - // language[key] = lazy(() => parsers[key](language)); - // })(key); - // } - // return language; - // } + export function seqMap<A, B>(a: MonadicParser<A>, b: MonadicParser<B>, c: any) { + const args = [].slice.call(arguments); + if (args.length === 0) { + throw new Error('seqMap needs at least one argument'); + } + const mapper = args.pop(); + assertFunction(mapper); + return seq.apply(null, args).map(function (results: any) { + return mapper.apply(null, results); + }); + } + + export function createLanguage(parsers: any) { + const language: any = {}; + for (const key of Object.keys(parsers)) { + (function (key) { + language[key] = lazy(() => parsers[key](language)); + })(key); + } + return language; + } export function seq<A>(a: MonadicParser<A>): MonadicParser<[A]> export function seq<A, B>(a: MonadicParser<A>, b: MonadicParser<B>): MonadicParser<[A, B]> @@ -326,7 +337,7 @@ export namespace MonadicParser { export function regexp(re: RegExp, group = 0) { const anchored = anchoredRegexp(re); const expected = '' + re; - return new MonadicParser(function (input, i) { + return new MonadicParser((input, i) => { const match = anchored.exec(input.slice(i)); if (match) { if (0 <= group && group <= match.length) { @@ -455,6 +466,15 @@ export namespace MonadicParser { export const crlf = string('\r\n'); export const newline = alt(crlf, lf, cr).desc('newline'); export const end = alt(newline, eof); + + export function of<A>(value: A) { + return succeed(value); + } + + export function regex(re: RegExp) { + return regexp(re); + } + } function seqPick(idx: number, ...parsers: MonadicParser<any>[]): MonadicParser<any> { @@ -550,4 +570,10 @@ function unsafeUnion(xs: string[], ys: string[]) { function isParser(obj: any): obj is MonadicParser<any> { return obj instanceof MonadicParser; -} \ No newline at end of file +} + +function assertFunction(x: any) { + if (typeof x !== 'function') { + throw new Error('not a function: ' + x); + } +} diff --git a/src/mol-util/object.ts b/src/mol-util/object.ts index d11c4bb9ef85675352f05aca5cdef5c26579021e..1cdbe8e9ec666d80b93bfb8279d582aeac819e63 100644 --- a/src/mol-util/object.ts +++ b/src/mol-util/object.ts @@ -8,7 +8,7 @@ const hasOwnProperty = Object.prototype.hasOwnProperty; /** Assign to the object if a given property in update is undefined */ -export function assignIfUndefined<T>(to: Partial<T>, full: T): T { +export function assignIfUndefined<T extends {}>(to: Partial<T>, full: T): T { for (const k of Object.keys(full)) { if (!hasOwnProperty.call(full, k)) continue; @@ -20,7 +20,7 @@ export function assignIfUndefined<T>(to: Partial<T>, full: T): T { } /** Create new object if any property in "update" changes in "source". */ -export function shallowMerge2<T>(source: T, update: Partial<T>): T { +export function shallowMerge2<T extends {}>(source: T, update: Partial<T>): T { // Adapted from LiteMol (https://github.com/dsehnal/LiteMol) let changed = false; for (const k of Object.keys(update)) { @@ -36,7 +36,7 @@ export function shallowMerge2<T>(source: T, update: Partial<T>): T { return Object.assign({}, source, update); } -export function shallowEqual<T>(a: T, b: T) { +export function shallowEqual<T extends {}>(a: T, b: T) { if (!a) { if (!b) return true; return false; @@ -52,11 +52,11 @@ export function shallowEqual<T>(a: T, b: T) { return true; } -export function shallowMerge<T>(source: T, ...rest: (Partial<T> | undefined)[]): T { +export function shallowMerge<T extends {}>(source: T, ...rest: (Partial<T> | undefined)[]): T { return shallowMergeArray(source, rest); } -export function shallowMergeArray<T>(source: T, rest: (Partial<T> | undefined)[]): T { +export function shallowMergeArray<T extends {}>(source: T, rest: (Partial<T> | undefined)[]): T { // Adapted from LiteMol (https://github.com/dsehnal/LiteMol) let ret: any = source; diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index 46840b54a6c17dcb13907257c6c9d726bb813594..a9697f5f454d63753f657bba63ff2ab32771eb3c 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -324,7 +324,7 @@ export namespace ParamDefinition { conditionForValue(v: T): keyof C conditionedValue(v: T, condition: keyof C): T, } - export function Conditioned<T, P extends Base<T>, C = { [k: string]: P }>(defaultValue: T, conditionParams: C, conditionForValue: (v: T) => keyof C, conditionedValue: (v: T, condition: keyof C) => T, info?: Info): Conditioned<T, P, C> { + export function Conditioned<T, P extends Base<T>, C extends {} = { [k: string]: P }>(defaultValue: T, conditionParams: C, conditionForValue: (v: T) => keyof C, conditionedValue: (v: T, condition: keyof C) => T, info?: Info): Conditioned<T, P, C> { const options = Object.keys(conditionParams).map(k => [k, k]) as [string, string][]; return setInfo({ type: 'conditioned', select: Select<string>(conditionForValue(defaultValue) as string, options, info), defaultValue, conditionParams, conditionForValue, conditionedValue }, info); } diff --git a/src/mol-util/single-async-queue.ts b/src/mol-util/single-async-queue.ts new file mode 100644 index 0000000000000000000000000000000000000000..be62698d1af38e9c140324e9784b21530f051749 --- /dev/null +++ b/src/mol-util/single-async-queue.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Adam Midlik <midlik@gmail.com> + */ + + +/** Job queue that allows at most one running and one pending job. + * A newly enqueued job will cancel any other pending jobs. */ +export class SingleAsyncQueue { + private isRunning: boolean; + private queue: { id: number, func: () => any }[]; + private counter: number; + private log: boolean; + constructor(log: boolean = false) { + this.isRunning = false; + this.queue = []; + this.counter = 0; + this.log = log; + } + enqueue(job: () => any) { + if (this.log) console.log('SingleAsyncQueue enqueue', this.counter); + this.queue[0] = { id: this.counter, func: job }; + this.counter++; + this.run(); // do not await + } + private async run() { + if (this.isRunning) return; + const job = this.queue.pop(); + if (!job) return; + this.isRunning = true; + try { + if (this.log) console.log('SingleAsyncQueue run', job.id); + await job.func(); + if (this.log) console.log('SingleAsyncQueue complete', job.id); + } finally { + this.isRunning = false; + this.run(); + } + } +} diff --git a/src/tests/browser/marching-cubes.ts b/src/tests/browser/marching-cubes.ts index 8c501aa700eaf6213f255cb5cb71cf5780f1c49d..b9ef65cb60e415ff238cd5fe8724cebc13df73b5 100644 --- a/src/tests/browser/marching-cubes.ts +++ b/src/tests/browser/marching-cubes.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -22,6 +22,7 @@ import { Representation } from '../../mol-repr/representation'; import { computeMarchingCubesMesh } from '../../mol-geo/util/marching-cubes/algorithm'; import { Mesh } from '../../mol-geo/geometry/mesh/mesh'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; +import { AssetManager } from '../../mol-util/assets'; const parent = document.getElementById('app')!; parent.style.width = '100%'; @@ -31,7 +32,9 @@ const canvas = document.createElement('canvas'); parent.appendChild(canvas); resizeCanvas(canvas, parent); -const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas), PD.merge(Canvas3DParams, PD.getDefaultValues(Canvas3DParams), { +const assetManager = new AssetManager(); + +const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas, assetManager), PD.merge(Canvas3DParams, PD.getDefaultValues(Canvas3DParams), { renderer: { backgroundColor: ColorNames.white }, camera: { mode: 'orthographic' } })); @@ -73,7 +76,7 @@ async function init() { console.timeEnd('gpu mc pyramid2'); console.time('gpu mc vert2'); - createIsosurfaceBuffers(webgl, activeVoxelsTex2, densityTextureData2.texture, compacted2, densityTextureData2.gridDim, densityTextureData2.gridTexDim, densityTextureData2.transform, isoValue, false, true, Vec3.create(0, 1, 2)); + createIsosurfaceBuffers(webgl, activeVoxelsTex2, densityTextureData2.texture, compacted2, densityTextureData2.gridDim, densityTextureData2.gridTexDim, densityTextureData2.transform, isoValue, false, true, Vec3.create(0, 1, 2), true); webgl.waitForGpuCommandsCompleteSync(); console.timeEnd('gpu mc vert2'); console.timeEnd('gpu mc2'); @@ -96,7 +99,7 @@ async function init() { console.timeEnd('gpu mc pyramid'); console.time('gpu mc vert'); - const gv = createIsosurfaceBuffers(webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoValue, false, true, Vec3.create(0, 1, 2)); + const gv = createIsosurfaceBuffers(webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoValue, false, true, Vec3.create(0, 1, 2), true); webgl.waitForGpuCommandsCompleteSync(); console.timeEnd('gpu mc vert'); console.timeEnd('gpu mc'); diff --git a/src/tests/browser/render-lines.ts b/src/tests/browser/render-lines.ts index d4996f777d1ffcb2865e951c41935c296d7aa03d..137ca2a69f9d71023fd9d3f5d4ba3af469b6bcf6 100644 --- a/src/tests/browser/render-lines.ts +++ b/src/tests/browser/render-lines.ts @@ -15,6 +15,7 @@ import { Color } from '../../mol-util/color'; import { createRenderObject } from '../../mol-gl/render-object'; import { Representation } from '../../mol-repr/representation'; import { ParamDefinition } from '../../mol-util/param-definition'; +import { AssetManager } from '../../mol-util/assets'; const parent = document.getElementById('app')!; parent.style.width = '100%'; @@ -24,7 +25,9 @@ const canvas = document.createElement('canvas'); parent.appendChild(canvas); resizeCanvas(canvas, parent); -const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas)); +const assetManager = new AssetManager(); + +const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas, assetManager)); canvas3d.animate(); function linesRepr() { diff --git a/src/tests/browser/render-mesh.ts b/src/tests/browser/render-mesh.ts index 12861bad246a92f054407259eef86686d45cfbde..e639b931e7dc826a5f7ab86808ed74cc8da1c8ce 100644 --- a/src/tests/browser/render-mesh.ts +++ b/src/tests/browser/render-mesh.ts @@ -17,6 +17,7 @@ import { createRenderObject } from '../../mol-gl/render-object'; import { Representation } from '../../mol-repr/representation'; import { Torus } from '../../mol-geo/primitive/torus'; import { ParamDefinition } from '../../mol-util/param-definition'; +import { AssetManager } from '../../mol-util/assets'; const parent = document.getElementById('app')!; parent.style.width = '100%'; @@ -26,7 +27,9 @@ const canvas = document.createElement('canvas'); parent.appendChild(canvas); resizeCanvas(canvas, parent); -const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas)); +const assetManager = new AssetManager(); + +const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas, assetManager)); canvas3d.animate(); function meshRepr() { diff --git a/src/tests/browser/render-shape.ts b/src/tests/browser/render-shape.ts index cf83c1c9a4f8518ecf84416e804b07e594ee5552..184d273939372d42893fa6086f06b32792266cd0 100644 --- a/src/tests/browser/render-shape.ts +++ b/src/tests/browser/render-shape.ts @@ -19,6 +19,7 @@ import { Sphere } from '../../mol-geo/primitive/sphere'; import { ColorNames } from '../../mol-util/color/names'; import { Shape } from '../../mol-model/shape'; import { ShapeRepresentation } from '../../mol-repr/shape/representation'; +import { AssetManager } from '../../mol-util/assets'; const parent = document.getElementById('app')!; parent.style.width = '100%'; @@ -28,6 +29,8 @@ const canvas = document.createElement('canvas'); parent.appendChild(canvas); resizeCanvas(canvas, parent); +const assetManager = new AssetManager(); + const info = document.createElement('div'); info.style.position = 'absolute'; info.style.fontFamily = 'sans-serif'; @@ -38,7 +41,7 @@ info.style.color = 'white'; parent.appendChild(info); let prevReprLoci = Representation.Loci.Empty; -const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas)); +const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas, assetManager)); canvas3d.animate(); canvas3d.input.move.subscribe(({ x, y }) => { const pickingId = canvas3d.identify(x, y)?.id; diff --git a/src/tests/browser/render-spheres.ts b/src/tests/browser/render-spheres.ts index 439429fe35d6efec99e0ec225ab266b48e98b8a0..ed4e92ae278411bd11b155dbb3cc23fd38fd6b67 100644 --- a/src/tests/browser/render-spheres.ts +++ b/src/tests/browser/render-spheres.ts @@ -13,6 +13,7 @@ import { Color } from '../../mol-util/color'; import { createRenderObject } from '../../mol-gl/render-object'; import { Representation } from '../../mol-repr/representation'; import { ParamDefinition } from '../../mol-util/param-definition'; +import { AssetManager } from '../../mol-util/assets'; const parent = document.getElementById('app')!; parent.style.width = '100%'; @@ -22,7 +23,9 @@ const canvas = document.createElement('canvas'); parent.appendChild(canvas); resizeCanvas(canvas, parent); -const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas)); +const assetManager = new AssetManager(); + +const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas, assetManager)); canvas3d.animate(); function spheresRepr() { diff --git a/src/tests/browser/render-structure.ts b/src/tests/browser/render-structure.ts index 07e23ee4918d7a8698ceb01cc843ec62a4ceb614..634ccd8adeb7366d499ef77d0afffc6444961317 100644 --- a/src/tests/browser/render-structure.ts +++ b/src/tests/browser/render-structure.ts @@ -37,7 +37,9 @@ const canvas = document.createElement('canvas'); parent.appendChild(canvas); resizeCanvas(canvas, parent); -const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas)); +const assetManager = new AssetManager(); + +const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas, assetManager)); canvas3d.animate(); const info = document.createElement('div'); @@ -123,7 +125,7 @@ function getMembraneOrientationRepr() { } async function init() { - const ctx = { runtime: SyncRuntimeContext, assetManager: new AssetManager() }; + const ctx = { runtime: SyncRuntimeContext, assetManager }; const cif = await downloadFromPdb('3pqr'); const models = await getModels(cif); diff --git a/src/tests/browser/render-text.ts b/src/tests/browser/render-text.ts index c25a45fa195c9d1e3d8eaf1f0b32db0a0058bcbe..b1b0a33a09346f88270be7bf3138ed366b0e4ece 100644 --- a/src/tests/browser/render-text.ts +++ b/src/tests/browser/render-text.ts @@ -15,6 +15,7 @@ import { SpheresBuilder } from '../../mol-geo/geometry/spheres/spheres-builder'; import { createRenderObject } from '../../mol-gl/render-object'; import { Spheres } from '../../mol-geo/geometry/spheres/spheres'; import { resizeCanvas } from '../../mol-canvas3d/util'; +import { AssetManager } from '../../mol-util/assets'; const parent = document.getElementById('app')!; parent.style.width = '100%'; @@ -24,7 +25,9 @@ const canvas = document.createElement('canvas'); parent.appendChild(canvas); resizeCanvas(canvas, parent); -const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas)); +const assetManager = new AssetManager(); + +const canvas3d = Canvas3D.create(Canvas3DContext.fromCanvas(canvas, assetManager)); canvas3d.animate(); function textRepr() { diff --git a/webpack.config.common.js b/webpack.config.common.js index 491eb8d551226831574049bcd850af99c1b939f7..380e1cc3e871daf21e71553e7b0efe452aacd3cc 100644 --- a/webpack.config.common.js +++ b/webpack.config.common.js @@ -30,7 +30,11 @@ const sharedConfig = { { loader: 'css-loader', options: { sourceMap: false } }, { loader: 'sass-loader', options: { sourceMap: false } }, ] - } + }, + { + test: /\.(jpg)$/i, + type: 'asset/resource', + }, ] }, plugins: [ @@ -76,7 +80,7 @@ function createEntry(src, outFolder, outFilename, isNode) { function createEntryPoint(name, dir, out, library) { return { entry: path.resolve(__dirname, `lib/${dir}/${name}.js`), - output: { filename: `${library || name}.js`, path: path.resolve(__dirname, `build/${out}`), library: library || out, libraryTarget: 'umd' }, + output: { filename: `${library || name}.js`, path: path.resolve(__dirname, `build/${out}`), library: library || out, libraryTarget: 'umd', assetModuleFilename: 'images/[hash][ext][query]', 'publicPath': '' }, ...sharedConfig }; }