mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 22:31:26 +08:00
Compare commits
198 Commits
v0.6.2
...
v0.7.0-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d42c9a6e15 | ||
|
|
da4dabc3f5 | ||
|
|
92217905f8 | ||
|
|
598441a727 | ||
|
|
f707cb19a4 | ||
|
|
63fc408be6 | ||
|
|
69dedd8c22 | ||
|
|
5480805754 | ||
|
|
655ae65b8d | ||
|
|
1a23cb672e | ||
|
|
19e18b4089 | ||
|
|
3d909d5012 | ||
|
|
ad521948b6 | ||
|
|
2f3b6a28c1 | ||
|
|
c779da674c | ||
|
|
d9b140f9f2 | ||
|
|
052648023e | ||
|
|
f5d12d440e | ||
|
|
99d7a90863 | ||
|
|
901d5c86e6 | ||
|
|
df9efd05e6 | ||
|
|
26b8adaec4 | ||
|
|
fc6f5a0336 | ||
|
|
f823a887b7 | ||
|
|
b346d4d85d | ||
|
|
70bd035898 | ||
|
|
7e5cdd8e06 | ||
|
|
a21dac60e0 | ||
|
|
9bd2e0d96e | ||
|
|
dd15a000e1 | ||
|
|
ebcfa44f22 | ||
|
|
43845adb71 | ||
|
|
9e3fff65a7 | ||
|
|
05c35a3a3a | ||
|
|
6f46965344 | ||
|
|
27ee576340 | ||
|
|
58492328df | ||
|
|
fd102bede1 | ||
|
|
3d8c47eefa | ||
|
|
c4e43228a2 | ||
|
|
6526090b8b | ||
|
|
094a018b5b | ||
|
|
a0a9c994b2 | ||
|
|
524ed90e3f | ||
|
|
7abba750b4 | ||
|
|
ecff1641d2 | ||
|
|
47ecf04386 | ||
|
|
c4697a9f45 | ||
|
|
53d5414492 | ||
|
|
311f5c09f5 | ||
|
|
be4439451f | ||
|
|
09c83e83ba | ||
|
|
bea4c64d85 | ||
|
|
0387dcd444 | ||
|
|
a89f21dafd | ||
|
|
7921c05d55 | ||
|
|
dafa946937 | ||
|
|
95673b0131 | ||
|
|
0118136869 | ||
|
|
573c2a7ad6 | ||
|
|
41977ea758 | ||
|
|
75ccded612 | ||
|
|
2429111a59 | ||
|
|
c475cb292e | ||
|
|
04d34b369a | ||
|
|
cfe4c6c559 | ||
|
|
4d13f99d22 | ||
|
|
62ebd4d8a9 | ||
|
|
026c25621f | ||
|
|
c096ae299d | ||
|
|
ae306d1761 | ||
|
|
4caae32933 | ||
|
|
6bbf20980e | ||
|
|
0fd00ecab8 | ||
|
|
70de73a95b | ||
|
|
578ad69c36 | ||
|
|
af263b8c88 | ||
|
|
bef6775de5 | ||
|
|
3c27450e82 | ||
|
|
9400f27f82 | ||
|
|
c176313f7b | ||
|
|
77461d5a23 | ||
|
|
a5c039bf11 | ||
|
|
e3c08f23bf | ||
|
|
67a26e93e4 | ||
|
|
a1d261b7c8 | ||
|
|
3826394940 | ||
|
|
791a54aeec | ||
|
|
48f1bb7755 | ||
|
|
53e028325f | ||
|
|
02eda0a1e2 | ||
|
|
92600160ff | ||
|
|
6ec67a7faf | ||
|
|
a4386744a2 | ||
|
|
09210279e1 | ||
|
|
e78cf18a38 | ||
|
|
9d1d7bbf72 | ||
|
|
60d5e85b4c | ||
|
|
2c263e216f | ||
|
|
e1f671a5b3 | ||
|
|
7ffaea48cb | ||
|
|
0e4527613c | ||
|
|
f363621de3 | ||
|
|
4901ec3b5d | ||
|
|
c592b8a53f | ||
|
|
325e4e44dc | ||
|
|
a81f7f6cfd | ||
|
|
31b83e9675 | ||
|
|
dc89f33cd0 | ||
|
|
f5f9237a78 | ||
|
|
4429ff0ac1 | ||
|
|
a0d38e6b10 | ||
|
|
c060664f84 | ||
|
|
10f7f15f70 | ||
|
|
d40ff29337 | ||
|
|
7ca666ae98 | ||
|
|
dcaea9d40b | ||
|
|
b878273d81 | ||
|
|
c15d29f2da | ||
|
|
3e3a13070a | ||
|
|
11bcd84e66 | ||
|
|
44b63e5953 | ||
|
|
fb0634a0f4 | ||
|
|
0b651db35b | ||
|
|
b24062b575 | ||
|
|
35c5c4cfc0 | ||
|
|
4b8bf57d1a | ||
|
|
a81f0d911a | ||
|
|
28e2227989 | ||
|
|
4f97b6836a | ||
|
|
5ab0dfaba8 | ||
|
|
66751fa006 | ||
|
|
f1d78e4805 | ||
|
|
715c78a04f | ||
|
|
0cd8b4ee8c | ||
|
|
c6efa475a5 | ||
|
|
e846e1fdd7 | ||
|
|
4221067f8f | ||
|
|
36951d6f19 | ||
|
|
ac45500e35 | ||
|
|
0f22eab8b9 | ||
|
|
5a8fd6d518 | ||
|
|
60ee04b9b9 | ||
|
|
d99b5bd505 | ||
|
|
6312c1f99b | ||
|
|
ef870a510f | ||
|
|
394ff05626 | ||
|
|
e90ccfdd20 | ||
|
|
92a86e324b | ||
|
|
e5d6816392 | ||
|
|
a1b2de16a3 | ||
|
|
f605021cfb | ||
|
|
619a2ccb3a | ||
|
|
0cc077c346 | ||
|
|
e34aad991b | ||
|
|
2a925cdd6a | ||
|
|
36c5e444ca | ||
|
|
c7f6041aa4 | ||
|
|
68401b3556 | ||
|
|
3843064eac | ||
|
|
d0de9073e7 | ||
|
|
35bd0ec5d8 | ||
|
|
373a69abbd | ||
|
|
9fa56bf76c | ||
|
|
3ad42fc7e0 | ||
|
|
7dd6efafb2 | ||
|
|
61e26df637 | ||
|
|
a47283bf5b | ||
|
|
d7e5385b04 | ||
|
|
8eaac83238 | ||
|
|
9fbe224f00 | ||
|
|
26dc7b2f65 | ||
|
|
bb6b6f9690 | ||
|
|
0fabfc9873 | ||
|
|
9c3e057410 | ||
|
|
f4cbb1ec73 | ||
|
|
1175f589eb | ||
|
|
8c7563d7f2 | ||
|
|
33533cbb09 | ||
|
|
0ec37a7b95 | ||
|
|
f3db0c171b | ||
|
|
cd10d23371 | ||
|
|
d60c88c506 | ||
|
|
7266aab4be | ||
|
|
54c2ef7a0b | ||
|
|
b882a72d77 | ||
|
|
1eab4dac96 | ||
|
|
b2376aea70 | ||
|
|
1641cfbd03 | ||
|
|
f27540ca02 | ||
|
|
b4320de291 | ||
|
|
1b26aa4b36 | ||
|
|
1ca91f35e2 | ||
|
|
1c5ac56ecf | ||
|
|
ee85e13206 | ||
|
|
440474d53b | ||
|
|
d9c251b2ce | ||
|
|
975b4aee2a |
3
.eslintignore
Normal file
3
.eslintignore
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules/*
|
||||
build/*
|
||||
lib/*
|
||||
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "tsconfig.json",
|
||||
"project": ["tsconfig.json", "tsconfig.servers.json"],
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
@@ -14,8 +14,9 @@
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-types": "warn",
|
||||
"@typescript-eslint/class-name-casing": "off",
|
||||
"indent": "off",
|
||||
"@typescript-eslint/indent": [
|
||||
"warn",
|
||||
"error",
|
||||
4
|
||||
],
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
@@ -33,7 +34,7 @@
|
||||
],
|
||||
"@typescript-eslint/prefer-namespace-keyword": "warn",
|
||||
"@typescript-eslint/quotes": [
|
||||
"warn",
|
||||
"error",
|
||||
"single",
|
||||
{
|
||||
"avoidEscape": true,
|
||||
@@ -44,22 +45,31 @@
|
||||
"off",
|
||||
null
|
||||
],
|
||||
"@typescript-eslint/type-annotation-spacing": "warn",
|
||||
"@typescript-eslint/type-annotation-spacing": "error",
|
||||
"arrow-parens": [
|
||||
"off",
|
||||
"as-needed"
|
||||
],
|
||||
"brace-style": "off",
|
||||
"@typescript-eslint/brace-style": [
|
||||
"error",
|
||||
"1tbs", { "allowSingleLine": true }
|
||||
],
|
||||
"comma-spacing": "off",
|
||||
"@typescript-eslint/comma-spacing": "error",
|
||||
"space-infix-ops": "error",
|
||||
"comma-dangle": "off",
|
||||
"eqeqeq": [
|
||||
"warn",
|
||||
"error",
|
||||
"smart"
|
||||
],
|
||||
"import/order": "off",
|
||||
"no-eval": "warn",
|
||||
"no-new-wrappers": "warn",
|
||||
"no-trailing-spaces": "warn",
|
||||
"no-trailing-spaces": "error",
|
||||
"no-unsafe-finally": "warn",
|
||||
"no-var": "warn",
|
||||
"spaced-comment": "warn"
|
||||
"no-var": "error",
|
||||
"spaced-comment": "error",
|
||||
"semi": "warn"
|
||||
}
|
||||
}
|
||||
18
.github/workflows/lint.yml
vendored
Normal file
18
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
name: eslint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: install node v12
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
- name: yarn install
|
||||
run: yarn install
|
||||
- name: eslint
|
||||
uses: icrawl/action-eslint@v1
|
||||
21
README.md
21
README.md
@@ -89,21 +89,20 @@ and navigate to `build/viewer`
|
||||
|
||||
### Code generation
|
||||
**CIF schemas**
|
||||
Install CIFTools `npm install ciftools -g`
|
||||
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD
|
||||
cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/cif-core.ts -p CifCore -aa
|
||||
node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/mmcif.ts -p mmCIF
|
||||
node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/ccd.ts -p CCD
|
||||
node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/bird.ts -p BIRD
|
||||
node ./lib/apps/cifschema -mip ../../../../mol-data -o src/mol-io/reader/cif/schema/cif-core.ts -p CifCore -aa
|
||||
|
||||
**GraphQL schemas**
|
||||
|
||||
./node_modules/.bin/graphql-codegen -c ./data/rcsb-graphql/codegen.yml
|
||||
./node_modules/.bin/graphql-codegen -c ./src/extensions/rcsb/graphql/codegen.yml
|
||||
|
||||
### Other scripts
|
||||
**Create chem comp bond table**
|
||||
|
||||
export NODE_PATH="lib"; node --max-old-space-size=4096 lib/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
|
||||
node --max-old-space-size=4096 lib/apps/chem-comp-bond/create-table.js build/data/ccb.bcif -b
|
||||
|
||||
**Test model server**
|
||||
|
||||
@@ -115,9 +114,13 @@ Install CIFTools `npm install ciftools -g`
|
||||
|
||||
**Convert any CIF to BinaryCIF**
|
||||
|
||||
node build/model-server/preprocess -i file.cif -ob file.bcif
|
||||
node lib/servers/model/preprocess -i file.cif -ob file.bcif
|
||||
|
||||
To see all available commands, use ``node build/model-server/preprocess -h``.
|
||||
To see all available commands, use ``node lib/servers/model/preprocess -h``.
|
||||
|
||||
Or
|
||||
|
||||
node ./lib/apps/cif2bcif
|
||||
|
||||
## Development
|
||||
|
||||
|
||||
88
data/cif-field-names/bird-field-names.csv
Normal file
88
data/cif-field-names/bird-field-names.csv
Normal file
@@ -0,0 +1,88 @@
|
||||
pdbx_reference_molecule.prd_id
|
||||
pdbx_reference_molecule.name
|
||||
pdbx_reference_molecule.represent_as
|
||||
pdbx_reference_molecule.type
|
||||
pdbx_reference_molecule.type_evidence_code
|
||||
pdbx_reference_molecule.class
|
||||
pdbx_reference_molecule.class_evidence_code
|
||||
pdbx_reference_molecule.formula
|
||||
pdbx_reference_molecule.chem_comp_id
|
||||
pdbx_reference_molecule.formula_weight
|
||||
pdbx_reference_molecule.release_status
|
||||
pdbx_reference_molecule.replaces
|
||||
pdbx_reference_molecule.replaced_by
|
||||
pdbx_reference_molecule.compound_details
|
||||
pdbx_reference_molecule.description
|
||||
pdbx_reference_molecule.representative_PDB_id_code
|
||||
|
||||
pdbx_reference_entity_list.prd_id
|
||||
pdbx_reference_entity_list.ref_entity_id
|
||||
pdbx_reference_entity_list.component_id
|
||||
pdbx_reference_entity_list.type
|
||||
pdbx_reference_entity_list.details
|
||||
|
||||
pdbx_reference_entity_nonpoly.prd_id
|
||||
pdbx_reference_entity_nonpoly.ref_entity_id
|
||||
pdbx_reference_entity_nonpoly.name
|
||||
pdbx_reference_entity_nonpoly.chem_comp_id
|
||||
|
||||
pdbx_reference_entity_link.prd_id
|
||||
pdbx_reference_entity_link.link_id
|
||||
pdbx_reference_entity_link.link_class
|
||||
pdbx_reference_entity_link.ref_entity_id_1
|
||||
pdbx_reference_entity_link.entity_seq_num_1
|
||||
pdbx_reference_entity_link.comp_id_1
|
||||
pdbx_reference_entity_link.atom_id_1
|
||||
pdbx_reference_entity_link.ref_entity_id_2
|
||||
pdbx_reference_entity_link.entity_seq_num_2
|
||||
pdbx_reference_entity_link.comp_id_2
|
||||
pdbx_reference_entity_link.atom_id_2
|
||||
pdbx_reference_entity_link.value_order
|
||||
pdbx_reference_entity_link.component_1
|
||||
pdbx_reference_entity_link.component_2
|
||||
pdbx_reference_entity_link.details
|
||||
|
||||
pdbx_reference_entity_poly_link.prd_id
|
||||
pdbx_reference_entity_poly_link.ref_entity_id
|
||||
pdbx_reference_entity_poly_link.link_id
|
||||
pdbx_reference_entity_poly_link.atom_id_1
|
||||
pdbx_reference_entity_poly_link.comp_id_1
|
||||
pdbx_reference_entity_poly_link.entity_seq_num_1
|
||||
pdbx_reference_entity_poly_link.atom_id_2
|
||||
pdbx_reference_entity_poly_link.comp_id_2
|
||||
pdbx_reference_entity_poly_link.entity_seq_num_2
|
||||
pdbx_reference_entity_poly_link.value_order
|
||||
pdbx_reference_entity_poly_link.component_id
|
||||
|
||||
pdbx_reference_entity_poly.prd_id
|
||||
pdbx_reference_entity_poly.ref_entity_id
|
||||
pdbx_reference_entity_poly.db_code
|
||||
pdbx_reference_entity_poly.db_name
|
||||
pdbx_reference_entity_poly.type
|
||||
|
||||
pdbx_reference_entity_sequence.prd_id
|
||||
pdbx_reference_entity_sequence.ref_entity_id
|
||||
pdbx_reference_entity_sequence.type
|
||||
pdbx_reference_entity_sequence.NRP_flag
|
||||
pdbx_reference_entity_sequence.one_letter_codes
|
||||
|
||||
pdbx_reference_entity_poly_seq.prd_id
|
||||
pdbx_reference_entity_poly_seq.ref_entity_id
|
||||
pdbx_reference_entity_poly_seq.num
|
||||
pdbx_reference_entity_poly_seq.mon_id
|
||||
pdbx_reference_entity_poly_seq.parent_mon_id
|
||||
pdbx_reference_entity_poly_seq.hetero
|
||||
pdbx_reference_entity_poly_seq.observed
|
||||
|
||||
pdbx_reference_entity_src_nat.prd_id
|
||||
pdbx_reference_entity_src_nat.ref_entity_id
|
||||
pdbx_reference_entity_src_nat.ordinal
|
||||
pdbx_reference_entity_src_nat.taxid
|
||||
pdbx_reference_entity_src_nat.organism_scientific
|
||||
pdbx_reference_entity_src_nat.db_code
|
||||
pdbx_reference_entity_src_nat.db_name
|
||||
|
||||
pdbx_prd_audit.prd_id
|
||||
pdbx_prd_audit.date
|
||||
pdbx_prd_audit.processing_site
|
||||
pdbx_prd_audit.action_type
|
||||
|
60
data/cif-field-names/ccd-field-names.csv
Normal file
60
data/cif-field-names/ccd-field-names.csv
Normal file
@@ -0,0 +1,60 @@
|
||||
chem_comp.id
|
||||
chem_comp.name
|
||||
chem_comp.type
|
||||
chem_comp.pdbx_type
|
||||
chem_comp.formula
|
||||
chem_comp.mon_nstd_parent_comp_id
|
||||
chem_comp.pdbx_synonyms
|
||||
chem_comp.pdbx_formal_charge
|
||||
chem_comp.pdbx_initial_date
|
||||
chem_comp.pdbx_modified_date
|
||||
chem_comp.pdbx_ambiguous_flag
|
||||
chem_comp.pdbx_release_status
|
||||
chem_comp.pdbx_replaced_by
|
||||
chem_comp.pdbx_replaces
|
||||
chem_comp.formula_weight
|
||||
chem_comp.one_letter_code
|
||||
chem_comp.three_letter_code
|
||||
chem_comp.pdbx_model_coordinates_details
|
||||
chem_comp.pdbx_model_coordinates_missing_flag
|
||||
chem_comp.pdbx_ideal_coordinates_details
|
||||
chem_comp.pdbx_ideal_coordinates_missing_flag
|
||||
chem_comp.pdbx_model_coordinates_db_code
|
||||
chem_comp.pdbx_processing_site
|
||||
|
||||
chem_comp_atom.comp_id
|
||||
chem_comp_atom.atom_id
|
||||
chem_comp_atom.alt_atom_id
|
||||
chem_comp_atom.type_symbol
|
||||
chem_comp_atom.charge
|
||||
chem_comp_atom.pdbx_align
|
||||
chem_comp_atom.pdbx_aromatic_flag
|
||||
chem_comp_atom.pdbx_leaving_atom_flag
|
||||
chem_comp_atom.pdbx_stereo_config
|
||||
chem_comp_atom.model_Cartn_x
|
||||
chem_comp_atom.model_Cartn_y
|
||||
chem_comp_atom.model_Cartn_z
|
||||
chem_comp_atom.pdbx_model_Cartn_x_ideal
|
||||
chem_comp_atom.pdbx_model_Cartn_y_ideal
|
||||
chem_comp_atom.pdbx_model_Cartn_z_ideal
|
||||
chem_comp_atom.pdbx_ordinal
|
||||
|
||||
chem_comp_bond.comp_id
|
||||
chem_comp_bond.atom_id_1
|
||||
chem_comp_bond.atom_id_2
|
||||
chem_comp_bond.value_order
|
||||
chem_comp_bond.pdbx_aromatic_flag
|
||||
chem_comp_bond.pdbx_stereo_config
|
||||
chem_comp_bond.pdbx_ordinal
|
||||
|
||||
pdbx_chem_comp_descriptor.comp_id
|
||||
pdbx_chem_comp_descriptor.type
|
||||
pdbx_chem_comp_descriptor.program
|
||||
pdbx_chem_comp_descriptor.program_version
|
||||
pdbx_chem_comp_descriptor.descriptor
|
||||
|
||||
pdbx_chem_comp_identifier.comp_id
|
||||
pdbx_chem_comp_identifier.type
|
||||
pdbx_chem_comp_identifier.program
|
||||
pdbx_chem_comp_identifier.program_version
|
||||
pdbx_chem_comp_identifier.identifier
|
||||
|
76
data/cif-field-names/cif-core-field-names.csv
Normal file
76
data/cif-field-names/cif-core-field-names.csv
Normal file
@@ -0,0 +1,76 @@
|
||||
audit.block_doi
|
||||
|
||||
database_code.depnum_ccdc_archive
|
||||
database_code.depnum_ccdc_fiz
|
||||
database_code.ICSD
|
||||
database_code.MDF
|
||||
database_code.NBS
|
||||
database_code.CSD
|
||||
database_code.COD
|
||||
|
||||
chemical.name_systematic
|
||||
chemical.name_common
|
||||
chemical.melting_point
|
||||
|
||||
chemical_formula.moiety
|
||||
chemical_formula.sum
|
||||
chemical_formula.weight
|
||||
|
||||
atom_type.symbol
|
||||
atom_type.description
|
||||
|
||||
atom_type_scat.dispersion_real
|
||||
atom_type_scat.dispersion_imag
|
||||
atom_type_scat.source
|
||||
|
||||
space_group.crystal_system
|
||||
space_group.name_H-M_full
|
||||
space_group.IT_number
|
||||
space_group_symop.operation_xyz
|
||||
|
||||
cell.length_a
|
||||
cell.length_b
|
||||
cell.length_c
|
||||
cell.angle_alpha
|
||||
cell.angle_beta
|
||||
cell.angle_gamma
|
||||
cell.volume
|
||||
cell.formula_units_Z
|
||||
|
||||
atom_site.label
|
||||
atom_site.type_symbol
|
||||
atom_site.fract_x
|
||||
atom_site.fract_y
|
||||
atom_site.fract_z
|
||||
atom_site.U_iso_or_equiv
|
||||
atom_site.adp_type
|
||||
atom_site.occupancy
|
||||
atom_site.calc_flag
|
||||
atom_site.refinement_flags
|
||||
atom_site.disorder_assembly
|
||||
atom_site.disorder_group
|
||||
atom_site.site_symmetry_multiplicity
|
||||
|
||||
atom_site_aniso.label
|
||||
atom_site_aniso.U
|
||||
atom_site_aniso.U_11
|
||||
atom_site_aniso.U_22
|
||||
atom_site_aniso.U_33
|
||||
atom_site_aniso.U_23
|
||||
atom_site_aniso.U_13
|
||||
atom_site_aniso.U_12
|
||||
atom_site_aniso.U_su
|
||||
atom_site_aniso.U_11_su
|
||||
atom_site_aniso.U_22_su
|
||||
atom_site_aniso.U_33_su
|
||||
atom_site_aniso.U_23_su
|
||||
atom_site_aniso.U_13_su
|
||||
atom_site_aniso.U_12_su
|
||||
|
||||
geom_bond.atom_site_label_1
|
||||
geom_bond.atom_site_label_2
|
||||
geom_bond.distance
|
||||
geom_bond.site_symmetry_1
|
||||
geom_bond.site_symmetry_2
|
||||
geom_bond.publ_flag
|
||||
geom_bond.valence
|
||||
|
805
data/cif-field-names/mmcif-field-names.csv
Normal file
805
data/cif-field-names/mmcif-field-names.csv
Normal file
@@ -0,0 +1,805 @@
|
||||
atom_sites.entry_id
|
||||
atom_sites.fract_transf_matrix
|
||||
atom_sites.fract_transf_vector
|
||||
|
||||
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.pdbx_formal_charge
|
||||
atom_site.Cartn_x
|
||||
atom_site.Cartn_y
|
||||
atom_site.Cartn_z
|
||||
atom_site.occupancy
|
||||
atom_site.B_iso_or_equiv
|
||||
atom_site.auth_atom_id
|
||||
atom_site.auth_comp_id
|
||||
atom_site.auth_asym_id
|
||||
atom_site.auth_seq_id
|
||||
atom_site.pdbx_PDB_model_num
|
||||
atom_site.ihm_model_id
|
||||
|
||||
atom_site_anisotrop.id
|
||||
atom_site_anisotrop.U
|
||||
atom_site_anisotrop.U_esd
|
||||
atom_site_anisotrop.pdbx_PDB_ins_code
|
||||
atom_site_anisotrop.pdbx_auth_asym_id
|
||||
atom_site_anisotrop.pdbx_auth_atom_id
|
||||
atom_site_anisotrop.pdbx_auth_comp_id
|
||||
atom_site_anisotrop.pdbx_auth_seq_id
|
||||
atom_site_anisotrop.pdbx_label_alt_id
|
||||
atom_site_anisotrop.pdbx_label_asym_id
|
||||
atom_site_anisotrop.pdbx_label_atom_id
|
||||
atom_site_anisotrop.pdbx_label_comp_id
|
||||
atom_site_anisotrop.pdbx_label_seq_id
|
||||
atom_site_anisotrop.type_symbol
|
||||
|
||||
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
|
||||
|
||||
chem_comp_bond.comp_id
|
||||
chem_comp_bond.pdbx_stereo_config
|
||||
chem_comp_bond.pdbx_ordinal
|
||||
chem_comp_bond.pdbx_aromatic_flag
|
||||
chem_comp_bond.atom_id_1
|
||||
chem_comp_bond.atom_id_2
|
||||
chem_comp_bond.value_order
|
||||
|
||||
pdbx_chem_comp_identifier.comp_id
|
||||
pdbx_chem_comp_identifier.type
|
||||
pdbx_chem_comp_identifier.program
|
||||
pdbx_chem_comp_identifier.program_version
|
||||
pdbx_chem_comp_identifier.identifier
|
||||
|
||||
pdbx_chem_comp_related.comp_id
|
||||
pdbx_chem_comp_related.related_comp_id
|
||||
pdbx_chem_comp_related.relationship_type
|
||||
pdbx_chem_comp_related.details
|
||||
|
||||
pdbx_chem_comp_synonyms.comp_id
|
||||
pdbx_chem_comp_synonyms.name
|
||||
pdbx_chem_comp_synonyms.provenance
|
||||
|
||||
cell.entry_id
|
||||
cell.length_a
|
||||
cell.length_b
|
||||
cell.length_c
|
||||
cell.angle_alpha
|
||||
cell.angle_beta
|
||||
cell.angle_gamma
|
||||
cell.Z_PDB
|
||||
cell.pdbx_unique_axis
|
||||
|
||||
pdbx_database_related.db_name
|
||||
pdbx_database_related.details
|
||||
pdbx_database_related.db_id
|
||||
pdbx_database_related.content_type
|
||||
|
||||
pdbx_database_status.status_code
|
||||
pdbx_database_status.status_code_sf
|
||||
pdbx_database_status.status_code_mr
|
||||
pdbx_database_status.entry_id
|
||||
pdbx_database_status.recvd_initial_deposition_date
|
||||
pdbx_database_status.SG_entry
|
||||
pdbx_database_status.deposit_site
|
||||
pdbx_database_status.process_site
|
||||
pdbx_database_status.status_code_cs
|
||||
pdbx_database_status.methods_development_category
|
||||
pdbx_database_status.pdb_format_compatible
|
||||
|
||||
entity.id
|
||||
entity.type
|
||||
entity.src_method
|
||||
entity.pdbx_description
|
||||
entity.formula_weight
|
||||
entity.pdbx_number_of_molecules
|
||||
entity.details
|
||||
entity.pdbx_mutation
|
||||
entity.pdbx_fragment
|
||||
entity.pdbx_ec
|
||||
|
||||
entity_poly.entity_id
|
||||
entity_poly.type
|
||||
entity_poly.nstd_linkage
|
||||
entity_poly.nstd_monomer
|
||||
entity_poly.pdbx_seq_one_letter_code
|
||||
entity_poly.pdbx_seq_one_letter_code_can
|
||||
entity_poly.pdbx_strand_id
|
||||
entity_poly.pdbx_target_identifier
|
||||
|
||||
entity_poly_seq.entity_id
|
||||
entity_poly_seq.num
|
||||
entity_poly_seq.mon_id
|
||||
entity_poly_seq.hetero
|
||||
|
||||
entity_src_gen.entity_id
|
||||
entity_src_gen.pdbx_src_id
|
||||
entity_src_gen.pdbx_beg_seq_num
|
||||
entity_src_gen.pdbx_end_seq_num
|
||||
entity_src_gen.pdbx_gene_src_gene
|
||||
entity_src_gen.pdbx_gene_src_scientific_name
|
||||
entity_src_gen.plasmid_name
|
||||
|
||||
entity_src_nat.entity_id
|
||||
entity_src_nat.pdbx_src_id
|
||||
entity_src_nat.pdbx_beg_seq_num
|
||||
entity_src_nat.pdbx_end_seq_num
|
||||
entity_src_nat.pdbx_organism_scientific
|
||||
entity_src_nat.pdbx_plasmid_name
|
||||
|
||||
pdbx_entity_instance_feature.ordinal
|
||||
pdbx_entity_instance_feature.feature_type
|
||||
pdbx_entity_instance_feature.details
|
||||
pdbx_entity_instance_feature.asym_id
|
||||
pdbx_entity_instance_feature.comp_id
|
||||
pdbx_entity_instance_feature.seq_num
|
||||
pdbx_entity_instance_feature.auth_asym_id
|
||||
pdbx_entity_instance_feature.auth_comp_id
|
||||
pdbx_entity_instance_feature.auth_seq_num
|
||||
|
||||
pdbx_entity_src_syn.entity_id
|
||||
pdbx_entity_src_syn.pdbx_src_id
|
||||
pdbx_entity_src_syn.pdbx_beg_seq_num
|
||||
pdbx_entity_src_syn.pdbx_end_seq_num
|
||||
pdbx_entity_src_syn.organism_scientific
|
||||
|
||||
pdbx_entity_branch.entity_id
|
||||
pdbx_entity_branch.type
|
||||
|
||||
pdbx_entity_branch_list.entity_id
|
||||
pdbx_entity_branch_list.comp_id
|
||||
pdbx_entity_branch_list.num
|
||||
pdbx_entity_branch_list.hetero
|
||||
|
||||
pdbx_entity_branch_link.link_id
|
||||
pdbx_entity_branch_link.entity_id
|
||||
pdbx_entity_branch_link.entity_branch_list_num_1
|
||||
pdbx_entity_branch_link.comp_id_1
|
||||
pdbx_entity_branch_link.atom_id_1
|
||||
pdbx_entity_branch_link.leaving_atom_id_1
|
||||
pdbx_entity_branch_link.atom_stereo_config_1
|
||||
pdbx_entity_branch_link.entity_branch_list_num_2
|
||||
pdbx_entity_branch_link.comp_id_2
|
||||
pdbx_entity_branch_link.atom_id_2
|
||||
pdbx_entity_branch_link.leaving_atom_id_2
|
||||
pdbx_entity_branch_link.atom_stereo_config_2
|
||||
pdbx_entity_branch_link.value_order
|
||||
pdbx_entity_branch_link.details
|
||||
|
||||
pdbx_branch_scheme.asym_id
|
||||
pdbx_branch_scheme.entity_id
|
||||
pdbx_branch_scheme.mon_id
|
||||
pdbx_branch_scheme.num
|
||||
pdbx_branch_scheme.auth_asym_id
|
||||
pdbx_branch_scheme.auth_mon_id
|
||||
pdbx_branch_scheme.auth_seq_num
|
||||
pdbx_branch_scheme.hetero
|
||||
pdbx_branch_scheme.pdb_mon_id
|
||||
pdbx_branch_scheme.pdb_asym_id
|
||||
pdbx_branch_scheme.pdb_seq_num
|
||||
|
||||
pdbx_entity_branch_descriptor.ordinal
|
||||
pdbx_entity_branch_descriptor.entity_id
|
||||
pdbx_entity_branch_descriptor.descriptor
|
||||
pdbx_entity_branch_descriptor.type
|
||||
pdbx_entity_branch_descriptor.program
|
||||
pdbx_entity_branch_descriptor.program_version
|
||||
|
||||
pdbx_entity_nonpoly.entity_id
|
||||
pdbx_entity_nonpoly.name
|
||||
pdbx_entity_nonpoly.comp_id
|
||||
|
||||
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
|
||||
|
||||
entry.id
|
||||
|
||||
audit_conform.dict_name
|
||||
audit_conform.dict_version
|
||||
audit_conform.dict_location
|
||||
|
||||
database_2.database_id
|
||||
database_2.database_code
|
||||
|
||||
audit_author.name
|
||||
audit_author.pdbx_ordinal
|
||||
audit_author.identifier_ORCID
|
||||
|
||||
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
|
||||
|
||||
citation_author.citation_id
|
||||
citation_author.name
|
||||
citation_author.ordinal
|
||||
|
||||
exptl.entry_id
|
||||
exptl.method
|
||||
|
||||
struct.entry_id
|
||||
struct.title
|
||||
struct.pdbx_descriptor
|
||||
|
||||
struct_asym.id
|
||||
struct_asym.pdbx_blank_PDB_chainid_flag
|
||||
struct_asym.pdbx_modified
|
||||
struct_asym.entity_id
|
||||
struct_asym.details
|
||||
|
||||
struct_conf.conf_type_id
|
||||
struct_conf.id
|
||||
struct_conf.pdbx_PDB_helix_id
|
||||
struct_conf.beg_label_comp_id
|
||||
struct_conf.beg_label_asym_id
|
||||
struct_conf.beg_label_seq_id
|
||||
struct_conf.pdbx_beg_PDB_ins_code
|
||||
struct_conf.end_label_comp_id
|
||||
struct_conf.end_label_asym_id
|
||||
struct_conf.end_label_seq_id
|
||||
struct_conf.pdbx_end_PDB_ins_code
|
||||
struct_conf.beg_auth_comp_id
|
||||
struct_conf.beg_auth_asym_id
|
||||
struct_conf.beg_auth_seq_id
|
||||
struct_conf.end_auth_comp_id
|
||||
struct_conf.end_auth_asym_id
|
||||
struct_conf.end_auth_seq_id
|
||||
struct_conf.pdbx_PDB_helix_class
|
||||
struct_conf.details
|
||||
struct_conf.pdbx_PDB_helix_length
|
||||
|
||||
struct_conn.id
|
||||
struct_conn.conn_type_id
|
||||
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
|
||||
|
||||
struct_conn_type.id
|
||||
struct_conn_type.criteria
|
||||
struct_conn_type.reference
|
||||
|
||||
struct_keywords.entry_id
|
||||
struct_keywords.pdbx_keywords
|
||||
struct_keywords.text
|
||||
|
||||
struct_ncs_oper.id
|
||||
struct_ncs_oper.code
|
||||
struct_ncs_oper.matrix
|
||||
struct_ncs_oper.vector
|
||||
struct_ncs_oper.details
|
||||
|
||||
struct_sheet_range.sheet_id
|
||||
struct_sheet_range.id
|
||||
struct_sheet_range.beg_label_comp_id
|
||||
struct_sheet_range.beg_label_asym_id
|
||||
struct_sheet_range.beg_label_seq_id
|
||||
struct_sheet_range.pdbx_beg_PDB_ins_code
|
||||
struct_sheet_range.end_label_comp_id
|
||||
struct_sheet_range.end_label_asym_id
|
||||
struct_sheet_range.end_label_seq_id
|
||||
struct_sheet_range.pdbx_end_PDB_ins_code
|
||||
struct_sheet_range.beg_auth_comp_id
|
||||
struct_sheet_range.beg_auth_asym_id
|
||||
struct_sheet_range.beg_auth_seq_id
|
||||
struct_sheet_range.end_auth_comp_id
|
||||
struct_sheet_range.end_auth_asym_id
|
||||
struct_sheet_range.end_auth_seq_id
|
||||
|
||||
struct_site.id
|
||||
struct_site.pdbx_evidence_code
|
||||
struct_site.pdbx_auth_asym_id
|
||||
struct_site.pdbx_auth_comp_id
|
||||
struct_site.pdbx_auth_seq_id
|
||||
struct_site.pdbx_auth_ins_code
|
||||
struct_site.pdbx_num_residues
|
||||
struct_site.details
|
||||
|
||||
struct_site_gen.id
|
||||
struct_site_gen.site_id
|
||||
struct_site_gen.pdbx_num_res
|
||||
struct_site_gen.label_comp_id
|
||||
struct_site_gen.label_asym_id
|
||||
struct_site_gen.label_seq_id
|
||||
struct_site_gen.pdbx_auth_ins_code
|
||||
struct_site_gen.auth_comp_id
|
||||
struct_site_gen.auth_asym_id
|
||||
struct_site_gen.auth_seq_id
|
||||
struct_site_gen.label_atom_id
|
||||
struct_site_gen.label_alt_id
|
||||
struct_site_gen.symmetry
|
||||
struct_site_gen.details
|
||||
|
||||
symmetry.entry_id
|
||||
symmetry.cell_setting
|
||||
symmetry.Int_Tables_number
|
||||
symmetry.space_group_name_Hall
|
||||
symmetry.space_group_name_H-M
|
||||
|
||||
pdbx_molecule.instance_id
|
||||
pdbx_molecule.prd_id
|
||||
pdbx_molecule.asym_id
|
||||
|
||||
pdbx_molecule_features.prd_id
|
||||
pdbx_molecule_features.name
|
||||
pdbx_molecule_features.type
|
||||
pdbx_molecule_features.class
|
||||
pdbx_molecule_features.details
|
||||
|
||||
pdbx_reference_entity_link.prd_id
|
||||
pdbx_reference_entity_link.link_id
|
||||
pdbx_reference_entity_link.link_class
|
||||
pdbx_reference_entity_link.ref_entity_id_1
|
||||
pdbx_reference_entity_link.entity_seq_num_1
|
||||
pdbx_reference_entity_link.comp_id_1
|
||||
pdbx_reference_entity_link.atom_id_1
|
||||
pdbx_reference_entity_link.ref_entity_id_2
|
||||
pdbx_reference_entity_link.entity_seq_num_2
|
||||
pdbx_reference_entity_link.comp_id_2
|
||||
pdbx_reference_entity_link.atom_id_2
|
||||
pdbx_reference_entity_link.value_order
|
||||
pdbx_reference_entity_link.component_1
|
||||
pdbx_reference_entity_link.component_2
|
||||
pdbx_reference_entity_link.details
|
||||
|
||||
pdbx_reference_entity_list.prd_id
|
||||
pdbx_reference_entity_list.ref_entity_id
|
||||
pdbx_reference_entity_list.component_id
|
||||
pdbx_reference_entity_list.type
|
||||
pdbx_reference_entity_list.details
|
||||
|
||||
pdbx_reference_entity_poly_link.prd_id
|
||||
pdbx_reference_entity_poly_link.ref_entity_id
|
||||
pdbx_reference_entity_poly_link.link_id
|
||||
pdbx_reference_entity_poly_link.atom_id_1
|
||||
pdbx_reference_entity_poly_link.comp_id_1
|
||||
pdbx_reference_entity_poly_link.entity_seq_num_1
|
||||
pdbx_reference_entity_poly_link.atom_id_2
|
||||
pdbx_reference_entity_poly_link.comp_id_2
|
||||
pdbx_reference_entity_poly_link.entity_seq_num_2
|
||||
pdbx_reference_entity_poly_link.value_order
|
||||
pdbx_reference_entity_poly_link.component_id
|
||||
|
||||
pdbx_struct_assembly.id
|
||||
pdbx_struct_assembly.details
|
||||
pdbx_struct_assembly.method_details
|
||||
pdbx_struct_assembly.oligomeric_details
|
||||
pdbx_struct_assembly.oligomeric_count
|
||||
|
||||
pdbx_struct_assembly_gen.assembly_id
|
||||
pdbx_struct_assembly_gen.oper_expression
|
||||
pdbx_struct_assembly_gen.asym_id_list
|
||||
|
||||
pdbx_struct_oper_list.id
|
||||
pdbx_struct_oper_list.type
|
||||
pdbx_struct_oper_list.name
|
||||
pdbx_struct_oper_list.symmetry_operation
|
||||
pdbx_struct_oper_list.matrix
|
||||
pdbx_struct_oper_list.vector
|
||||
|
||||
pdbx_struct_mod_residue.id
|
||||
pdbx_struct_mod_residue.label_asym_id
|
||||
pdbx_struct_mod_residue.label_seq_id
|
||||
pdbx_struct_mod_residue.label_comp_id
|
||||
pdbx_struct_mod_residue.auth_asym_id
|
||||
pdbx_struct_mod_residue.auth_seq_id
|
||||
pdbx_struct_mod_residue.auth_comp_id
|
||||
pdbx_struct_mod_residue.PDB_ins_code
|
||||
pdbx_struct_mod_residue.parent_comp_id
|
||||
pdbx_struct_mod_residue.details
|
||||
|
||||
pdbx_unobs_or_zero_occ_residues.id
|
||||
pdbx_unobs_or_zero_occ_residues.PDB_model_num
|
||||
pdbx_unobs_or_zero_occ_residues.polymer_flag
|
||||
pdbx_unobs_or_zero_occ_residues.occupancy_flag
|
||||
pdbx_unobs_or_zero_occ_residues.auth_asym_id
|
||||
pdbx_unobs_or_zero_occ_residues.auth_comp_id
|
||||
pdbx_unobs_or_zero_occ_residues.auth_seq_id
|
||||
pdbx_unobs_or_zero_occ_residues.PDB_ins_code
|
||||
pdbx_unobs_or_zero_occ_residues.label_asym_id
|
||||
pdbx_unobs_or_zero_occ_residues.label_comp_id
|
||||
pdbx_unobs_or_zero_occ_residues.label_seq_id
|
||||
|
||||
ihm_struct_assembly.id
|
||||
ihm_struct_assembly.name
|
||||
ihm_struct_assembly.description
|
||||
|
||||
ihm_struct_assembly_details.id
|
||||
ihm_struct_assembly_details.assembly_id
|
||||
ihm_struct_assembly_details.parent_assembly_id
|
||||
ihm_struct_assembly_details.entity_description
|
||||
ihm_struct_assembly_details.entity_id
|
||||
ihm_struct_assembly_details.asym_id
|
||||
ihm_struct_assembly_details.entity_poly_segment_id
|
||||
|
||||
ihm_model_representation.id
|
||||
ihm_model_representation.name
|
||||
ihm_model_representation.details
|
||||
|
||||
ihm_model_representation_details.id
|
||||
ihm_model_representation_details.representation_id
|
||||
ihm_model_representation_details.entity_id
|
||||
ihm_model_representation_details.entity_description
|
||||
ihm_model_representation_details.entity_asym_id
|
||||
ihm_model_representation_details.entity_poly_segment_id
|
||||
ihm_model_representation_details.model_object_primitive
|
||||
ihm_model_representation_details.starting_model_id
|
||||
ihm_model_representation_details.model_mode
|
||||
ihm_model_representation_details.model_granularity
|
||||
ihm_model_representation_details.model_object_count
|
||||
|
||||
ihm_external_reference_info.reference_id
|
||||
ihm_external_reference_info.reference_provider
|
||||
ihm_external_reference_info.reference_type
|
||||
ihm_external_reference_info.reference
|
||||
ihm_external_reference_info.refers_to
|
||||
ihm_external_reference_info.associated_url
|
||||
|
||||
ihm_external_files.id
|
||||
ihm_external_files.reference_id
|
||||
ihm_external_files.file_path
|
||||
ihm_external_files.content_type
|
||||
ihm_external_files.file_size_bytes
|
||||
ihm_external_files.details
|
||||
|
||||
ihm_dataset_list.id
|
||||
ihm_dataset_list.data_type
|
||||
ihm_dataset_list.database_hosted
|
||||
|
||||
ihm_dataset_group.id
|
||||
ihm_dataset_group.name
|
||||
ihm_dataset_group.application
|
||||
ihm_dataset_group.details
|
||||
|
||||
ihm_dataset_group_link.group_id
|
||||
ihm_dataset_group_link.dataset_list_id
|
||||
|
||||
ihm_dataset_external_reference.id
|
||||
ihm_dataset_external_reference.dataset_list_id
|
||||
ihm_dataset_external_reference.file_id
|
||||
|
||||
ihm_dataset_related_db_reference.id
|
||||
ihm_dataset_related_db_reference.dataset_list_id
|
||||
ihm_dataset_related_db_reference.db_name
|
||||
ihm_dataset_related_db_reference.accession_code
|
||||
ihm_dataset_related_db_reference.version
|
||||
ihm_dataset_related_db_reference.details
|
||||
|
||||
ihm_related_datasets.dataset_list_id_derived
|
||||
ihm_related_datasets.dataset_list_id_primary
|
||||
|
||||
ihm_poly_residue_feature.ordinal_id
|
||||
ihm_poly_residue_feature.feature_id
|
||||
ihm_poly_residue_feature.entity_id
|
||||
ihm_poly_residue_feature.asym_id
|
||||
ihm_poly_residue_feature.seq_id_begin
|
||||
ihm_poly_residue_feature.comp_id_begin
|
||||
ihm_poly_residue_feature.seq_id_end
|
||||
ihm_poly_residue_feature.comp_id_end
|
||||
|
||||
ihm_feature_list.feature_id
|
||||
ihm_feature_list.feature_type
|
||||
ihm_feature_list.entity_type
|
||||
|
||||
ihm_cross_link_list.id
|
||||
ihm_cross_link_list.group_id
|
||||
ihm_cross_link_list.entity_description_1
|
||||
ihm_cross_link_list.entity_id_1
|
||||
ihm_cross_link_list.seq_id_1
|
||||
ihm_cross_link_list.comp_id_1
|
||||
ihm_cross_link_list.entity_description_2
|
||||
ihm_cross_link_list.entity_id_2
|
||||
ihm_cross_link_list.seq_id_2
|
||||
ihm_cross_link_list.comp_id_2
|
||||
ihm_cross_link_list.linker_type
|
||||
ihm_cross_link_list.dataset_list_id
|
||||
|
||||
ihm_cross_link_restraint.id
|
||||
ihm_cross_link_restraint.group_id
|
||||
ihm_cross_link_restraint.entity_id_1
|
||||
ihm_cross_link_restraint.asym_id_1
|
||||
ihm_cross_link_restraint.seq_id_1
|
||||
ihm_cross_link_restraint.atom_id_1
|
||||
ihm_cross_link_restraint.comp_id_1
|
||||
ihm_cross_link_restraint.entity_id_2
|
||||
ihm_cross_link_restraint.asym_id_2
|
||||
ihm_cross_link_restraint.seq_id_2
|
||||
ihm_cross_link_restraint.atom_id_2
|
||||
ihm_cross_link_restraint.comp_id_2
|
||||
ihm_cross_link_restraint.restraint_type
|
||||
ihm_cross_link_restraint.conditional_crosslink_flag
|
||||
ihm_cross_link_restraint.model_granularity
|
||||
ihm_cross_link_restraint.distance_threshold
|
||||
ihm_cross_link_restraint.psi
|
||||
ihm_cross_link_restraint.sigma_1
|
||||
ihm_cross_link_restraint.sigma_2
|
||||
|
||||
ihm_cross_link_result_parameters.id
|
||||
ihm_cross_link_result_parameters.restraint_id
|
||||
ihm_cross_link_result_parameters.model_id
|
||||
ihm_cross_link_result_parameters.psi
|
||||
ihm_cross_link_result_parameters.sigma_1
|
||||
ihm_cross_link_result_parameters.sigma_2
|
||||
|
||||
ihm_sas_restraint.id
|
||||
ihm_sas_restraint.dataset_list_id
|
||||
ihm_sas_restraint.model_id
|
||||
ihm_sas_restraint.struct_assembly_id
|
||||
ihm_sas_restraint.profile_segment_flag
|
||||
ihm_sas_restraint.fitting_atom_type
|
||||
ihm_sas_restraint.fitting_method
|
||||
ihm_sas_restraint.fitting_state
|
||||
ihm_sas_restraint.radius_of_gyration
|
||||
ihm_sas_restraint.chi_value
|
||||
ihm_sas_restraint.details
|
||||
|
||||
ihm_derived_distance_restraint.id
|
||||
ihm_derived_distance_restraint.group_id
|
||||
ihm_derived_distance_restraint.feature_id_1
|
||||
ihm_derived_distance_restraint.feature_id_2
|
||||
ihm_derived_distance_restraint.group_conditionality
|
||||
ihm_derived_distance_restraint.restraint_type
|
||||
ihm_derived_distance_restraint.distance_upper_limit
|
||||
ihm_derived_distance_restraint.random_exclusion_fraction
|
||||
ihm_derived_distance_restraint.dataset_list_id
|
||||
|
||||
ihm_2dem_class_average_restraint.id
|
||||
ihm_2dem_class_average_restraint.dataset_list_id
|
||||
ihm_2dem_class_average_restraint.number_raw_micrographs
|
||||
ihm_2dem_class_average_restraint.pixel_size_width
|
||||
ihm_2dem_class_average_restraint.pixel_size_height
|
||||
ihm_2dem_class_average_restraint.image_resolution
|
||||
ihm_2dem_class_average_restraint.image_segment_flag
|
||||
ihm_2dem_class_average_restraint.number_of_projections
|
||||
ihm_2dem_class_average_restraint.struct_assembly_id
|
||||
ihm_2dem_class_average_restraint.details
|
||||
|
||||
ihm_2dem_class_average_fitting.id
|
||||
ihm_2dem_class_average_fitting.restraint_id
|
||||
ihm_2dem_class_average_fitting.model_id
|
||||
ihm_2dem_class_average_fitting.cross_correlation_coefficient
|
||||
ihm_2dem_class_average_fitting.rot_matrix
|
||||
ihm_2dem_class_average_fitting.tr_vector
|
||||
|
||||
ihm_3dem_restraint.id
|
||||
ihm_3dem_restraint.dataset_list_id
|
||||
ihm_3dem_restraint.fitting_method
|
||||
ihm_3dem_restraint.struct_assembly_id
|
||||
ihm_3dem_restraint.number_of_gaussians
|
||||
ihm_3dem_restraint.model_id
|
||||
ihm_3dem_restraint.cross_correlation_coefficient
|
||||
|
||||
ihm_predicted_contact_restraint.id
|
||||
ihm_predicted_contact_restraint.group_id
|
||||
ihm_predicted_contact_restraint.entity_id_1
|
||||
ihm_predicted_contact_restraint.asym_id_1
|
||||
ihm_predicted_contact_restraint.seq_id_1
|
||||
ihm_predicted_contact_restraint.comp_id_1
|
||||
ihm_predicted_contact_restraint.rep_atom_1
|
||||
ihm_predicted_contact_restraint.entity_id_2
|
||||
ihm_predicted_contact_restraint.asym_id_2
|
||||
ihm_predicted_contact_restraint.seq_id_2
|
||||
ihm_predicted_contact_restraint.comp_id_2
|
||||
ihm_predicted_contact_restraint.rep_atom_2
|
||||
ihm_predicted_contact_restraint.restraint_type
|
||||
ihm_predicted_contact_restraint.distance_lower_limit
|
||||
ihm_predicted_contact_restraint.distance_upper_limit
|
||||
ihm_predicted_contact_restraint.probability
|
||||
ihm_predicted_contact_restraint.model_granularity
|
||||
ihm_predicted_contact_restraint.dataset_list_id
|
||||
ihm_predicted_contact_restraint.software_id
|
||||
|
||||
ihm_starting_model_details.starting_model_id
|
||||
ihm_starting_model_details.entity_id
|
||||
ihm_starting_model_details.entity_description
|
||||
ihm_starting_model_details.asym_id
|
||||
ihm_starting_model_details.entity_poly_segment_id
|
||||
ihm_starting_model_details.starting_model_source
|
||||
ihm_starting_model_details.starting_model_auth_asym_id
|
||||
ihm_starting_model_details.starting_model_sequence_offset
|
||||
ihm_starting_model_details.dataset_list_id
|
||||
|
||||
ihm_starting_comparative_models.id
|
||||
ihm_starting_comparative_models.starting_model_id
|
||||
ihm_starting_comparative_models.starting_model_auth_asym_id
|
||||
ihm_starting_comparative_models.starting_model_seq_id_begin
|
||||
ihm_starting_comparative_models.starting_model_seq_id_end
|
||||
ihm_starting_comparative_models.template_auth_asym_id
|
||||
ihm_starting_comparative_models.template_seq_id_begin
|
||||
ihm_starting_comparative_models.template_seq_id_end
|
||||
ihm_starting_comparative_models.template_sequence_identity
|
||||
ihm_starting_comparative_models.template_sequence_identity_denominator
|
||||
ihm_starting_comparative_models.template_dataset_list_id
|
||||
ihm_starting_comparative_models.alignment_file_id
|
||||
|
||||
ihm_starting_model_coord.starting_model_id
|
||||
ihm_starting_model_coord.group_PDB
|
||||
ihm_starting_model_coord.id
|
||||
ihm_starting_model_coord.type_symbol
|
||||
ihm_starting_model_coord.atom_id
|
||||
ihm_starting_model_coord.comp_id
|
||||
ihm_starting_model_coord.entity_id
|
||||
ihm_starting_model_coord.asym_id
|
||||
ihm_starting_model_coord.seq_id
|
||||
ihm_starting_model_coord.Cartn_x
|
||||
ihm_starting_model_coord.Cartn_y
|
||||
ihm_starting_model_coord.Cartn_z
|
||||
ihm_starting_model_coord.B_iso_or_equiv
|
||||
ihm_starting_model_coord.ordinal_id
|
||||
|
||||
ihm_starting_model_seq_dif.id
|
||||
ihm_starting_model_seq_dif.entity_id
|
||||
ihm_starting_model_seq_dif.asym_id
|
||||
ihm_starting_model_seq_dif.seq_id
|
||||
ihm_starting_model_seq_dif.comp_id
|
||||
ihm_starting_model_seq_dif.starting_model_id
|
||||
ihm_starting_model_seq_dif.db_asym_id
|
||||
ihm_starting_model_seq_dif.db_seq_id
|
||||
ihm_starting_model_seq_dif.db_comp_id
|
||||
ihm_starting_model_seq_dif.details
|
||||
|
||||
ihm_modeling_protocol.id
|
||||
ihm_modeling_protocol.protocol_name
|
||||
ihm_modeling_protocol.num_steps
|
||||
|
||||
ihm_modeling_protocol_details.id
|
||||
ihm_modeling_protocol_details.protocol_id
|
||||
ihm_modeling_protocol_details.step_id
|
||||
ihm_modeling_protocol_details.struct_assembly_id
|
||||
ihm_modeling_protocol_details.dataset_group_id
|
||||
ihm_modeling_protocol_details.struct_assembly_description
|
||||
ihm_modeling_protocol_details.step_name
|
||||
ihm_modeling_protocol_details.step_method
|
||||
ihm_modeling_protocol_details.num_models_begin
|
||||
ihm_modeling_protocol_details.num_models_end
|
||||
ihm_modeling_protocol_details.multi_scale_flag
|
||||
ihm_modeling_protocol_details.multi_state_flag
|
||||
ihm_modeling_protocol_details.ordered_flag
|
||||
ihm_modeling_protocol_details.software_id
|
||||
ihm_modeling_protocol_details.script_file_id
|
||||
|
||||
ihm_modeling_post_process.id
|
||||
ihm_modeling_post_process.protocol_id
|
||||
ihm_modeling_post_process.analysis_id
|
||||
ihm_modeling_post_process.step_id
|
||||
ihm_modeling_post_process.type
|
||||
ihm_modeling_post_process.feature
|
||||
ihm_modeling_post_process.num_models_begin
|
||||
ihm_modeling_post_process.num_models_end
|
||||
|
||||
ihm_ensemble_info.ensemble_id
|
||||
ihm_ensemble_info.ensemble_name
|
||||
ihm_ensemble_info.post_process_id
|
||||
ihm_ensemble_info.model_group_id
|
||||
ihm_ensemble_info.ensemble_clustering_method
|
||||
ihm_ensemble_info.ensemble_clustering_feature
|
||||
ihm_ensemble_info.num_ensemble_models
|
||||
ihm_ensemble_info.num_ensemble_models_deposited
|
||||
ihm_ensemble_info.ensemble_precision_value
|
||||
ihm_ensemble_info.ensemble_file_id
|
||||
|
||||
ihm_localization_density_files.id
|
||||
ihm_localization_density_files.file_id
|
||||
ihm_localization_density_files.ensemble_id
|
||||
ihm_localization_density_files.entity_id
|
||||
ihm_localization_density_files.asym_id
|
||||
ihm_localization_density_files.entity_poly_segment_id
|
||||
|
||||
ihm_model_list.model_id
|
||||
ihm_model_list.model_name
|
||||
ihm_model_list.assembly_id
|
||||
ihm_model_list.protocol_id
|
||||
ihm_model_list.representation_id
|
||||
|
||||
ihm_model_group.id
|
||||
ihm_model_group.name
|
||||
ihm_model_group.details
|
||||
|
||||
ihm_model_group_link.group_id
|
||||
ihm_model_group_link.model_id
|
||||
|
||||
ihm_model_representative.id
|
||||
ihm_model_representative.model_group_id
|
||||
ihm_model_representative.model_id
|
||||
ihm_model_representative.selection_criteria
|
||||
|
||||
ihm_sphere_obj_site.id
|
||||
ihm_sphere_obj_site.entity_id
|
||||
ihm_sphere_obj_site.seq_id_begin
|
||||
ihm_sphere_obj_site.seq_id_end
|
||||
ihm_sphere_obj_site.asym_id
|
||||
ihm_sphere_obj_site.Cartn_x
|
||||
ihm_sphere_obj_site.Cartn_y
|
||||
ihm_sphere_obj_site.Cartn_z
|
||||
ihm_sphere_obj_site.object_radius
|
||||
ihm_sphere_obj_site.rmsf
|
||||
ihm_sphere_obj_site.model_id
|
||||
|
||||
ihm_gaussian_obj_site.id
|
||||
ihm_gaussian_obj_site.entity_id
|
||||
ihm_gaussian_obj_site.seq_id_begin
|
||||
ihm_gaussian_obj_site.seq_id_end
|
||||
ihm_gaussian_obj_site.asym_id
|
||||
ihm_gaussian_obj_site.mean_Cartn_x
|
||||
ihm_gaussian_obj_site.mean_Cartn_y
|
||||
ihm_gaussian_obj_site.mean_Cartn_z
|
||||
ihm_gaussian_obj_site.weight
|
||||
ihm_gaussian_obj_site.covariance_matrix
|
||||
ihm_gaussian_obj_site.model_id
|
||||
|
||||
ihm_gaussian_obj_ensemble.id
|
||||
ihm_gaussian_obj_ensemble.entity_id
|
||||
ihm_gaussian_obj_ensemble.seq_id_begin
|
||||
ihm_gaussian_obj_ensemble.seq_id_end
|
||||
ihm_gaussian_obj_ensemble.asym_id
|
||||
ihm_gaussian_obj_ensemble.mean_Cartn_x
|
||||
ihm_gaussian_obj_ensemble.mean_Cartn_y
|
||||
ihm_gaussian_obj_ensemble.mean_Cartn_z
|
||||
ihm_gaussian_obj_ensemble.weight
|
||||
ihm_gaussian_obj_ensemble.covariance_matrix
|
||||
ihm_gaussian_obj_ensemble.ensemble_id
|
||||
|
||||
ihm_multi_state_modeling.state_id
|
||||
ihm_multi_state_modeling.state_group_id
|
||||
ihm_multi_state_modeling.population_fraction
|
||||
ihm_multi_state_modeling.population_fraction_sd
|
||||
ihm_multi_state_modeling.state_type
|
||||
ihm_multi_state_modeling.state_name
|
||||
ihm_multi_state_modeling.experiment_type
|
||||
ihm_multi_state_modeling.details
|
||||
|
76
data/cif-field-names/mmtf-filter.csv
Normal file
76
data/cif-field-names/mmtf-filter.csv
Normal file
@@ -0,0 +1,76 @@
|
||||
cell.length_a
|
||||
cell.length_b
|
||||
cell.length_c
|
||||
cell.angle_alpha
|
||||
cell.angle_beta
|
||||
cell.angle_gamma
|
||||
|
||||
symmetry.space_group_name_H-M
|
||||
|
||||
entry.id
|
||||
|
||||
struct.title
|
||||
|
||||
pdbx_database_status.recvd_initial_deposition_date
|
||||
|
||||
pdbx_audit_revision_history.revision_date
|
||||
|
||||
struct_ncs_oper
|
||||
|
||||
pdbx_struct_assembly_gen
|
||||
|
||||
pdbx_struct_oper_list
|
||||
|
||||
entity.id
|
||||
entity.type
|
||||
entity.pdbx_description
|
||||
|
||||
entity_poly.entity_id
|
||||
entity_poly.pdbx_seq_one_letter_code
|
||||
entity_poly.pdbx_strand_id
|
||||
|
||||
exptl.method
|
||||
|
||||
refine.ls_d_res_low
|
||||
refine.ls_R_factor_R_free
|
||||
refine.ls_R_factor_R_work
|
||||
|
||||
atom_site.pdbx_formal_charge
|
||||
atom_site.label_atom_id
|
||||
atom_site.type_symbol
|
||||
|
||||
chem_comp.id
|
||||
chem_comp.type
|
||||
chem_comp.name
|
||||
|
||||
chem_comp_bond
|
||||
|
||||
atom_site.Cartn_x
|
||||
atom_site.Cartn_y
|
||||
atom_site.Cartn_z
|
||||
atom_site.B_iso_or_equiv
|
||||
atom_site.id
|
||||
atom_site.label_alt_id
|
||||
atom_site.occupancy
|
||||
atom_site.label_seq_id
|
||||
atom_site.label_comp_id
|
||||
|
||||
struct_sheet_range.id
|
||||
struct_sheet_range.beg_label_asym_id
|
||||
struct_sheet_range.beg_label_seq_id
|
||||
struct_sheet_range.pdbx_beg_PDB_ins_code
|
||||
struct_sheet_range.end_label_asym_id
|
||||
struct_sheet_range.end_label_seq_id
|
||||
struct_sheet_range.pdbx_end_PDB_ins_code
|
||||
struct_conf.conf_type_id
|
||||
struct_conf.id
|
||||
struct_conf.beg_label_asym_id
|
||||
struct_conf.beg_label_seq_id
|
||||
struct_conf.pdbx_beg_PDB_ins_code
|
||||
struct_conf.end_label_asym_id
|
||||
struct_conf.end_label_seq_id
|
||||
struct_conf.pdbx_end_PDB_ins_code
|
||||
|
||||
atom_site.pdbx_PDB_ins_code
|
||||
atom_site.label_asym_id
|
||||
atom_site.auth_asym_id
|
||||
|
@@ -54,12 +54,12 @@ Sometimes nodejs might run into problems with memory. This is usually resolved b
|
||||
|
||||
## Preprocessor
|
||||
|
||||
The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. ``node lib/servers/model/preprocess`` or ``model-server-preprocess`` binary from the NPM package.
|
||||
The preprocessor application allows to add custom data to CIF files and/or convert CIF to BinaryCIF. ``node lib/servers/servers/model/preprocess`` or ``model-server-preprocess`` binary from the NPM package.
|
||||
|
||||
|
||||
## Local Mode
|
||||
|
||||
The server can be run in local/file based mode using ``node lib/servers/model/query`` (``model-server-query`` binary from the NPM package).
|
||||
The server can be run in local/file based mode using ``node lib/servers/servers/model/query`` (``model-server-query`` binary from the NPM package).
|
||||
|
||||
Custom Properties
|
||||
=================
|
||||
|
||||
@@ -28,7 +28,7 @@ npm run build-tsc
|
||||
and run the server by
|
||||
|
||||
```
|
||||
node lib/servers/volume/server
|
||||
node lib/servers/servers/volume/server
|
||||
```
|
||||
|
||||
## From NPM
|
||||
@@ -60,11 +60,11 @@ Sometimes nodejs might run into problems with memory. This is usually resolved b
|
||||
## Preparing the Data
|
||||
|
||||
For the server to work, CCP4/MAP (models 0, 1, 2 are supported) input data need to be converted into a custom block format.
|
||||
To achieve this, use the ``pack`` application (``node lib/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
|
||||
To achieve this, use the ``pack`` application (``node lib/servers/servers/volume/pack`` or ``volume-server-pack`` binary from the NPM package).
|
||||
|
||||
## Local Mode
|
||||
|
||||
The program ``lib/servers/volume/pack`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
|
||||
The program ``lib/servers/servers/volume/pack`` (``volume-server-query`` in NPM package) can be used to query the data without running a http server.
|
||||
|
||||
## Navigating the Source Code
|
||||
|
||||
|
||||
7001
package-lock.json
generated
7001
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
81
package.json
81
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "0.6.2",
|
||||
"version": "0.7.0-dev.2",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -11,25 +11,29 @@
|
||||
"url": "https://github.com/molstar/molstar/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint src/**/*.ts",
|
||||
"lint": "eslint ./**/*.{ts,tsx}",
|
||||
"lint-fix": "eslint ./**/*.{ts,tsx} --fix",
|
||||
"test": "npm run lint && jest",
|
||||
"build": "npm run build-tsc && npm run build-extra && npm run build-webpack",
|
||||
"build-tsc": "tsc --incremental",
|
||||
"build-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/",
|
||||
"build-webpack": "webpack --mode production",
|
||||
"watch": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack\"",
|
||||
"build-viewer": "npm run build-tsc && npm run build-extra && npm run build-webpack-viewer",
|
||||
"build-tsc": "tsc --incremental && tsc --build tsconfig.servers.json --incremental",
|
||||
"build-extra": "cpx \"src/**/*.{scss,html,ico}\" 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\"",
|
||||
"watch-viewer": "concurrently -c \"green,gray,gray\" --names \"tsc,ext,wpc\" --kill-others \"npm:watch-tsc\" \"npm:watch-extra\" \"npm:watch-webpack-viewer\"",
|
||||
"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-extra": "cpx \"src/**/*.{scss,woff,woff2,ttf,otf,eot,svg,html,ico}\" lib/ --watch",
|
||||
"watch-servers": "tsc --build tsconfig.servers.json --watch --incremental",
|
||||
"watch-extra": "cpx \"src/**/*.{scss,html,ico}\" lib/ --watch",
|
||||
"watch-webpack": "webpack -w --mode development --display minimal",
|
||||
"watch-webpack-viewer": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.js",
|
||||
"watch-webpack-viewer-debug": "webpack -w --mode development --display errors-only --info-verbosity verbose --config ./webpack.config.viewer.debug.js",
|
||||
"serve": "http-server -p 1338",
|
||||
"model-server": "node lib/servers/model/server.js",
|
||||
"model-server-watch": "nodemon --watch lib lib/servers/model/server.js",
|
||||
"volume-server-test": "node lib/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
|
||||
"plugin-state": "node lib/servers/plugin-state/index.js",
|
||||
"model-server": "node lib/servers/servers/model/server.js",
|
||||
"model-server-watch": "nodemon --watch lib lib/servers/servers/model/server.js",
|
||||
"volume-server-test": "node lib/servers/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
|
||||
"plugin-state": "node lib/servers/servers/plugin-state/index.js",
|
||||
"preversion": "npm run test",
|
||||
"postversion": "git push && git push --tags",
|
||||
"prepublishOnly": "npm run test && npm run build"
|
||||
@@ -38,12 +42,14 @@
|
||||
"lib/"
|
||||
],
|
||||
"bin": {
|
||||
"model-server": "lib/servers/model/server.js",
|
||||
"model-server-query": "lib/servers/model/local.js",
|
||||
"model-server-preprocess": "lib/servers/model/preprocess.js",
|
||||
"volume-server": "lib/servers/volume/server.js",
|
||||
"volume-server-query": "lib/servers/volume/query.js",
|
||||
"volume-server-pack": "lib/servers/volume/pack.js"
|
||||
"cif2bcif": "lib/apps/cif2bcif/index.js",
|
||||
"cifschema": "lib/apps/cifschema/index.js",
|
||||
"model-server": "lib/servers/servers/model/server.js",
|
||||
"model-server-query": "lib/servers/servers/model/query.js",
|
||||
"model-server-preprocess": "lib/servers/servers/model/preprocess.js",
|
||||
"volume-server": "lib/servers/servers/volume/server.js",
|
||||
"volume-server-query": "lib/servers/servers/volume/query.js",
|
||||
"volume-server-pack": "lib/servers/servers/volume/pack.js"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ignoreRoot": [
|
||||
@@ -76,50 +82,52 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^1.13.1",
|
||||
"@graphql-codegen/cli": "^1.13.1",
|
||||
"@graphql-codegen/time": "^1.13.1",
|
||||
"@graphql-codegen/typescript": "^1.13.1",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.13.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.13.1",
|
||||
"@graphql-codegen/typescript-operations": "^1.13.1",
|
||||
"@graphql-codegen/add": "^1.13.2",
|
||||
"@graphql-codegen/cli": "^1.13.2",
|
||||
"@graphql-codegen/time": "^1.13.2",
|
||||
"@graphql-codegen/typescript": "^1.13.2",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^1.13.2",
|
||||
"@graphql-codegen/typescript-graphql-request": "^1.13.2",
|
||||
"@graphql-codegen/typescript-operations": "^1.13.2",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^2.26.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.28.0",
|
||||
"@typescript-eslint/parser": "^2.28.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"circular-dependency-plugin": "^5.2.0",
|
||||
"concurrently": "^5.1.0",
|
||||
"cpx2": "^2.0.0",
|
||||
"css-loader": "^3.4.2",
|
||||
"css-loader": "^3.5.2",
|
||||
"eslint": "^6.8.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.0.0",
|
||||
"fs-extra": "^9.0.0",
|
||||
"graphql": "^15.0.0",
|
||||
"http-server": "^0.12.1",
|
||||
"jest": "^25.2.7",
|
||||
"jest": "^25.3.0",
|
||||
"jest-raw-loader": "^1.0.1",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"node-sass": "^4.13.1",
|
||||
"pascal-case": "^3.1.1",
|
||||
"raw-loader": "^4.0.0",
|
||||
"raw-loader": "^4.0.1",
|
||||
"resolve-url-loader": "^3.1.1",
|
||||
"sass-loader": "^8.0.2",
|
||||
"simple-git": "^1.132.0",
|
||||
"style-loader": "^1.1.3",
|
||||
"ts-jest": "^25.3.1",
|
||||
"style-loader": "^1.1.4",
|
||||
"ts-jest": "^25.4.0",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.9.10",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@types/argparse": "^1.0.38",
|
||||
"@types/benchmark": "^1.0.31",
|
||||
"@types/compression": "1.7.0",
|
||||
"@types/express": "^4.17.4",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/jest": "^25.2.1",
|
||||
"@types/node": "^13.11.0",
|
||||
"@types/node-fetch": "^2.5.5",
|
||||
"@types/react": "^16.9.32",
|
||||
"@types/node": "^13.13.0",
|
||||
"@types/node-fetch": "^2.5.6",
|
||||
"@types/react": "^16.9.34",
|
||||
"@types/react-dom": "^16.9.6",
|
||||
"@types/swagger-ui-dist": "3.0.5",
|
||||
"argparse": "^1.0.10",
|
||||
@@ -127,7 +135,6 @@
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"graphql": "^14.6.0",
|
||||
"immer": "^6.0.3",
|
||||
"immutable": "^3.8.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginUIComponent } from '../../mol-plugin-ui/base';
|
||||
import * as React from 'react';
|
||||
import { TransformUpdaterControl } from '../../mol-plugin-ui/state/update-transform';
|
||||
|
||||
export class BasicWrapperControls extends PluginUIComponent {
|
||||
|
||||
render() {
|
||||
return <div style={{ overflowY: 'auto', display: 'block', height: '100%' }}>
|
||||
<TransformUpdaterControl nodeRef='asm' />
|
||||
<TransformUpdaterControl nodeRef='seq-visual' header={{ name: 'Sequence Visual' }} />
|
||||
<TransformUpdaterControl nodeRef='het-visual' header={{ name: 'HET Visual' }} />
|
||||
<TransformUpdaterControl nodeRef='water-visual' header={{ name: 'Water Visual' }} initiallyCollapsed={true} />
|
||||
<TransformUpdaterControl nodeRef='ihm-visual' header={{ name: 'I/HM Visual' }} initiallyCollapsed={true} />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomToastMessage extends PluginUIComponent {
|
||||
render() {
|
||||
return <>
|
||||
Custom <i>Toast</i> content. No timeout.
|
||||
</>;
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { StateBuilder } from '../../mol-state';
|
||||
import Expression from '../../mol-script/language/expression';
|
||||
import { ColorTheme } from '../../mol-theme/color';
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
type SupportedFormats = 'cif' | 'pdb'
|
||||
|
||||
export namespace StateHelper {
|
||||
export function download(b: StateBuilder.To<PSO.Root>, url: string, ref?: string) {
|
||||
return b.apply(StateTransforms.Data.Download, { url, isBinary: false }, { ref });
|
||||
}
|
||||
|
||||
export function getModel(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, modelIndex = 0) {
|
||||
const parsed = format === 'cif'
|
||||
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
|
||||
return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex });
|
||||
}
|
||||
|
||||
export function structure(b: StateBuilder.To<PSO.Molecule.Model>) {
|
||||
return b.apply(StateTransforms.Model.StructureFromModel, void 0, { tags: 'structure' })
|
||||
};
|
||||
|
||||
export function selectChain(b: StateBuilder.To<PSO.Molecule.Structure>, auth_asym_id: string) {
|
||||
const expression = MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), auth_asym_id])
|
||||
})
|
||||
return b.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression, label: `Chain ${auth_asym_id}` });
|
||||
}
|
||||
|
||||
export function select(b: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression) {
|
||||
return b.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression });
|
||||
}
|
||||
|
||||
export function selectSurroundingsOfFirstResidue(b: StateBuilder.To<PSO.Molecule.Structure>, comp_id: string, radius: number) {
|
||||
const expression = MS.struct.modifier.includeSurroundings({
|
||||
0: MS.struct.filter.first([
|
||||
MS.struct.generator.atomGroups({
|
||||
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), comp_id]),
|
||||
'group-by': MS.struct.atomProperty.macromolecular.residueKey()
|
||||
})
|
||||
]),
|
||||
radius
|
||||
})
|
||||
return b.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression, label: `Surr. ${comp_id} (${radius} ang)` });
|
||||
}
|
||||
|
||||
export function identityTransform(b: StateBuilder.To<PSO.Molecule.Structure>, m: Mat4) {
|
||||
return b.apply(StateTransforms.Model.TransformStructureConformation,
|
||||
{ transform: { name: 'components', params: { axis: Vec3.create(1, 0, 0), angle: 0, translation: Vec3.zero() } } },
|
||||
{ tags: 'transform' });
|
||||
}
|
||||
|
||||
export function transform(b: StateBuilder.To<PSO.Molecule.Structure>, matrix: Mat4) {
|
||||
return b.apply(StateTransforms.Model.TransformStructureConformation, {
|
||||
transform: { name: 'matrix', params: matrix }
|
||||
}, { tags: 'transform' });
|
||||
}
|
||||
|
||||
export function assemble(b: StateBuilder.To<PSO.Molecule.Model>, id?: string) {
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: id || 'deposited' }
|
||||
}
|
||||
}
|
||||
return b.apply(StateTransforms.Model.StructureFromModel, props, { tags: 'asm' })
|
||||
}
|
||||
|
||||
export function visual(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
createStructureRepresentationParams(ctx, void 0, { type: 'cartoon' }), { tags: 'seq-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
createStructureRepresentationParams(ctx, void 0, { type: 'ball-and-stick' }), { tags: 'het-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
export function ballsAndSticks(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression, color?: ColorTheme.BuiltIn) {
|
||||
visualRoot
|
||||
.apply(StateTransforms.Model.StructureSelectionFromExpression, { expression })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
createStructureRepresentationParams(ctx, void 0, { type: 'ball-and-stick', color }), { tags: 'het-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { PluginStateObject as PSO, PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in';
|
||||
import { StateBuilder, StateTransform } from '../../mol-state';
|
||||
import { StripedResidues } from './coloring';
|
||||
import { StaticSuperpositionTestData, buildStaticSuperposition, dynamicSuperpositionTest } from './superposition';
|
||||
import { PDBeStructureQualityReport } from '../../mol-plugin/behavior/dynamic/custom-props';
|
||||
import { CustomToastMessage } from './controls';
|
||||
import { EmptyLoci } from '../../mol-model/loci';
|
||||
import { StructureSelection } from '../../mol-model/structure';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
|
||||
type SupportedFormats = 'cif' | 'pdb'
|
||||
type LoadParams = { url: string, format?: SupportedFormats, assemblyId?: string }
|
||||
|
||||
class BasicWrapper {
|
||||
plugin: PluginContext;
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: {
|
||||
// left: 'none',
|
||||
// right: BasicWrapperControls
|
||||
}
|
||||
},
|
||||
components: {
|
||||
remoteState: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!);
|
||||
this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
|
||||
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
|
||||
}
|
||||
|
||||
private download(b: StateBuilder.To<PSO.Root>, url: string) {
|
||||
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
|
||||
}
|
||||
|
||||
private parse(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) {
|
||||
const parsed = format === 'cif'
|
||||
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
return parsed
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
|
||||
.apply(StateTransforms.Model.CustomModelProperties, { autoAttach: [StripedResidues.propertyProvider.descriptor.name], properties: {} }, { ref: 'props', state: { isGhost: false } })
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { ref: 'asm' });
|
||||
}
|
||||
|
||||
private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' }, { ref: 'seq' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'cartoon' }), { ref: 'seq-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'ball-and-stick' }), { ref: 'het-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'ball-and-stick', typeParams: { alpha: 0.51 } }), { ref: 'water-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'spheres' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'spacefill' }), { ref: 'ihm-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = '' }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
|
||||
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
|
||||
loadType = 'full';
|
||||
} else if (this.loadedParams.url === url) {
|
||||
if (state.select('asm').length > 0) loadType = 'update';
|
||||
}
|
||||
|
||||
let tree: StateBuilder.Root;
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref });
|
||||
tree = state.build();
|
||||
this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId));
|
||||
} else {
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
|
||||
tree = state.build();
|
||||
tree.to('asm').update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
this.loadedParams = { url, format, assemblyId };
|
||||
PluginCommands.Camera.Reset(this.plugin, { });
|
||||
}
|
||||
|
||||
setBackground(color: number) {
|
||||
const renderer = this.plugin.canvas3d!.props.renderer;
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: Color(color) } } });
|
||||
}
|
||||
|
||||
toggleSpin() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
const trackball = this.plugin.canvas3d.props.trackball;
|
||||
const spinning = trackball.spin;
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { trackball: { ...trackball, spin: !trackball.spin } } });
|
||||
if (!spinning) PluginCommands.Camera.Reset(this.plugin, { });
|
||||
}
|
||||
|
||||
animate = {
|
||||
modelIndex: {
|
||||
maxFPS: 8,
|
||||
onceForward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }) },
|
||||
onceBackward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }) },
|
||||
palindrome: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }) },
|
||||
loop: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }) },
|
||||
stop: () => this.plugin.state.animation.stop()
|
||||
}
|
||||
}
|
||||
|
||||
coloring = {
|
||||
applyStripes: async () => {
|
||||
const state = this.plugin.state.data;
|
||||
|
||||
const visuals = state.selectQ(q => q.ofTransformer(StateTransforms.Representation.StructureRepresentation3D));
|
||||
const tree = state.build();
|
||||
const colorTheme = { name: StripedResidues.propertyProvider.descriptor.name, params: this.plugin.representation.structure.themes.colorThemeRegistry.get(StripedResidues.propertyProvider.descriptor.name).defaultValues };
|
||||
|
||||
for (const v of visuals) {
|
||||
tree.to(v).update(old => ({ ...old, colorTheme }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
}
|
||||
}
|
||||
|
||||
interactivity = {
|
||||
highlightOn: () => {
|
||||
const seq_id = 7;
|
||||
const data = (this.plugin.state.data.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
const sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
|
||||
'residue-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), seq_id]),
|
||||
'group-by': Q.struct.atomProperty.macromolecular.residueKey()
|
||||
}), data);
|
||||
const loci = StructureSelection.toLociWithSourceUnits(sel);
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci });
|
||||
},
|
||||
clearHighlight: () => {
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
|
||||
}
|
||||
}
|
||||
|
||||
tests = {
|
||||
staticSuperposition: async () => {
|
||||
const state = this.plugin.state.data;
|
||||
const tree = buildStaticSuperposition(this.plugin, StaticSuperpositionTestData);
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: StateTransform.RootRef });
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
},
|
||||
dynamicSuperposition: async () => {
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state: this.plugin.state.data, ref: StateTransform.RootRef });
|
||||
await dynamicSuperpositionTest(this.plugin, ['1tqn', '2hhb', '4hhb'], 'HEM');
|
||||
},
|
||||
toggleValidationTooltip: async () => {
|
||||
const state = this.plugin.state.behaviors;
|
||||
const tree = state.build().to(PDBeStructureQualityReport.id).update(PDBeStructureQualityReport, p => ({ ...p, showTooltip: !p.showTooltip }));
|
||||
await PluginCommands.State.Update(this.plugin, { state, tree });
|
||||
},
|
||||
showToasts: () => {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Toast 1',
|
||||
message: 'This is an example text, timeout 3s',
|
||||
key: 'toast-1',
|
||||
timeoutMs: 3000
|
||||
});
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Toast 2',
|
||||
message: CustomToastMessage,
|
||||
key: 'toast-2'
|
||||
});
|
||||
},
|
||||
hideToasts: () => {
|
||||
PluginCommands.Toast.Hide(this.plugin, { key: 'toast-1' });
|
||||
PluginCommands.Toast.Hide(this.plugin, { key: 'toast-2' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).BasicMolStarWrapper = new BasicWrapper();
|
||||
@@ -1,108 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
// TODO: move to an "example"
|
||||
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { StateHelper } from './helpers';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { StateSelection, StateBuilder } from '../../mol-state';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
import { StructureSelection, QueryContext } from '../../mol-model/structure';
|
||||
import { superposeStructures } from '../../mol-model/structure/structure/util/superposition';
|
||||
import Expression from '../../mol-script/language/expression';
|
||||
|
||||
export type SuperpositionTestInput = {
|
||||
pdbId: string,
|
||||
auth_asym_id: string,
|
||||
matrix: Mat4
|
||||
}[];
|
||||
|
||||
// function getAxisAngleTranslation(m: Mat4) {
|
||||
// const translation = Mat4.getTranslation(Vec3.zero(), m);
|
||||
// const axis = Vec3.zero();
|
||||
// const angle = 180 / Math.PI * Quat.getAxisAngle(axis, Mat4.getRotation(Quat.zero(), m));
|
||||
// return { translation, axis, angle };
|
||||
// }
|
||||
|
||||
export function buildStaticSuperposition(ctx: PluginContext, src: SuperpositionTestInput) {
|
||||
const b = ctx.state.data.build().toRoot();
|
||||
for (const s of src) {
|
||||
StateHelper.visual(ctx,
|
||||
StateHelper.transform(
|
||||
StateHelper.selectChain(
|
||||
StateHelper.structure(
|
||||
StateHelper.getModel(StateHelper.download(b, `https://www.ebi.ac.uk/pdbe/static/entry/${s.pdbId}_updated.cif`), 'cif')),
|
||||
s.auth_asym_id
|
||||
),
|
||||
s.matrix
|
||||
)
|
||||
);
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
export const StaticSuperpositionTestData: SuperpositionTestInput = [
|
||||
{ pdbId: '1aj5', auth_asym_id: 'A', matrix: Mat4.identity() },
|
||||
{ pdbId: '1df0', auth_asym_id: 'B', matrix: Mat4.ofRows([
|
||||
[0.406, 0.879, 0.248, -200.633],
|
||||
[0.693, -0.473, 0.544, 73.403],
|
||||
[0.596, -0.049, -0.802, -14.209],
|
||||
[0, 0, 0, 1]] )},
|
||||
{ pdbId: '1dvi', auth_asym_id: 'A', matrix: Mat4.ofRows([
|
||||
[-0.053, -0.077, 0.996, -45.633],
|
||||
[-0.312, 0.949, 0.057, -12.255],
|
||||
[-0.949, -0.307, -0.074, 53.562],
|
||||
[0, 0, 0, 1]] )}
|
||||
];
|
||||
|
||||
export async function dynamicSuperpositionTest(ctx: PluginContext, src: string[], comp_id: string) {
|
||||
const state = ctx.state.data;
|
||||
|
||||
const structures = state.build().toRoot();
|
||||
for (const s of src) {
|
||||
StateHelper.structure(
|
||||
StateHelper.getModel(StateHelper.download(structures, `https://www.ebi.ac.uk/pdbe/static/entry/${s}_updated.cif`), 'cif'));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update(ctx, { state, tree: structures });
|
||||
|
||||
const pivot = MS.struct.filter.first([
|
||||
MS.struct.generator.atomGroups({
|
||||
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), comp_id]),
|
||||
'group-by': MS.struct.atomProperty.macromolecular.residueKey()
|
||||
})
|
||||
]);
|
||||
const rest = MS.struct.modifier.exceptBy({
|
||||
0: MS.struct.generator.all(),
|
||||
by: pivot
|
||||
});
|
||||
|
||||
const query = compile<StructureSelection>(pivot);
|
||||
const xs = state.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure));
|
||||
const selections = xs.map(s => StructureSelection.toLociWithCurrentUnits(query(new QueryContext(s.obj!.data))));
|
||||
|
||||
const transforms = superposeStructures(selections);
|
||||
const visuals = state.build();
|
||||
|
||||
siteVisual(ctx, StateHelper.selectSurroundingsOfFirstResidue(visuals.to(xs[0].transform.ref), 'HEM', 7), pivot, rest);
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
const root = visuals.to(xs[i].transform.ref);
|
||||
siteVisual(ctx,
|
||||
StateHelper.transform(StateHelper.selectSurroundingsOfFirstResidue(root, 'HEM', 7), transforms[i - 1].bTransform),
|
||||
pivot, rest);
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update(ctx, { state, tree: visuals });
|
||||
}
|
||||
|
||||
function siteVisual(ctx: PluginContext, b: StateBuilder.To<PSO.Molecule.Structure>, pivot: Expression, rest: Expression) {
|
||||
StateHelper.ballsAndSticks(ctx, b, pivot, 'residue-name');
|
||||
StateHelper.ballsAndSticks(ctx, b, rest, 'uniform');
|
||||
}
|
||||
@@ -4,57 +4,57 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as argparse from 'argparse'
|
||||
import * as util from 'util'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import * as zlib from 'zlib'
|
||||
import fetch from 'node-fetch'
|
||||
require('util.promisify').shim()
|
||||
const readFile = util.promisify(fs.readFile)
|
||||
const writeFile = util.promisify(fs.writeFile)
|
||||
import * as argparse from 'argparse';
|
||||
import * as util from 'util';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as zlib from 'zlib';
|
||||
import fetch from 'node-fetch';
|
||||
require('util.promisify').shim();
|
||||
const readFile = util.promisify(fs.readFile);
|
||||
const writeFile = util.promisify(fs.writeFile);
|
||||
|
||||
import { Progress } from '../../mol-task'
|
||||
import { Database, Table, DatabaseCollection } from '../../mol-data/db'
|
||||
import { CIF } from '../../mol-io/reader/cif'
|
||||
import { CifWriter } from '../../mol-io/writer/cif'
|
||||
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd'
|
||||
import { SetUtils } from '../../mol-util/set'
|
||||
import { DefaultMap } from '../../mol-util/map'
|
||||
import { Progress } from '../../mol-task';
|
||||
import { Database, Table, DatabaseCollection } from '../../mol-data/db';
|
||||
import { CIF } from '../../mol-io/reader/cif';
|
||||
import { CifWriter } from '../../mol-io/writer/cif';
|
||||
import { CCD_Schema } from '../../mol-io/reader/cif/schema/ccd';
|
||||
import { SetUtils } from '../../mol-util/set';
|
||||
import { DefaultMap } from '../../mol-util/map';
|
||||
import { mmCIF_chemCompBond_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
|
||||
export async function ensureAvailable(path: string, url: string) {
|
||||
if (FORCE_DOWNLOAD || !fs.existsSync(path)) {
|
||||
console.log(`downloading ${url}...`)
|
||||
const data = await fetch(url)
|
||||
console.log(`downloading ${url}...`);
|
||||
const data = await fetch(url);
|
||||
if (!fs.existsSync(DATA_DIR)) {
|
||||
fs.mkdirSync(DATA_DIR);
|
||||
}
|
||||
if (url.endsWith('.gz')) {
|
||||
await writeFile(path, zlib.gunzipSync(await data.buffer()))
|
||||
await writeFile(path, zlib.gunzipSync(await data.buffer()));
|
||||
} else {
|
||||
await writeFile(path, await data.text())
|
||||
await writeFile(path, await data.text());
|
||||
}
|
||||
console.log(`done downloading ${url}`)
|
||||
console.log(`done downloading ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function ensureDataAvailable() {
|
||||
await ensureAvailable(CCD_PATH, CCD_URL)
|
||||
await ensureAvailable(PVCD_PATH, PVCD_URL)
|
||||
await ensureAvailable(CCD_PATH, CCD_URL);
|
||||
await ensureAvailable(PVCD_PATH, PVCD_URL);
|
||||
}
|
||||
|
||||
export async function readFileAsCollection<S extends Database.Schema>(path: string, schema: S) {
|
||||
const parsed = await parseCif(await readFile(path, 'utf8'))
|
||||
return CIF.toDatabaseCollection(schema, parsed.result)
|
||||
const parsed = await parseCif(await readFile(path, 'utf8'));
|
||||
return CIF.toDatabaseCollection(schema, parsed.result);
|
||||
}
|
||||
|
||||
export async function readCCD() {
|
||||
return readFileAsCollection(CCD_PATH, CCD_Schema)
|
||||
return readFileAsCollection(CCD_PATH, CCD_Schema);
|
||||
}
|
||||
|
||||
export async function readPVCD() {
|
||||
return readFileAsCollection(PVCD_PATH, CCD_Schema)
|
||||
return readFileAsCollection(PVCD_PATH, CCD_Schema);
|
||||
}
|
||||
|
||||
async function parseCif(data: string | Uint8Array) {
|
||||
@@ -63,12 +63,12 @@ async function parseCif(data: string | Uint8Array) {
|
||||
const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
|
||||
console.timeEnd('parse cif');
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed
|
||||
return parsed;
|
||||
}
|
||||
|
||||
export function getEncodedCif(name: string, database: Database<Database.Schema>, binary = false) {
|
||||
const encoder = CifWriter.createEncoder({ binary, encoderName: 'mol*' });
|
||||
CifWriter.Encoder.writeDatabase(encoder, name, database)
|
||||
CifWriter.Encoder.writeDatabase(encoder, name, database);
|
||||
return encoder.getData();
|
||||
}
|
||||
|
||||
@@ -76,58 +76,58 @@ type CCB = Table<CCD_Schema['chem_comp_bond']>
|
||||
type CCA = Table<CCD_Schema['chem_comp_atom']>
|
||||
|
||||
function ccbKey(compId: string, atomId1: string, atomId2: string) {
|
||||
return atomId1 < atomId2 ? `${compId}:${atomId1}-${atomId2}` : `${compId}:${atomId2}-${atomId1}`
|
||||
return atomId1 < atomId2 ? `${compId}:${atomId1}-${atomId2}` : `${compId}:${atomId2}-${atomId1}`;
|
||||
}
|
||||
|
||||
function addChemCompBondToSet(set: Set<string>, ccb: CCB) {
|
||||
for (let i = 0, il = ccb._rowCount; i < il; ++i) {
|
||||
set.add(ccbKey(ccb.comp_id.value(i), ccb.atom_id_1.value(i), ccb.atom_id_2.value(i)))
|
||||
set.add(ccbKey(ccb.comp_id.value(i), ccb.atom_id_1.value(i), ccb.atom_id_2.value(i)));
|
||||
}
|
||||
return set
|
||||
return set;
|
||||
}
|
||||
|
||||
function addChemCompAtomToSet(set: Set<string>, cca: CCA) {
|
||||
for (let i = 0, il = cca._rowCount; i < il; ++i) {
|
||||
set.add(cca.atom_id.value(i))
|
||||
set.add(cca.atom_id.value(i));
|
||||
}
|
||||
return set
|
||||
return set;
|
||||
}
|
||||
|
||||
function checkAddingBondsFromPVCD(pvcd: DatabaseCollection<CCD_Schema>) {
|
||||
const ccbSetByParent = DefaultMap<string, Set<string>>(() => new Set())
|
||||
const ccbSetByParent = DefaultMap<string, Set<string>>(() => new Set());
|
||||
|
||||
for (const k in pvcd) {
|
||||
const { chem_comp, chem_comp_bond } = pvcd[k]
|
||||
const { chem_comp, chem_comp_bond } = pvcd[k];
|
||||
if (chem_comp_bond._rowCount) {
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0)
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0);
|
||||
if (parentIds.length === 0) {
|
||||
const set = ccbSetByParent.getDefault(chem_comp.id.value(0))
|
||||
addChemCompBondToSet(set, chem_comp_bond)
|
||||
const set = ccbSetByParent.getDefault(chem_comp.id.value(0));
|
||||
addChemCompBondToSet(set, chem_comp_bond);
|
||||
} else {
|
||||
for (let i = 0, il = parentIds.length; i < il; ++i) {
|
||||
const parentId = parentIds[i]
|
||||
const set = ccbSetByParent.getDefault(parentId)
|
||||
addChemCompBondToSet(set, chem_comp_bond)
|
||||
const parentId = parentIds[i];
|
||||
const set = ccbSetByParent.getDefault(parentId);
|
||||
addChemCompBondToSet(set, chem_comp_bond);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const k in pvcd) {
|
||||
const { chem_comp, chem_comp_atom, chem_comp_bond } = pvcd[k]
|
||||
const { chem_comp, chem_comp_atom, chem_comp_bond } = pvcd[k];
|
||||
if (chem_comp_bond._rowCount) {
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0)
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0);
|
||||
if (parentIds.length > 0) {
|
||||
for (let i = 0, il = parentIds.length; i < il; ++i) {
|
||||
const entryBonds = addChemCompBondToSet(new Set<string>(), chem_comp_bond)
|
||||
const entryAtoms = addChemCompAtomToSet(new Set<string>(), chem_comp_atom)
|
||||
const extraBonds = SetUtils.difference(ccbSetByParent.get(parentIds[i])!, entryBonds)
|
||||
const entryBonds = addChemCompBondToSet(new Set<string>(), chem_comp_bond);
|
||||
const entryAtoms = addChemCompAtomToSet(new Set<string>(), chem_comp_atom);
|
||||
const extraBonds = SetUtils.difference(ccbSetByParent.get(parentIds[i])!, entryBonds);
|
||||
extraBonds.forEach(bk => {
|
||||
const [a1, a2] = bk.split('|')
|
||||
const [a1, a2] = bk.split('|');
|
||||
if (entryAtoms.has(a1) && entryAtoms.has(a2)) {
|
||||
console.error(`Adding all PVCD bonds would wrongly add bond ${bk} for ${k}`)
|
||||
console.error(`Adding all PVCD bonds would wrongly add bond ${bk} for ${k}`);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,51 +135,51 @@ function checkAddingBondsFromPVCD(pvcd: DatabaseCollection<CCD_Schema>) {
|
||||
}
|
||||
|
||||
async function createBonds() {
|
||||
await ensureDataAvailable()
|
||||
const ccd = await readCCD()
|
||||
const pvcd = await readPVCD()
|
||||
await ensureDataAvailable();
|
||||
const ccd = await readCCD();
|
||||
const pvcd = await readPVCD();
|
||||
|
||||
const ccbSet = new Set<string>()
|
||||
const ccbSet = new Set<string>();
|
||||
|
||||
const comp_id: string[] = []
|
||||
const atom_id_1: string[] = []
|
||||
const atom_id_2: string[] = []
|
||||
const value_order: typeof mmCIF_chemCompBond_schema['value_order']['T'][] = []
|
||||
const pdbx_aromatic_flag: typeof mmCIF_chemCompBond_schema['pdbx_aromatic_flag']['T'][] = []
|
||||
const pdbx_stereo_config: typeof mmCIF_chemCompBond_schema['pdbx_stereo_config']['T'][] = []
|
||||
const molstar_protonation_variant: string[] = []
|
||||
const comp_id: string[] = [];
|
||||
const atom_id_1: string[] = [];
|
||||
const atom_id_2: string[] = [];
|
||||
const value_order: typeof mmCIF_chemCompBond_schema['value_order']['T'][] = [];
|
||||
const pdbx_aromatic_flag: typeof mmCIF_chemCompBond_schema['pdbx_aromatic_flag']['T'][] = [];
|
||||
const pdbx_stereo_config: typeof mmCIF_chemCompBond_schema['pdbx_stereo_config']['T'][] = [];
|
||||
const molstar_protonation_variant: string[] = [];
|
||||
|
||||
function addBonds(compId: string, ccb: CCB, protonationVariant: boolean) {
|
||||
for (let i = 0, il = ccb._rowCount; i < il; ++i) {
|
||||
const atomId1 = ccb.atom_id_1.value(i)
|
||||
const atomId2 = ccb.atom_id_2.value(i)
|
||||
const k = ccbKey(compId, atomId1, atomId2)
|
||||
const atomId1 = ccb.atom_id_1.value(i);
|
||||
const atomId2 = ccb.atom_id_2.value(i);
|
||||
const k = ccbKey(compId, atomId1, atomId2);
|
||||
if (!ccbSet.has(k)) {
|
||||
atom_id_1.push(atomId1)
|
||||
atom_id_2.push(atomId2)
|
||||
comp_id.push(compId)
|
||||
value_order.push(ccb.value_order.value(i))
|
||||
pdbx_aromatic_flag.push(ccb.pdbx_aromatic_flag.value(i))
|
||||
pdbx_stereo_config.push(ccb.pdbx_stereo_config.value(i))
|
||||
molstar_protonation_variant.push(protonationVariant ? 'Y' : 'N')
|
||||
ccbSet.add(k)
|
||||
atom_id_1.push(atomId1);
|
||||
atom_id_2.push(atomId2);
|
||||
comp_id.push(compId);
|
||||
value_order.push(ccb.value_order.value(i));
|
||||
pdbx_aromatic_flag.push(ccb.pdbx_aromatic_flag.value(i));
|
||||
pdbx_stereo_config.push(ccb.pdbx_stereo_config.value(i));
|
||||
molstar_protonation_variant.push(protonationVariant ? 'Y' : 'N');
|
||||
ccbSet.add(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check adding bonds from PVCD
|
||||
checkAddingBondsFromPVCD(pvcd)
|
||||
checkAddingBondsFromPVCD(pvcd);
|
||||
|
||||
// add bonds from PVCD
|
||||
for (const k in pvcd) {
|
||||
const { chem_comp, chem_comp_bond } = pvcd[k]
|
||||
const { chem_comp, chem_comp_bond } = pvcd[k];
|
||||
if (chem_comp_bond._rowCount) {
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0)
|
||||
const parentIds = chem_comp.mon_nstd_parent_comp_id.value(0);
|
||||
if (parentIds.length === 0) {
|
||||
addBonds(chem_comp.id.value(0), chem_comp_bond, false)
|
||||
addBonds(chem_comp.id.value(0), chem_comp_bond, false);
|
||||
} else {
|
||||
for (let i = 0, il = parentIds.length; i < il; ++i) {
|
||||
addBonds(parentIds[i], chem_comp_bond, true)
|
||||
addBonds(parentIds[i], chem_comp_bond, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,43 +187,43 @@ async function createBonds() {
|
||||
|
||||
// add bonds from CCD
|
||||
for (const k in ccd) {
|
||||
const { chem_comp, chem_comp_bond } = ccd[k]
|
||||
const { chem_comp, chem_comp_bond } = ccd[k];
|
||||
if (chem_comp_bond._rowCount) {
|
||||
addBonds(chem_comp.id.value(0), chem_comp_bond, false)
|
||||
addBonds(chem_comp.id.value(0), chem_comp_bond, false);
|
||||
}
|
||||
}
|
||||
|
||||
const bondTable = Table.ofArrays(mmCIF_chemCompBond_schema, {
|
||||
comp_id, atom_id_1, atom_id_2, value_order,
|
||||
pdbx_aromatic_flag, pdbx_stereo_config, molstar_protonation_variant
|
||||
})
|
||||
});
|
||||
|
||||
const bondDatabase = Database.ofTables(
|
||||
TABLE_NAME,
|
||||
{ chem_comp_bond: mmCIF_chemCompBond_schema },
|
||||
{ chem_comp_bond: bondTable }
|
||||
)
|
||||
);
|
||||
|
||||
return bondDatabase
|
||||
return bondDatabase;
|
||||
}
|
||||
|
||||
async function run(out: string, binary = false) {
|
||||
const bonds = await createBonds()
|
||||
const bonds = await createBonds();
|
||||
|
||||
const cif = getEncodedCif(TABLE_NAME, bonds, binary)
|
||||
const cif = getEncodedCif(TABLE_NAME, bonds, binary);
|
||||
if (!fs.existsSync(path.dirname(out))) {
|
||||
fs.mkdirSync(path.dirname(out));
|
||||
}
|
||||
writeFile(out, cif)
|
||||
writeFile(out, cif);
|
||||
}
|
||||
|
||||
const TABLE_NAME = 'CHEM_COMP_BONDS'
|
||||
const TABLE_NAME = 'CHEM_COMP_BONDS';
|
||||
|
||||
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')
|
||||
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif'
|
||||
const PVCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/aa-variants-v1.cif'
|
||||
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');
|
||||
const CCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/components.cif';
|
||||
const PVCD_URL = 'http://ftp.wwpdb.org/pub/pdb/data/monomers/aa-variants-v1.cif';
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
@@ -247,6 +247,6 @@ interface Args {
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
|
||||
const FORCE_DOWNLOAD = args.forceDownload
|
||||
const FORCE_DOWNLOAD = args.forceDownload;
|
||||
|
||||
run(args.out, args.binary)
|
||||
run(args.out, args.binary);
|
||||
|
||||
119
src/apps/cif2bcif/converter.ts
Normal file
119
src/apps/cif2bcif/converter.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
*/
|
||||
|
||||
import { CIF, CifCategory, getCifFieldType, CifField, CifFile } from '../../mol-io/reader/cif';
|
||||
import { CifWriter, EncodingStrategyHint } from '../../mol-io/writer/cif';
|
||||
import * as util from 'util';
|
||||
import * as fs from 'fs';
|
||||
import * as zlib from 'zlib';
|
||||
import { Progress, Task, RuntimeContext } from '../../mol-task';
|
||||
import { classifyFloatArray, classifyIntArray } from '../../mol-io/common/binary-cif';
|
||||
import { BinaryEncodingProvider } from '../../mol-io/writer/cif/encoder/binary';
|
||||
import { Category } from '../../mol-io/writer/cif/encoder';
|
||||
import { ReaderResult } from '../../mol-io/reader/result';
|
||||
|
||||
function showProgress(p: Progress) {
|
||||
process.stdout.write(`\r${new Array(80).join(' ')}`);
|
||||
process.stdout.write(`\r${Progress.format(p)}`);
|
||||
}
|
||||
|
||||
const readFileAsync = util.promisify(fs.readFile);
|
||||
const unzipAsync = util.promisify<zlib.InputType, Buffer>(zlib.unzip);
|
||||
|
||||
async function readFile(ctx: RuntimeContext, filename: string): Promise<ReaderResult<CifFile>> {
|
||||
const isGz = /\.gz$/i.test(filename);
|
||||
if (filename.match(/\.bcif/)) {
|
||||
let input = await readFileAsync(filename);
|
||||
if (isGz) input = await unzipAsync(input);
|
||||
return await CIF.parseBinary(new Uint8Array(input)).runInContext(ctx);
|
||||
} else {
|
||||
let str: string;
|
||||
if (isGz) {
|
||||
const data = await unzipAsync(await readFileAsync(filename));
|
||||
str = data.toString('utf8');
|
||||
} else {
|
||||
str = await readFileAsync(filename, 'utf8');
|
||||
}
|
||||
return await CIF.parseText(str).runInContext(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
async function getCIF(ctx: RuntimeContext, filename: string) {
|
||||
const parsed = await readFile(ctx, filename);
|
||||
if (parsed.isError) {
|
||||
throw new Error(parsed.toString());
|
||||
}
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
function getCategoryInstanceProvider(cat: CifCategory, fields: CifWriter.Field[]): CifWriter.Category {
|
||||
return {
|
||||
name: cat.name,
|
||||
instance: () => CifWriter.categoryInstance(fields, { data: cat, rowCount: cat.rowCount })
|
||||
};
|
||||
}
|
||||
|
||||
function classify(name: string, field: CifField): CifWriter.Field {
|
||||
const type = getCifFieldType(field);
|
||||
if (type['@type'] === 'str') {
|
||||
return { name, type: CifWriter.Field.Type.Str, value: field.str, valueKind: field.valueKind };
|
||||
} else if (type['@type'] === 'float') {
|
||||
const encoder = classifyFloatArray(field.toFloatArray({ array: Float64Array }));
|
||||
return CifWriter.Field.float(name, field.float, { valueKind: field.valueKind, encoder, typedArray: Float64Array });
|
||||
} else {
|
||||
const encoder = classifyIntArray(field.toIntArray({ array: Int32Array }));
|
||||
return CifWriter.Field.int(name, field.int, { valueKind: field.valueKind, encoder, typedArray: Int32Array });
|
||||
}
|
||||
}
|
||||
|
||||
export default function convert(path: string, asText = false, hints?: EncodingStrategyHint[], filter?: string) {
|
||||
return Task.create<Uint8Array>('BinaryCIF', async ctx => {
|
||||
const encodingProvider: BinaryEncodingProvider = hints
|
||||
? CifWriter.createEncodingProviderFromJsonConfig(hints)
|
||||
: { get: (c, f) => void 0 };
|
||||
const cif = await getCIF(ctx, path);
|
||||
|
||||
const encoder = CifWriter.createEncoder({
|
||||
binary: !asText,
|
||||
encoderName: 'mol*/ciftools cif2bcif',
|
||||
binaryAutoClassifyEncoding: true,
|
||||
binaryEncodingPovider: encodingProvider
|
||||
});
|
||||
|
||||
if (filter) {
|
||||
encoder.setFilter(Category.filterOf(filter));
|
||||
}
|
||||
|
||||
let maxProgress = 0;
|
||||
for (const b of cif.blocks) {
|
||||
maxProgress += b.categoryNames.length;
|
||||
for (const c of b.categoryNames) maxProgress += b.categories[c].fieldNames.length;
|
||||
}
|
||||
|
||||
let current = 0;
|
||||
for (const b of cif.blocks) {
|
||||
encoder.startDataBlock(b.header);
|
||||
for (const c of b.categoryNames) {
|
||||
const cat = b.categories[c];
|
||||
const fields: CifWriter.Field[] = [];
|
||||
for (const f of cat.fieldNames) {
|
||||
fields.push(classify(f, cat.getField(f)!));
|
||||
current++;
|
||||
if (ctx.shouldUpdate) await ctx.update({ message: 'Encoding...', current, max: maxProgress });
|
||||
}
|
||||
|
||||
encoder.writeCategory(getCategoryInstanceProvider(b.categories[c], fields));
|
||||
current++;
|
||||
if (ctx.shouldUpdate) await ctx.update({ message: 'Encoding...', current, max: maxProgress });
|
||||
}
|
||||
}
|
||||
await ctx.update('Exporting...');
|
||||
const ret = encoder.getData() as Uint8Array;
|
||||
await ctx.update('Done.\n');
|
||||
return ret;
|
||||
}).run(showProgress, 250);
|
||||
}
|
||||
67
src/apps/cif2bcif/index.ts
Normal file
67
src/apps/cif2bcif/index.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 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 argparse from 'argparse';
|
||||
import * as util from 'util';
|
||||
import * as fs from 'fs';
|
||||
import * as zlib from 'zlib';
|
||||
import convert from './converter';
|
||||
|
||||
require('util.promisify').shim();
|
||||
|
||||
async function process(srcPath: string, outPath: string, configPath?: string, filterPath?: string) {
|
||||
const config = configPath ? JSON.parse(fs.readFileSync(configPath, 'utf8')) : void 0;
|
||||
const filter = filterPath ? fs.readFileSync(filterPath, 'utf8') : void 0;
|
||||
|
||||
const res = await convert(srcPath, false, config, filter);
|
||||
await write(outPath, res);
|
||||
}
|
||||
|
||||
const zipAsync = util.promisify<zlib.InputType, Buffer>(zlib.gzip);
|
||||
|
||||
async function write(outPath: string, res: Uint8Array) {
|
||||
const isGz = /\.gz$/i.test(outPath);
|
||||
if (isGz) {
|
||||
res = await zipAsync(res);
|
||||
}
|
||||
fs.writeFileSync(outPath, res);
|
||||
}
|
||||
|
||||
function run(args: Args) {
|
||||
process(args.src, args.out, args.config, args.filter);
|
||||
}
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
description: 'Convert any CIF file to a BCIF file'
|
||||
});
|
||||
parser.addArgument([ 'src' ], {
|
||||
help: 'Source CIF path'
|
||||
});
|
||||
parser.addArgument([ 'out' ], {
|
||||
help: 'Output BCIF path'
|
||||
});
|
||||
parser.addArgument([ '-c', '--config' ], {
|
||||
help: 'Optional encoding strategy/precision config path',
|
||||
required: false
|
||||
});
|
||||
parser.addArgument([ '-f', '--filter' ], {
|
||||
help: 'Optional filter whitelist/blacklist path',
|
||||
required: false
|
||||
});
|
||||
|
||||
interface Args {
|
||||
src: string
|
||||
out: string
|
||||
config?: string
|
||||
filter?: string
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
|
||||
if (args) {
|
||||
run(args);
|
||||
}
|
||||
274
src/apps/cifschema/index.ts
Normal file
274
src/apps/cifschema/index.ts
Normal file
@@ -0,0 +1,274 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import * as argparse from 'argparse';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
import { parseCsv } from '../../mol-io/reader/csv/parser';
|
||||
import { CifFrame, CifBlock } from '../../mol-io/reader/cif';
|
||||
import parseText from '../../mol-io/reader/cif/text/parser';
|
||||
import { generateSchema } from './util/cif-dic';
|
||||
import { generate } from './util/generate';
|
||||
import { Filter, Database } from './util/schema';
|
||||
import { parseImportGet } from './util/helper';
|
||||
|
||||
function getDicVersion(block: CifBlock) {
|
||||
return block.categories.dictionary.getField('version')!.str(0);
|
||||
}
|
||||
|
||||
function getDicNamespace(block: CifBlock) {
|
||||
return block.categories.dictionary.getField('namespace')!.str(0);
|
||||
}
|
||||
|
||||
async function runGenerateSchemaMmcif(name: string, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
|
||||
await ensureMmcifDicAvailable();
|
||||
const mmcifDic = await parseText(fs.readFileSync(MMCIF_DIC_PATH, 'utf8')).run();
|
||||
if (mmcifDic.isError) throw mmcifDic;
|
||||
|
||||
await ensureIhmDicAvailable();
|
||||
const ihmDic = await parseText(fs.readFileSync(IHM_DIC_PATH, 'utf8')).run();
|
||||
if (ihmDic.isError) throw ihmDic;
|
||||
|
||||
await ensureCarbBranchDicAvailable();
|
||||
const carbBranchDic = await parseText(fs.readFileSync(CARB_BRANCH_DIC_PATH, 'utf8')).run();
|
||||
if (carbBranchDic.isError) throw carbBranchDic;
|
||||
|
||||
await ensureCarbCompDicAvailable();
|
||||
const carbCompDic = await parseText(fs.readFileSync(CARB_COMP_DIC_PATH, 'utf8')).run();
|
||||
if (carbCompDic.isError) throw carbCompDic;
|
||||
|
||||
const mmcifDicVersion = getDicVersion(mmcifDic.result.blocks[0]);
|
||||
const ihmDicVersion = getDicVersion(ihmDic.result.blocks[0]);
|
||||
const carbDicVersion = 'draft';
|
||||
const version = `Dictionary versions: mmCIF ${mmcifDicVersion}, IHM ${ihmDicVersion}, CARB ${carbDicVersion}.`;
|
||||
|
||||
const frames: CifFrame[] = [...mmcifDic.result.blocks[0].saveFrames, ...ihmDic.result.blocks[0].saveFrames, ...carbBranchDic.result.blocks[0].saveFrames, ...carbCompDic.result.blocks[0].saveFrames];
|
||||
const schema = generateSchema(frames);
|
||||
|
||||
await runGenerateSchema(name, version, schema, fieldNamesPath, typescript, out, moldbImportPath, addAliases);
|
||||
}
|
||||
|
||||
async function runGenerateSchemaCifCore(name: string, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
|
||||
await ensureCifCoreDicAvailable();
|
||||
const cifCoreDic = await parseText(fs.readFileSync(CIF_CORE_DIC_PATH, 'utf8')).run();
|
||||
if (cifCoreDic.isError) throw cifCoreDic;
|
||||
|
||||
const cifCoreDicVersion = getDicVersion(cifCoreDic.result.blocks[0]);
|
||||
const version = `Dictionary versions: CifCore ${cifCoreDicVersion}.`;
|
||||
|
||||
const frames: CifFrame[] = [...cifCoreDic.result.blocks[0].saveFrames];
|
||||
const imports = await resolveImports(frames, DIC_DIR);
|
||||
const schema = generateSchema(frames, imports);
|
||||
|
||||
await runGenerateSchema(name, version, schema, fieldNamesPath, typescript, out, moldbImportPath, addAliases);
|
||||
}
|
||||
|
||||
async function resolveImports(frames: CifFrame[], baseDir: string): Promise<Map<string, CifFrame[]>> {
|
||||
const imports = new Map<string, CifFrame[]>();
|
||||
|
||||
for (const d of frames) {
|
||||
if ('import' in d.categories) {
|
||||
const importGet = parseImportGet(d.categories['import'].getField('get')!.str(0));
|
||||
for (const g of importGet) {
|
||||
const { file } = g;
|
||||
if (!file) continue;
|
||||
if (imports.has(file)) continue;
|
||||
|
||||
const dic = await parseText(fs.readFileSync(path.join(baseDir, file), 'utf8')).run();
|
||||
if (dic.isError) throw dic;
|
||||
|
||||
imports.set(file, [...dic.result.blocks[0].saveFrames]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
async function runGenerateSchemaDic(name: string, dicPath: string, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
|
||||
const dic = await parseText(fs.readFileSync(dicPath, 'utf8')).run();
|
||||
if (dic.isError) throw dic;
|
||||
|
||||
const dicVersion = getDicVersion(dic.result.blocks[0]);
|
||||
const dicName = getDicNamespace(dic.result.blocks[0]);
|
||||
const version = `Dictionary versions: ${dicName} ${dicVersion}.`;
|
||||
|
||||
const frames: CifFrame[] = [...dic.result.blocks[0].saveFrames];
|
||||
const imports = await resolveImports(frames, path.dirname(dicPath));
|
||||
const schema = generateSchema(frames, imports);
|
||||
|
||||
await runGenerateSchema(name, version, schema, fieldNamesPath, typescript, out, moldbImportPath, addAliases);
|
||||
}
|
||||
|
||||
async function runGenerateSchema(name: string, version: string, schema: Database, fieldNamesPath: string, typescript = false, out: string, moldbImportPath: string, addAliases: boolean) {
|
||||
const filter = fieldNamesPath ? await getFieldNamesFilter(fieldNamesPath) : undefined;
|
||||
const output = typescript ? generate(name, version, schema, filter, moldbImportPath, addAliases) : JSON.stringify(schema, undefined, 4);
|
||||
|
||||
if (out) {
|
||||
fs.writeFileSync(out, output);
|
||||
} else {
|
||||
console.log(output);
|
||||
}
|
||||
}
|
||||
|
||||
async function getFieldNamesFilter(fieldNamesPath: string): Promise<Filter> {
|
||||
const fieldNamesStr = fs.readFileSync(fieldNamesPath, 'utf8');
|
||||
const parsed = await parseCsv(fieldNamesStr, { noColumnNames: true }).run();
|
||||
if (parsed.isError) throw parser.error;
|
||||
const csvFile = parsed.result;
|
||||
|
||||
const fieldNamesCol = csvFile.table.getColumn('0');
|
||||
if (!fieldNamesCol) throw 'error getting fields columns';
|
||||
const fieldNames = fieldNamesCol.toStringArray();
|
||||
|
||||
const filter: Filter = {};
|
||||
fieldNames.forEach((name, i) => {
|
||||
const [ category, field ] = name.split('.');
|
||||
// console.log(category, field)
|
||||
if (!filter[ category ]) filter[ category ] = {};
|
||||
filter[ category ][ field ] = true;
|
||||
});
|
||||
return filter;
|
||||
}
|
||||
|
||||
async function ensureMmcifDicAvailable() { await ensureDicAvailable(MMCIF_DIC_PATH, MMCIF_DIC_URL); }
|
||||
async function ensureIhmDicAvailable() { await ensureDicAvailable(IHM_DIC_PATH, IHM_DIC_URL); }
|
||||
async function ensureCarbBranchDicAvailable() { await ensureDicAvailable(CARB_BRANCH_DIC_PATH, CARB_BRANCH_DIC_URL); }
|
||||
async function ensureCarbCompDicAvailable() { await ensureDicAvailable(CARB_COMP_DIC_PATH, CARB_COMP_DIC_URL); }
|
||||
async function ensureCifCoreDicAvailable() {
|
||||
await ensureDicAvailable(CIF_CORE_DIC_PATH, CIF_CORE_DIC_URL);
|
||||
await ensureDicAvailable(CIF_CORE_ENUM_PATH, CIF_CORE_ENUM_URL);
|
||||
await ensureDicAvailable(CIF_CORE_ATTR_PATH, CIF_CORE_ATTR_URL);
|
||||
}
|
||||
|
||||
async function ensureDicAvailable(dicPath: string, dicUrl: string) {
|
||||
if (FORCE_DIC_DOWNLOAD || !fs.existsSync(dicPath)) {
|
||||
const name = dicUrl.substr(dicUrl.lastIndexOf('/') + 1);
|
||||
console.log(`downloading ${name}...`);
|
||||
const data = await fetch(dicUrl);
|
||||
if (!fs.existsSync(DIC_DIR)) {
|
||||
fs.mkdirSync(DIC_DIR);
|
||||
}
|
||||
fs.writeFileSync(dicPath, await data.text());
|
||||
console.log(`done downloading ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
const DIC_DIR = path.resolve(__dirname, '../../../build/dics/');
|
||||
const MMCIF_DIC_PATH = `${DIC_DIR}/mmcif_pdbx_v50.dic`;
|
||||
const MMCIF_DIC_URL = 'http://mmcif.wwpdb.org/dictionaries/ascii/mmcif_pdbx_v50.dic';
|
||||
const IHM_DIC_PATH = `${DIC_DIR}/ihm-extension.dic`;
|
||||
const IHM_DIC_URL = 'https://raw.githubusercontent.com/ihmwg/IHM-dictionary/master/ihm-extension.dic';
|
||||
const CARB_BRANCH_DIC_PATH = `${DIC_DIR}/entity_branch-extension.dic`;
|
||||
const CARB_BRANCH_DIC_URL = 'https://raw.githubusercontent.com/pdbxmmcifwg/carbohydrate-extension/master/dict/entity_branch-extension.dic';
|
||||
const CARB_COMP_DIC_PATH = `${DIC_DIR}/chem_comp-extension.dic`;
|
||||
const CARB_COMP_DIC_URL = 'https://raw.githubusercontent.com/pdbxmmcifwg/carbohydrate-extension/master/dict/chem_comp-extension.dic';
|
||||
|
||||
const CIF_CORE_DIC_PATH = `${DIC_DIR}/cif_core.dic`;
|
||||
const CIF_CORE_DIC_URL = 'https://raw.githubusercontent.com/COMCIFS/cif_core/master/cif_core.dic';
|
||||
const CIF_CORE_ENUM_PATH = `${DIC_DIR}/templ_enum.cif`;
|
||||
const CIF_CORE_ENUM_URL = 'https://raw.githubusercontent.com/COMCIFS/cif_core/master/templ_enum.cif';
|
||||
const CIF_CORE_ATTR_PATH = `${DIC_DIR}/templ_attr.cif`;
|
||||
const CIF_CORE_ATTR_URL = 'https://raw.githubusercontent.com/COMCIFS/cif_core/master/templ_attr.cif';
|
||||
|
||||
const parser = new argparse.ArgumentParser({
|
||||
addHelp: true,
|
||||
description: 'Create schema from mmcif dictionary (v50 plus IHM and entity_branch extensions, downloaded from wwPDB)'
|
||||
});
|
||||
parser.addArgument([ '--preset', '-p' ], {
|
||||
defaultValue: '',
|
||||
choices: ['', 'mmCIF', 'CCD', 'BIRD', 'CifCore'],
|
||||
help: 'Preset name'
|
||||
});
|
||||
parser.addArgument([ '--name', '-n' ], {
|
||||
defaultValue: '',
|
||||
help: 'Schema name'
|
||||
});
|
||||
parser.addArgument([ '--out', '-o' ], {
|
||||
help: 'Generated schema output path, if not given printed to stdout'
|
||||
});
|
||||
parser.addArgument([ '--targetFormat', '-tf' ], {
|
||||
defaultValue: 'typescript-molstar',
|
||||
choices: ['typescript-molstar', 'json-internal'],
|
||||
help: 'Target format'
|
||||
});
|
||||
parser.addArgument([ '--dicPath', '-d' ], {
|
||||
defaultValue: '',
|
||||
help: 'Path to dictionary'
|
||||
});
|
||||
parser.addArgument([ '--fieldNamesPath', '-fn' ], {
|
||||
defaultValue: '',
|
||||
help: 'Field names to include'
|
||||
});
|
||||
parser.addArgument([ '--forceDicDownload', '-f' ], {
|
||||
action: 'storeTrue',
|
||||
help: 'Force download of dictionaries'
|
||||
});
|
||||
parser.addArgument([ '--moldataImportPath', '-mip' ], {
|
||||
defaultValue: 'molstar/lib/mol-data',
|
||||
help: 'mol-data import path (for typescript target only)'
|
||||
});
|
||||
parser.addArgument([ '--addAliases', '-aa' ], {
|
||||
action: 'storeTrue',
|
||||
help: 'Add field name/path aliases'
|
||||
});
|
||||
interface Args {
|
||||
name: string
|
||||
preset: '' | 'mmCIF' | 'CCD' | 'BIRD' | 'CifCore'
|
||||
forceDicDownload: boolean
|
||||
dic: '' | 'mmCIF' | 'CifCore'
|
||||
dicPath: string,
|
||||
fieldNamesPath: string
|
||||
targetFormat: 'typescript-molstar' | 'json-internal'
|
||||
out: string,
|
||||
moldataImportPath: string
|
||||
addAliases: boolean
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
|
||||
const FORCE_DIC_DOWNLOAD = args.forceDicDownload;
|
||||
|
||||
switch (args.preset) {
|
||||
case 'mmCIF':
|
||||
args.name = 'mmCIF';
|
||||
args.dic = 'mmCIF';
|
||||
args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/mmcif-field-names.csv');
|
||||
break;
|
||||
case 'CCD':
|
||||
args.name = 'CCD';
|
||||
args.dic = 'mmCIF';
|
||||
args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/ccd-field-names.csv');
|
||||
break;
|
||||
case 'BIRD':
|
||||
args.name = 'BIRD';
|
||||
args.dic = 'mmCIF';
|
||||
args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/bird-field-names.csv');
|
||||
break;
|
||||
case 'CifCore':
|
||||
args.name = 'CifCore';
|
||||
args.dic = 'CifCore';
|
||||
args.fieldNamesPath = path.resolve(__dirname, '../../../data/cif-field-names/cif-core-field-names.csv');
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.name) {
|
||||
const typescript = args.targetFormat === 'typescript-molstar';
|
||||
if (args.dicPath) {
|
||||
runGenerateSchemaDic(args.name, args.dicPath, args.fieldNamesPath, typescript, args.out, args.moldataImportPath, args.addAliases).catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
} else if (args.dic === 'mmCIF') {
|
||||
runGenerateSchemaMmcif(args.name, args.fieldNamesPath, typescript, args.out, args.moldataImportPath, args.addAliases).catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
} else if (args.dic === 'CifCore') {
|
||||
runGenerateSchemaCifCore(args.name, args.fieldNamesPath, typescript, args.out, args.moldataImportPath, args.addAliases).catch(e => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
475
src/apps/cifschema/util/cif-dic.ts
Normal file
475
src/apps/cifschema/util/cif-dic.ts
Normal file
@@ -0,0 +1,475 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Database, Column, EnumCol, StrCol, IntCol, ListCol, FloatCol, CoordCol, MatrixCol, VectorCol } from './schema';
|
||||
import { parseImportGet } from './helper';
|
||||
import * as Data from '../../../mol-io/reader/cif/data-model';
|
||||
import { CifFrame } from '../../../mol-io/reader/cif/data-model';
|
||||
|
||||
export function getFieldType(type: string, description: string, values?: string[], container?: string): Column {
|
||||
switch (type) {
|
||||
// mmCIF
|
||||
case 'code':
|
||||
case 'ucode':
|
||||
case 'line':
|
||||
case 'uline':
|
||||
case 'text':
|
||||
case 'char':
|
||||
case 'uchar3':
|
||||
case 'uchar1':
|
||||
case 'boolean':
|
||||
return values && values.length ? EnumCol(values, 'str', description) : StrCol(description);
|
||||
case 'aliasname':
|
||||
case 'name':
|
||||
case 'idname':
|
||||
case 'any':
|
||||
case 'atcode':
|
||||
case 'fax':
|
||||
case 'phone':
|
||||
case 'email':
|
||||
case 'code30':
|
||||
case 'seq-one-letter-code':
|
||||
case 'author':
|
||||
case 'orcid_id':
|
||||
case 'sequence_dep':
|
||||
case 'pdb_id':
|
||||
case 'emd_id':
|
||||
// todo, consider adding specialised fields
|
||||
case 'yyyy-mm-dd':
|
||||
case 'yyyy-mm-dd:hh:mm':
|
||||
case 'yyyy-mm-dd:hh:mm-flex':
|
||||
case 'int-range':
|
||||
case 'float-range':
|
||||
case 'binary':
|
||||
case 'operation_expression':
|
||||
case 'point_symmetry':
|
||||
case '4x3_matrix':
|
||||
case '3x4_matrices':
|
||||
case 'point_group':
|
||||
case 'point_group_helical':
|
||||
case 'symmetry_operation':
|
||||
case 'date_dep':
|
||||
case 'url':
|
||||
case 'symop':
|
||||
case 'exp_data_doi':
|
||||
case 'asym_id':
|
||||
return StrCol(description);
|
||||
case 'int':
|
||||
case 'non_negative_int':
|
||||
case 'positive_int':
|
||||
return values && values.length ? EnumCol(values, 'int', description) : IntCol(description);
|
||||
case 'float':
|
||||
return FloatCol(description);
|
||||
case 'ec-type':
|
||||
case 'ucode-alphanum-csv':
|
||||
case 'id_list':
|
||||
return ListCol('str', ',', description);
|
||||
case 'id_list_spc':
|
||||
return ListCol('str', ' ', description);
|
||||
|
||||
// cif
|
||||
case 'Text':
|
||||
case 'Code':
|
||||
case 'Complex':
|
||||
case 'Symop':
|
||||
case 'List':
|
||||
case 'List(Real,Real)':
|
||||
case 'List(Real,Real,Real,Real)':
|
||||
case 'Date':
|
||||
case 'Datetime':
|
||||
case 'Tag':
|
||||
case 'Implied':
|
||||
return wrapContainer('str', ',', description, container);
|
||||
case 'Real':
|
||||
return wrapContainer('float', ',', description, container);
|
||||
case 'Integer':
|
||||
return wrapContainer('int', ',', description, container);
|
||||
|
||||
}
|
||||
console.log(`unknown type '${type}'`);
|
||||
return StrCol(description);
|
||||
}
|
||||
|
||||
function ColFromType(type: 'int' | 'str' | 'float' | 'coord', description: string): Column {
|
||||
switch (type) {
|
||||
case 'int': return IntCol(description);
|
||||
case 'str': return StrCol(description);
|
||||
case 'float': return FloatCol(description);
|
||||
case 'coord': return CoordCol(description);
|
||||
}
|
||||
}
|
||||
|
||||
function wrapContainer(type: 'int' | 'str' | 'float' | 'coord', separator: string, description: string, container?: string) {
|
||||
return container && container === 'List' ? ListCol(type, separator, description) : ColFromType(type, description);
|
||||
}
|
||||
|
||||
type FrameCategories = { [category: string]: Data.CifFrame }
|
||||
type FrameLinks = { [k: string]: string }
|
||||
|
||||
interface FrameData {
|
||||
categories: FrameCategories
|
||||
links: FrameLinks
|
||||
}
|
||||
|
||||
type Imports = Map<string, CifFrame[]>
|
||||
|
||||
function getImportFrames(d: Data.CifFrame, imports: Imports) {
|
||||
const frames: Data.CifFrame[] = [];
|
||||
if (!('import' in d.categories)) return frames;
|
||||
|
||||
const importGet = parseImportGet(d.categories['import'].getField('get')!.str(0));
|
||||
for (const g of importGet) {
|
||||
const { file, save } = g;
|
||||
if (!file || !save) {
|
||||
console.warn(`missing 'save' or 'file' for import in '${d.header}'`);
|
||||
continue;
|
||||
}
|
||||
const importFrames = imports.get(file);
|
||||
if (!importFrames) {
|
||||
console.warn(`missing '${file}' entry in imports`);
|
||||
continue;
|
||||
}
|
||||
const importSave = importFrames.find(id => id.header.toLowerCase() === save.toLowerCase());
|
||||
if (!importSave) {
|
||||
console.warn(`missing '${save}' save frame in '${file}'`);
|
||||
continue;
|
||||
}
|
||||
|
||||
frames.push(importSave);
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
/** get field from given or linked category */
|
||||
function getField(category: string, field: string, d: Data.CifFrame, imports: Imports, ctx: FrameData): Data.CifField|undefined {
|
||||
const { categories, links } = ctx;
|
||||
const cat = d.categories[category];
|
||||
if (cat) {
|
||||
return cat.getField(field);
|
||||
} else if (d.header in links) {
|
||||
const linkName = links[d.header];
|
||||
if (linkName in categories) {
|
||||
return getField(category, field, categories[linkName], imports, ctx);
|
||||
} else {
|
||||
// console.log(`link '${linkName}' not found`)
|
||||
}
|
||||
} else {
|
||||
const importFrames = getImportFrames(d, imports);
|
||||
for (const idf of importFrames) {
|
||||
return getField(category, field, idf, imports, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getEnums(d: Data.CifFrame, imports: Imports, ctx: FrameData) {
|
||||
const value = getField('item_enumeration', 'value', d, imports, ctx);
|
||||
const enums: string[] = [];
|
||||
if (value) {
|
||||
for (let i = 0; i < value.rowCount; ++i) {
|
||||
enums.push(value.str(i));
|
||||
// console.log(value.str(i))
|
||||
}
|
||||
return enums;
|
||||
} else {
|
||||
// console.log(`item_enumeration.value not found for '${d.header}'`)
|
||||
}
|
||||
}
|
||||
|
||||
function getContainer(d: Data.CifFrame, imports: Imports, ctx: FrameData) {
|
||||
const value = getField('type', 'container', d, imports, ctx);
|
||||
return value ? value.str(0) : undefined;
|
||||
}
|
||||
|
||||
function getCode(d: Data.CifFrame, imports: Imports, ctx: FrameData): [string, string[] | undefined, string | undefined ] | undefined {
|
||||
const code = getField('item_type', 'code', d, imports, ctx) || getField('type', 'contents', d, imports, ctx);
|
||||
if (code) {
|
||||
return [ code.str(0), getEnums(d, imports, ctx), getContainer(d, imports, ctx) ];
|
||||
} else {
|
||||
console.log(`item_type.code or type.contents not found for '${d.header}'`);
|
||||
}
|
||||
}
|
||||
|
||||
function getSubCategory(d: Data.CifFrame, imports: Imports, ctx: FrameData): string | undefined {
|
||||
const value = getField('item_sub_category', 'id', d, imports, ctx);
|
||||
if (value) {
|
||||
return value.str(0);
|
||||
}
|
||||
}
|
||||
|
||||
function getDescription(d: Data.CifFrame, imports: Imports, ctx: FrameData): string | undefined {
|
||||
const value = getField('item_description', 'description', d, imports, ctx) || getField('description', 'text', d, imports, ctx);
|
||||
if (value) {
|
||||
// trim (after newlines) and remove references to square brackets
|
||||
return value.str(0).trim()
|
||||
.replace(/(\r\n|\r|\n)([ \t]+)/g, '\n')
|
||||
.replace(/(\[[1-3]\])+ element/, 'elements')
|
||||
.replace(/(\[[1-3]\])+/, '');
|
||||
}
|
||||
}
|
||||
|
||||
function getAliases(d: Data.CifFrame, imports: Imports, ctx: FrameData): string[] | undefined {
|
||||
const value = getField('item_aliases', 'alias_name', d, imports, ctx) || getField('alias', 'definition_id', d, imports, ctx);
|
||||
return value ? value.toStringArray().map(v => v.substr(1)) : undefined;
|
||||
}
|
||||
|
||||
const reMatrixField = /\[[1-3]\]\[[1-3]\]/;
|
||||
const reVectorField = /\[[1-3]\]/;
|
||||
|
||||
const FORCE_INT_FIELDS = [
|
||||
'_atom_site.id',
|
||||
'_atom_site.auth_seq_id',
|
||||
'_atom_site_anisotrop.id',
|
||||
'_pdbx_struct_mod_residue.auth_seq_id',
|
||||
'_struct_conf.beg_auth_seq_id',
|
||||
'_struct_conf.end_auth_seq_id',
|
||||
'_struct_conn.ptnr1_auth_seq_id',
|
||||
'_struct_conn.ptnr2_auth_seq_id',
|
||||
'_struct_sheet_range.beg_auth_seq_id',
|
||||
'_struct_sheet_range.end_auth_seq_id',
|
||||
];
|
||||
|
||||
const FORCE_MATRIX_FIELDS_MAP: { [k: string]: string } = {
|
||||
'atom_site_aniso.U_11': 'U',
|
||||
'atom_site_aniso.U_22': 'U',
|
||||
'atom_site_aniso.U_33': 'U',
|
||||
'atom_site_aniso.U_23': 'U',
|
||||
'atom_site_aniso.U_13': 'U',
|
||||
'atom_site_aniso.U_12': 'U',
|
||||
'atom_site_aniso.U_11_su': 'U_su',
|
||||
'atom_site_aniso.U_22_su': 'U_su',
|
||||
'atom_site_aniso.U_33_su': 'U_su',
|
||||
'atom_site_aniso.U_23_su': 'U_su',
|
||||
'atom_site_aniso.U_13_su': 'U_su',
|
||||
'atom_site_aniso.U_12_su': 'U_su',
|
||||
};
|
||||
const FORCE_MATRIX_FIELDS = Object.keys(FORCE_MATRIX_FIELDS_MAP);
|
||||
|
||||
const EXTRA_ALIASES: Database['aliases'] = {
|
||||
'atom_site_aniso.U': [
|
||||
'atom_site_anisotrop_U'
|
||||
],
|
||||
'atom_site_aniso.U_su': [
|
||||
'atom_site_aniso_U_esd',
|
||||
'atom_site_anisotrop_U_esd',
|
||||
],
|
||||
};
|
||||
|
||||
const COMMA_SEPARATED_LIST_FIELDS = [
|
||||
'_atom_site.pdbx_struct_group_id',
|
||||
'_chem_comp.mon_nstd_parent_comp_id',
|
||||
'_diffrn_radiation.pdbx_wavelength_list',
|
||||
'_diffrn_source.pdbx_wavelength_list',
|
||||
'_em_diffraction.tilt_angle_list', // 20,40,50,55
|
||||
'_em_entity_assembly.entity_id_list',
|
||||
'_entity.pdbx_description', // Endolysin,Beta-2 adrenergic receptor
|
||||
'_entity.pdbx_ec',
|
||||
'_entity_poly.pdbx_strand_id', // A,B
|
||||
'_entity_src_gen.pdbx_gene_src_gene', // ADRB2, ADRB2R, B2AR
|
||||
'_pdbx_depui_entry_details.experimental_methods',
|
||||
'_pdbx_depui_entry_details.requested_accession_types',
|
||||
'_pdbx_soln_scatter_model.software_list', // INSIGHT II, HOMOLOGY, DISCOVERY, BIOPOLYMER, DELPHI
|
||||
'_pdbx_soln_scatter_model.software_author_list', // MSI
|
||||
'_pdbx_soln_scatter_model.entry_fitting_list', // Odd example: 'PDB CODE 1HFI, 1HCC, 1HFH, 1VCC'
|
||||
'_pdbx_struct_assembly_gen.entity_inst_id',
|
||||
'_pdbx_struct_assembly_gen.asym_id_list',
|
||||
'_pdbx_struct_assembly_gen.auth_asym_id_list',
|
||||
'_pdbx_struct_assembly_gen_depositor_info.asym_id_list',
|
||||
'_pdbx_struct_assembly_gen_depositor_info.chain_id_list',
|
||||
'_pdbx_struct_group_list.group_enumeration_type',
|
||||
'_reflns.pdbx_diffrn_id',
|
||||
'_refine.pdbx_diffrn_id',
|
||||
'_reflns_shell.pdbx_diffrn_id',
|
||||
'_struct_keywords.text',
|
||||
];
|
||||
|
||||
const SPACE_SEPARATED_LIST_FIELDS = [
|
||||
'_chem_comp.pdbx_subcomponent_list', // TSM DPH HIS CHF EMR
|
||||
'_pdbx_soln_scatter.data_reduction_software_list', // OTOKO
|
||||
'_pdbx_soln_scatter.data_analysis_software_list', // SCTPL5 GNOM
|
||||
];
|
||||
|
||||
const SEMICOLON_SEPARATED_LIST_FIELDS = [
|
||||
'_chem_comp.pdbx_synonyms' // GLYCERIN; PROPANE-1,2,3-TRIOL
|
||||
];
|
||||
|
||||
/**
|
||||
* Useful when a dictionary extension will add enum values to an existing dictionary.
|
||||
* By adding them here, the dictionary extension can be tested before the added enum
|
||||
* values are available in the existing dictionary.
|
||||
*/
|
||||
const EXTRA_ENUM_VALUES: { [k: string]: string[] } = {
|
||||
|
||||
};
|
||||
|
||||
export function generateSchema(frames: CifFrame[], imports: Imports = new Map()): Database {
|
||||
const tables: Database['tables'] = {};
|
||||
const aliases: Database['aliases'] = { ...EXTRA_ALIASES };
|
||||
|
||||
const categories: FrameCategories = {};
|
||||
const links: FrameLinks = {};
|
||||
const ctx = { categories, links };
|
||||
|
||||
// get category metadata
|
||||
frames.forEach(d => {
|
||||
// category definitions in mmCIF start with '_' and don't include a '.'
|
||||
// category definitions in cifCore don't include a '.'
|
||||
if (d.header[0] === '_' || d.header.includes('.')) return;
|
||||
const categoryName = d.header.toLowerCase();
|
||||
// console.log(d.header, d.categoryNames, d.categories)
|
||||
let descriptionField: Data.CifField | undefined;
|
||||
const categoryKeyNames = new Set<string>();
|
||||
|
||||
if ('category' in d.categories && 'category_key' in d.categories) {
|
||||
const category = d.categories['category'];
|
||||
const categoryKey = d.categories['category_key'];
|
||||
if (categoryKey) {
|
||||
const categoryKey_names = categoryKey.getField('name');
|
||||
if (categoryKey_names) {
|
||||
for (let i = 0, il = categoryKey_names.rowCount; i < il; ++i) {
|
||||
categoryKeyNames.add(categoryKey_names.str(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
descriptionField = category.getField('description');
|
||||
|
||||
if (categoryKeyNames.size === 0) {
|
||||
console.log(`no key given for category '${categoryName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
if ('description' in d.categories) {
|
||||
descriptionField = d.categories['description'].getField('text');
|
||||
}
|
||||
|
||||
let description = '';
|
||||
if (descriptionField) {
|
||||
description = descriptionField.str(0).trim()
|
||||
.replace(/(\r\n|\r|\n)([ \t]+)/g, '\n'); // remove padding after newlines
|
||||
} else {
|
||||
console.log(`no description given for category '${categoryName}'`);
|
||||
}
|
||||
|
||||
tables[categoryName] = { description, key: categoryKeyNames, columns: {} };
|
||||
|
||||
// console.log('++++++++++++++++++++++++++++++++++++++++++')
|
||||
// console.log('name', categoryName)
|
||||
// console.log('desc', description)
|
||||
// console.log('key', categoryKeyNames)
|
||||
});
|
||||
|
||||
// build list of links between categories
|
||||
frames.forEach(d => {
|
||||
if (d.header[0] !== '_' && !d.header.includes('.')) return;
|
||||
categories[d.header] = d;
|
||||
const item_linked = d.categories['item_linked'];
|
||||
if (item_linked) {
|
||||
const child_name = item_linked.getField('child_name');
|
||||
const parent_name = item_linked.getField('parent_name');
|
||||
if (child_name && parent_name) {
|
||||
for (let i = 0; i < item_linked.rowCount; ++i) {
|
||||
const childName = child_name.str(i);
|
||||
const parentName = parent_name.str(i);
|
||||
if (childName in links && links[childName] !== parentName) {
|
||||
console.log(`${childName} linked to ${links[childName]}, ignoring link to ${parentName}`);
|
||||
}
|
||||
links[childName] = parentName;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// get field data
|
||||
Object.keys(categories).forEach(fullName => {
|
||||
const d = categories[fullName];
|
||||
if (!d) {
|
||||
console.log(`'${fullName}' not found, moving on`);
|
||||
return;
|
||||
}
|
||||
|
||||
const categoryName = d.header.substring(d.header[0] === '_' ? 1 : 0, d.header.indexOf('.'));
|
||||
const itemName = d.header.substring(d.header.indexOf('.') + 1);
|
||||
let fields: { [k: string]: Column };
|
||||
if (categoryName in tables) {
|
||||
fields = tables[categoryName].columns;
|
||||
tables[categoryName].key.add(itemName);
|
||||
} else if (categoryName.toLowerCase() in tables) {
|
||||
// take case from category name in 'field' data as it is better if data is from cif dictionaries
|
||||
tables[categoryName] = tables[categoryName.toLowerCase()];
|
||||
fields = tables[categoryName].columns;
|
||||
} else {
|
||||
console.log(`category '${categoryName}' has no metadata`);
|
||||
fields = {};
|
||||
tables[categoryName] = {
|
||||
description: '',
|
||||
key: new Set(),
|
||||
columns: fields
|
||||
};
|
||||
}
|
||||
|
||||
const itemAliases = getAliases(d, imports, ctx);
|
||||
if (itemAliases) aliases[`${categoryName}.${itemName}`] = itemAliases;
|
||||
|
||||
const description = getDescription(d, imports, ctx) || '';
|
||||
|
||||
// need to use regex to check for matrix or vector items
|
||||
// as sub_category assignment is missing for some entries
|
||||
const subCategory = getSubCategory(d, imports, ctx);
|
||||
if (subCategory === 'cartesian_coordinate' || subCategory === 'fractional_coordinate') {
|
||||
fields[itemName] = CoordCol(description);
|
||||
} else if (FORCE_INT_FIELDS.includes(d.header)) {
|
||||
fields[itemName] = IntCol(description);
|
||||
console.log(`forcing int: ${d.header}`);
|
||||
} else if (FORCE_MATRIX_FIELDS.includes(d.header)) {
|
||||
fields[itemName] = FloatCol(description);
|
||||
fields[FORCE_MATRIX_FIELDS_MAP[d.header]] = MatrixCol(3, 3, description);
|
||||
console.log(`forcing matrix: ${d.header}`);
|
||||
} else if (subCategory === 'matrix') {
|
||||
fields[itemName.replace(reMatrixField, '')] = MatrixCol(3, 3, description);
|
||||
} else if (subCategory === 'vector') {
|
||||
fields[itemName.replace(reVectorField, '')] = VectorCol(3, description);
|
||||
} else {
|
||||
if (itemName.match(reMatrixField)) {
|
||||
fields[itemName.replace(reMatrixField, '')] = MatrixCol(3, 3, description);
|
||||
console.log(`${d.header} should have 'matrix' _item_sub_category.id`);
|
||||
} else if (itemName.match(reVectorField)) {
|
||||
fields[itemName.replace(reVectorField, '')] = VectorCol(3, description);
|
||||
console.log(`${d.header} should have 'vector' _item_sub_category.id`);
|
||||
} else {
|
||||
const code = getCode(d, imports, ctx);
|
||||
if (code) {
|
||||
let fieldType = getFieldType(code[0], description, code[1], code[2]);
|
||||
if (fieldType.type === 'str') {
|
||||
if (COMMA_SEPARATED_LIST_FIELDS.includes(d.header)) {
|
||||
fieldType = ListCol('str', ',', description);
|
||||
console.log(`forcing comma separated: ${d.header}`);
|
||||
} else if (SPACE_SEPARATED_LIST_FIELDS.includes(d.header)) {
|
||||
fieldType = ListCol('str', ' ', description);
|
||||
console.log(`forcing space separated: ${d.header}`);
|
||||
} else if (SEMICOLON_SEPARATED_LIST_FIELDS.includes(d.header)) {
|
||||
fieldType = ListCol('str', ';', description);
|
||||
console.log(`forcing space separated: ${d.header}`);
|
||||
}
|
||||
}
|
||||
if (d.header in EXTRA_ENUM_VALUES) {
|
||||
if (fieldType.type === 'enum') {
|
||||
fieldType.values.push(...EXTRA_ENUM_VALUES[d.header]);
|
||||
} else {
|
||||
console.warn(`expected enum: ${d.header}`);
|
||||
}
|
||||
}
|
||||
fields[itemName] = fieldType;
|
||||
} else {
|
||||
fields[itemName] = StrCol(description);
|
||||
// console.log(`could not determine code for '${d.header}'`)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { tables, aliases };
|
||||
}
|
||||
151
src/apps/cifschema/util/generate.ts
Normal file
151
src/apps/cifschema/util/generate.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Database, Filter, Column } from './schema';
|
||||
import { indentString } from '../../../mol-util/string';
|
||||
import { FieldPath } from '../../../mol-io/reader/cif/schema';
|
||||
|
||||
function header (name: string, info: string, moldataImportPath: string) {
|
||||
return `/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated '${name}' schema file. ${info}
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
import { Database, Column } from '${moldataImportPath}/db'
|
||||
|
||||
import Schema = Column.Schema`;
|
||||
}
|
||||
|
||||
function footer (name: string) {
|
||||
return `
|
||||
export type ${name}_Schema = typeof ${name}_Schema;
|
||||
export interface ${name}_Database extends Database<${name}_Schema> {}`;
|
||||
}
|
||||
|
||||
function getTypeShorthands(schema: Database, fields?: Filter) {
|
||||
const types = new Set<string>();
|
||||
Object.keys(schema.tables).forEach(table => {
|
||||
if (fields && !fields[table]) return;
|
||||
const { columns } = schema.tables[table];
|
||||
Object.keys(columns).forEach(columnName => {
|
||||
if (fields && !fields[table][columnName]) return;
|
||||
types.add(schema.tables[table].columns[columnName].type);
|
||||
});
|
||||
});
|
||||
const shorthands: string[] = [];
|
||||
types.forEach(type => {
|
||||
switch (type) {
|
||||
case 'str': shorthands.push('const str = Schema.str;'); break;
|
||||
case 'int': shorthands.push('const int = Schema.int;'); break;
|
||||
case 'float': shorthands.push('const float = Schema.float;'); break;
|
||||
case 'coord': shorthands.push('const coord = Schema.coord;'); break;
|
||||
case 'enum': shorthands.push('const Aliased = Schema.Aliased;'); break;
|
||||
case 'matrix': shorthands.push('const Matrix = Schema.Matrix;'); break;
|
||||
case 'vector': shorthands.push('const Vector = Schema.Vector;'); break;
|
||||
case 'list': shorthands.push('const List = Schema.List;'); break;
|
||||
}
|
||||
});
|
||||
return shorthands.join('\n');
|
||||
}
|
||||
|
||||
function getTypeDef(c: Column): string {
|
||||
switch (c.type) {
|
||||
case 'str': return 'str';
|
||||
case 'int': return 'int';
|
||||
case 'float': return 'float';
|
||||
case 'coord': return 'coord';
|
||||
case 'enum':
|
||||
return `Aliased<'${c.values.map(v => v.replace(/'/g, '\\\'')).join(`' | '`)}'>(${c.subType})`;
|
||||
case 'matrix':
|
||||
return `Matrix(${c.rows}, ${c.columns})`;
|
||||
case 'vector':
|
||||
return `Vector(${c.length})`;
|
||||
case 'list':
|
||||
if (c.subType === 'int') {
|
||||
return `List('${c.separator}', x => parseInt(x, 10))`;
|
||||
} else if (c.subType === 'float' || c.subType === 'coord') {
|
||||
return `List('${c.separator}', x => parseFloat(x))`;
|
||||
} else {
|
||||
return `List('${c.separator}', x => x)`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const reSafePropertyName = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
|
||||
function safePropertyString(name: string) { return name.match(reSafePropertyName) ? name : `'${name}'`; }
|
||||
|
||||
function doc(description: string, spacesCount: number) {
|
||||
const spaces = ' '.repeat(spacesCount);
|
||||
return [
|
||||
`${spaces}/**`,
|
||||
`${indentString(description, 1, `${spaces} * `)}`.replace(/ +\n/g, '\n'),
|
||||
`${spaces} */`
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
export function generate (name: string, info: string, schema: Database, fields: Filter | undefined, moldataImportPath: string, addAliases: boolean) {
|
||||
const codeLines: string[] = [];
|
||||
|
||||
if (fields) {
|
||||
Object.keys(fields).forEach(table => {
|
||||
if (table in schema.tables) {
|
||||
const schemaTable = schema.tables[table];
|
||||
Object.keys(fields[table]).forEach(column => {
|
||||
if (!(column in schemaTable.columns)) {
|
||||
console.log(`filter field '${table}.${column}' not found in schema`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log(`filter category '${table}' not found in schema`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
codeLines.push(`export const ${name}_Schema = {`);
|
||||
Object.keys(schema.tables).forEach(table => {
|
||||
if (fields && !fields[table]) return;
|
||||
const { description, columns } = schema.tables[table];
|
||||
if (description) codeLines.push(doc(description, 4));
|
||||
codeLines.push(` ${safePropertyString(table)}: {`);
|
||||
Object.keys(columns).forEach(columnName => {
|
||||
if (fields && !fields[table][columnName]) return;
|
||||
const c = columns[columnName];
|
||||
const typeDef = getTypeDef(c);
|
||||
if (c.description) codeLines.push(doc(c.description, 8));
|
||||
codeLines.push(` ${safePropertyString(columnName)}: ${typeDef},`);
|
||||
});
|
||||
codeLines.push(' },');
|
||||
});
|
||||
codeLines.push('}');
|
||||
|
||||
if (addAliases) {
|
||||
codeLines.push('');
|
||||
codeLines.push(`export const ${name}_Aliases = {`);
|
||||
Object.keys(schema.aliases).forEach(path => {
|
||||
const [ table, columnName ] = path.split('.');
|
||||
if (fields && !fields[table]) return;
|
||||
if (fields && !fields[table][columnName]) return;
|
||||
|
||||
const filteredAliases = new Set<string>();
|
||||
schema.aliases[path].forEach(p => {
|
||||
if (!FieldPath.equal(p, path)) filteredAliases.add(FieldPath.canonical(p));
|
||||
});
|
||||
|
||||
if (filteredAliases.size === 0) return;
|
||||
codeLines.push(` ${safePropertyString(path)}: [`);
|
||||
filteredAliases.forEach(alias => {
|
||||
codeLines.push(` '${alias}',`);
|
||||
});
|
||||
codeLines.push(' ],');
|
||||
});
|
||||
codeLines.push('}');
|
||||
}
|
||||
|
||||
return `${header(name, info, moldataImportPath)}\n\n${getTypeShorthands(schema, fields)}\n\n${codeLines.join('\n')}\n${footer(name)}`;
|
||||
}
|
||||
20
src/apps/cifschema/util/helper.ts
Normal file
20
src/apps/cifschema/util/helper.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
export type Import = { save?: string, file?: string }
|
||||
|
||||
export function parseImportGet(s: string): Import[] {
|
||||
// [{'save':hi_ang_Fox_coeffs 'file':templ_attr.cif} {'save':hi_ang_Fox_c0 'file':templ_enum.cif}]
|
||||
// [{"file":'templ_enum.cif' "save":'H_M_ref'}]
|
||||
return s.trim().substring(2, s.length - 2).split(/}[ \n\t]*{/g).map(s => {
|
||||
const save = s.match(/('save'|"save"):([^ \t\n]+)/);
|
||||
const file = s.match(/('file'|"file"):([^ \t\n]+)/);
|
||||
return {
|
||||
save: save ? save[0].substr(7).replace(/['"]/g, '') : undefined,
|
||||
file: file ? file[0].substr(7).replace(/['"]/g, '') : undefined
|
||||
};
|
||||
});
|
||||
}
|
||||
77
src/apps/cifschema/util/schema.ts
Normal file
77
src/apps/cifschema/util/schema.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
export interface Database {
|
||||
tables: { [ tableName: string ]: Table }
|
||||
aliases: { [ path: string ]: string[] }
|
||||
}
|
||||
export interface Table {
|
||||
description: string
|
||||
key: Set<string>
|
||||
columns: { [ columnName: string ]: Column }
|
||||
}
|
||||
export type Column = IntCol | StrCol | FloatCol | CoordCol | EnumCol | VectorCol | MatrixCol | ListCol
|
||||
|
||||
type BaseCol = { description: string }
|
||||
|
||||
export type IntCol = { type: 'int' } & BaseCol
|
||||
export function IntCol(description: string): IntCol { return { type: 'int', description }; }
|
||||
|
||||
export type StrCol = { type: 'str' } & BaseCol
|
||||
export function StrCol(description: string): StrCol { return { type: 'str', description }; }
|
||||
|
||||
export type FloatCol = { type: 'float' } & BaseCol
|
||||
export function FloatCol(description: string): FloatCol { return { type: 'float', description }; }
|
||||
|
||||
export type CoordCol = { type: 'coord' } & BaseCol
|
||||
export function CoordCol(description: string): CoordCol { return { type: 'coord', description }; }
|
||||
|
||||
export type EnumCol = { type: 'enum', subType: 'int' | 'str', values: string[] } & BaseCol
|
||||
export function EnumCol(values: string[], subType: 'int' | 'str', description: string): EnumCol {
|
||||
return { type: 'enum', description, values, subType };
|
||||
}
|
||||
|
||||
export type VectorCol = { type: 'vector', length: number } & BaseCol
|
||||
export function VectorCol(length: number, description: string): VectorCol {
|
||||
return { type: 'vector', description, length };
|
||||
}
|
||||
|
||||
export type MatrixCol = { type: 'matrix', rows: number, columns: number } & BaseCol
|
||||
export function MatrixCol(columns: number, rows: number, description: string): MatrixCol {
|
||||
return { type: 'matrix', description, columns, rows };
|
||||
}
|
||||
|
||||
export type ListCol = { type: 'list', subType: 'int' | 'str' | 'float' | 'coord', separator: string } & BaseCol
|
||||
export function ListCol(subType: 'int' | 'str' | 'float' | 'coord', separator: string, description: string): ListCol {
|
||||
return { type: 'list', description, separator, subType };
|
||||
}
|
||||
|
||||
export type Filter = { [ table: string ]: { [ column: string ]: true } }
|
||||
|
||||
export function mergeFilters (...filters: Filter[]) {
|
||||
const n = filters.length;
|
||||
const mergedFilter: Filter = {};
|
||||
const fields: Map<string, number> = new Map();
|
||||
filters.forEach(filter => {
|
||||
Object.keys(filter).forEach(category => {
|
||||
Object.keys(filter[ category ]).forEach(field => {
|
||||
const key = `${category}.${field}`;
|
||||
const value = fields.get(key) || 0;
|
||||
fields.set(key, value + 1);
|
||||
});
|
||||
});
|
||||
});
|
||||
fields.forEach((v, k) => {
|
||||
if (v !== n) return;
|
||||
const [categoryName, fieldName] = k.split('.');
|
||||
if (categoryName in mergedFilter) {
|
||||
mergedFilter[categoryName][fieldName] = true;
|
||||
} else {
|
||||
mergedFilter[categoryName] = { fieldName: true };
|
||||
}
|
||||
});
|
||||
return mergedFilter;
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createPlugin, DefaultPluginSpec } from '../../../mol-plugin';
|
||||
import './index.html'
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { StateTransforms } from '../../../mol-plugin-state/transforms';
|
||||
import { PluginStateObject as PSO } from '../../../mol-plugin-state/objects';
|
||||
import { StateBuilder } from '../../../mol-state';
|
||||
import { Canvas3DProps } from '../../../mol-canvas3d/canvas3d';
|
||||
import { createStructureRepresentationParams } from '../../../mol-plugin-state/helpers/structure-representation-params';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
|
||||
type SupportedFormats = 'cif' | 'pdb'
|
||||
type LoadParams = { url: string, format?: SupportedFormats, assemblyId?: string }
|
||||
|
||||
const Canvas3DPresets = {
|
||||
illustrative: {
|
||||
multiSample: {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusionEnable: true,
|
||||
occlusionBias: 0.8,
|
||||
occlusionKernelSize: 6,
|
||||
outlineEnable: true,
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 1,
|
||||
lightIntensity: 0,
|
||||
}
|
||||
},
|
||||
occlusion: {
|
||||
multiSample: {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusionEnable: true,
|
||||
occlusionBias: 0.8,
|
||||
occlusionKernelSize: 6,
|
||||
outlineEnable: false,
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
lightIntensity: 0.6,
|
||||
}
|
||||
},
|
||||
standard: {
|
||||
multiSample: {
|
||||
mode: 'off' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusionEnable: false,
|
||||
outlineEnable: false,
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
lightIntensity: 0.6,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Canvas3DPreset = keyof typeof Canvas3DPresets
|
||||
|
||||
function getPreset(preset: Canvas3DPreset) {
|
||||
switch (preset) {
|
||||
case 'illustrative': return Canvas3DPresets['illustrative']
|
||||
case 'standard': return Canvas3DPresets['standard']
|
||||
case 'occlusion': return Canvas3DPresets['occlusion']
|
||||
}
|
||||
}
|
||||
|
||||
class LightingDemo {
|
||||
plugin: PluginContext;
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
|
||||
}
|
||||
});
|
||||
|
||||
this.setPreset('illustrative');
|
||||
}
|
||||
|
||||
setPreset(preset: Canvas3DPreset) {
|
||||
const props = getPreset(preset)
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
|
||||
...props,
|
||||
multiSample: {
|
||||
...this.plugin.canvas3d!.props.multiSample,
|
||||
...props.multiSample
|
||||
},
|
||||
renderer: {
|
||||
...this.plugin.canvas3d!.props.renderer,
|
||||
...props.renderer
|
||||
},
|
||||
postprocessing: {
|
||||
...this.plugin.canvas3d!.props.postprocessing,
|
||||
...props.postprocessing
|
||||
},
|
||||
}});
|
||||
}
|
||||
|
||||
private download(b: StateBuilder.To<PSO.Root>, url: string) {
|
||||
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
|
||||
}
|
||||
|
||||
private parse(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, assemblyId: string) {
|
||||
const parsed = format === 'cif'
|
||||
? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
|
||||
: b.apply(StateTransforms.Model.TrajectoryFromPDB);
|
||||
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
return parsed
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 })
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { ref: 'asm' });
|
||||
}
|
||||
|
||||
private visual(visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'spacefill', color: 'illustrative' }), { ref: 'seq-visual' });
|
||||
visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
createStructureRepresentationParams(this.plugin, void 0, { type: 'ball-and-stick' }), { ref: 'het-visual' });
|
||||
return visualRoot;
|
||||
}
|
||||
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = '' }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
|
||||
if (this.loadedParams.url !== url || this.loadedParams.format !== format) {
|
||||
loadType = 'full';
|
||||
} else if (this.loadedParams.url === url) {
|
||||
if (state.select('asm').length > 0) loadType = 'update';
|
||||
}
|
||||
|
||||
let tree: StateBuilder.Root;
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref });
|
||||
tree = state.build();
|
||||
this.visual(this.parse(this.download(tree.toRoot(), url), format, assemblyId));
|
||||
} else {
|
||||
const props = {
|
||||
type: {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
tree = state.build();
|
||||
tree.to('asm').update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
this.loadedParams = { url, format, assemblyId };
|
||||
PluginCommands.Camera.Reset(this.plugin, { });
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).LightingDemo = new LightingDemo();
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as _ from '../../mol-plugin-state/transforms'
|
||||
import * as _ from '../../mol-plugin-state/transforms';
|
||||
import { StateTransformer, StateObject } from '../../mol-state';
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
import * as fs from 'fs';
|
||||
@@ -13,7 +13,7 @@ import { PluginContext } from '../../mol-plugin/context';
|
||||
import { ParamDefinition } from '../../mol-util/param-definition';
|
||||
|
||||
// force the transform to be evaluated
|
||||
_.StateTransforms.Data.Download.id
|
||||
_.StateTransforms.Data.Download.id;
|
||||
|
||||
// Empty plugin context
|
||||
const ctx = new PluginContext({
|
||||
@@ -32,7 +32,7 @@ function writeTransformer(t: StateTransformer) {
|
||||
StringBuilder.write(builder, `## <a name="${t.id.replace('.', '-')}"></a>${t.id} :: ${typeToString(t.definition.from)} -> ${typeToString(t.definition.to)}`);
|
||||
StringBuilder.newline(builder);
|
||||
if (t.definition.display.description) {
|
||||
StringBuilder.write(builder, `*${t.definition.display.description}*`)
|
||||
StringBuilder.write(builder, `*${t.definition.display.description}*`);
|
||||
StringBuilder.newline(builder);
|
||||
}
|
||||
StringBuilder.newline(builder);
|
||||
@@ -48,7 +48,7 @@ function writeTransformer(t: StateTransformer) {
|
||||
StringBuilder.write(builder, `\`\`\`js\n${JSON.stringify(ParamDefinition.getDefaultValues(params), null, 2)}\n\`\`\``);
|
||||
StringBuilder.newline(builder);
|
||||
}
|
||||
StringBuilder.write(builder, '----------------------------')
|
||||
StringBuilder.write(builder, '----------------------------');
|
||||
StringBuilder.newline(builder);
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ transformers.forEach(t => {
|
||||
StringBuilder.newline(builder);
|
||||
});
|
||||
StringBuilder.newline(builder);
|
||||
StringBuilder.write(builder, '----------------------------')
|
||||
StringBuilder.write(builder, '----------------------------');
|
||||
StringBuilder.newline(builder);
|
||||
transformers.forEach(t => writeTransformer(t));
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ function paramInfo(param: PD.Any, offset: number): string {
|
||||
case 'color-list': return `A list of colors as 0xrrggbb`;
|
||||
case 'vec3': return `3D vector [x, y, z]`;
|
||||
case 'mat4': return `4x4 transformation matrix`;
|
||||
case 'url': return `URL couple with unique identifier`;
|
||||
case 'file': return `JavaScript File Handle`;
|
||||
case 'file-list': return `JavaScript FileList Handle`;
|
||||
case 'select': return `One of ${oToS(param.options)}`;
|
||||
|
||||
@@ -4,19 +4,19 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as util from 'util'
|
||||
import * as fs from 'fs'
|
||||
import fetch from 'node-fetch'
|
||||
import * as util from 'util';
|
||||
import * as fs from 'fs';
|
||||
import fetch from 'node-fetch';
|
||||
require('util.promisify').shim();
|
||||
|
||||
import { CIF } from '../../mol-io/reader/cif'
|
||||
import { Progress } from '../../mol-task'
|
||||
import { CIF } from '../../mol-io/reader/cif';
|
||||
import { Progress } from '../../mol-task';
|
||||
|
||||
const readFileAsync = util.promisify(fs.readFile);
|
||||
|
||||
async function readFile(path: string) {
|
||||
if (path.match(/\.bcif$/)) {
|
||||
const input = await readFileAsync(path)
|
||||
const input = await readFileAsync(path);
|
||||
const data = new Uint8Array(input.byteLength);
|
||||
for (let i = 0; i < input.byteLength; i++) data[i] = input[i];
|
||||
return data;
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as argparse from 'argparse'
|
||||
import * as argparse from 'argparse';
|
||||
require('util.promisify').shim();
|
||||
|
||||
import { CifFrame } from '../../mol-io/reader/cif'
|
||||
import { Model, Structure, StructureElement, Unit, StructureProperties, UnitRing } from '../../mol-model/structure'
|
||||
import { CifFrame } from '../../mol-io/reader/cif';
|
||||
import { Model, Structure, StructureElement, Unit, StructureProperties, UnitRing } from '../../mol-model/structure';
|
||||
// import { Run, Progress } from '../../mol-task'
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { openCif, downloadCif } from './helpers';
|
||||
@@ -32,30 +32,30 @@ export async function readCifFile(path: string) {
|
||||
}
|
||||
|
||||
export function atomLabel(model: Model, aI: number) {
|
||||
const { atoms, residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy
|
||||
const { label_atom_id } = atoms
|
||||
const { label_comp_id, label_seq_id } = residues
|
||||
const { label_asym_id } = chains
|
||||
const rI = residueAtomSegments.index[aI]
|
||||
const cI = chainAtomSegments.index[aI]
|
||||
return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`
|
||||
const { atoms, residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy;
|
||||
const { label_atom_id } = atoms;
|
||||
const { label_comp_id, label_seq_id } = residues;
|
||||
const { label_asym_id } = chains;
|
||||
const rI = residueAtomSegments.index[aI];
|
||||
const cI = chainAtomSegments.index[aI];
|
||||
return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`;
|
||||
}
|
||||
|
||||
export function residueLabel(model: Model, rI: number) {
|
||||
const { residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy
|
||||
const { label_comp_id, label_seq_id } = residues
|
||||
const { label_asym_id } = chains
|
||||
const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]]
|
||||
return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`
|
||||
const { residues, chains, residueAtomSegments, chainAtomSegments } = model.atomicHierarchy;
|
||||
const { label_comp_id, label_seq_id } = residues;
|
||||
const { label_asym_id } = chains;
|
||||
const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]];
|
||||
return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`;
|
||||
}
|
||||
|
||||
export function printSecStructure(model: Model) {
|
||||
console.log('\nSecondary Structure\n=============');
|
||||
const { residues } = model.atomicHierarchy;
|
||||
const secondaryStructure = ModelSecondaryStructure.Provider.get(model);
|
||||
if (!secondaryStructure) return
|
||||
if (!secondaryStructure) return;
|
||||
|
||||
const { key, elements } = secondaryStructure
|
||||
const { key, elements } = secondaryStructure;
|
||||
const count = residues._rowCount;
|
||||
let rI = 0;
|
||||
while (rI < count) {
|
||||
@@ -116,7 +116,7 @@ export function printSequence(model: Model) {
|
||||
const { byEntityKey } = model.sequence;
|
||||
for (const key of Object.keys(byEntityKey)) {
|
||||
const { sequence, entityId } = byEntityKey[+key];
|
||||
const { seqId, compId } = sequence
|
||||
const { seqId, compId } = sequence;
|
||||
console.log(`${entityId} (${sequence.kind} ${seqId.value(0)} (offset ${sequence.offset}), ${seqId.value(seqId.rowCount - 1)}) (${compId.value(0)}, ${compId.value(compId.rowCount - 1)})`);
|
||||
console.log(`${Sequence.getSequenceString(sequence)}`);
|
||||
}
|
||||
@@ -132,7 +132,7 @@ export function printRings(structure: Structure) {
|
||||
for (let i = 0, _i = Math.min(5, all.length); i < _i; i++) {
|
||||
fps[fps.length] = UnitRing.fingerprint(unit, all[i]);
|
||||
}
|
||||
if (all.length > 5) fps.push('...')
|
||||
if (all.length > 5) fps.push('...');
|
||||
console.log(`Unit ${unit.id}, ${all.length} ring(s), ${byFingerprint.size} different fingerprint(s).\n ${fps.join(', ')}`);
|
||||
}
|
||||
console.log();
|
||||
@@ -171,8 +171,8 @@ export function printUnits(structure: Structure) {
|
||||
|
||||
export function printSymmetryInfo(model: Model) {
|
||||
console.log('\nSymmetry Info\n=============');
|
||||
const symmetry = ModelSymmetry.Provider.get(model)
|
||||
if (!symmetry) return
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
if (!symmetry) return;
|
||||
const { size, anglesInRadians } = symmetry.spacegroup.cell;
|
||||
console.log(`Spacegroup: ${symmetry.spacegroup.name} size: ${Vec3.toString(size)} angles: ${Vec3.toString(anglesInRadians)}`);
|
||||
console.log(`Assembly names: ${symmetry.assemblies.map(a => a.id).join(', ')}`);
|
||||
@@ -213,7 +213,7 @@ async function run(frame: CifFrame, args: Args) {
|
||||
}
|
||||
|
||||
async function runDL(pdb: string, args: Args) {
|
||||
const mmcif = await downloadFromPdb(pdb)
|
||||
const mmcif = await downloadFromPdb(pdb);
|
||||
run(mmcif, args);
|
||||
}
|
||||
|
||||
@@ -255,5 +255,5 @@ interface Args {
|
||||
}
|
||||
const args: Args = parser.parseArgs();
|
||||
|
||||
if (args.download) runDL(args.download, args)
|
||||
else if (args.file) runFile(args.file, args)
|
||||
if (args.download) runDL(args.download, args);
|
||||
else if (args.file) runFile(args.file, args);
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as fs from 'fs'
|
||||
import * as argparse from 'argparse'
|
||||
import * as util from 'util'
|
||||
import * as fs from 'fs';
|
||||
import * as argparse from 'argparse';
|
||||
import * as util from 'util';
|
||||
|
||||
import { VolumeData, VolumeIsoValue } from '../../mol-model/volume'
|
||||
import { downloadCif } from './helpers'
|
||||
import { CIF } from '../../mol-io/reader/cif'
|
||||
import { VolumeData, VolumeIsoValue } from '../../mol-model/volume';
|
||||
import { downloadCif } from './helpers';
|
||||
import { CIF } from '../../mol-io/reader/cif';
|
||||
import { DensityServer_Data_Database } from '../../mol-io/reader/cif/schema/density-server';
|
||||
import { Table } from '../../mol-data/db';
|
||||
import { StringBuilder } from '../../mol-util';
|
||||
@@ -34,9 +34,8 @@ function print(data: Volume) {
|
||||
const { volume_data_3d_info } = data.source;
|
||||
const row = Table.getRow(volume_data_3d_info, 0);
|
||||
console.log(row);
|
||||
console.log(data.volume.cell);
|
||||
if (data.volume.transform) console.log(data.volume.transform);
|
||||
console.log(data.volume.dataStats);
|
||||
console.log(data.volume.fractionalBox);
|
||||
}
|
||||
|
||||
async function doMesh(data: Volume, filename: string) {
|
||||
|
||||
@@ -1,402 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateAction, StateBuilder, StateTransformer, State } from '../../../../mol-state';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Ingredient, CellPacking } from './data';
|
||||
import { getFromPdb, getFromCellPackDB } from './util';
|
||||
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../../../mol-model/structure';
|
||||
import { trajectoryFromMmCIF, MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { trajectoryFromPDB } from '../../../../mol-model-formats/structure/pdb';
|
||||
import { Mat4, Vec3, Quat } from '../../../../mol-math/linear-algebra';
|
||||
import { SymmetryOperator } from '../../../../mol-math/geometry';
|
||||
import { Task, RuntimeContext } from '../../../../mol-task';
|
||||
import { StateTransforms } from '../../../../mol-plugin-state/transforms';
|
||||
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl } from './state';
|
||||
import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
|
||||
import { getMatFromResamplePoints } from './curve';
|
||||
import { compile } from '../../../../mol-script/runtime/query/compiler';
|
||||
import { CifCategory, CifField } from '../../../../mol-io/reader/cif';
|
||||
import { mmCIF_Schema } from '../../../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Column } from '../../../../mol-data/db';
|
||||
import { createModels } from '../../../../mol-model-formats/structure/basic/parser';
|
||||
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
|
||||
import { AjaxTask } from '../../../../mol-util/data-source';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
import { CellPackColorThemeProvider } from './color';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/results/${fileName}`
|
||||
}
|
||||
|
||||
async function getModel(id: string, baseUrl: string) {
|
||||
let model: Model;
|
||||
if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
|
||||
// return
|
||||
const cif = await getFromPdb(id)
|
||||
model = (await trajectoryFromMmCIF(cif).run())[0]
|
||||
} else {
|
||||
const pdb = await getFromCellPackDB(id, baseUrl)
|
||||
model = (await trajectoryFromPDB(pdb).run())[0]
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
async function getStructure(model: Model, props: { assembly?: string } = {}) {
|
||||
let structure = Structure.ofModel(model)
|
||||
const { assembly } = props
|
||||
|
||||
if (assembly) {
|
||||
structure = await StructureSymmetry.buildAssembly(structure, assembly).run()
|
||||
}
|
||||
|
||||
const query = MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer'])
|
||||
})
|
||||
])
|
||||
const compiled = compile<StructureSelection>(query)
|
||||
const result = compiled(new QueryContext(structure))
|
||||
structure = StructureSelection.unionStructure(result)
|
||||
|
||||
return structure
|
||||
}
|
||||
|
||||
function getTransform(trans: Vec3, rot: Quat) {
|
||||
const q: Quat = Quat.create(-rot[3], rot[0], rot[1], rot[2])
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q)
|
||||
Mat4.transpose(m, m)
|
||||
Mat4.scale(m, m, Vec3.create(-1.0, 1.0, -1.0))
|
||||
Mat4.setTranslation(m, trans)
|
||||
return m
|
||||
}
|
||||
|
||||
function getResultTransforms(results: Ingredient['results']) {
|
||||
return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1]))
|
||||
}
|
||||
|
||||
function getCurveTransforms(ingredient: Ingredient) {
|
||||
const n = ingredient.nbCurve || 0
|
||||
const instances: Mat4[] = []
|
||||
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const cname = `curve${i}`
|
||||
if (!(cname in ingredient)) {
|
||||
// console.warn(`Expected '${cname}' in ingredient`)
|
||||
continue
|
||||
}
|
||||
const _points = ingredient[cname] as Vec3[]
|
||||
if (_points.length <= 2) {
|
||||
// TODO handle curve with 2 or less points
|
||||
continue
|
||||
}
|
||||
const points = new Float32Array(_points.length * 3)
|
||||
for (let i = 0, il = _points.length; i < il; ++i) Vec3.toArray(_points[i], points, i * 3)
|
||||
const newInstances = getMatFromResamplePoints(points)
|
||||
instances.push(...newInstances)
|
||||
}
|
||||
|
||||
return instances
|
||||
}
|
||||
|
||||
function getAssembly(transforms: Mat4[], structure: Structure) {
|
||||
const builder = Structure.Builder()
|
||||
const { units } = structure;
|
||||
|
||||
for (let i = 0, il = transforms.length; i < il; ++i) {
|
||||
const id = `${i + 1}`
|
||||
const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [ id ] } })
|
||||
for (const unit of units) {
|
||||
builder.addWithOperator(unit, op)
|
||||
}
|
||||
}
|
||||
|
||||
return builder.getStructure();
|
||||
}
|
||||
|
||||
function getCifCurve(name: string, transforms: Mat4[], model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) throw new Error('mmcif source data needed')
|
||||
|
||||
const { db } = model.sourceData.data
|
||||
const d = db.atom_site
|
||||
const n = d._rowCount
|
||||
const rowCount = n * transforms.length
|
||||
|
||||
const { offsets, count } = model.atomicHierarchy.chainAtomSegments
|
||||
|
||||
const x = d.Cartn_x.toArray()
|
||||
const y = d.Cartn_y.toArray()
|
||||
const z = d.Cartn_z.toArray()
|
||||
|
||||
const Cartn_x = new Float32Array(rowCount)
|
||||
const Cartn_y = new Float32Array(rowCount)
|
||||
const Cartn_z = new Float32Array(rowCount)
|
||||
const map = new Uint32Array(rowCount)
|
||||
const seq = new Int32Array(rowCount)
|
||||
let offset = 0
|
||||
for (let c = 0; c < count; ++c) {
|
||||
const cStart = offsets[c]
|
||||
const cEnd = offsets[c + 1]
|
||||
const cLength = cEnd - cStart
|
||||
for (let t = 0, tl = transforms.length; t < tl; ++t) {
|
||||
const m = transforms[t]
|
||||
for (let j = cStart; j < cEnd; ++j) {
|
||||
const i = offset + j - cStart
|
||||
const xj = x[j], yj = y[j], zj = z[j]
|
||||
Cartn_x[i] = m[0] * xj + m[4] * yj + m[8] * zj + m[12]
|
||||
Cartn_y[i] = m[1] * xj + m[5] * yj + m[9] * zj + m[13]
|
||||
Cartn_z[i] = m[2] * xj + m[6] * yj + m[10] * zj + m[14]
|
||||
map[i] = j
|
||||
seq[i] = t + 1
|
||||
}
|
||||
offset += cLength
|
||||
}
|
||||
}
|
||||
|
||||
function multColumn<T>(column: Column<T>) {
|
||||
const array = column.toArray()
|
||||
return Column.ofLambda({
|
||||
value: row => array[map[row]],
|
||||
areValuesEqual: (rowA, rowB) => map[rowA] === map[rowB] || array[map[rowA]] === array[map[rowB]],
|
||||
rowCount, schema: column.schema
|
||||
})
|
||||
}
|
||||
|
||||
const _atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = {
|
||||
auth_asym_id: CifField.ofColumn(multColumn(d.auth_asym_id)),
|
||||
auth_atom_id: CifField.ofColumn(multColumn(d.auth_atom_id)),
|
||||
auth_comp_id: CifField.ofColumn(multColumn(d.auth_comp_id)),
|
||||
auth_seq_id: CifField.ofNumbers(seq),
|
||||
|
||||
B_iso_or_equiv: CifField.ofColumn(Column.ofConst(0, rowCount, Column.Schema.float)),
|
||||
Cartn_x: CifField.ofNumbers(Cartn_x),
|
||||
Cartn_y: CifField.ofNumbers(Cartn_y),
|
||||
Cartn_z: CifField.ofNumbers(Cartn_z),
|
||||
group_PDB: CifField.ofColumn(Column.ofConst('ATOM', rowCount, Column.Schema.str)),
|
||||
id: CifField.ofColumn(Column.ofLambda({
|
||||
value: row => row,
|
||||
areValuesEqual: (rowA, rowB) => rowA === rowB,
|
||||
rowCount, schema: d.id.schema,
|
||||
})),
|
||||
|
||||
label_alt_id: CifField.ofColumn(multColumn(d.label_alt_id)),
|
||||
|
||||
label_asym_id: CifField.ofColumn(multColumn(d.label_asym_id)),
|
||||
label_atom_id: CifField.ofColumn(multColumn(d.label_atom_id)),
|
||||
label_comp_id: CifField.ofColumn(multColumn(d.label_comp_id)),
|
||||
label_seq_id: CifField.ofNumbers(seq),
|
||||
label_entity_id: CifField.ofColumn(Column.ofConst('1', rowCount, Column.Schema.str)),
|
||||
|
||||
occupancy: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.float)),
|
||||
type_symbol: CifField.ofColumn(multColumn(d.type_symbol)),
|
||||
|
||||
pdbx_PDB_ins_code: CifField.ofColumn(Column.ofConst('', rowCount, Column.Schema.str)),
|
||||
pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.int)),
|
||||
}
|
||||
|
||||
const categories = {
|
||||
entity: CifCategory.ofTable('entity', db.entity),
|
||||
chem_comp: CifCategory.ofTable('chem_comp', db.chem_comp),
|
||||
atom_site: CifCategory.ofFields('atom_site', _atom_site)
|
||||
}
|
||||
|
||||
return {
|
||||
header: name,
|
||||
categoryNames: Object.keys(categories),
|
||||
categories
|
||||
};
|
||||
}
|
||||
|
||||
async function getCurve(name: string, transforms: Mat4[], model: Model) {
|
||||
const cif = getCifCurve(name, transforms, model)
|
||||
|
||||
const curveModelTask = Task.create('Curve Model', async ctx => {
|
||||
const format = MmcifFormat.fromFrame(cif)
|
||||
const models = await createModels(format.data.db, format, ctx)
|
||||
return models[0]
|
||||
})
|
||||
|
||||
const curveModel = await curveModelTask.run()
|
||||
return getStructure(curveModel)
|
||||
}
|
||||
|
||||
async function getIngredientStructure(ingredient: Ingredient, baseUrl: string) {
|
||||
const { name, source, results, nbCurve } = ingredient
|
||||
|
||||
// TODO can these be added to the library?
|
||||
if (name === 'HIV1_CAhex_0_1_0') return
|
||||
if (name === 'HIV1_CAhexCyclophilA_0_1_0') return
|
||||
if (name === 'iLDL') return
|
||||
if (name === 'peptides') return
|
||||
if (name === 'lypoglycane') return
|
||||
|
||||
if (source.pdb === 'None') return
|
||||
|
||||
const model = await getModel(source.pdb || name, baseUrl)
|
||||
if (!model) return
|
||||
|
||||
if (nbCurve) {
|
||||
return getCurve(name, getCurveTransforms(ingredient), model)
|
||||
} else {
|
||||
const structure = await getStructure(model, { assembly: source.biomt ? '1' : undefined })
|
||||
return getAssembly(getResultTransforms(results), structure)
|
||||
}
|
||||
}
|
||||
|
||||
export function createStructureFromCellPack(packing: CellPacking, baseUrl: string) {
|
||||
return Task.create('Create Packing Structure', async ctx => {
|
||||
const { ingredients, name } = packing
|
||||
const structures: Structure[] = []
|
||||
for (const iName in ingredients) {
|
||||
if (ctx.shouldUpdate) await ctx.update(iName)
|
||||
const s = await getIngredientStructure(ingredients[iName], baseUrl)
|
||||
if (s) structures.push(s)
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - units`)
|
||||
const builder = Structure.Builder({ label: name })
|
||||
let offsetInvariantId = 0
|
||||
for (const s of structures) {
|
||||
if (ctx.shouldUpdate) await ctx.update(`${s.label}`)
|
||||
let maxInvariantId = 0
|
||||
for (const u of s.units) {
|
||||
const invariantId = u.invariantId + offsetInvariantId
|
||||
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId
|
||||
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId)
|
||||
}
|
||||
offsetInvariantId += maxInvariantId + 1
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`)
|
||||
const s = builder.getStructure()
|
||||
for( let i = 0, il = s.models.length; i < il; ++i) {
|
||||
const { trajectoryInfo } = s.models[i]
|
||||
trajectoryInfo.size = il
|
||||
trajectoryInfo.index = i
|
||||
}
|
||||
return s
|
||||
})
|
||||
}
|
||||
|
||||
async function handleHivRna(ctx: { runtime: RuntimeContext, fetch: AjaxTask }, packings: CellPacking[], baseUrl: string) {
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
|
||||
const url = `${baseUrl}/extras/rna_allpoints.json`
|
||||
const data = await ctx.fetch({ url, type: 'string' }).runInContext(ctx.runtime);
|
||||
const { points } = await (new Response(data)).json() as { points: number[] }
|
||||
|
||||
const curve0: Vec3[] = []
|
||||
for (let j = 0, jl = points.length; j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j))
|
||||
}
|
||||
packings[i].ingredients['RNA'] = {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadHivMembrane(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
|
||||
const url = `${params.baseUrl}/membranes/hiv_lipids.bcif`
|
||||
const membraneBuilder = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.Download, { label: 'hiv_lipids', url, isBinary: true }, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.StructureFromModel)
|
||||
await state.updateTree(membraneBuilder).runInContext(runtime)
|
||||
|
||||
const membraneParams = {
|
||||
representation: params.preset.representation,
|
||||
}
|
||||
const membrane = state.build().to(membraneBuilder.ref)
|
||||
await plugin.updateDataState(membrane, { revertOnError: true });
|
||||
await CellpackMembranePreset.apply(membrane.selector, membraneParams, plugin)
|
||||
}
|
||||
|
||||
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
|
||||
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>
|
||||
if (params.source.name === 'id') {
|
||||
const url = getCellPackModelUrl(params.source.params, params.baseUrl)
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } })
|
||||
} else {
|
||||
const file = params.source.params
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.ReadFile, { file, isBinary: false, label: file.name }, { state: { isGhost: true } })
|
||||
}
|
||||
|
||||
const cellPackBuilder = cellPackJson
|
||||
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack)
|
||||
|
||||
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime)
|
||||
const { packings } = cellPackObject.data
|
||||
|
||||
await handleHivRna({ runtime, fetch: plugin.fetch }, packings, params.baseUrl)
|
||||
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
const p = { packing: i, baseUrl: params.baseUrl }
|
||||
|
||||
const packing = state.build().to(cellPackBuilder.ref).apply(StructureFromCellpack, p)
|
||||
await plugin.updateDataState(packing, { revertOnError: true });
|
||||
|
||||
const structure = packing.selector.obj?.data
|
||||
if (structure) {
|
||||
await CellPackInfoProvider.attach({ fetch: plugin.fetch, runtime }, structure, {
|
||||
info: { packingsCount: packings.length, packingIndex: i }
|
||||
})
|
||||
}
|
||||
|
||||
const packingParams = {
|
||||
traceOnly: params.preset.traceOnly,
|
||||
representation: params.preset.representation,
|
||||
}
|
||||
await CellpackPackingPreset.apply(packing.selector, packingParams, plugin)
|
||||
}
|
||||
}
|
||||
|
||||
const LoadCellPackModelParams = {
|
||||
source: PD.MappedStatic('id', {
|
||||
'id': PD.Select('influenza_model1.json', [
|
||||
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
||||
['hiv_lipids.bcif', 'hiv_lipids'],
|
||||
['influenza_model1.json', 'influenza_model1'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
] as const),
|
||||
'file': PD.File({ accept: 'id' }),
|
||||
}, { options: [['id', 'Id'], ['file', 'File']] }),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
preset: PD.Group({
|
||||
traceOnly: PD.Boolean(false),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'ellipsoid']))
|
||||
}, { isExpanded: true })
|
||||
}
|
||||
type LoadCellPackModelParams = PD.Values<typeof LoadCellPackModelParams>
|
||||
|
||||
export const LoadCellPackModel = StateAction.build({
|
||||
display: { name: 'Load CellPack', description: 'Open or download a model' },
|
||||
params: LoadCellPackModelParams,
|
||||
from: PSO.Root
|
||||
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
|
||||
if (!ctx.representation.structure.themes.colorThemeRegistry.has(CellPackColorThemeProvider)) {
|
||||
ctx.representation.structure.themes.colorThemeRegistry.add(CellPackColorThemeProvider)
|
||||
}
|
||||
|
||||
if (params.source.name === 'id' && params.source.params === 'hiv_lipids.bcif') {
|
||||
await loadHivMembrane(ctx, taskCtx, state, params)
|
||||
} else {
|
||||
await loadPackings(ctx, taskCtx, state, params)
|
||||
}
|
||||
}));
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Task } from '../../../../mol-task';
|
||||
import { CellPack as _CellPack, Cell, CellPacking } from './data';
|
||||
import { createStructureFromCellPack } from './model';
|
||||
|
||||
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/'
|
||||
|
||||
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
|
||||
|
||||
export { ParseCellPack }
|
||||
type ParseCellPack = typeof ParseCellPack
|
||||
const ParseCellPack = PluginStateTransform.BuiltIn({
|
||||
name: 'parse-cellpack',
|
||||
display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
|
||||
from: PSO.Format.Json,
|
||||
to: CellPack
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse CellPack', async ctx => {
|
||||
const cell = a.data as Cell
|
||||
|
||||
const packings: CellPacking[] = []
|
||||
const { compartments, cytoplasme } = cell
|
||||
if (compartments) {
|
||||
for (const name in compartments) {
|
||||
const { surface, interior } = compartments[name]
|
||||
if (surface) packings.push({ name, location: 'surface', ingredients: surface.ingredients })
|
||||
if (interior) packings.push({ name, location: 'interior', ingredients: interior.ingredients })
|
||||
}
|
||||
}
|
||||
if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients })
|
||||
|
||||
return new CellPack({ cell, packings });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export { StructureFromCellpack }
|
||||
type StructureFromCellpack = typeof ParseCellPack
|
||||
const StructureFromCellpack = PluginStateTransform.BuiltIn({
|
||||
name: 'structure-from-cellpack',
|
||||
display: { name: 'Structure from CellPack', description: 'Create Structure from CellPack Packing' },
|
||||
from: CellPack,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: a => {
|
||||
if (!a) {
|
||||
return {
|
||||
packing: PD.Numeric(0, {}, { description: 'Packing Index' }),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl)
|
||||
};
|
||||
}
|
||||
const options = a.data.packings.map((d, i) => [i, d.name] as [number, string])
|
||||
return {
|
||||
packing: PD.Select(0, options),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl)
|
||||
}
|
||||
}
|
||||
})({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Structure from CellPack', async ctx => {
|
||||
const packing = a.data.packings[params.packing]
|
||||
const structure = await createStructureFromCellPack(packing, params.baseUrl).runInContext(ctx)
|
||||
return new PSO.Molecule.Structure(structure, { label: packing.name })
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CIF } from '../../../../mol-io/reader/cif'
|
||||
import { parsePDB } from '../../../../mol-io/reader/pdb/parser';
|
||||
|
||||
async function parseCif(data: string|Uint8Array) {
|
||||
const comp = CIF.parse(data);
|
||||
const parsed = await comp.run();
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
async function parsePDBfile(data: string, id: string) {
|
||||
const comp = parsePDB(data, id);
|
||||
const parsed = await comp.run();
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
async function downloadCif(url: string, isBinary: boolean) {
|
||||
const data = await fetch(url);
|
||||
return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text());
|
||||
}
|
||||
|
||||
async function downloadPDB(url: string, id: string) {
|
||||
const data = await fetch(url);
|
||||
return parsePDBfile(await data.text(), id);
|
||||
}
|
||||
|
||||
export async function getFromPdb(id: string) {
|
||||
const parsed = await downloadCif(`https://files.rcsb.org/download/${id}.cif`, false);
|
||||
return parsed.blocks[0];
|
||||
}
|
||||
|
||||
function getCellPackDataUrl(id: string, baseUrl: string) {
|
||||
const url = `${baseUrl}/other/${id}`
|
||||
return url.endsWith('.pdb') ? url : `${url}.pdb`
|
||||
}
|
||||
|
||||
export async function getFromCellPackDB(id: string, baseUrl: string) {
|
||||
const name = id.endsWith('.pdb') ? id.substring(0, id.length - 4) : id
|
||||
const parsed = await downloadPDB(getCellPackDataUrl(id, baseUrl), name);
|
||||
return parsed;
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
// /**
|
||||
// * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
// *
|
||||
// * @author David Sehnal <david.sehnal@gmail.com>
|
||||
// */
|
||||
|
||||
// import { StateTree, StateBuilder, StateAction, State } from '../../../mol-state';
|
||||
// import { StateTransforms } from '../../../mol-plugin/state/transforms';
|
||||
// import { createModelTree } from '../../../mol-plugin/state/actions/structure';
|
||||
// import { PluginContext } from '../../../mol-plugin/context';
|
||||
// import { PluginStateObject } from '../../../mol-plugin/state/objects';
|
||||
// import { ParamDefinition } from '../../../mol-util/param-definition';
|
||||
// import { PluginCommands } from '../../../mol-plugin/command';
|
||||
// import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
// import { PluginStateSnapshotManager } from '../../../mol-plugin/state/snapshots';
|
||||
// import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
|
||||
// import { Text } from '../../../mol-geo/geometry/text/text';
|
||||
// import { UUID } from '../../../mol-util';
|
||||
// import { ColorNames } from '../../../mol-util/color/names';
|
||||
// import { Camera } from '../../../mol-canvas3d/camera';
|
||||
// import { createStructureRepresentation3dParamss } from '../../../mol-plugin/state/transforms/representation';
|
||||
// import { createDefaultStructureComplex } from '../../../mol-plugin/util/structure-complex-helper';
|
||||
|
||||
// export const CreateJoleculeState = StateAction.build({
|
||||
// display: { name: 'Jolecule State Import' },
|
||||
// params: { id: ParamDefinition.Text('1mbo') },
|
||||
// from: PluginStateObject.Root
|
||||
// })(async ({ ref, state, params }, plugin: PluginContext) => {
|
||||
// try {
|
||||
// const id = params.id.trim().toLowerCase();
|
||||
// const data = await plugin.runTask(plugin.fetch({ url: `https://jolecule.appspot.com/pdb/${id}.views.json`, type: 'json' })) as JoleculeSnapshot[];
|
||||
|
||||
// data.sort((a, b) => a.order - b.order);
|
||||
|
||||
// await PluginCommands.State.RemoveObject.dispatch(plugin, { state, ref });
|
||||
// plugin.state.snapshots.clear();
|
||||
|
||||
// const template = createTemplate(plugin, state, id);
|
||||
// const snapshots = data.map((e, idx) => buildSnapshot(plugin, template, { e, idx, len: data.length }));
|
||||
// for (const s of snapshots) {
|
||||
// plugin.state.snapshots.add(s);
|
||||
// }
|
||||
|
||||
// PluginCommands.State.Snapshots.Apply.dispatch(plugin, { id: snapshots[0].snapshot.id });
|
||||
// } catch (e) {
|
||||
// plugin.log.error(`Jolecule Failed: ${e}`);
|
||||
// }
|
||||
// });
|
||||
|
||||
// interface JoleculeSnapshot {
|
||||
// order: number,
|
||||
// distances: { i_atom1: number, i_atom2: number }[],
|
||||
// labels: { i_atom: number, text: string }[],
|
||||
// camera: { up: Vec3, pos: Vec3, in: Vec3, slab: { z_front: number, z_back: number, zoom: number } },
|
||||
// selected: number[],
|
||||
// text: string
|
||||
// }
|
||||
|
||||
// function createTemplate(plugin: PluginContext, state: State, id: string) {
|
||||
// const b = new StateBuilder.Root(state.tree);
|
||||
// const data = b.toRoot().apply(StateTransforms.Data.Download, { url: `https://www.ebi.ac.uk/pdbe/static/entry/${id}_updated.cif` }, { state: { isGhost: true }});
|
||||
// const model = createModelTree(data, 'cif');
|
||||
// const structure = model.apply(StateTransforms.Model.StructureFromModel);
|
||||
// createDefaultStructureComplex(plugin, structure);
|
||||
// return { tree: b.getTree(), structure: structure.ref };
|
||||
// }
|
||||
|
||||
// const labelOptions: ParamDefinition.Values<Text.Params> = {
|
||||
// ...ParamDefinition.getDefaultValues(Text.Params),
|
||||
// tether: true,
|
||||
// sizeFactor: 1.3,
|
||||
// attachment: 'bottom-right',
|
||||
// offsetZ: 10,
|
||||
// background: true,
|
||||
// backgroundMargin: 0.2,
|
||||
// backgroundColor: ColorNames.skyblue,
|
||||
// backgroundOpacity: 0.9
|
||||
// }
|
||||
|
||||
// // const distanceLabelOptions = {
|
||||
// // ...ParamDefinition.getDefaultValues(Text.Params),
|
||||
// // sizeFactor: 1,
|
||||
// // offsetX: 0,
|
||||
// // offsetY: 0,
|
||||
// // offsetZ: 10,
|
||||
// // background: true,
|
||||
// // backgroundMargin: 0.2,
|
||||
// // backgroundColor: ColorNames.snow,
|
||||
// // backgroundOpacity: 0.9
|
||||
// // }
|
||||
|
||||
// function buildSnapshot(plugin: PluginContext, template: { tree: StateTree, structure: string }, params: { e: JoleculeSnapshot, idx: number, len: number }): PluginStateSnapshotManager.Entry {
|
||||
// const b = new StateBuilder.Root(template.tree);
|
||||
|
||||
// let i = 0;
|
||||
// for (const l of params.e.labels) {
|
||||
// const expression = createExpression([l.i_atom]);
|
||||
// const group = b.to(template.structure)
|
||||
// .group(StateTransforms.Misc.CreateGroup, { label: `Label ${++i}` });
|
||||
|
||||
// group
|
||||
// .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression, label: 'Atom' })
|
||||
// .apply(StateTransforms.Representation.StructureLabels3D, {
|
||||
// target: { name: 'static-text', params: { value: l.text || '' } },
|
||||
// options: labelOptions
|
||||
// });
|
||||
|
||||
// group
|
||||
// .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: MS.struct.modifier.wholeResidues([ expression ]), label: 'Residue' })
|
||||
// .apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
// createStructureRepresentation3dParamss.getDefaultParamsStatic(plugin, 'ball-and-stick', { }));
|
||||
// }
|
||||
// if (params.e.selected && params.e.selected.length > 0) {
|
||||
// b.to(template.structure)
|
||||
// .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: createExpression(params.e.selected), label: `Selected` })
|
||||
// .apply(StateTransforms.Representation.StructureRepresentation3D,
|
||||
// createStructureRepresentation3dParamss.getDefaultParamsStatic(plugin, 'ball-and-stick'));
|
||||
// }
|
||||
// // TODO
|
||||
// // for (const l of params.e.distances) {
|
||||
// // b.to('structure')
|
||||
// // .apply(StateTransforms.Model.StructureSelectionFromExpression, { query: createQuery([l.i_atom1, l.i_atom2]), label: `Distance ${++i}` })
|
||||
// // .apply(StateTransforms.Representation.StructureLabels3D, {
|
||||
// // target: { name: 'static-text', params: { value: l. || '' } },
|
||||
// // options: labelOptions
|
||||
// // });
|
||||
// // }
|
||||
// return PluginStateSnapshotManager.Entry({
|
||||
// id: UUID.create22(),
|
||||
// data: { tree: StateTree.toJSON(b.getTree()) },
|
||||
// camera: {
|
||||
// current: getCameraSnapshot(params.e.camera),
|
||||
// transitionStyle: 'animate',
|
||||
// transitionDurationInMs: 350
|
||||
// }
|
||||
// }, {
|
||||
// name: params.e.text
|
||||
// });
|
||||
// }
|
||||
|
||||
// function getCameraSnapshot(e: JoleculeSnapshot['camera']): Camera.Snapshot {
|
||||
// const direction = Vec3.sub(Vec3(), e.pos, e.in);
|
||||
// Vec3.normalize(direction, direction);
|
||||
// const up = Vec3.sub(Vec3(), e.pos, e.up);
|
||||
// Vec3.normalize(up, up);
|
||||
|
||||
// const s: Camera.Snapshot = {
|
||||
// mode: 'perspective',
|
||||
// fov: Math.PI / 4,
|
||||
// position: Vec3.scaleAndAdd(Vec3(), e.pos, direction, e.slab.zoom),
|
||||
// target: e.pos,
|
||||
// radius: (e.slab.z_back - e.slab.z_front) / 2,
|
||||
// fog: 50,
|
||||
// up,
|
||||
// };
|
||||
// return s;
|
||||
// }
|
||||
|
||||
// function createExpression(atomIndices: number[]) {
|
||||
// if (atomIndices.length === 0) return MS.struct.generator.empty();
|
||||
|
||||
// return MS.struct.generator.atomGroups({
|
||||
// 'atom-test': atomIndices.length === 1
|
||||
// ? MS.core.rel.eq([MS.struct.atomProperty.core.sourceIndex(), atomIndices[0]])
|
||||
// : MS.core.set.has([MS.set.apply(null, atomIndices), MS.struct.atomProperty.core.sourceIndex()]),
|
||||
// 'group-by': 0
|
||||
// });
|
||||
// }
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2020 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>
|
||||
@@ -7,16 +7,18 @@
|
||||
|
||||
import '../../mol-util/polyfill';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import './favicon.ico'
|
||||
import './index.html';
|
||||
import './favicon.ico';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginSpec } from '../../mol-plugin/spec';
|
||||
import { LoadCellPackModel } from './extensions/cellpack/model';
|
||||
import { StructureFromCellpack } from './extensions/cellpack/state';
|
||||
import { DownloadStructure } from '../../mol-plugin-state/actions/structure';
|
||||
import { PluginConfig } from '../../mol-plugin/config';
|
||||
require('mol-plugin-ui/skin/light.scss')
|
||||
import { CellPack } from '../../extensions/cellpack';
|
||||
import { RCSBAssemblySymmetry, RCSBValidationReport } from '../../extensions/rcsb';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
function getParam(name: string, regex: string): string {
|
||||
let r = new RegExp(`${name}=(${regex})[&]?`, 'i');
|
||||
@@ -27,13 +29,14 @@ const hideControls = getParam('hide-controls', `[^&]+`) === '1';
|
||||
|
||||
function init() {
|
||||
const spec: PluginSpec = {
|
||||
actions: [
|
||||
...DefaultPluginSpec.actions,
|
||||
// PluginSpec.Action(CreateJoleculeState),
|
||||
PluginSpec.Action(LoadCellPackModel),
|
||||
PluginSpec.Action(StructureFromCellpack),
|
||||
actions: [...DefaultPluginSpec.actions],
|
||||
behaviors: [
|
||||
...DefaultPluginSpec.behaviors,
|
||||
PluginSpec.Behavior(CellPack),
|
||||
PluginSpec.Behavior(PDBeStructureQualityReport),
|
||||
PluginSpec.Behavior(RCSBAssemblySymmetry),
|
||||
PluginSpec.Behavior(RCSBValidationReport),
|
||||
],
|
||||
behaviors: [...DefaultPluginSpec.behaviors],
|
||||
animations: [...DefaultPluginSpec.animations || []],
|
||||
customParamEditors: DefaultPluginSpec.customParamEditors,
|
||||
layout: {
|
||||
@@ -62,7 +65,7 @@ async function trySetSnapshot(ctx: PluginContext) {
|
||||
const url = snapshotId
|
||||
? `https://webchem.ncbr.muni.cz/molstar-state/get/${snapshotId}`
|
||||
: snapshotUrl;
|
||||
await PluginCommands.State.Snapshots.Fetch(ctx, { url })
|
||||
await PluginCommands.State.Snapshots.Fetch(ctx, { url });
|
||||
} catch (e) {
|
||||
ctx.log.error('Failed to load snapshot.');
|
||||
console.warn('Failed to load snapshot', e);
|
||||
@@ -86,7 +89,7 @@ async function tryLoadFromUrl(ctx: PluginContext) {
|
||||
source: {
|
||||
name: 'url',
|
||||
params: {
|
||||
url,
|
||||
url: Asset.Url(url),
|
||||
format: format as any,
|
||||
isBinary,
|
||||
options: params.source.params.options,
|
||||
|
||||
@@ -18,13 +18,13 @@ export const StripedResidues = CustomElementProperty.create<number>({
|
||||
for (let i = 0, _i = model.atomicHierarchy.atoms._rowCount; i < _i; i++) {
|
||||
map.set(i as ElementIndex, residueIndex[i] % 2);
|
||||
}
|
||||
return map;
|
||||
return { value: map };
|
||||
},
|
||||
coloring: {
|
||||
getColor(e) { return e === 0 ? Color(0xff0000) : Color(0x0000ff) },
|
||||
getColor(e) { return e === 0 ? Color(0xff0000) : Color(0x0000ff); },
|
||||
defaultColor: Color(0x777777)
|
||||
},
|
||||
getLabel(e) {
|
||||
return e === 0 ? 'Odd stripe' : 'Even stripe'
|
||||
return e === 0 ? 'Odd stripe' : 'Even stripe';
|
||||
}
|
||||
})
|
||||
});
|
||||
16
src/examples/basic-wrapper/controls.tsx
Normal file
16
src/examples/basic-wrapper/controls.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginUIComponent } from '../../mol-plugin-ui/base';
|
||||
import * as React from 'react';
|
||||
|
||||
export class CustomToastMessage extends PluginUIComponent {
|
||||
render() {
|
||||
return <>
|
||||
Custom <i>Toast</i> content. No timeout.
|
||||
</>;
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,7 @@
|
||||
<input type='text' id='url' placeholder='url' />
|
||||
<input type='text' id='assemblyId' placeholder='assembly id' />
|
||||
<select id='format'>
|
||||
<option value='cif' selected>CIF</option>
|
||||
<option value='mmcif' selected>mmCIF</option>
|
||||
<option value='pdb'>PDB</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
var pdbId = '1grm', assemblyId= '1';
|
||||
var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif';
|
||||
var format = 'cif';
|
||||
var format = 'mmcif';
|
||||
|
||||
$('url').value = url;
|
||||
$('url').onchange = function (e) { url = e.target.value; }
|
||||
@@ -104,6 +104,7 @@
|
||||
addHeader('Misc');
|
||||
|
||||
addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes());
|
||||
addControl('Default Coloring', () => BasicMolStarWrapper.coloring.applyDefault());
|
||||
|
||||
addHeader('Interactivity');
|
||||
addControl('Highlight seq_id=7', () => BasicMolStarWrapper.interactivity.highlightOn());
|
||||
156
src/examples/basic-wrapper/index.ts
Normal file
156
src/examples/basic-wrapper/index.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { EmptyLoci } from '../../mol-model/loci';
|
||||
import { StructureSelection } from '../../mol-model/structure';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import { AnimateModelIndex } from '../../mol-plugin-state/animation/built-in';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { PluginStateObject } from '../../mol-plugin-state/objects';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { Script } from '../../mol-script/script';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { StripedResidues } from './coloring';
|
||||
import { CustomToastMessage } from './controls';
|
||||
import './index.html';
|
||||
import { buildStaticSuperposition, dynamicSuperpositionTest, StaticSuperpositionTestData } from './superposition';
|
||||
import { PDBeStructureQualityReport } from '../../extensions/pdbe';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }
|
||||
|
||||
class BasicWrapper {
|
||||
plugin: PluginContext;
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: {
|
||||
// left: 'none'
|
||||
}
|
||||
},
|
||||
components: {
|
||||
remoteState: 'none'
|
||||
}
|
||||
});
|
||||
|
||||
this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!);
|
||||
this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
|
||||
this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
|
||||
}
|
||||
|
||||
async load({ url, format = 'mmcif', isBinary = false, assemblyId = '' }: LoadParams) {
|
||||
await this.plugin.clear();
|
||||
|
||||
const data = await this.plugin.builders.data.download({ url: Asset.Url(url), isBinary }, { state: { isGhost: true } });
|
||||
const trajectory = await this.plugin.builders.structure.parseTrajectory(data, format);
|
||||
|
||||
await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, 'default', {
|
||||
structure: assemblyId ? { name: 'assembly', params: { id: assemblyId } } : { name: 'deposited', params: { } },
|
||||
showUnitcell: false,
|
||||
representationPreset: 'auto'
|
||||
});
|
||||
}
|
||||
|
||||
setBackground(color: number) {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: props => { props.renderer.backgroundColor = Color(color); } });
|
||||
}
|
||||
|
||||
toggleSpin() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
settings: props => {
|
||||
props.trackball.spin = !props.trackball.spin;
|
||||
}
|
||||
});
|
||||
if (!this.plugin.canvas3d.props.trackball.spin) PluginCommands.Camera.Reset(this.plugin, {});
|
||||
}
|
||||
|
||||
animate = {
|
||||
modelIndex: {
|
||||
maxFPS: 8,
|
||||
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }); },
|
||||
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }); },
|
||||
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }); },
|
||||
stop: () => this.plugin.managers.animation.stop()
|
||||
}
|
||||
}
|
||||
|
||||
coloring = {
|
||||
applyStripes: async () => {
|
||||
this.plugin.dataTransaction(async () => {
|
||||
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(s.components, { color: StripedResidues.propertyProvider.descriptor.name as any });
|
||||
}
|
||||
});
|
||||
},
|
||||
applyDefault: async () => {
|
||||
this.plugin.dataTransaction(async () => {
|
||||
for (const s of this.plugin.managers.structure.hierarchy.current.structures) {
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(s.components, { color: 'default' });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interactivity = {
|
||||
highlightOn: () => {
|
||||
const seq_id = 7;
|
||||
const data = (this.plugin.state.data.select('asm')[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
const sel = Script.getStructureSelection(Q => Q.struct.generator.atomGroups({
|
||||
'residue-test': Q.core.rel.eq([Q.struct.atomProperty.macromolecular.label_seq_id(), seq_id]),
|
||||
'group-by': Q.struct.atomProperty.macromolecular.residueKey()
|
||||
}), data);
|
||||
const loci = StructureSelection.toLociWithSourceUnits(sel);
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci });
|
||||
},
|
||||
clearHighlight: () => {
|
||||
this.plugin.managers.interactivity.lociHighlights.highlightOnly({ loci: EmptyLoci });
|
||||
}
|
||||
}
|
||||
|
||||
tests = {
|
||||
staticSuperposition: async () => {
|
||||
await this.plugin.clear();
|
||||
return buildStaticSuperposition(this.plugin, StaticSuperpositionTestData);
|
||||
},
|
||||
dynamicSuperposition: async () => {
|
||||
await this.plugin.clear();
|
||||
return dynamicSuperpositionTest(this.plugin, ['1tqn', '2hhb', '4hhb'], 'HEM');
|
||||
},
|
||||
toggleValidationTooltip: () => {
|
||||
return this.plugin.state.updateBehavior(PDBeStructureQualityReport, params => { params.showTooltip = !params.showTooltip; });
|
||||
},
|
||||
showToasts: () => {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Toast 1',
|
||||
message: 'This is an example text, timeout 3s',
|
||||
key: 'toast-1',
|
||||
timeoutMs: 3000
|
||||
});
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Toast 2',
|
||||
message: CustomToastMessage,
|
||||
key: 'toast-2'
|
||||
});
|
||||
},
|
||||
hideToasts: () => {
|
||||
PluginCommands.Toast.Hide(this.plugin, { key: 'toast-1' });
|
||||
PluginCommands.Toast.Hide(this.plugin, { key: 'toast-2' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).BasicMolStarWrapper = new BasicWrapper();
|
||||
119
src/examples/basic-wrapper/superposition.ts
Normal file
119
src/examples/basic-wrapper/superposition.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { QueryContext, StructureSelection } from '../../mol-model/structure';
|
||||
import { superposeStructures } from '../../mol-model/structure/structure/util/superposition';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import Expression from '../../mol-script/language/expression';
|
||||
import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
|
||||
export type SuperpositionTestInput = {
|
||||
pdbId: string,
|
||||
auth_asym_id: string,
|
||||
matrix: Mat4
|
||||
}[];
|
||||
|
||||
export function buildStaticSuperposition(plugin: PluginContext, src: SuperpositionTestInput) {
|
||||
return plugin.dataTransaction(async () => {
|
||||
for (const s of src) {
|
||||
const { structure } = await loadStructure(plugin, `https://www.ebi.ac.uk/pdbe/static/entry/${s.pdbId}_updated.cif`, 'mmcif');
|
||||
await transform(plugin, structure, s.matrix);
|
||||
const chain = await plugin.builders.structure.tryCreateComponentFromExpression(structure, chainSelection(s.auth_asym_id), `Chain ${s.auth_asym_id}`);
|
||||
if (chain) await plugin.builders.structure.representation.addRepresentation(chain, { type: 'cartoon' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const StaticSuperpositionTestData: SuperpositionTestInput = [
|
||||
{
|
||||
pdbId: '1aj5', auth_asym_id: 'A', matrix: Mat4.identity()
|
||||
},
|
||||
{
|
||||
pdbId: '1df0', auth_asym_id: 'B', matrix: Mat4.ofRows([
|
||||
[0.406, 0.879, 0.248, -200.633],
|
||||
[0.693, -0.473, 0.544, 73.403],
|
||||
[0.596, -0.049, -0.802, -14.209],
|
||||
[0, 0, 0, 1]])
|
||||
},
|
||||
{
|
||||
pdbId: '1dvi', auth_asym_id: 'A', matrix: Mat4.ofRows([
|
||||
[-0.053, -0.077, 0.996, -45.633],
|
||||
[-0.312, 0.949, 0.057, -12.255],
|
||||
[-0.949, -0.307, -0.074, 53.562],
|
||||
[0, 0, 0, 1]])
|
||||
}
|
||||
];
|
||||
|
||||
export function dynamicSuperpositionTest(plugin: PluginContext, src: string[], comp_id: string) {
|
||||
return plugin.dataTransaction(async () => {
|
||||
for (const s of src) {
|
||||
await loadStructure(plugin, `https://www.ebi.ac.uk/pdbe/static/entry/${s}_updated.cif`, 'mmcif');
|
||||
}
|
||||
|
||||
const pivot = MS.struct.filter.first([
|
||||
MS.struct.generator.atomGroups({
|
||||
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), comp_id]),
|
||||
'group-by': MS.struct.atomProperty.macromolecular.residueKey()
|
||||
})
|
||||
]);
|
||||
|
||||
const rest = MS.struct.modifier.exceptBy({
|
||||
0: MS.struct.modifier.includeSurroundings({
|
||||
0: pivot,
|
||||
radius: 5
|
||||
}),
|
||||
by: pivot
|
||||
});
|
||||
|
||||
const query = compile<StructureSelection>(pivot);
|
||||
const xs = plugin.managers.structure.hierarchy.current.structures;
|
||||
const selections = xs.map(s => StructureSelection.toLociWithCurrentUnits(query(new QueryContext(s.cell.obj!.data))));
|
||||
|
||||
const transforms = superposeStructures(selections);
|
||||
|
||||
await siteVisual(plugin, xs[0].cell, pivot, rest);
|
||||
for (let i = 1; i < selections.length; i++) {
|
||||
await transform(plugin, xs[i].cell, transforms[i - 1].bTransform);
|
||||
await siteVisual(plugin, xs[i].cell, pivot, rest);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function siteVisual(plugin: PluginContext, s: StateObjectRef<PSO.Molecule.Structure>, pivot: Expression, rest: Expression) {
|
||||
const center = await plugin.builders.structure.tryCreateComponentFromExpression(s, pivot, 'pivot');
|
||||
if (center) await plugin.builders.structure.representation.addRepresentation(center, { type: 'ball-and-stick', color: 'residue-name' });
|
||||
|
||||
const surr = await plugin.builders.structure.tryCreateComponentFromExpression(s, rest, 'rest');
|
||||
if (surr) await plugin.builders.structure.representation.addRepresentation(surr, { type: 'ball-and-stick', color: 'uniform', size: 'uniform', sizeParams: { value: 0.33 } });
|
||||
}
|
||||
|
||||
async function loadStructure(plugin: PluginContext, url: string, format: BuiltInTrajectoryFormat, assemblyId?: string) {
|
||||
const data = await plugin.builders.data.download({ url: Asset.Url(url) });
|
||||
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
|
||||
const model = await plugin.builders.structure.createModel(trajectory);
|
||||
const structure = await plugin.builders.structure.createStructure(model, assemblyId ? { name: 'assembly', params: { id: assemblyId } } : void 0);
|
||||
|
||||
return { data, trajectory, model, structure };
|
||||
}
|
||||
|
||||
function chainSelection(auth_asym_id: string) {
|
||||
return MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), auth_asym_id])
|
||||
});
|
||||
}
|
||||
|
||||
function transform(plugin: PluginContext, s: StateObjectRef<PSO.Molecule.Structure>, matrix: Mat4) {
|
||||
const b = plugin.state.data.build().to(s)
|
||||
.insert(StateTransforms.Model.TransformStructureConformation, { transform: { name: 'matrix', params: { data: matrix, transpose: false } } });
|
||||
return plugin.runTask(plugin.state.data.updateTree(b));
|
||||
}
|
||||
@@ -4,9 +4,9 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Table } from '../../mol-data/db'
|
||||
import { CifWriter } from '../../mol-io/writer/cif'
|
||||
import * as S from './schemas'
|
||||
import { Table } from '../../mol-data/db';
|
||||
import { CifWriter } from '../../mol-io/writer/cif';
|
||||
import * as S from './schemas';
|
||||
// import { getCategoryInstanceProvider } from './utils'
|
||||
|
||||
export default function create(allData: any) {
|
||||
@@ -4,21 +4,21 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Column } from '../../mol-data/db'
|
||||
import { Column } from '../../mol-data/db';
|
||||
|
||||
import Type = Column.Schema
|
||||
|
||||
export const Sources = {
|
||||
id: Type.str,
|
||||
count: Type.int
|
||||
}
|
||||
};
|
||||
export type Sources = typeof Sources
|
||||
|
||||
export const Base = {
|
||||
id: Type.str,
|
||||
identifier: Type.str,
|
||||
mapping_group_id: Type.int
|
||||
}
|
||||
};
|
||||
export type Base = typeof Base
|
||||
|
||||
export const mapping = {
|
||||
@@ -36,17 +36,17 @@ export const mapping = {
|
||||
end_label_seq_id: Type.int,
|
||||
end_auth_seq_id: Type.int,
|
||||
pdbx_end_PDB_ins_code: Type.str
|
||||
}
|
||||
};
|
||||
export type mapping = typeof mapping
|
||||
|
||||
export const Pfam = {
|
||||
description: Type.str
|
||||
}
|
||||
};
|
||||
export type Pfam = typeof Pfam
|
||||
|
||||
export const InterPro = {
|
||||
name: Type.str
|
||||
}
|
||||
};
|
||||
export type InterPro = typeof InterPro
|
||||
|
||||
export const CATH = {
|
||||
@@ -56,32 +56,32 @@ export const CATH = {
|
||||
identifier: Type.str,
|
||||
class: Type.str,
|
||||
topology: Type.str,
|
||||
}
|
||||
};
|
||||
export type CATH = typeof CATH
|
||||
|
||||
export const EC = {
|
||||
accepted_name: Type.str,
|
||||
reaction: Type.str,
|
||||
systematic_name: Type.str
|
||||
}
|
||||
};
|
||||
export type EC = typeof EC
|
||||
|
||||
export const UniProt = {
|
||||
name: Type.str
|
||||
}
|
||||
};
|
||||
export type UniProt = typeof UniProt
|
||||
|
||||
export const SCOP = {
|
||||
sccs: Type.str,
|
||||
description: Type.str
|
||||
}
|
||||
};
|
||||
export type SCOP = typeof SCOP
|
||||
|
||||
export const GO = {
|
||||
category: Type.str,
|
||||
definition: Type.str,
|
||||
name: Type.str
|
||||
}
|
||||
};
|
||||
export type GO = typeof GO
|
||||
|
||||
export const categories = {
|
||||
@@ -92,4 +92,4 @@ export const categories = {
|
||||
UniProt,
|
||||
SCOP,
|
||||
GO
|
||||
}
|
||||
};
|
||||
@@ -4,9 +4,9 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import * as express from 'express'
|
||||
import fetch from 'node-fetch'
|
||||
import createMapping from './mapping'
|
||||
import express from 'express';
|
||||
import fetch from 'node-fetch';
|
||||
import createMapping from './mapping';
|
||||
|
||||
async function getMappings(id: string) {
|
||||
const data = await fetch(`https://www.ebi.ac.uk/pdbe/api/mappings/${id}`);
|
||||
@@ -19,7 +19,7 @@ let PORT = process.env.port || 1338;
|
||||
|
||||
const app = express();
|
||||
|
||||
const PREFIX = '/'
|
||||
const PREFIX = '/';
|
||||
|
||||
app.get(`${PREFIX}/:id`, async (req, res) => {
|
||||
try {
|
||||
@@ -41,7 +41,7 @@ app.get(`${PREFIX}/:id`, async (req, res) => {
|
||||
app.get(`${PREFIX}`, (req, res) => {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||
res.end('Usage: /pdb_id, e.g. /1tqn');
|
||||
})
|
||||
});
|
||||
|
||||
app.listen(PORT);
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import fetch from 'node-fetch'
|
||||
import createMapping from './mapping'
|
||||
import fetch from 'node-fetch';
|
||||
import createMapping from './mapping';
|
||||
|
||||
(async function () {
|
||||
const data = await fetch('https://www.ebi.ac.uk/pdbe/api/mappings/1tqn?pretty=true');
|
||||
118
src/examples/lighting/index.ts
Normal file
118
src/examples/lighting/index.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Canvas3DProps } from '../../mol-canvas3d/canvas3d';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import { BuiltInTrajectoryFormat } from '../../mol-plugin-state/formats/trajectory';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import './index.html';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
require('mol-plugin-ui/skin/light.scss');
|
||||
|
||||
type LoadParams = { url: string, format?: BuiltInTrajectoryFormat, isBinary?: boolean, assemblyId?: string }
|
||||
|
||||
type _Preset = Pick<Canvas3DProps, 'multiSample' | 'postprocessing' | 'renderer'>
|
||||
type Preset = { [K in keyof _Preset]: Partial<_Preset[K]> }
|
||||
|
||||
const Canvas3DPresets = {
|
||||
illustrative: <Preset> {
|
||||
multiSample: {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } },
|
||||
outline: { name: 'on', params: { scale: 1, threshold: 0.8 } }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 1,
|
||||
lightIntensity: 0,
|
||||
}
|
||||
},
|
||||
occlusion: <Preset> {
|
||||
multiSample: {
|
||||
mode: 'temporal' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { bias: 0.8, kernelSize: 6, radius: 64 } },
|
||||
outline: { name: 'off', params: { } }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
lightIntensity: 0.6,
|
||||
}
|
||||
},
|
||||
standard: <Preset> {
|
||||
multiSample: {
|
||||
mode: 'off' as Canvas3DProps['multiSample']['mode']
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: { name: 'off', params: { } },
|
||||
outline: { name: 'off', params: { } }
|
||||
},
|
||||
renderer: {
|
||||
ambientIntensity: 0.4,
|
||||
lightIntensity: 0.6,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
type Canvas3DPreset = keyof typeof Canvas3DPresets
|
||||
|
||||
class LightingDemo {
|
||||
plugin: PluginContext;
|
||||
|
||||
init(target: string | HTMLElement) {
|
||||
this.plugin = createPlugin(typeof target === 'string' ? document.getElementById(target)! : target, {
|
||||
...DefaultPluginSpec,
|
||||
layout: {
|
||||
initial: {
|
||||
isExpanded: false,
|
||||
showControls: false
|
||||
},
|
||||
controls: { left: 'none', right: 'none', top: 'none', bottom: 'none' }
|
||||
}
|
||||
});
|
||||
|
||||
this.setPreset('illustrative');
|
||||
}
|
||||
|
||||
setPreset(preset: Canvas3DPreset) {
|
||||
const props = Canvas3DPresets[preset];
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: {
|
||||
...props,
|
||||
multiSample: {
|
||||
...this.plugin.canvas3d!.props.multiSample,
|
||||
...props.multiSample
|
||||
},
|
||||
renderer: {
|
||||
...this.plugin.canvas3d!.props.renderer,
|
||||
...props.renderer
|
||||
},
|
||||
postprocessing: {
|
||||
...this.plugin.canvas3d!.props.postprocessing,
|
||||
...props.postprocessing
|
||||
},
|
||||
}});
|
||||
}
|
||||
|
||||
async load({ url, format = 'mmcif', isBinary = false, assemblyId = '' }: LoadParams) {
|
||||
await this.plugin.clear();
|
||||
|
||||
const data = await this.plugin.builders.data.download({ url: Asset.Url(url), isBinary }, { state: { isGhost: true } });
|
||||
const trajectory = await this.plugin.builders.structure.parseTrajectory(data, format);
|
||||
const model = await this.plugin.builders.structure.createModel(trajectory);
|
||||
const structure = await this.plugin.builders.structure.createStructure(model, assemblyId ? { name: 'assembly', params: { id: assemblyId } } : { name: 'deposited', params: { } });
|
||||
|
||||
const polymer = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'polymer');
|
||||
if (polymer) await this.plugin.builders.structure.representation.addRepresentation(polymer, { type: 'spacefill', color: 'illustrative' });
|
||||
|
||||
const ligand = await this.plugin.builders.structure.tryCreateComponentStatic(structure, 'ligand');
|
||||
if (ligand) await this.plugin.builders.structure.representation.addRepresentation(ligand, { type: 'ball-and-stick' });
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).LightingDemo = new LightingDemo();
|
||||
@@ -9,6 +9,7 @@ import { CustomElementProperty } from '../../mol-model-props/common/custom-eleme
|
||||
import { Model, ElementIndex, ResidueIndex } from '../../mol-model/structure';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
|
||||
const EvolutionaryConservationPalette: Color[] = [
|
||||
[255, 255, 129], // insufficient
|
||||
@@ -30,9 +31,9 @@ export const EvolutionaryConservation = CustomElementProperty.create<number>({
|
||||
type: 'static',
|
||||
async getData(model: Model, ctx: CustomProperty.Context) {
|
||||
const id = model.entryId.toLowerCase();
|
||||
const url = `https://proteopedia.org/cgi-bin/cnsrf?${id}`
|
||||
const json = await ctx.fetch({ url, type: 'json' }).runInContext(ctx.runtime)
|
||||
const annotations = (json && json.residueAnnotations) || [];
|
||||
const url = Asset.getUrlAsset(ctx.assetManager, `https://proteopedia.org/cgi-bin/cnsrf?${id}`);
|
||||
const json = await ctx.assetManager.resolve(url, 'json').runInContext(ctx.runtime);
|
||||
const annotations = json.data?.residueAnnotations || [];
|
||||
|
||||
const conservationMap = new Map<string, number>();
|
||||
|
||||
@@ -58,7 +59,7 @@ export const EvolutionaryConservation = CustomElementProperty.create<number>({
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
return { value: map, assets: [json] };
|
||||
},
|
||||
coloring: {
|
||||
getColor(e: number) {
|
||||
@@ -68,7 +69,7 @@ export const EvolutionaryConservation = CustomElementProperty.create<number>({
|
||||
defaultColor: EvolutionaryConservationDefaultColor
|
||||
},
|
||||
getLabel(e) {
|
||||
if (e === 10) return `Evolutionary Conservation: InsufficientData`;
|
||||
if (e === 10) return `Evolutionary Conservation: Insufficient Data`;
|
||||
return e ? `Evolutionary Conservation: ${e}` : void 0;
|
||||
}
|
||||
});
|
||||
@@ -11,58 +11,58 @@ import { Unit, StructureProperties, StructureElement, Bond } from '../../mol-mod
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { Location } from '../../mol-model/location';
|
||||
import { ColorTheme, LocationColor } from '../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition'
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ThemeDataContext } from '../../mol-theme/theme';
|
||||
import { Column } from '../../mol-data/db';
|
||||
|
||||
const Description = 'Gives every chain a color from a list based on its `asym_id` value.'
|
||||
const Description = 'Gives every chain a color from a list based on its `asym_id` value.';
|
||||
|
||||
export function createProteopediaCustomTheme(colors: number[]) {
|
||||
const ProteopediaCustomColorThemeParams = {
|
||||
colors: PD.ObjectList({ color: PD.Color(Color(0xffffff)) }, ({ color }) => Color.toHexString(color),
|
||||
{ defaultValue: colors.map(c => ({ color: Color(c) })) })
|
||||
}
|
||||
};
|
||||
type ProteopediaCustomColorThemeParams = typeof ProteopediaCustomColorThemeParams
|
||||
function getChainIdColorThemeParams(ctx: ThemeDataContext) {
|
||||
return ProteopediaCustomColorThemeParams // TODO return copy
|
||||
return ProteopediaCustomColorThemeParams; // TODO return copy
|
||||
}
|
||||
|
||||
function getAsymId(unit: Unit): StructureElement.Property<string> {
|
||||
switch (unit.kind) {
|
||||
case Unit.Kind.Atomic:
|
||||
return StructureProperties.chain.label_asym_id
|
||||
return StructureProperties.chain.label_asym_id;
|
||||
case Unit.Kind.Spheres:
|
||||
case Unit.Kind.Gaussians:
|
||||
return StructureProperties.coarse.asym_id
|
||||
return StructureProperties.coarse.asym_id;
|
||||
}
|
||||
}
|
||||
|
||||
function addAsymIds(map: Map<string, number>, data: Column<string>) {
|
||||
let j = map.size
|
||||
let j = map.size;
|
||||
for (let o = 0, ol = data.rowCount; o < ol; ++o) {
|
||||
const k = data.value(o)
|
||||
const k = data.value(o);
|
||||
if (!map.has(k)) {
|
||||
map.set(k, j)
|
||||
j += 1
|
||||
map.set(k, j);
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function ProteopediaCustomColorTheme(ctx: ThemeDataContext, props: PD.Values<ProteopediaCustomColorThemeParams>): ColorTheme<ProteopediaCustomColorThemeParams> {
|
||||
let color: LocationColor
|
||||
let color: LocationColor;
|
||||
|
||||
const colors = props.colors, colorCount = colors.length, defaultColor = colors[0].color;
|
||||
|
||||
if (ctx.structure) {
|
||||
const l = StructureElement.Location.create(ctx.structure)
|
||||
const { models } = ctx.structure
|
||||
const asymIdSerialMap = new Map<string, number>()
|
||||
const l = StructureElement.Location.create(ctx.structure);
|
||||
const { models } = ctx.structure;
|
||||
const asymIdSerialMap = new Map<string, number>();
|
||||
for (let i = 0, il = models.length; i < il; ++i) {
|
||||
const m = models[i]
|
||||
addAsymIds(asymIdSerialMap, m.atomicHierarchy.chains.label_asym_id)
|
||||
const m = models[i];
|
||||
addAsymIds(asymIdSerialMap, m.atomicHierarchy.chains.label_asym_id);
|
||||
if (m.coarseHierarchy.isDefined) {
|
||||
addAsymIds(asymIdSerialMap, m.coarseHierarchy.spheres.asym_id)
|
||||
addAsymIds(asymIdSerialMap, m.coarseHierarchy.gaussians.asym_id)
|
||||
addAsymIds(asymIdSerialMap, m.coarseHierarchy.spheres.asym_id);
|
||||
addAsymIds(asymIdSerialMap, m.coarseHierarchy.gaussians.asym_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,16 +72,16 @@ export function createProteopediaCustomTheme(colors: number[]) {
|
||||
const o = asymIdSerialMap.get(asym_id(location)) || 0;
|
||||
return colors[o % colorCount].color;
|
||||
} else if (Bond.isLocation(location)) {
|
||||
const asym_id = getAsymId(location.aUnit)
|
||||
l.unit = location.aUnit
|
||||
l.element = location.aUnit.elements[location.aIndex]
|
||||
const asym_id = getAsymId(location.aUnit);
|
||||
l.unit = location.aUnit;
|
||||
l.element = location.aUnit.elements[location.aIndex];
|
||||
const o = asymIdSerialMap.get(asym_id(l)) || 0;
|
||||
return colors[o % colorCount].color;
|
||||
}
|
||||
return defaultColor
|
||||
}
|
||||
return defaultColor;
|
||||
};
|
||||
} else {
|
||||
color = () => defaultColor
|
||||
color = () => defaultColor;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -91,7 +91,7 @@ export function createProteopediaCustomTheme(colors: number[]) {
|
||||
props,
|
||||
description: Description,
|
||||
legend: undefined
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -102,5 +102,5 @@ export function createProteopediaCustomTheme(colors: number[]) {
|
||||
getParams: getChainIdColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(ProteopediaCustomColorThemeParams),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -4,12 +4,11 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { ResidueIndex, Model } from '../../mol-model/structure';
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
import { Model, ResidueIndex } from '../../mol-model/structure';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StructureRepresentationRegistry } from '../../mol-repr/structure/registry';
|
||||
import { ColorTheme } from '../../mol-theme/color';
|
||||
import { PolymerType } from '../../mol-model/structure/model/types';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
|
||||
|
||||
export interface ModelInfo {
|
||||
hetResidues: { name: string, indices: ResidueIndex[] }[],
|
||||
@@ -54,11 +53,10 @@ export namespace ModelInfo {
|
||||
const hetMap = new Map<string, ModelInfo['hetResidues'][0]>();
|
||||
|
||||
for (let rI = 0 as ResidueIndex; rI < residueCount; rI++) {
|
||||
if (model.atomicHierarchy.derived.residue.polymerType[rI] !== PolymerType.NA) continue;
|
||||
|
||||
const cI = chainIndex[residueOffsets[rI]];
|
||||
const eI = model.atomicHierarchy.index.getEntityFromChain(cI);
|
||||
if (model.entities.data.type.value(eI) === 'water') continue;
|
||||
const entityType = model.entities.data.type.value(eI);
|
||||
if (entityType !== 'non-polymer' && entityType !== 'branched') continue;
|
||||
|
||||
const comp_id = model.atomicHierarchy.residues.label_comp_id.value(rI);
|
||||
|
||||
@@ -72,7 +70,7 @@ export namespace ModelInfo {
|
||||
}
|
||||
|
||||
const preferredAssemblyId = await pref;
|
||||
const symmetry = ModelSymmetry.Provider.get(model)
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
|
||||
return {
|
||||
hetResidues: hetResidues,
|
||||
@@ -86,6 +84,7 @@ export type SupportedFormats = 'cif' | 'pdb'
|
||||
export interface LoadParams {
|
||||
url: string,
|
||||
format?: SupportedFormats,
|
||||
isBinary?: boolean,
|
||||
assemblyId?: string,
|
||||
representationStyle?: RepresentationStyle
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
<option value='cif' selected>CIF</option>
|
||||
<option value='pdb'>PDB</option>
|
||||
</select>
|
||||
<input type='checkbox' id='isBinary' style="display: inline-block; width: auto" /> <label for="isBinary"> Binary</label><br />
|
||||
</div>
|
||||
<div id="app"></div>
|
||||
<div id="volume-streaming-wrapper"></div>
|
||||
@@ -74,8 +75,8 @@
|
||||
|
||||
function $(id) { return document.getElementById(id); }
|
||||
|
||||
var pdbId = '1cbs', assemblyId= 'preferred';
|
||||
var url = 'https://www.ebi.ac.uk/pdbe/static/entry/' + pdbId + '_updated.cif';
|
||||
var pdbId = '1cbs', assemblyId= 'preferred', isBinary = true;
|
||||
var url = 'https://www.ebi.ac.uk/pdbe/entry-files/download/' + pdbId + '.bcif'
|
||||
var format = 'cif';
|
||||
|
||||
$('url').value = url;
|
||||
@@ -84,13 +85,15 @@
|
||||
$('assemblyId').onchange = function (e) { assemblyId = e.target.value; }
|
||||
$('format').value = format;
|
||||
$('format').onchange = function (e) { format = e.target.value; }
|
||||
$('isBinary').checked = isBinary;
|
||||
$('isBinary').onchange = function (e) { isBinary = !!e.target.checked; };
|
||||
|
||||
// var url = 'https://www.ebi.ac.uk/pdbe/entry-files/pdb' + pdbId + '.ent';
|
||||
// var format = 'pdb';
|
||||
// var assemblyId = 'deposited';
|
||||
|
||||
var representationStyle = {
|
||||
sequence: { coloring: 'proteopedia-custom' }, // or just { }
|
||||
// sequence: { coloring: 'proteopedia-custom' }, // or just { }
|
||||
hetGroups: { kind: 'ball-and-stick' }, // or 'spacefill
|
||||
water: { hide: true },
|
||||
snfg3d: { hide: false }
|
||||
@@ -100,7 +103,7 @@
|
||||
customColorList: CustomColors
|
||||
});
|
||||
PluginWrapper.setBackground(0xffffff);
|
||||
PluginWrapper.load({ url: url, format: format, assemblyId: assemblyId, representationStyle: representationStyle });
|
||||
PluginWrapper.load({ url: url, format: format, isBinary: isBinary, assemblyId: assemblyId, representationStyle: representationStyle });
|
||||
PluginWrapper.toggleSpin();
|
||||
|
||||
PluginWrapper.events.modelInfo.subscribe(function (info) {
|
||||
@@ -108,8 +111,8 @@
|
||||
listHetGroups(info);
|
||||
});
|
||||
|
||||
addControl('Load Asym Unit', () => PluginWrapper.load({ url: url, format: format }));
|
||||
addControl('Load Assembly', () => PluginWrapper.load({ url: url, format: format, assemblyId: assemblyId }));
|
||||
addControl('Load Asym Unit', () => PluginWrapper.load({ url: url, format: format, isBinary }));
|
||||
addControl('Load Assembly', () => PluginWrapper.load({ url: url, format: format, isBinary, assemblyId: assemblyId }));
|
||||
|
||||
addSeparator();
|
||||
|
||||
@@ -173,16 +176,19 @@
|
||||
|
||||
var snapshot;
|
||||
addControl('Set Snapshot', () => {
|
||||
snapshot = PluginWrapper.snapshot.get();
|
||||
// could use JSON.stringify(snapshot) and upload the data
|
||||
// const options = { data: true, behavior: false, animation: false, interactivity: false, canvas3d: false, camera: false, cameraTransition: false };
|
||||
snapshot = PluginWrapper.plugin.state.getSnapshot(/** options */);
|
||||
// console.log(JSON.stringify(snapshot, null, 2));
|
||||
});
|
||||
addControl('Restore Snapshot', () => {
|
||||
if (!snapshot) return;
|
||||
PluginWrapper.snapshot.set(snapshot);
|
||||
});
|
||||
addControl('Download Snapshot', () => {
|
||||
snapshot = PluginWrapper.snapshot.download();
|
||||
addControl('Download State', () => {
|
||||
snapshot = PluginWrapper.snapshot.download('molj');
|
||||
});
|
||||
addControl('Download Session', () => {
|
||||
snapshot = PluginWrapper.snapshot.download('molx');
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
@@ -218,7 +224,7 @@
|
||||
var l = document.createElement('button');
|
||||
l.innerText = r.name;
|
||||
l.onclick = function () {
|
||||
PluginWrapper.hetGroups.focusFirst(r.name);
|
||||
PluginWrapper.hetGroups.focusFirst(r.name, { doNotLabelWaters: true });
|
||||
};
|
||||
div.appendChild(l);
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { createPlugin, DefaultPluginSpec } from '../../mol-plugin';
|
||||
import './index.html'
|
||||
import './index.html';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
@@ -28,11 +28,12 @@ import { DefaultCanvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3
|
||||
import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
|
||||
import { download } from '../../mol-util/download';
|
||||
import { getFormattedTime } from '../../mol-util/date';
|
||||
require('../../mol-plugin-ui/skin/light.scss')
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
require('../../mol-plugin-ui/skin/light.scss');
|
||||
|
||||
class MolStarProteopediaWrapper {
|
||||
static VERSION_MAJOR = 5;
|
||||
static VERSION_MINOR = 0;
|
||||
static VERSION_MINOR = 4;
|
||||
|
||||
private _ev = RxEventHelper.create();
|
||||
|
||||
@@ -73,8 +74,8 @@ class MolStarProteopediaWrapper {
|
||||
return this.plugin.state.data;
|
||||
}
|
||||
|
||||
private download(b: StateBuilder.To<PSO.Root>, url: string) {
|
||||
return b.apply(StateTransforms.Data.Download, { url, isBinary: false })
|
||||
private download(b: StateBuilder.To<PSO.Root>, url: string, isBinary: boolean) {
|
||||
return b.apply(StateTransforms.Data.Download, { url: Asset.Url(url), isBinary });
|
||||
}
|
||||
|
||||
private model(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats) {
|
||||
@@ -93,7 +94,7 @@ class MolStarProteopediaWrapper {
|
||||
name: 'assembly' as const,
|
||||
params: { id: assemblyId || 'deposited' }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const s = model
|
||||
.apply(StateTransforms.Model.StructureFromModel, props, { ref: StateElements.Assembly });
|
||||
@@ -185,7 +186,7 @@ class MolStarProteopediaWrapper {
|
||||
const model = this.getObj<PluginStateObject.Molecule.Model>('model');
|
||||
if (!model) return;
|
||||
|
||||
const info = await ModelInfo.get(this.plugin, model, checkPreferredAssembly)
|
||||
const info = await ModelInfo.get(this.plugin, model, checkPreferredAssembly);
|
||||
this.events.modelInfo.next(info);
|
||||
return info;
|
||||
}
|
||||
@@ -194,8 +195,9 @@ class MolStarProteopediaWrapper {
|
||||
return PluginCommands.State.Update(this.plugin, { state: this.plugin.state.data, tree });
|
||||
}
|
||||
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = 'deposited', representationStyle }: LoadParams) {
|
||||
private emptyLoadedParams: LoadParams = { url: '', format: 'cif', isBinary: false, assemblyId: '' };
|
||||
private loadedParams: LoadParams = { url: '', format: 'cif', isBinary: false, assemblyId: '' };
|
||||
async load({ url, format = 'cif', assemblyId = 'deposited', isBinary = false, representationStyle }: LoadParams) {
|
||||
let loadType: 'full' | 'update' = 'full';
|
||||
|
||||
const state = this.plugin.state.data;
|
||||
@@ -208,7 +210,7 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
if (loadType === 'full') {
|
||||
await PluginCommands.State.RemoveObject(this.plugin, { state, ref: state.tree.root.ref });
|
||||
const modelTree = this.model(this.download(state.build().toRoot(), url), format);
|
||||
const modelTree = this.model(this.download(state.build().toRoot(), url, isBinary), format);
|
||||
await this.applyState(modelTree);
|
||||
const info = await this.doInfo(true);
|
||||
const asmId = (assemblyId === 'preferred' && info && info.preferredAssemblyId) || assemblyId;
|
||||
@@ -223,7 +225,7 @@ class MolStarProteopediaWrapper {
|
||||
name: 'assembly' as const,
|
||||
params: { id: asmId || 'deposited' }
|
||||
}
|
||||
}
|
||||
};
|
||||
tree.to(StateElements.Assembly).update(StateTransforms.Model.StructureFromModel, p => ({ ...p, ...props }));
|
||||
await this.applyState(tree);
|
||||
}
|
||||
@@ -264,36 +266,17 @@ class MolStarProteopediaWrapper {
|
||||
|
||||
camera = {
|
||||
toggleSpin: () => this.toggleSpin(),
|
||||
resetPosition: () => PluginCommands.Camera.Reset(this.plugin, { }),
|
||||
// setClip: (options?: { distance?: number, near?: number, far?: number }) => {
|
||||
// if (!options) {
|
||||
// PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
// settings: {
|
||||
// cameraClipDistance: DefaultCanvas3DParams.cameraClipDistance,
|
||||
// clip: DefaultCanvas3DParams.clip
|
||||
// }
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
|
||||
// options = options || { };
|
||||
// const props = this.plugin.canvas3d.props;
|
||||
// const clipNear = typeof options.near === 'undefined' ? props.clip[0] : options.near;
|
||||
// const clipFar = typeof options.far === 'undefined' ? props.clip[1] : options.far;
|
||||
// PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
// settings: { cameraClipDistance: options.distance, clip: [clipNear, clipFar] }
|
||||
// });
|
||||
// }
|
||||
resetPosition: () => PluginCommands.Camera.Reset(this.plugin, { })
|
||||
}
|
||||
|
||||
animate = {
|
||||
modelIndex: {
|
||||
maxFPS: 8,
|
||||
onceForward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }) },
|
||||
onceBackward: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }) },
|
||||
palindrome: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }) },
|
||||
loop: () => { this.plugin.state.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }) },
|
||||
stop: () => this.plugin.state.animation.stop()
|
||||
onceForward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'forward' } } }); },
|
||||
onceBackward: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'once', params: { direction: 'backward' } } }); },
|
||||
palindrome: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'palindrome', params: {} } }); },
|
||||
loop: () => { this.plugin.managers.animation.play(AnimateModelIndex, { maxFPS: Math.max(0.5, this.animate.modelIndex.maxFPS | 0), mode: { name: 'loop', params: {} } }); },
|
||||
stop: () => this.plugin.managers.animation.stop()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,11 +287,6 @@ class MolStarProteopediaWrapper {
|
||||
}
|
||||
|
||||
const state = this.state;
|
||||
|
||||
// const visuals = state.selectQ(q => q.ofType(PluginStateObject.Molecule.Structure.Representation3D).filter(c => c.transform.transformer === StateTransforms.Representation.StructureRepresentation3D));
|
||||
// for (const v of visuals) {
|
||||
// }
|
||||
|
||||
const tree = state.build();
|
||||
const colorTheme = { name: EvolutionaryConservation.propertyProvider.descriptor.name, params: this.plugin.representation.structure.themes.colorThemeRegistry.get(EvolutionaryConservation.propertyProvider.descriptor.name).defaultValues };
|
||||
|
||||
@@ -353,12 +331,10 @@ class MolStarProteopediaWrapper {
|
||||
PluginCommands.State.Update(this.plugin, { state: this.state, tree: update });
|
||||
PluginCommands.Camera.Reset(this.plugin, { });
|
||||
},
|
||||
focusFirst: async (compId: string) => {
|
||||
focusFirst: async (compId: string, options?: { hideLabels: boolean, doNotLabelWaters: boolean }) => {
|
||||
if (!this.state.transforms.has(StateElements.Assembly)) return;
|
||||
await PluginCommands.Camera.Reset(this.plugin, { });
|
||||
|
||||
// const asm = (this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
|
||||
const update = this.state.build();
|
||||
|
||||
update.delete(StateElements.HetGroupFocusGroup);
|
||||
@@ -372,69 +348,66 @@ class MolStarProteopediaWrapper {
|
||||
const surroundings = MS.struct.modifier.includeSurroundings({ 0: core, radius: 5, 'as-whole-residues': true });
|
||||
|
||||
const group = update.to(StateElements.Assembly).group(StateTransforms.Misc.CreateGroup, { label: compId }, { ref: StateElements.HetGroupFocusGroup });
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
|
||||
const coreSel = group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Core', expression: core }, { ref: StateElements.HetGroupFocus });
|
||||
|
||||
|
||||
coreSel.apply(StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'ball-and-stick'
|
||||
}));
|
||||
coreSel.apply(StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'label',
|
||||
typeParams: { level: 'element' }
|
||||
}), { tags: ['proteopedia-labels'] });
|
||||
|
||||
group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Core', expression: core }, { ref: StateElements.HetGroupFocus })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D, this.createCoreVisualParams());
|
||||
group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Surroundings', expression: surroundings })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D, this.createSurVisualParams());
|
||||
// sel.apply(StateTransforms.Representation.StructureLabels3D, {
|
||||
// target: { name: 'residues', params: { } },
|
||||
// options: {
|
||||
// ...ParamDefinition.getDefaultValues(Text.Params),
|
||||
// background: true,
|
||||
// backgroundMargin: 0.2,
|
||||
// backgroundColor: ColorNames.snow,
|
||||
// backgroundOpacity: 0.9,
|
||||
// }
|
||||
// });
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'ball-and-stick',
|
||||
color: 'uniform', colorParams: { value: ColorNames.gray },
|
||||
size: 'uniform', sizeParams: { value: 0.33 }
|
||||
}));
|
||||
|
||||
if (!options?.hideLabels) {
|
||||
// Labels
|
||||
const waters = MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.entityType(), 'water']),
|
||||
});
|
||||
const exclude = options?.doNotLabelWaters ? MS.struct.combinator.merge([core, waters]) : core;
|
||||
const onlySurroundings = MS.struct.modifier.exceptBy({ 0: surroundings, by: exclude });
|
||||
|
||||
group.apply(StateTransforms.Model.StructureSelectionFromExpression, { label: 'Surroundings (only)', expression: onlySurroundings })
|
||||
.apply(StateTransforms.Representation.StructureRepresentation3D, createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'label',
|
||||
typeParams: { level: 'residue' }
|
||||
}), { tags: ['proteopedia-labels'] }); // the tag can later be used to toggle the labels
|
||||
}
|
||||
|
||||
await PluginCommands.State.Update(this.plugin, { state: this.state, tree: update });
|
||||
|
||||
const focus = (this.state.select(StateElements.HetGroupFocus)[0].obj as PluginStateObject.Molecule.Structure).data;
|
||||
const sphere = focus.boundary.sphere;
|
||||
// const asmCenter = asm.boundary.sphere.center;
|
||||
// const position = Vec3.sub(Vec3.zero(), sphere.center, asmCenter);
|
||||
// Vec3.normalize(position, position);
|
||||
// Vec3.scaleAndAdd(position, sphere.center, position, sphere.radius);
|
||||
const radius = Math.max(sphere.radius, 5)
|
||||
const radius = Math.max(sphere.radius, 5);
|
||||
const snapshot = this.plugin.canvas3d!.camera.getFocus(sphere.center, radius);
|
||||
PluginCommands.Camera.SetSnapshot(this.plugin, { snapshot, durationMs: 250 });
|
||||
}
|
||||
}
|
||||
|
||||
private createSurVisualParams() {
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
|
||||
return createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'ball-and-stick',
|
||||
color: 'uniform', colorParams: { value: ColorNames.gray },
|
||||
size: 'uniform', sizeParams: { value: 0.33 }
|
||||
});
|
||||
}
|
||||
|
||||
private createCoreVisualParams() {
|
||||
const asm = this.state.select(StateElements.Assembly)[0].obj as PluginStateObject.Molecule.Structure;
|
||||
return createStructureRepresentationParams(this.plugin, asm.data, {
|
||||
type: 'ball-and-stick'
|
||||
});
|
||||
}
|
||||
|
||||
snapshot = {
|
||||
get: () => {
|
||||
return this.plugin.state.getSnapshot();
|
||||
get: (params?: PluginState.SnapshotParams) => {
|
||||
return this.plugin.state.getSnapshot(params);
|
||||
},
|
||||
set: (snapshot: PluginState.Snapshot) => {
|
||||
return this.plugin.state.setSnapshot(snapshot);
|
||||
},
|
||||
download: () => {
|
||||
const json = JSON.stringify(this.plugin.state.getSnapshot(), null, 2);
|
||||
const blob = new Blob([json], {type : 'application/json;charset=utf-8'});
|
||||
download(blob, `mol-star_state_${(name || getFormattedTime())}.json`)
|
||||
download: async (type: 'molj' | 'molx' = 'molj', params?: PluginState.SnapshotParams) => {
|
||||
const data = await this.plugin.managers.snapshot.serialize({ type, params });
|
||||
download(data, `mol-star_state_${(name || getFormattedTime())}.${type}`);
|
||||
},
|
||||
fetch: async (url: string) => {
|
||||
fetch: async (url: string, type: 'molj' | 'molx' = 'molj') => {
|
||||
try {
|
||||
const snapshot = await this.plugin.runTask(this.plugin.fetch({ url, type: 'json' }));
|
||||
// TODO: is this OK to test for snapshots from server?
|
||||
await this.plugin.state.setSnapshot(snapshot?.data?.entries?.[0]?.snapshot || snapshot);
|
||||
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'binary' }));
|
||||
this.loadedParams = { ...this.emptyLoadedParams };
|
||||
await this.plugin.managers.snapshot.open(new File([data], `state.${type}`));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import { StateElements } from '../helpers';
|
||||
|
||||
export function volumeStreamingControls(plugin: PluginContext, parent: Element) {
|
||||
ReactDOM.render(<PluginContextContainer plugin={plugin}>
|
||||
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
|
||||
</PluginContextContainer>,
|
||||
parent);
|
||||
<TransformUpdaterControl nodeRef={StateElements.VolumeStreaming} />
|
||||
</PluginContextContainer>, parent);
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Task, Progress, Scheduler, MultistepTask, chunkedSubtask } from '../mol-task'
|
||||
import { Task, Progress, Scheduler, MultistepTask, chunkedSubtask } from '../mol-task';
|
||||
import { now } from '../mol-util/now';
|
||||
|
||||
export async function test1() {
|
||||
@@ -85,19 +85,19 @@ export const ms = MultistepTask('ms-task', ['step 1', 'step 2', 'step 3'], async
|
||||
await step(0);
|
||||
|
||||
const child = Task.create('chunked', async ctx => {
|
||||
const s = await chunkedSubtask(ctx, 25, { i: 0, current: 0, total: 125 }, processChunk, (ctx, s, p) => ctx.update('chunk test ' + p))
|
||||
const s = await chunkedSubtask(ctx, 25, { i: 0, current: 0, total: 125 }, processChunk, (ctx, s, p) => ctx.update('chunk test ' + p));
|
||||
return s.i;
|
||||
});
|
||||
|
||||
await child.runAsChild(ctx);
|
||||
await Scheduler.delay(250);
|
||||
await step(1);
|
||||
await chunkedSubtask(ctx, 25, { i: 0, current: 0, total: 80 }, processChunk, (ctx, s, p) => ctx.update('chunk test ' + p))
|
||||
await chunkedSubtask(ctx, 25, { i: 0, current: 0, total: 80 }, processChunk, (ctx, s, p) => ctx.update('chunk test ' + p));
|
||||
await Scheduler.delay(250);
|
||||
await step(2);
|
||||
await Scheduler.delay(250);
|
||||
return p.i + 3;
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
export function abortingObserver(p: Progress) {
|
||||
|
||||
@@ -4,40 +4,40 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ThemeDataContext } from '../../../../mol-theme/theme'
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition'
|
||||
import { Color } from '../../../../mol-util/color'
|
||||
import { getPalette } from '../../../../mol-util/color/palette'
|
||||
import { ColorTheme, LocationColor } from '../../../../mol-theme/color'
|
||||
import { ScaleLegend, TableLegend } from '../../../../mol-util/legend'
|
||||
import { StructureElement, Bond } from '../../../../mol-model/structure'
|
||||
import { Location } from '../../../../mol-model/location';
|
||||
import { CellPackInfoProvider } from './property'
|
||||
import { distinctColors } from '../../../../mol-util/color/distinct'
|
||||
import { Hcl } from '../../../../mol-util/color/spaces/hcl'
|
||||
import { ThemeDataContext } from '../../mol-theme/theme';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Color } from '../../mol-util/color';
|
||||
import { getPalette } from '../../mol-util/color/palette';
|
||||
import { ColorTheme, LocationColor } from '../../mol-theme/color';
|
||||
import { ScaleLegend, TableLegend } from '../../mol-util/legend';
|
||||
import { StructureElement, Bond } from '../../mol-model/structure';
|
||||
import { Location } from '../../mol-model/location';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
import { distinctColors } from '../../mol-util/color/distinct';
|
||||
import { Hcl } from '../../mol-util/color/spaces/hcl';
|
||||
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC)
|
||||
const Description = 'Gives every model in a CellPack packing a unique color similar to other models in the packing.'
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
const Description = 'Gives every model in a CellPack packing a unique color similar to other models in the packing.';
|
||||
|
||||
export const CellPackColorThemeParams = {}
|
||||
export const CellPackColorThemeParams = {};
|
||||
export type CellPackColorThemeParams = typeof CellPackColorThemeParams
|
||||
export function getCellPackColorThemeParams(ctx: ThemeDataContext) {
|
||||
return CellPackColorThemeParams // TODO return copy
|
||||
return CellPackColorThemeParams; // TODO return copy
|
||||
}
|
||||
|
||||
export function CellPackColorTheme(ctx: ThemeDataContext, props: PD.Values<CellPackColorThemeParams>): ColorTheme<CellPackColorThemeParams> {
|
||||
let color: LocationColor
|
||||
let legend: ScaleLegend | TableLegend | undefined
|
||||
let color: LocationColor;
|
||||
let legend: ScaleLegend | TableLegend | undefined;
|
||||
|
||||
const info = ctx.structure && CellPackInfoProvider.get(ctx.structure).value
|
||||
const info = ctx.structure && CellPackInfoProvider.get(ctx.structure).value;
|
||||
|
||||
if (ctx.structure && info) {
|
||||
const colors = distinctColors(info.packingsCount)
|
||||
const hcl = Hcl.fromColor(Hcl(), colors[info.packingIndex])
|
||||
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number]
|
||||
const colors = distinctColors(info.packingsCount);
|
||||
const hcl = Hcl.fromColor(Hcl(), colors[info.packingIndex]);
|
||||
const hue = [Math.max(0, hcl[0] - 35), Math.min(360, hcl[0] + 35)] as [number, number];
|
||||
|
||||
const { models } = ctx.structure.root
|
||||
const { models } = ctx.structure.root;
|
||||
|
||||
let size = 0;
|
||||
for (const m of models) size = Math.max(size, m.trajectoryInfo.size);
|
||||
@@ -49,24 +49,24 @@ export function CellPackColorTheme(ctx: ThemeDataContext, props: PD.Values<CellP
|
||||
clusteringStepCount: 50, minSampleCount: 800, maxCount: 75,
|
||||
minLabel: 'Min', maxLabel: 'Max', valueLabel: (i: number) => `${i + 1}`,
|
||||
}
|
||||
}})
|
||||
legend = palette.legend
|
||||
const modelColor = new Map<number, Color>()
|
||||
for (let i = 0, il = models.length; i <il; ++i) {
|
||||
}});
|
||||
legend = palette.legend;
|
||||
const modelColor = new Map<number, Color>();
|
||||
for (let i = 0, il = models.length; i < il; ++i) {
|
||||
const idx = models[i].trajectoryInfo.index;
|
||||
modelColor.set(models[i].trajectoryInfo.index, palette.color(idx))
|
||||
modelColor.set(models[i].trajectoryInfo.index, palette.color(idx));
|
||||
}
|
||||
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
return modelColor.get(location.unit.model.trajectoryInfo.index)!
|
||||
return modelColor.get(location.unit.model.trajectoryInfo.index)!;
|
||||
} else if (Bond.isLocation(location)) {
|
||||
return modelColor.get(location.aUnit.model.trajectoryInfo.index)!
|
||||
return modelColor.get(location.aUnit.model.trajectoryInfo.index)!;
|
||||
}
|
||||
return DefaultColor
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
} else {
|
||||
color = () => DefaultColor
|
||||
color = () => DefaultColor;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -76,7 +76,7 @@ export function CellPackColorTheme(ctx: ThemeDataContext, props: PD.Values<CellP
|
||||
props,
|
||||
description: Description,
|
||||
legend
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const CellPackColorThemeProvider: ColorTheme.Provider<CellPackColorThemeParams, 'cellpack'> = {
|
||||
@@ -91,6 +91,6 @@ export const CellPackColorThemeProvider: ColorTheme.Provider<CellPackColorThemeP
|
||||
!!ctx.structure && ctx.structure.elementCount > 0 &&
|
||||
ctx.structure.models[0].trajectoryInfo.size > 1 &&
|
||||
!!CellPackInfoProvider.get(ctx.structure).value
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -5,8 +5,8 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec3, Quat, Mat4 } from '../../../../mol-math/linear-algebra';
|
||||
import { NumberArray } from '../../../../mol-util/type-helpers';
|
||||
import { Vec3, Quat, Mat4 } from '../../mol-math/linear-algebra';
|
||||
import { NumberArray } from '../../mol-util/type-helpers';
|
||||
|
||||
interface Frame {
|
||||
t: Vec3,
|
||||
@@ -14,215 +14,210 @@ interface Frame {
|
||||
s: Vec3,
|
||||
}
|
||||
|
||||
const a0Tmp = Vec3()
|
||||
const a1Tmp = Vec3()
|
||||
const a2Tmp = Vec3()
|
||||
const a3Tmp = Vec3()
|
||||
const a0Tmp = Vec3();
|
||||
const a1Tmp = Vec3();
|
||||
const a2Tmp = Vec3();
|
||||
const a3Tmp = Vec3();
|
||||
function CubicInterpolate(out: Vec3, y0: Vec3, y1: Vec3, y2: Vec3, y3: Vec3, mu: number): Vec3 {
|
||||
const mu2 = mu * mu;
|
||||
Vec3.sub(a0Tmp, y3, y2)
|
||||
Vec3.sub(a0Tmp, a0Tmp, y0)
|
||||
Vec3.add(a0Tmp, a0Tmp, y1)
|
||||
Vec3.sub(a0Tmp, y3, y2);
|
||||
Vec3.sub(a0Tmp, a0Tmp, y0);
|
||||
Vec3.add(a0Tmp, a0Tmp, y1);
|
||||
|
||||
Vec3.sub(a1Tmp, y0, y1)
|
||||
Vec3.sub(a1Tmp, a1Tmp, a0Tmp)
|
||||
Vec3.sub(a1Tmp, y0, y1);
|
||||
Vec3.sub(a1Tmp, a1Tmp, a0Tmp);
|
||||
|
||||
Vec3.sub(a2Tmp, y2, y0)
|
||||
Vec3.sub(a2Tmp, y2, y0);
|
||||
|
||||
Vec3.copy(a3Tmp, y1)
|
||||
Vec3.copy(a3Tmp, y1);
|
||||
|
||||
out[0] = a0Tmp[0] * mu * mu2 + a1Tmp[0] * mu2 + a2Tmp[0] * mu + a3Tmp[0]
|
||||
out[1] = a0Tmp[1] * mu * mu2 + a1Tmp[1] * mu2 + a2Tmp[1] * mu + a3Tmp[1]
|
||||
out[2] = a0Tmp[2] * mu * mu2 + a1Tmp[2] * mu2 + a2Tmp[2] * mu + a3Tmp[2]
|
||||
out[0] = a0Tmp[0] * mu * mu2 + a1Tmp[0] * mu2 + a2Tmp[0] * mu + a3Tmp[0];
|
||||
out[1] = a0Tmp[1] * mu * mu2 + a1Tmp[1] * mu2 + a2Tmp[1] * mu + a3Tmp[1];
|
||||
out[2] = a0Tmp[2] * mu * mu2 + a1Tmp[2] * mu2 + a2Tmp[2] * mu + a3Tmp[2];
|
||||
|
||||
return out
|
||||
return out;
|
||||
}
|
||||
|
||||
const cp0 = Vec3()
|
||||
const cp1 = Vec3()
|
||||
const cp2 = Vec3()
|
||||
const cp3 = Vec3()
|
||||
const currentPosition = Vec3()
|
||||
const cp0 = Vec3();
|
||||
const cp1 = Vec3();
|
||||
const cp2 = Vec3();
|
||||
const cp3 = Vec3();
|
||||
const currentPosition = Vec3();
|
||||
function ResampleControlPoints(points: NumberArray, segmentLength: number) {
|
||||
const nP = points.length / 3
|
||||
const nP = points.length / 3;
|
||||
// insert a point at the end and at the begining
|
||||
// controlPoints.Insert(0, controlPoints[0] + (controlPoints[0] - controlPoints[1]) / 2.0f);
|
||||
// controlPoints.Add(controlPoints[nP - 1] + (controlPoints[nP - 1] - controlPoints[nP - 2]) / 2.0f);
|
||||
|
||||
let resampledControlPoints: Vec3[] = []
|
||||
let resampledControlPoints: Vec3[] = [];
|
||||
// resampledControlPoints.Add(controlPoints[0]);
|
||||
// resampledControlPoints.Add(controlPoints[1]);
|
||||
|
||||
let idx = 1
|
||||
let idx = 1;
|
||||
// const currentPosition = Vec3.create(points[idx * 3], points[idx * 3 + 1], points[idx * 3 + 2])
|
||||
Vec3.fromArray(currentPosition, points, idx * 3)
|
||||
Vec3.fromArray(currentPosition, points, idx * 3);
|
||||
|
||||
let lerpValue = 0.0
|
||||
let lerpValue = 0.0;
|
||||
|
||||
// Normalize the distance between control points
|
||||
while (true) {
|
||||
if (idx + 2 >= nP) break
|
||||
Vec3.fromArray(cp0, points, (idx - 1) * 3)
|
||||
Vec3.fromArray(cp1, points, idx * 3)
|
||||
Vec3.fromArray(cp2, points, (idx + 1) * 3)
|
||||
Vec3.fromArray(cp3, points, (idx + 2) * 3)
|
||||
if (idx + 2 >= nP) break;
|
||||
Vec3.fromArray(cp0, points, (idx - 1) * 3);
|
||||
Vec3.fromArray(cp1, points, idx * 3);
|
||||
Vec3.fromArray(cp2, points, (idx + 1) * 3);
|
||||
Vec3.fromArray(cp3, points, (idx + 2) * 3);
|
||||
// const cp0 = Vec3.create(points[(idx-1)*3], points[(idx-1)*3+1], points[(idx-1)*3+2]) // controlPoints[currentPointId - 1];
|
||||
// const cp1 = Vec3.create(points[idx*3], points[idx*3+1], points[idx*3+2]) // controlPoints[currentPointId];
|
||||
// const cp2 = Vec3.create(points[(idx+1)*3], points[(idx+1)*3+1], points[(idx+1)*3+2]) // controlPoints[currentPointId + 1];
|
||||
// const cp3 = Vec3.create(points[(idx+2)*3], points[(idx+2)*3+1], points[(idx+2)*3+2]); // controlPoints[currentPointId + 2];
|
||||
let found = false
|
||||
let found = false;
|
||||
for (; lerpValue <= 1; lerpValue += 0.01) {
|
||||
// lerp?slerp
|
||||
// let candidate:Vec3 = Vec3.lerp(Vec3.zero(), cp0, cp1, lerpValue);
|
||||
// const candidate:Vec3 = Vec3.bezier(Vec3.zero(), cp0, cp1, cp2, cp3, lerpValue);
|
||||
const candidate = CubicInterpolate(Vec3(), cp0, cp1, cp2, cp3, lerpValue)
|
||||
const candidate = CubicInterpolate(Vec3(), cp0, cp1, cp2, cp3, lerpValue);
|
||||
const d = Vec3.distance(currentPosition, candidate);
|
||||
if (d > segmentLength) {
|
||||
resampledControlPoints.push(candidate)
|
||||
Vec3.copy(currentPosition, candidate)
|
||||
found = true
|
||||
break
|
||||
resampledControlPoints.push(candidate);
|
||||
Vec3.copy(currentPosition, candidate);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
lerpValue = 0
|
||||
idx += 1
|
||||
lerpValue = 0;
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
return resampledControlPoints
|
||||
return resampledControlPoints;
|
||||
}
|
||||
|
||||
|
||||
const prevV = Vec3()
|
||||
const tmpV1 = Vec3()
|
||||
const tmpV2 = Vec3()
|
||||
const tmpV3 = Vec3()
|
||||
const prevV = Vec3();
|
||||
const tmpV1 = Vec3();
|
||||
const tmpV2 = Vec3();
|
||||
const tmpV3 = Vec3();
|
||||
|
||||
// easier to align to theses normals
|
||||
function GetSmoothNormals(points: Vec3[]) {
|
||||
const nP: number = points.length;
|
||||
const smoothNormals: Vec3[] = []
|
||||
const smoothNormals: Vec3[] = [];
|
||||
if (points.length < 3) {
|
||||
for (let i = 0; i < points.length; ++i)
|
||||
smoothNormals.push(Vec3.normalize(Vec3(), points[i]))
|
||||
smoothNormals.push(Vec3.normalize(Vec3(), points[i]));
|
||||
return smoothNormals;
|
||||
}
|
||||
let p0 = points[0]
|
||||
let p1 = points[1]
|
||||
let p2 = points[2]
|
||||
const p21 = Vec3.sub(tmpV1, p2, p1)
|
||||
const p01 = Vec3.sub(tmpV2, p0, p1)
|
||||
const p0121 = Vec3.cross(tmpV3, p01, p21)
|
||||
Vec3.normalize(prevV, p0121)
|
||||
smoothNormals.push(Vec3.clone(prevV))
|
||||
let p0 = points[0];
|
||||
let p1 = points[1];
|
||||
let p2 = points[2];
|
||||
const p21 = Vec3.sub(tmpV1, p2, p1);
|
||||
const p01 = Vec3.sub(tmpV2, p0, p1);
|
||||
const p0121 = Vec3.cross(tmpV3, p01, p21);
|
||||
Vec3.normalize(prevV, p0121);
|
||||
smoothNormals.push(Vec3.clone(prevV));
|
||||
for (let i = 1; i < points.length - 1; ++i) {
|
||||
p0 = points[i - 1]
|
||||
p1 = points[i]
|
||||
p2 = points[i + 1]
|
||||
const t = Vec3.normalize(tmpV1, Vec3.sub(tmpV1, p2 , p0))
|
||||
const b = Vec3.normalize(tmpV2, Vec3.cross(tmpV2, t, prevV))
|
||||
const n = Vec3.normalize(Vec3(), Vec3.cross(tmpV3, t, b))
|
||||
Vec3.negate(n, n)
|
||||
Vec3.copy(prevV, n)
|
||||
smoothNormals.push(n)
|
||||
p0 = points[i - 1];
|
||||
p1 = points[i];
|
||||
p2 = points[i + 1];
|
||||
const t = Vec3.normalize(tmpV1, Vec3.sub(tmpV1, p2, p0));
|
||||
const b = Vec3.normalize(tmpV2, Vec3.cross(tmpV2, t, prevV));
|
||||
const n = Vec3.normalize(Vec3(), Vec3.cross(tmpV3, t, b));
|
||||
Vec3.negate(n, n);
|
||||
Vec3.copy(prevV, n);
|
||||
smoothNormals.push(n);
|
||||
}
|
||||
const last = Vec3()
|
||||
const last = Vec3();
|
||||
Vec3.normalize(last, Vec3.cross(last,
|
||||
Vec3.sub(tmpV1, points[nP - 3], points[nP-2]),
|
||||
Vec3.sub(tmpV2, points[nP-2] , points[nP-1]))
|
||||
)
|
||||
smoothNormals.push(last)
|
||||
Vec3.sub(tmpV1, points[nP - 3], points[nP - 2]),
|
||||
Vec3.sub(tmpV2, points[nP - 2], points[nP - 1]))
|
||||
);
|
||||
smoothNormals.push(last);
|
||||
return smoothNormals;
|
||||
}
|
||||
|
||||
const frameTmpV1 = Vec3()
|
||||
const frameTmpV2 = Vec3()
|
||||
const frameTmpV3 = Vec3()
|
||||
const frameTmpV1 = Vec3();
|
||||
const frameTmpV2 = Vec3();
|
||||
const frameTmpV3 = Vec3();
|
||||
|
||||
function getFrame(reference: Vec3, tangent: Vec3) {
|
||||
const t = Vec3.normalize(Vec3(), tangent);
|
||||
// make reference vector orthogonal to tangent
|
||||
const proj_r_to_t = Vec3.scale(
|
||||
frameTmpV1, tangent, Vec3.dot(reference, tangent) / Vec3.dot(tangent, tangent)
|
||||
)
|
||||
const r = Vec3.normalize(Vec3(), Vec3.sub(frameTmpV2, reference, proj_r_to_t))
|
||||
);
|
||||
const r = Vec3.normalize(Vec3(), Vec3.sub(frameTmpV2, reference, proj_r_to_t));
|
||||
// make bitangent vector orthogonal to the others
|
||||
const s = Vec3.normalize(Vec3(), Vec3.cross(frameTmpV3, t, r))
|
||||
return { t, r, s }
|
||||
const s = Vec3.normalize(Vec3(), Vec3.cross(frameTmpV3, t, r));
|
||||
return { t, r, s };
|
||||
}
|
||||
|
||||
const mfTmpV1 = Vec3()
|
||||
const mfTmpV2 = Vec3()
|
||||
const mfTmpV3 = Vec3()
|
||||
const mfTmpV4 = Vec3()
|
||||
const mfTmpV5 = Vec3()
|
||||
const mfTmpV6 = Vec3()
|
||||
const mfTmpV7 = Vec3()
|
||||
const mfTmpV8 = Vec3()
|
||||
const mfTmpV9 = Vec3()
|
||||
const mfTmpV1 = Vec3();
|
||||
const mfTmpV2 = Vec3();
|
||||
const mfTmpV3 = Vec3();
|
||||
const mfTmpV4 = Vec3();
|
||||
const mfTmpV5 = Vec3();
|
||||
const mfTmpV6 = Vec3();
|
||||
const mfTmpV7 = Vec3();
|
||||
const mfTmpV8 = Vec3();
|
||||
const mfTmpV9 = Vec3();
|
||||
|
||||
// easier to align to theses normals
|
||||
// https://github.com/bzamecnik/gpg/blob/master/rotation-minimizing-frame/rmf.py
|
||||
function GetMiniFrame(points: Vec3[], normals: Vec3[]) {
|
||||
const frames: Frame[] = [];
|
||||
const t0 = Vec3.normalize(mfTmpV1, Vec3.sub(mfTmpV1, points[1], points[0]))
|
||||
frames.push(getFrame(normals[0], t0))
|
||||
const t0 = Vec3.normalize(mfTmpV1, Vec3.sub(mfTmpV1, points[1], points[0]));
|
||||
frames.push(getFrame(normals[0], t0));
|
||||
|
||||
for (let i = 0; i< points.length-2; ++i) {
|
||||
const t2 = Vec3.normalize(mfTmpV1, Vec3.sub(mfTmpV1, points[i+2], points[i+1]))
|
||||
const v1 = Vec3.sub(mfTmpV2, points[i + 1], points[i]) // this is tangeant
|
||||
const c1 = Vec3.dot(v1, v1)
|
||||
for (let i = 0; i < points.length - 2; ++i) {
|
||||
const t2 = Vec3.normalize(mfTmpV1, Vec3.sub(mfTmpV1, points[i + 2], points[i + 1]));
|
||||
const v1 = Vec3.sub(mfTmpV2, points[i + 1], points[i]); // this is tangeant
|
||||
const c1 = Vec3.dot(v1, v1);
|
||||
// compute r_i^L = R_1 * r_i
|
||||
const v1r = Vec3.scale(mfTmpV3, v1, (2.0 / c1) * Vec3.dot(v1, frames[i].r))
|
||||
const ref_L_i = Vec3.sub(mfTmpV4, frames[i].r, v1r)
|
||||
const v1r = Vec3.scale(mfTmpV3, v1, (2.0 / c1) * Vec3.dot(v1, frames[i].r));
|
||||
const ref_L_i = Vec3.sub(mfTmpV4, frames[i].r, v1r);
|
||||
// compute t_i^L = R_1 * t_i
|
||||
const v1t = Vec3.scale(mfTmpV5, v1, (2.0 / c1) * Vec3.dot(v1, frames[i].t))
|
||||
const tan_L_i = Vec3.sub(mfTmpV6, frames[i].t, v1t)
|
||||
const v1t = Vec3.scale(mfTmpV5, v1, (2.0 / c1) * Vec3.dot(v1, frames[i].t));
|
||||
const tan_L_i = Vec3.sub(mfTmpV6, frames[i].t, v1t);
|
||||
// # compute reflection vector of R_2
|
||||
const v2 = Vec3.sub(mfTmpV7, t2 , tan_L_i)
|
||||
const c2 = Vec3.dot(v2, v2)
|
||||
const v2 = Vec3.sub(mfTmpV7, t2, tan_L_i);
|
||||
const c2 = Vec3.dot(v2, v2);
|
||||
// compute r_(i+1) = R_2 * r_i^L
|
||||
const v2l = Vec3.scale(mfTmpV8, v1, (2.0/c2) * Vec3.dot(v2, ref_L_i))
|
||||
const ref_next = Vec3.sub(mfTmpV9, ref_L_i, v2l) // ref_L_i - (2 / c2) * v2.dot(ref_L_i) * v2
|
||||
frames.push(getFrame(ref_next, t2)) // frames.append(Frame(ref_next, tangents[i+1]))
|
||||
const v2l = Vec3.scale(mfTmpV8, v1, (2.0 / c2) * Vec3.dot(v2, ref_L_i));
|
||||
const ref_next = Vec3.sub(mfTmpV9, ref_L_i, v2l); // ref_L_i - (2 / c2) * v2.dot(ref_L_i) * v2
|
||||
frames.push(getFrame(ref_next, t2)); // frames.append(Frame(ref_next, tangents[i+1]))
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
const rpTmpVec1 = Vec3()
|
||||
const rpTmpVec1 = Vec3();
|
||||
|
||||
export function getMatFromResamplePoints(points: NumberArray) {
|
||||
const segmentLength = 3.4
|
||||
const new_points = ResampleControlPoints(points, 3.4)
|
||||
const npoints = new_points.length
|
||||
const new_normal = GetSmoothNormals(new_points)
|
||||
const frames = GetMiniFrame(new_points, new_normal)
|
||||
const limit = npoints
|
||||
const transforms: Mat4[] = []
|
||||
export function getMatFromResamplePoints(points: NumberArray, segmentLength: number) {
|
||||
const new_points = ResampleControlPoints(points, segmentLength);
|
||||
const npoints = new_points.length;
|
||||
const new_normal = GetSmoothNormals(new_points);
|
||||
const frames = GetMiniFrame(new_points, new_normal);
|
||||
const limit = npoints;
|
||||
const transforms: Mat4[] = [];
|
||||
const pti = Vec3.copy(rpTmpVec1, new_points[0]);
|
||||
// console.log(new_points.length)
|
||||
// console.log(points.length/3)
|
||||
// console.log(limit)
|
||||
// console.log(segmentLength)
|
||||
for (let i = 0; i<npoints-2; ++i) {
|
||||
const pti1: Vec3 = new_points[i+1] // Vec3.create(points[(i+1)*3],points[(i+1)*3+1],points[(i+1)*3+2]);
|
||||
const d = Vec3.distance(pti, pti1)
|
||||
for (let i = 0; i < npoints - 2; ++i) {
|
||||
const pti1: Vec3 = new_points[i + 1]; // Vec3.create(points[(i+1)*3],points[(i+1)*3+1],points[(i+1)*3+2]);
|
||||
const d = Vec3.distance(pti, pti1);
|
||||
if (d >= segmentLength) {
|
||||
// use twist or random?
|
||||
const quat = Quat.rotationTo(Quat.zero(), Vec3.create(0, 0, 1), frames[i].t) // Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),new_normal[i]);//Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),direction);new_normal
|
||||
const rq = Quat.setAxisAngle(Quat.zero(), frames[i].t, Math.random()*3.60 ) // Quat.setAxisAngle(Quat.zero(),direction, Math.random()*3.60 );//Quat.identity();//
|
||||
const m = Mat4.fromQuat(Mat4.zero(), Quat.multiply(Quat.zero(), rq, quat)) // Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),quat1,quat2));//Mat4.fromQuat(Mat4.zero(),quat);//Mat4.identity();//Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),rq,quat));
|
||||
const quat = Quat.rotationTo(Quat.zero(), Vec3.create(0, 0, 1), frames[i].t); // Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),new_normal[i]);//Quat.rotationTo(Quat.zero(), Vec3.create(0,0,1),direction);new_normal
|
||||
const rq = Quat.setAxisAngle(Quat.zero(), frames[i].t, Math.random() * 3.60 ); // Quat.setAxisAngle(Quat.zero(),direction, Math.random()*3.60 );//Quat.identity();//
|
||||
const m = Mat4.fromQuat(Mat4.zero(), Quat.multiply(Quat.zero(), rq, quat)); // Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),quat1,quat2));//Mat4.fromQuat(Mat4.zero(),quat);//Mat4.identity();//Mat4.fromQuat(Mat4.zero(),Quat.multiply(Quat.zero(),rq,quat));
|
||||
// let pos:Vec3 = Vec3.add(Vec3.zero(),pti1,pti)
|
||||
// pos = Vec3.scale(pos,pos,1.0/2.0);
|
||||
// Vec3.makeRotation(Mat4.zero(),Vec3.create(0,0,1),frames[i].t);//
|
||||
Mat4.setTranslation(m, pti1)
|
||||
Mat4.setTranslation(m, pti1);
|
||||
// let m2:Mat4 = GetTubePropertiesMatrix(pti,pti1);
|
||||
// let q:Quat = Quat.rotationTo(Quat.zero(), Vec3.create(0,1,0),Vec3.create(0,0,1))
|
||||
// m2=Mat4.mul(Mat4.identity(),Mat4.fromQuat(Mat4.zero(),q),m2);
|
||||
transforms.push(m)
|
||||
Vec3.copy(pti, pti1)
|
||||
transforms.push(m);
|
||||
Vec3.copy(pti, pti1);
|
||||
}
|
||||
if (transforms.length >= limit) break
|
||||
if (transforms.length >= limit) break;
|
||||
}
|
||||
return transforms
|
||||
return transforms;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Vec3, Quat } from '../../../../mol-math/linear-algebra';
|
||||
import { Vec3, Quat } from '../../mol-math/linear-algebra';
|
||||
|
||||
export interface CellPack {
|
||||
cell: Cell
|
||||
@@ -27,8 +27,7 @@ export interface Cell {
|
||||
|
||||
export interface Recipe {
|
||||
setupfile: string
|
||||
/** First entry is name, secound is path: [name: string, path: string][] */
|
||||
paths: [string, string][]
|
||||
paths: [string, string][] // [name: string, path: string][]
|
||||
version: string
|
||||
name: string
|
||||
}
|
||||
@@ -42,21 +41,41 @@ export interface Packing {
|
||||
ingredients: { [key: string]: Ingredient }
|
||||
}
|
||||
|
||||
export interface Ingredient {
|
||||
source: IngredientSource
|
||||
results: [Vec3, Quat][]
|
||||
name: string
|
||||
positions?: [Vec3[]] // why wrapped in an extra array?
|
||||
radii?: [number[]] // why wrapped in an extra array?
|
||||
export interface Positions {
|
||||
coords?: Vec3[];
|
||||
}
|
||||
|
||||
export interface Radii {
|
||||
radii?: number[];
|
||||
}
|
||||
|
||||
export interface Ingredient {
|
||||
source: IngredientSource;
|
||||
results: [Vec3, Quat][];
|
||||
name: string;
|
||||
/** Vec3[]];CoarseGraind Beads coordinates LOD */
|
||||
positions?: [Positions];
|
||||
/** number[]];CoarseGraind Beads radii LOD */
|
||||
radii?: [Radii];
|
||||
/** Number of `curveX` properties in the object where `X` is a 0-indexed number */
|
||||
nbCurve?: number
|
||||
nbCurve?: number;
|
||||
/** Curve properties are Vec3[] but that is not expressable in TypeScript */
|
||||
[curveX: string]: unknown
|
||||
[curveX: string]: unknown;
|
||||
/** the orientation in the membrane */
|
||||
principalAxis?: Vec3;
|
||||
/** offset along membrane */
|
||||
offset?: Vec3;
|
||||
ingtype?: string;
|
||||
}
|
||||
|
||||
export interface IngredientSource {
|
||||
pdb: string
|
||||
transform: { center: boolean, translate?: Vec3 }
|
||||
biomt?: boolean
|
||||
}
|
||||
pdb: string;
|
||||
bu?: string; /** biological unit e.g AU,BU1,etc.. */
|
||||
selection?: string; /** NGL selection or :A or :B etc.. */
|
||||
model?: string; /** model number e.g 0,1,2... */
|
||||
transform: {
|
||||
center: boolean;
|
||||
translate?: Vec3;
|
||||
};
|
||||
biomt?: boolean;
|
||||
}
|
||||
30
src/extensions/cellpack/index.ts
Normal file
30
src/extensions/cellpack/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior';
|
||||
import { CellPackColorThemeProvider } from './color';
|
||||
import { LoadCellPackModel } from './model';
|
||||
|
||||
|
||||
export const CellPack = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
|
||||
name: 'cellpack',
|
||||
category: 'custom-props',
|
||||
display: {
|
||||
name: 'CellPack',
|
||||
description: 'CellPack Model Loading and Viewing.'
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
|
||||
register(): void {
|
||||
this.ctx.state.data.actions.add(LoadCellPackModel);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(CellPackColorThemeProvider);
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.ctx.state.data.actions.remove(LoadCellPackModel);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(CellPackColorThemeProvider);
|
||||
}
|
||||
}
|
||||
});
|
||||
524
src/extensions/cellpack/model.ts
Normal file
524
src/extensions/cellpack/model.ts
Normal file
@@ -0,0 +1,524 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateAction, StateBuilder, StateTransformer, State } from '../../mol-state';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Ingredient, IngredientSource, CellPacking } from './data';
|
||||
import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean, getFromOPM } from './util';
|
||||
import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../mol-model/structure';
|
||||
import { trajectoryFromMmCIF, MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { trajectoryFromPDB } from '../../mol-model-formats/structure/pdb';
|
||||
import { Mat4, Vec3, Quat } from '../../mol-math/linear-algebra';
|
||||
import { SymmetryOperator } from '../../mol-math/geometry';
|
||||
import { Task, RuntimeContext } from '../../mol-task';
|
||||
import { StateTransforms } from '../../mol-plugin-state/transforms';
|
||||
import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl } from './state';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { getMatFromResamplePoints } from './curve';
|
||||
import { compile } from '../../mol-script/runtime/query/compiler';
|
||||
import { CifCategory, CifField } from '../../mol-io/reader/cif';
|
||||
import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
|
||||
import { Column } from '../../mol-data/db';
|
||||
import { createModels } from '../../mol-model-formats/structure/basic/parser';
|
||||
import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
|
||||
function getCellPackModelUrl(fileName: string, baseUrl: string) {
|
||||
return `${baseUrl}/results/${fileName}`;
|
||||
}
|
||||
|
||||
async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient, baseUrl: string, file?: Asset.File) {
|
||||
const assetManager = plugin.managers.asset;
|
||||
const model_id = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
|
||||
const surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
|
||||
let model: Model;
|
||||
let assets: Asset.Wrapper[] = [];
|
||||
if (file) {
|
||||
if (file.name.endsWith('.cif')) {
|
||||
const text = await plugin.runTask(assetManager.resolve(file, 'string'));
|
||||
assets.push(text);
|
||||
const cif = (await parseCif(plugin, text.data)).blocks[0];
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(cif)))[model_id];
|
||||
} else if (file.name.endsWith('.bcif')) {
|
||||
const binary = await plugin.runTask(assetManager.resolve(file, 'binary'));
|
||||
assets.push(binary);
|
||||
const cif = (await parseCif(plugin, binary.data)).blocks[0];
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(cif)))[model_id];
|
||||
} else if (file.name.endsWith('.pdb')) {
|
||||
const text = await plugin.runTask(assetManager.resolve(file, 'string'));
|
||||
assets.push(text);
|
||||
const pdb = await parsePDBfile(plugin, text.data, id);
|
||||
model = (await plugin.runTask(trajectoryFromPDB(pdb)))[model_id];
|
||||
} else {
|
||||
throw new Error(`unsupported file type '${file.name}'`);
|
||||
}
|
||||
} else if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
|
||||
if (surface){
|
||||
const data = await getFromOPM(plugin, id, assetManager);
|
||||
if (data.asset){
|
||||
assets.push(data.asset);
|
||||
model = (await plugin.runTask(trajectoryFromPDB(data.pdb)))[model_id];
|
||||
} else {
|
||||
const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
|
||||
assets.push(asset);
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(mmcif)))[model_id];
|
||||
}
|
||||
} else {
|
||||
const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
|
||||
assets.push(asset);
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(mmcif)))[model_id];
|
||||
}
|
||||
} else {
|
||||
const data = await getFromCellPackDB(plugin, id, baseUrl, assetManager);
|
||||
assets.push(data.asset);
|
||||
if ('pdb' in data) {
|
||||
model = (await plugin.runTask(trajectoryFromPDB(data.pdb)))[model_id];
|
||||
} else {
|
||||
model = (await plugin.runTask(trajectoryFromMmCIF(data.mmcif)))[model_id];
|
||||
}
|
||||
}
|
||||
return { model, assets };
|
||||
}
|
||||
|
||||
async function getStructure(plugin: PluginContext, model: Model, source: IngredientSource, props: { assembly?: string } = {}) {
|
||||
let structure = Structure.ofModel(model);
|
||||
const { assembly } = props;
|
||||
|
||||
if (assembly) {
|
||||
structure = await plugin.runTask(StructureSymmetry.buildAssembly(structure, assembly));
|
||||
}
|
||||
let query;
|
||||
if (source.selection){
|
||||
const asymIds: string[] = source.selection.replace(' :', '').split(' or');
|
||||
query = MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
|
||||
'chain-test': MS.core.set.has([MS.set(...asymIds), MS.ammp('auth_asym_id')])
|
||||
})
|
||||
]);
|
||||
} else {
|
||||
query = MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer'])
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
const compiled = compile<StructureSelection>(query);
|
||||
const result = compiled(new QueryContext(structure));
|
||||
structure = StructureSelection.unionStructure(result);
|
||||
|
||||
return structure;
|
||||
}
|
||||
|
||||
function getTransformLegacy(trans: Vec3, rot: Quat) {
|
||||
const q: Quat = Quat.create(-rot[3], rot[0], rot[1], rot[2]);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
|
||||
Mat4.transpose(m, m);
|
||||
Mat4.scale(m, m, Vec3.create(-1.0, 1.0, -1.0));
|
||||
Mat4.setTranslation(m, trans);
|
||||
return m;
|
||||
}
|
||||
|
||||
function getTransform(trans: Vec3, rot: Quat) {
|
||||
const q: Quat = Quat.create(rot[0], rot[1], rot[2], rot[3]);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
|
||||
const p: Vec3 = Vec3.create(trans[0], trans[1], trans[2]);
|
||||
Mat4.setTranslation(m, p);
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
function getResultTransforms(results: Ingredient['results'], legacy: boolean) {
|
||||
if (legacy) return results.map((r: Ingredient['results'][0]) => getTransformLegacy(r[0], r[1]));
|
||||
else return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1]));
|
||||
}
|
||||
|
||||
function getCurveTransforms(ingredient: Ingredient) {
|
||||
const n = ingredient.nbCurve || 0;
|
||||
const instances: Mat4[] = [];
|
||||
const segmentLength = ingredient.radii
|
||||
? (ingredient.radii[0].radii
|
||||
? ingredient.radii[0].radii[0] * 2.0
|
||||
: 3.4)
|
||||
: 3.4;
|
||||
for (let i = 0; i < n; ++i) {
|
||||
const cname = `curve${i}`;
|
||||
if (!(cname in ingredient)) {
|
||||
// console.warn(`Expected '${cname}' in ingredient`)
|
||||
continue;
|
||||
}
|
||||
const _points = ingredient[cname] as Vec3[];
|
||||
if (_points.length <= 2) {
|
||||
// TODO handle curve with 2 or less points
|
||||
continue;
|
||||
}
|
||||
const points = new Float32Array(_points.length * 3);
|
||||
for (let i = 0, il = _points.length; i < il; ++i) Vec3.toArray(_points[i], points, i * 3);
|
||||
const newInstances = getMatFromResamplePoints(points, segmentLength);
|
||||
instances.push(...newInstances);
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
function getAssembly(transforms: Mat4[], structure: Structure) {
|
||||
const builder = Structure.Builder();
|
||||
const { units } = structure;
|
||||
|
||||
for (let i = 0, il = transforms.length; i < il; ++i) {
|
||||
const id = `${i + 1}`;
|
||||
const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [ id ] } });
|
||||
for (const unit of units) {
|
||||
builder.addWithOperator(unit, op);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.getStructure();
|
||||
}
|
||||
|
||||
function getCifCurve(name: string, transforms: Mat4[], model: Model) {
|
||||
if (!MmcifFormat.is(model.sourceData)) throw new Error('mmcif source data needed');
|
||||
|
||||
const { db } = model.sourceData.data;
|
||||
const d = db.atom_site;
|
||||
const n = d._rowCount;
|
||||
const rowCount = n * transforms.length;
|
||||
|
||||
const { offsets, count } = model.atomicHierarchy.chainAtomSegments;
|
||||
|
||||
const x = d.Cartn_x.toArray();
|
||||
const y = d.Cartn_y.toArray();
|
||||
const z = d.Cartn_z.toArray();
|
||||
|
||||
const Cartn_x = new Float32Array(rowCount);
|
||||
const Cartn_y = new Float32Array(rowCount);
|
||||
const Cartn_z = new Float32Array(rowCount);
|
||||
const map = new Uint32Array(rowCount);
|
||||
const seq = new Int32Array(rowCount);
|
||||
let offset = 0;
|
||||
for (let c = 0; c < count; ++c) {
|
||||
const cStart = offsets[c];
|
||||
const cEnd = offsets[c + 1];
|
||||
const cLength = cEnd - cStart;
|
||||
for (let t = 0, tl = transforms.length; t < tl; ++t) {
|
||||
const m = transforms[t];
|
||||
for (let j = cStart; j < cEnd; ++j) {
|
||||
const i = offset + j - cStart;
|
||||
const xj = x[j], yj = y[j], zj = z[j];
|
||||
Cartn_x[i] = m[0] * xj + m[4] * yj + m[8] * zj + m[12];
|
||||
Cartn_y[i] = m[1] * xj + m[5] * yj + m[9] * zj + m[13];
|
||||
Cartn_z[i] = m[2] * xj + m[6] * yj + m[10] * zj + m[14];
|
||||
map[i] = j;
|
||||
seq[i] = t + 1;
|
||||
}
|
||||
offset += cLength;
|
||||
}
|
||||
}
|
||||
|
||||
function multColumn<T>(column: Column<T>) {
|
||||
const array = column.toArray();
|
||||
return Column.ofLambda({
|
||||
value: row => array[map[row]],
|
||||
areValuesEqual: (rowA, rowB) => map[rowA] === map[rowB] || array[map[rowA]] === array[map[rowB]],
|
||||
rowCount, schema: column.schema
|
||||
});
|
||||
}
|
||||
|
||||
const _atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = {
|
||||
auth_asym_id: CifField.ofColumn(multColumn(d.auth_asym_id)),
|
||||
auth_atom_id: CifField.ofColumn(multColumn(d.auth_atom_id)),
|
||||
auth_comp_id: CifField.ofColumn(multColumn(d.auth_comp_id)),
|
||||
auth_seq_id: CifField.ofNumbers(seq),
|
||||
|
||||
B_iso_or_equiv: CifField.ofColumn(Column.ofConst(0, rowCount, Column.Schema.float)),
|
||||
Cartn_x: CifField.ofNumbers(Cartn_x),
|
||||
Cartn_y: CifField.ofNumbers(Cartn_y),
|
||||
Cartn_z: CifField.ofNumbers(Cartn_z),
|
||||
group_PDB: CifField.ofColumn(Column.ofConst('ATOM', rowCount, Column.Schema.str)),
|
||||
id: CifField.ofColumn(Column.ofLambda({
|
||||
value: row => row,
|
||||
areValuesEqual: (rowA, rowB) => rowA === rowB,
|
||||
rowCount, schema: d.id.schema,
|
||||
})),
|
||||
|
||||
label_alt_id: CifField.ofColumn(multColumn(d.label_alt_id)),
|
||||
|
||||
label_asym_id: CifField.ofColumn(multColumn(d.label_asym_id)),
|
||||
label_atom_id: CifField.ofColumn(multColumn(d.label_atom_id)),
|
||||
label_comp_id: CifField.ofColumn(multColumn(d.label_comp_id)),
|
||||
label_seq_id: CifField.ofNumbers(seq),
|
||||
label_entity_id: CifField.ofColumn(Column.ofConst('1', rowCount, Column.Schema.str)),
|
||||
|
||||
occupancy: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.float)),
|
||||
type_symbol: CifField.ofColumn(multColumn(d.type_symbol)),
|
||||
|
||||
pdbx_PDB_ins_code: CifField.ofColumn(Column.ofConst('', rowCount, Column.Schema.str)),
|
||||
pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.int)),
|
||||
};
|
||||
|
||||
const categories = {
|
||||
entity: CifCategory.ofTable('entity', db.entity),
|
||||
chem_comp: CifCategory.ofTable('chem_comp', db.chem_comp),
|
||||
atom_site: CifCategory.ofFields('atom_site', _atom_site)
|
||||
};
|
||||
|
||||
return {
|
||||
header: name,
|
||||
categoryNames: Object.keys(categories),
|
||||
categories
|
||||
};
|
||||
}
|
||||
|
||||
async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredient, transforms: Mat4[], model: Model) {
|
||||
const cif = getCifCurve(name, transforms, model);
|
||||
|
||||
const curveModelTask = Task.create('Curve Model', async ctx => {
|
||||
const format = MmcifFormat.fromFrame(cif);
|
||||
const models = await createModels(format.data.db, format, ctx);
|
||||
return models[0];
|
||||
});
|
||||
|
||||
const curveModel = await plugin.runTask(curveModelTask);
|
||||
return getStructure(plugin, curveModel, ingredient.source);
|
||||
}
|
||||
|
||||
async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles) {
|
||||
const { name, source, results, nbCurve } = ingredient;
|
||||
if (source.pdb === 'None') return;
|
||||
|
||||
const file = ingredientFiles[source.pdb];
|
||||
if (!file) {
|
||||
// TODO can these be added to the library?
|
||||
if (name === 'HIV1_CAhex_0_1_0') return;
|
||||
if (name === 'HIV1_CAhexCyclophilA_0_1_0') return;
|
||||
if (name === 'iLDL') return;
|
||||
if (name === 'peptides') return;
|
||||
if (name === 'lypoglycane') return;
|
||||
}
|
||||
|
||||
// model id in case structure is NMR
|
||||
const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, file);
|
||||
if (!model) return;
|
||||
|
||||
let structure: Structure;
|
||||
if (nbCurve) {
|
||||
structure = await getCurve(plugin, name, ingredient, getCurveTransforms(ingredient), model);
|
||||
} else {
|
||||
let bu: string|undefined = source.bu ? source.bu : undefined;
|
||||
if (bu){
|
||||
if (bu === 'AU') {
|
||||
bu = undefined;
|
||||
} else {
|
||||
bu = bu.slice(2);
|
||||
}
|
||||
}
|
||||
structure = await getStructure(plugin, model, source, { assembly: bu });
|
||||
// transform with offset and pcp
|
||||
let legacy: boolean = true;
|
||||
if (ingredient.offset || ingredient.principalAxis){
|
||||
legacy = false;
|
||||
const structureMean = getStructureMean(structure);
|
||||
Vec3.negate(structureMean, structureMean);
|
||||
const m1: Mat4 = Mat4.identity();
|
||||
Mat4.setTranslation(m1, structureMean);
|
||||
structure = Structure.transform(structure, m1);
|
||||
if (ingredient.offset){
|
||||
if (!Vec3.exactEquals(ingredient.offset, Vec3.zero())){
|
||||
const m: Mat4 = Mat4.identity();
|
||||
Mat4.setTranslation(m, ingredient.offset);
|
||||
structure = Structure.transform(structure, m);
|
||||
}
|
||||
}
|
||||
if (ingredient.principalAxis){
|
||||
if (!Vec3.exactEquals(ingredient.principalAxis, Vec3.unitZ)){
|
||||
const q: Quat = Quat.identity();
|
||||
Quat.rotationTo(q, ingredient.principalAxis, Vec3.unitZ);
|
||||
const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
|
||||
structure = Structure.transform(structure, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
structure = getAssembly(getResultTransforms(results, legacy), structure);
|
||||
}
|
||||
|
||||
return { structure, assets };
|
||||
}
|
||||
|
||||
export function createStructureFromCellPack(plugin: PluginContext, packing: CellPacking, baseUrl: string, ingredientFiles: IngredientFiles) {
|
||||
return Task.create('Create Packing Structure', async ctx => {
|
||||
const { ingredients, name } = packing;
|
||||
const assets: Asset.Wrapper[] = [];
|
||||
const structures: Structure[] = [];
|
||||
for (const iName in ingredients) {
|
||||
if (ctx.shouldUpdate) await ctx.update(iName);
|
||||
const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles);
|
||||
if (ingredientStructure) {
|
||||
structures.push(ingredientStructure.structure);
|
||||
assets.push(...ingredientStructure.assets);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - units`);
|
||||
const builder = Structure.Builder({ label: name });
|
||||
let offsetInvariantId = 0;
|
||||
for (const s of structures) {
|
||||
if (ctx.shouldUpdate) await ctx.update(`${s.label}`);
|
||||
let maxInvariantId = 0;
|
||||
for (const u of s.units) {
|
||||
const invariantId = u.invariantId + offsetInvariantId;
|
||||
if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
|
||||
builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, Unit.Trait.None, invariantId);
|
||||
}
|
||||
offsetInvariantId += maxInvariantId + 1;
|
||||
}
|
||||
|
||||
if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
|
||||
const structure = builder.getStructure();
|
||||
for( let i = 0, il = structure.models.length; i < il; ++i) {
|
||||
const { trajectoryInfo } = structure.models[i];
|
||||
trajectoryInfo.size = il;
|
||||
trajectoryInfo.index = i;
|
||||
}
|
||||
return { structure, assets };
|
||||
});
|
||||
}
|
||||
|
||||
async function handleHivRna(plugin: PluginContext, packings: CellPacking[], baseUrl: string) {
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0') {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${baseUrl}/extras/rna_allpoints.json`);
|
||||
const json = await plugin.runTask(plugin.managers.asset.resolve(url, 'json', false));
|
||||
const points = json.data.points as number[];
|
||||
|
||||
const curve0: Vec3[] = [];
|
||||
for (let j = 0, jl = points.length; j < jl; j += 3) {
|
||||
curve0.push(Vec3.fromArray(Vec3(), points, j));
|
||||
}
|
||||
packings[i].ingredients['RNA'] = {
|
||||
source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
|
||||
results: [],
|
||||
name: 'RNA',
|
||||
nbCurve: 1,
|
||||
curve0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMembrane(plugin: PluginContext, name: string, state: State, params: LoadCellPackModelParams) {
|
||||
let file: Asset.File | undefined = undefined;
|
||||
if (params.ingredients.files !== null) {
|
||||
const fileName = `${name}.bcif`;
|
||||
for (const f of params.ingredients.files) {
|
||||
if (fileName === f.name) {
|
||||
file = f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let b = state.build().toRoot();
|
||||
if (file) {
|
||||
b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } });
|
||||
} else {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
|
||||
b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
|
||||
}
|
||||
|
||||
const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Model.StructureFromModel)
|
||||
.commit({ revertOnError: true });
|
||||
|
||||
const membraneParams = {
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
|
||||
}
|
||||
|
||||
async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
|
||||
let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
|
||||
if (params.source.name === 'id') {
|
||||
const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl));
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } });
|
||||
} else {
|
||||
const file = params.source.params;
|
||||
if (file === null) {
|
||||
plugin.log.error('No file selected');
|
||||
return;
|
||||
}
|
||||
cellPackJson = state.build().toRoot()
|
||||
.apply(StateTransforms.Data.ReadFile, { file, isBinary: false, label: file.name }, { state: { isGhost: true } });
|
||||
}
|
||||
|
||||
const cellPackBuilder = cellPackJson
|
||||
.apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
|
||||
.apply(ParseCellPack);
|
||||
|
||||
const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime);
|
||||
const { packings } = cellPackObject.obj!.data;
|
||||
|
||||
await handleHivRna(plugin, packings, params.baseUrl);
|
||||
|
||||
for (let i = 0, il = packings.length; i < il; ++i) {
|
||||
const p = { packing: i, baseUrl: params.baseUrl, ingredientFiles: params.ingredients.files };
|
||||
|
||||
const packing = await state.build()
|
||||
.to(cellPackBuilder.ref)
|
||||
.apply(StructureFromCellpack, p)
|
||||
.commit({ revertOnError: true });
|
||||
|
||||
const packingParams = {
|
||||
traceOnly: params.preset.traceOnly,
|
||||
representation: params.preset.representation,
|
||||
};
|
||||
await CellpackPackingPreset.apply(packing, packingParams, plugin);
|
||||
if ( packings[i].location === 'surface' ){
|
||||
await loadMembrane(plugin, packings[i].name, state, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const LoadCellPackModelParams = {
|
||||
source: PD.MappedStatic('id', {
|
||||
'id': PD.Select('influenza_model1.json', [
|
||||
['blood_hiv_immature_inside.json', 'blood_hiv_immature_inside'],
|
||||
['BloodHIV1.0_mixed_fixed_nc1.cpr', 'BloodHIV1.0_mixed_fixed_nc1'],
|
||||
['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'],
|
||||
['hiv_lipids.bcif', 'hiv_lipids'],
|
||||
['influenza_model1.json', 'influenza_model1'],
|
||||
['ExosomeModel.json', 'ExosomeModel'],
|
||||
['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'],
|
||||
] as const),
|
||||
'file': PD.File({ accept: 'id' }),
|
||||
}, { options: [['id', 'Id'], ['file', 'File']] }),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
ingredients : PD.Group({
|
||||
files: PD.FileList({ accept: '.cif,.bcif,.pdb' })
|
||||
}, { isExpanded: true }),
|
||||
preset: PD.Group({
|
||||
traceOnly: PD.Boolean(false),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation']))
|
||||
}, { isExpanded: true })
|
||||
};
|
||||
type LoadCellPackModelParams = PD.Values<typeof LoadCellPackModelParams>
|
||||
|
||||
export const LoadCellPackModel = StateAction.build({
|
||||
display: { name: 'Load CellPack', description: 'Open or download a model' },
|
||||
params: LoadCellPackModelParams,
|
||||
from: PSO.Root
|
||||
})(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
|
||||
if (params.source.name === 'id' && params.source.params === 'hiv_lipids.bcif') {
|
||||
await loadMembrane(ctx, 'hiv_lipids', state, params);
|
||||
} else {
|
||||
await loadPackings(ctx, taskCtx, state, params);
|
||||
}
|
||||
}));
|
||||
@@ -4,16 +4,16 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { StateObjectRef } from '../../../../mol-state';
|
||||
import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../../../mol-util/color/names';
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
import { StructureRepresentationPresetProvider, presetStaticComponent } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { ColorNames } from '../../mol-util/color/names';
|
||||
import { CellPackColorThemeProvider } from './color';
|
||||
|
||||
export const CellpackPackingPresetParams = {
|
||||
traceOnly: PD.Boolean(true),
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'])),
|
||||
}
|
||||
};
|
||||
export type CellpackPackingPresetParams = PD.ValuesFor<typeof CellpackPackingPresetParams>
|
||||
|
||||
export const CellpackPackingPreset = StructureRepresentationPresetProvider({
|
||||
@@ -35,18 +35,18 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
|
||||
if (params.representation === 'gaussian-surface') {
|
||||
Object.assign(reprProps, {
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2, doubleSided: false
|
||||
})
|
||||
});
|
||||
} else if (params.representation === 'spacefill' && params.traceOnly) {
|
||||
Object.assign(reprProps, { sizeFactor: 2 })
|
||||
Object.assign(reprProps, { sizeFactor: 2 });
|
||||
}
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
|
||||
const color = CellPackColorThemeProvider.name
|
||||
const color = CellPackColorThemeProvider.name;
|
||||
const representations = {
|
||||
polymer: builder.buildRepresentation<any>(update, components.polymer, { type: params.representation, typeParams: { ...typeParams, ...reprProps }, color }, { tag: 'polymer' })
|
||||
};
|
||||
|
||||
await plugin.updateDataState(update, { revertOnError: true });
|
||||
await update.commit({ revertOnError: true });
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
@@ -55,7 +55,7 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
|
||||
|
||||
export const CellpackMembranePresetParams = {
|
||||
representation: PD.Select('gaussian-surface', PD.arrayToOptions(['gaussian-surface', 'spacefill', 'point', 'orientation'])),
|
||||
}
|
||||
};
|
||||
export type CellpackMembranePresetParams = PD.ValuesFor<typeof CellpackMembranePresetParams>
|
||||
|
||||
export const CellpackMembranePreset = StructureRepresentationPresetProvider({
|
||||
@@ -76,7 +76,7 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({
|
||||
if (params.representation === 'gaussian-surface') {
|
||||
Object.assign(reprProps, {
|
||||
quality: 'custom', resolution: 10, radiusOffset: 2, doubleSided: false
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const { update, builder, typeParams } = StructureRepresentationPresetProvider.reprBuilder(plugin, {});
|
||||
@@ -84,7 +84,7 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({
|
||||
membrane: builder.buildRepresentation(update, components.membrane, { type: 'gaussian-surface', typeParams: { ...typeParams, ...reprProps }, color: 'uniform', colorParams: { value: ColorNames.lightgrey } }, { tag: 'all' })
|
||||
};
|
||||
|
||||
await plugin.updateDataState(update, { revertOnError: true });
|
||||
await update.commit({ revertOnError: true });
|
||||
return { components, representations };
|
||||
}
|
||||
});
|
||||
@@ -4,10 +4,10 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CustomStructureProperty } from '../../../../mol-model-props/common/custom-structure-property'
|
||||
import { Structure, CustomPropertyDescriptor } from '../../../../mol-model/structure'
|
||||
import { CustomProperty } from '../../../../mol-model-props/common/custom-property'
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition'
|
||||
import { CustomStructureProperty } from '../../mol-model-props/common/custom-structure-property';
|
||||
import { Structure, CustomPropertyDescriptor } from '../../mol-model/structure';
|
||||
import { CustomProperty } from '../../mol-model-props/common/custom-property';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
export type CellPackInfoValue = {
|
||||
packingsCount: number
|
||||
@@ -16,7 +16,7 @@ export type CellPackInfoValue = {
|
||||
|
||||
const CellPackInfoParams = {
|
||||
info: PD.Value<CellPackInfoValue>({ packingsCount: 1, packingIndex: 0 }, { isHidden: true })
|
||||
}
|
||||
};
|
||||
type CellPackInfoParams = PD.Values<typeof CellPackInfoParams>
|
||||
|
||||
export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellPackInfoParams, CellPackInfoValue> = CustomStructureProperty.createProvider({
|
||||
@@ -27,6 +27,8 @@ export const CellPackInfoProvider: CustomStructureProperty.Provider<typeof CellP
|
||||
getParams: (data: Structure) => CellPackInfoParams,
|
||||
isApplicable: (data: Structure) => true,
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: CellPackInfoParams) => {
|
||||
return { ...CellPackInfoParams.info.defaultValue, ...props.info }
|
||||
return {
|
||||
value: { ...CellPackInfoParams.info.defaultValue, ...props.info }
|
||||
};
|
||||
}
|
||||
})
|
||||
});
|
||||
97
src/extensions/cellpack/state.ts
Normal file
97
src/extensions/cellpack/state.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginStateObject as PSO, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Task } from '../../mol-task';
|
||||
import { CellPack as _CellPack, Cell, CellPacking } from './data';
|
||||
import { createStructureFromCellPack } from './model';
|
||||
import { IngredientFiles } from './util';
|
||||
import { Asset } from '../../mol-util/assets';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { CellPackInfoProvider } from './property';
|
||||
|
||||
export const DefaultCellPackBaseUrl = 'https://mesoscope.scripps.edu/data/cellPACK_data/cellPACK_database_1.1.0/';
|
||||
|
||||
export class CellPack extends PSO.Create<_CellPack>({ name: 'CellPack', typeClass: 'Object' }) { }
|
||||
|
||||
export { ParseCellPack };
|
||||
type ParseCellPack = typeof ParseCellPack
|
||||
const ParseCellPack = PluginStateTransform.BuiltIn({
|
||||
name: 'parse-cellpack',
|
||||
display: { name: 'Parse CellPack', description: 'Parse CellPack from JSON data' },
|
||||
from: PSO.Format.Json,
|
||||
to: CellPack
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse CellPack', async ctx => {
|
||||
const cell = a.data as Cell;
|
||||
|
||||
const packings: CellPacking[] = [];
|
||||
const { compartments, cytoplasme } = cell;
|
||||
if (compartments) {
|
||||
for (const name in compartments) {
|
||||
const { surface, interior } = compartments[name];
|
||||
if (surface) packings.push({ name, location: 'surface', ingredients: surface.ingredients });
|
||||
if (interior) packings.push({ name, location: 'interior', ingredients: interior.ingredients });
|
||||
}
|
||||
}
|
||||
if (cytoplasme) packings.push({ name: 'Cytoplasme', location: 'cytoplasme', ingredients: cytoplasme.ingredients });
|
||||
|
||||
return new CellPack({ cell, packings });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export { StructureFromCellpack };
|
||||
type StructureFromCellpack = typeof ParseCellPack
|
||||
const StructureFromCellpack = PluginStateTransform.BuiltIn({
|
||||
name: 'structure-from-cellpack',
|
||||
display: { name: 'Structure from CellPack', description: 'Create Structure from CellPack Packing' },
|
||||
from: CellPack,
|
||||
to: PSO.Molecule.Structure,
|
||||
params: a => {
|
||||
const options = a ? a.data.packings.map((d, i) => [i, d.name] as const) : [];
|
||||
return {
|
||||
packing: PD.Select(0, options),
|
||||
baseUrl: PD.Text(DefaultCellPackBaseUrl),
|
||||
ingredientFiles: PD.FileList({ accept: '.cif,.bcif,.pdb' })
|
||||
};
|
||||
}
|
||||
})({
|
||||
apply({ a, params, cache }, plugin: PluginContext) {
|
||||
return Task.create('Structure from CellPack', async ctx => {
|
||||
const packing = a.data.packings[params.packing];
|
||||
const ingredientFiles: IngredientFiles = {};
|
||||
if (params.ingredientFiles !== null) {
|
||||
for (const file of params.ingredientFiles) {
|
||||
ingredientFiles[file.name] = file;
|
||||
}
|
||||
}
|
||||
const { structure, assets } = await createStructureFromCellPack(plugin, packing, params.baseUrl, ingredientFiles).runInContext(ctx);
|
||||
|
||||
await CellPackInfoProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, structure, {
|
||||
info: { packingsCount: a.data.packings.length, packingIndex: params.packing }
|
||||
});
|
||||
|
||||
(cache as any).assets = assets;
|
||||
return new PSO.Molecule.Structure(structure, { label: packing.name });
|
||||
});
|
||||
},
|
||||
dispose({ b, cache }) {
|
||||
const assets = (cache as any).assets as Asset.Wrapper[];
|
||||
if(assets) {
|
||||
for (const a of assets) a.dispose();
|
||||
}
|
||||
|
||||
if (b) {
|
||||
b.data.customPropertyDescriptors.dispose();
|
||||
for (const m of b.data.models) {
|
||||
m.customProperties.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
77
src/extensions/cellpack/util.ts
Normal file
77
src/extensions/cellpack/util.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { CIF } from '../../mol-io/reader/cif';
|
||||
import { parsePDB } from '../../mol-io/reader/pdb/parser';
|
||||
import { AssetManager, Asset } from '../../mol-util/assets';
|
||||
import { Structure } from '../../mol-model/structure';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
|
||||
export async function parseCif(plugin: PluginContext, data: string | Uint8Array) {
|
||||
const comp = CIF.parse(data);
|
||||
const parsed = await plugin.runTask(comp);
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
export async function parsePDBfile(plugin: PluginContext, data: string, id: string) {
|
||||
const comp = parsePDB(data, id);
|
||||
const parsed = await plugin.runTask(comp);
|
||||
if (parsed.isError) throw parsed;
|
||||
return parsed.result;
|
||||
}
|
||||
|
||||
async function downloadCif(plugin: PluginContext, url: string, isBinary: boolean, assetManager: AssetManager) {
|
||||
const type = isBinary ? 'binary' : 'string';
|
||||
const asset = await plugin.runTask(assetManager.resolve(Asset.getUrlAsset(assetManager, url), type));
|
||||
return { cif: await parseCif(plugin, asset.data), asset };
|
||||
}
|
||||
|
||||
async function downloadPDB(plugin: PluginContext, url: string, id: string, assetManager: AssetManager) {
|
||||
const asset = await assetManager.resolve(Asset.getUrlAsset(assetManager, url), 'string').run();
|
||||
return { pdb: await parsePDBfile(plugin, asset.data, id), asset };
|
||||
}
|
||||
|
||||
export async function getFromPdb(plugin: PluginContext, pdbId: string, assetManager: AssetManager) {
|
||||
const { cif, asset } = await downloadCif(plugin, `https://models.rcsb.org/${pdbId.toUpperCase()}.bcif`, true, assetManager);
|
||||
return { mmcif: cif.blocks[0], asset };
|
||||
}
|
||||
|
||||
export async function getFromOPM(plugin: PluginContext, pdbId: string, assetManager: AssetManager){
|
||||
const asset = await plugin.runTask(assetManager.resolve(Asset.getUrlAsset(assetManager, `https://opm-assets.storage.googleapis.com/pdb/${pdbId.toLowerCase()}.pdb`), 'string'));
|
||||
return { pdb: await parsePDBfile(plugin, asset.data, pdbId), asset };
|
||||
}
|
||||
|
||||
export async function getFromCellPackDB(plugin: PluginContext, id: string, baseUrl: string, assetManager: AssetManager) {
|
||||
if (id.toLowerCase().endsWith('.cif') || id.toLowerCase().endsWith('.bcif')) {
|
||||
const isBinary = id.toLowerCase().endsWith('.bcif');
|
||||
const { cif, asset } = await downloadCif(plugin, `${baseUrl}/other/${id}`, isBinary, assetManager);
|
||||
return { mmcif: cif.blocks[0], asset };
|
||||
} else {
|
||||
const name = id.endsWith('.pdb') ? id.substring(0, id.length - 4) : id;
|
||||
return await downloadPDB(plugin, `${baseUrl}/other/${name}.pdb`, name, assetManager);
|
||||
}
|
||||
}
|
||||
|
||||
export type IngredientFiles = { [name: string]: Asset.File }
|
||||
|
||||
export function getStructureMean(structure: Structure) {
|
||||
let xSum = 0, ySum = 0, zSum = 0;
|
||||
for (let i = 0, il = structure.units.length; i < il; ++i) {
|
||||
const unit = structure.units[i];
|
||||
const { elements } = unit;
|
||||
const { x, y, z } = unit.conformation;
|
||||
for (let j = 0, jl = elements.length; j < jl; ++j) {
|
||||
const eI = elements[j];
|
||||
xSum += x(eI);
|
||||
ySum += y(eI);
|
||||
zSum += z(eI);
|
||||
}
|
||||
}
|
||||
const { elementCount } = structure;
|
||||
return Vec3.create(xSum / elementCount, ySum / elementCount, zSum / elementCount);
|
||||
}
|
||||
9
src/extensions/pdbe/index.ts
Normal file
9
src/extensions/pdbe/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
export { PDBeStructureQualityReport } from './structure-quality-report/behavior';
|
||||
export { PDBePreferredAssembly } from './preferred-assembly';
|
||||
export { PDBeStructRefDomain } from './struct-ref-domain';
|
||||
@@ -15,7 +15,7 @@ export namespace PDBePreferredAssembly {
|
||||
export type Property = string
|
||||
|
||||
export function getFirstFromModel(model: Model): Property {
|
||||
const symmetry = ModelSymmetry.Provider.get(model)
|
||||
const symmetry = ModelSymmetry.Provider.get(model);
|
||||
return symmetry?.assemblies.length ? symmetry.assemblies[0].id : '';
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Column, Table } from '../../mol-data/db';
|
||||
import { toTable } from '../../mol-io/reader/cif/schema';
|
||||
import { CifWriter } from '../../mol-io/writer/cif';
|
||||
import { Model, CustomPropertyDescriptor } from '../../mol-model/structure';
|
||||
import { PropertyWrapper } from '../common/wrapper';
|
||||
import { PropertyWrapper } from '../../mol-model-props/common/wrapper';
|
||||
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
|
||||
export namespace PDBeStructRefDomain {
|
||||
@@ -111,7 +111,7 @@ function fromPDBeJson(modelData: Model, data: any): PDBeStructRefDomain.Property
|
||||
beg_pdbx_PDB_ins_code: map.start.author_insertion_code,
|
||||
end_label_seq_id: map.end.residue_number,
|
||||
end_pdbx_PDB_ins_code: map.end.author_insertion_code,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,13 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { OrderedSet } from '../../../../../mol-data/int';
|
||||
import { StructureQualityReport, StructureQualityReportProvider } from '../../../../../mol-model-props/pdbe/structure-quality-report';
|
||||
import { StructureQualityReportColorThemeProvider } from '../../../../../mol-model-props/pdbe/themes/structure-quality-report';
|
||||
import { Loci } from '../../../../../mol-model/loci';
|
||||
import { StructureElement } from '../../../../../mol-model/structure';
|
||||
import { ParamDefinition as PD } from '../../../../../mol-util/param-definition';
|
||||
import { PluginBehavior } from '../../../behavior';
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { StructureQualityReport, StructureQualityReportProvider } from './prop';
|
||||
import { StructureQualityReportColorThemeProvider } from './color';
|
||||
import { Loci } from '../../../mol-model/loci';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
|
||||
|
||||
export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
|
||||
name: 'pdbe-structure-quality-report-prop',
|
||||
@@ -48,11 +48,11 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
|
||||
this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
|
||||
this.ctx.managers.lociLabels.addProvider(this.labelPDBeValidation);
|
||||
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(StructureQualityReportColorThemeProvider)
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(StructureQualityReportColorThemeProvider);
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean, showTooltip: boolean }) {
|
||||
let updated = this.params.autoAttach !== p.autoAttach
|
||||
let updated = this.params.autoAttach !== p.autoAttach;
|
||||
this.params.autoAttach = p.autoAttach;
|
||||
this.params.showTooltip = p.showTooltip;
|
||||
this.ctx.customModelProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
|
||||
@@ -62,7 +62,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
|
||||
unregister() {
|
||||
this.ctx.customModelProperties.unregister(StructureQualityReportProvider.descriptor.name);
|
||||
this.ctx.managers.lociLabels.removeProvider(this.labelPDBeValidation);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(StructureQualityReportColorThemeProvider)
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(StructureQualityReportColorThemeProvider);
|
||||
}
|
||||
},
|
||||
params: () => ({
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { StructureQualityReport, StructureQualityReportProvider } from '../../../mol-model-props/pdbe/structure-quality-report';
|
||||
import { StructureQualityReport, StructureQualityReportProvider } from './prop';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
@@ -12,7 +12,7 @@ import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { TableLegend } from '../../../mol-util/legend';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
|
||||
const ValidationColors = [
|
||||
Color.fromRgb(170, 170, 170), // not applicable
|
||||
@@ -20,7 +20,7 @@ const ValidationColors = [
|
||||
Color.fromRgb(255, 255, 0), // 1
|
||||
Color.fromRgb(255, 128, 0), // 2
|
||||
Color.fromRgb(255, 0, 0), // 3 or more
|
||||
]
|
||||
];
|
||||
|
||||
const ValidationColorTable: [string, Color][] = [
|
||||
['No Issues', ValidationColors[1]],
|
||||
@@ -28,7 +28,7 @@ const ValidationColorTable: [string, Color][] = [
|
||||
['Two Issues', ValidationColors[3]],
|
||||
['Three Or More Issues', ValidationColors[4]],
|
||||
['Not Applicable', ValidationColors[9]]
|
||||
]
|
||||
];
|
||||
|
||||
export const StructureQualityReportColorThemeParams = {
|
||||
type: PD.MappedStatic('issue-count', {
|
||||
@@ -42,7 +42,7 @@ export const StructureQualityReportColorThemeParams = {
|
||||
type Params = typeof StructureQualityReportColorThemeParams
|
||||
|
||||
export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: PD.Values<Params>): ColorTheme<Params> {
|
||||
let color: LocationColor
|
||||
let color: LocationColor;
|
||||
|
||||
if (ctx.structure && !ctx.structure.isEmpty && ctx.structure.models[0].customProperties.has(StructureQualityReportProvider.descriptor)) {
|
||||
const getIssues = StructureQualityReport.getIssues;
|
||||
@@ -53,7 +53,7 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
|
||||
return ValidationColors[Math.min(3, getIssues(location).length) + 1];
|
||||
}
|
||||
return ValidationColors[0];
|
||||
}
|
||||
};
|
||||
} else {
|
||||
const issue = props.type.params.kind;
|
||||
color = (location: Location) => {
|
||||
@@ -61,7 +61,7 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
|
||||
return ValidationColors[4];
|
||||
}
|
||||
return ValidationColors[0];
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
color = () => ValidationColors[0];
|
||||
@@ -74,7 +74,7 @@ export function StructureQualityReportColorTheme(ctx: ThemeDataContext, props: P
|
||||
props: props,
|
||||
description: 'Assigns residue colors according to the number of quality issues or a specific quality issue. Data from wwPDB Validation Report, obtained via PDBe.',
|
||||
legend: TableLegend(ValidationColorTable)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Params, 'pdbe-structure-quality-report'> = {
|
||||
@@ -107,4 +107,4 @@ export const StructureQualityReportColorThemeProvider: ColorTheme.Provider<Param
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? StructureQualityReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(StructureQualityReportProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -5,24 +5,25 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Column, Table } from '../../mol-data/db';
|
||||
import { toTable } from '../../mol-io/reader/cif/schema';
|
||||
import { mmCIF_residueId_schema } from '../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
import { CifWriter } from '../../mol-io/writer/cif';
|
||||
import { Model, CustomPropertyDescriptor, ResidueIndex, Unit, IndexedCustomProperty } from '../../mol-model/structure';
|
||||
import { residueIdFields } from '../../mol-model/structure/export/categories/atom_site';
|
||||
import { StructureElement, CifExportContext, Structure } from '../../mol-model/structure/structure';
|
||||
import { CustomPropSymbol } from '../../mol-script/language/symbol';
|
||||
import Type from '../../mol-script/language/type';
|
||||
import { QuerySymbolRuntime } from '../../mol-script/runtime/query/compiler';
|
||||
import { PropertyWrapper } from '../common/wrapper';
|
||||
import { CustomModelProperty } from '../common/custom-model-property';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition'
|
||||
import { CustomProperty } from '../common/custom-property';
|
||||
import { arraySetAdd } from '../../mol-util/array';
|
||||
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { Column, Table } from '../../../mol-data/db';
|
||||
import { toTable } from '../../../mol-io/reader/cif/schema';
|
||||
import { mmCIF_residueId_schema } from '../../../mol-io/reader/cif/schema/mmcif-extras';
|
||||
import { CifWriter } from '../../../mol-io/writer/cif';
|
||||
import { Model, CustomPropertyDescriptor, ResidueIndex, Unit, IndexedCustomProperty } from '../../../mol-model/structure';
|
||||
import { residueIdFields } from '../../../mol-model/structure/export/categories/atom_site';
|
||||
import { StructureElement, CifExportContext, Structure } from '../../../mol-model/structure/structure';
|
||||
import { CustomPropSymbol } from '../../../mol-script/language/symbol';
|
||||
import Type from '../../../mol-script/language/type';
|
||||
import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { arraySetAdd } from '../../../mol-util/array';
|
||||
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
import { PropertyWrapper } from '../../../mol-model-props/common/wrapper';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
|
||||
import { Asset } from '../../../mol-util/assets';
|
||||
|
||||
export { StructureQualityReport }
|
||||
export { StructureQualityReport };
|
||||
|
||||
type StructureQualityReport = PropertyWrapper<{
|
||||
issues: IndexedCustomProperty.Residue<string[]>,
|
||||
@@ -30,18 +31,13 @@ type StructureQualityReport = PropertyWrapper<{
|
||||
}| undefined>
|
||||
|
||||
namespace StructureQualityReport {
|
||||
export const DefaultServerUrl = 'https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/'
|
||||
export const DefaultServerUrl = 'https://www.ebi.ac.uk/pdbe/api/validation/residuewise_outlier_summary/entry/';
|
||||
export function getEntryUrl(pdbId: string, serverUrl: string) {
|
||||
return `${serverUrl}/${pdbId.toLowerCase()}`
|
||||
return `${serverUrl}/${pdbId.toLowerCase()}`;
|
||||
}
|
||||
|
||||
export function isApplicable(model?: Model): boolean {
|
||||
return (
|
||||
!!model &&
|
||||
MmcifFormat.is(model.sourceData) &&
|
||||
(model.sourceData.data.db.database_2.database_id.isDefined ||
|
||||
model.entryId.length === 4)
|
||||
)
|
||||
return !!model && Model.isFromPdbArchive(model);
|
||||
}
|
||||
|
||||
export const Schema = {
|
||||
@@ -64,27 +60,28 @@ namespace StructureQualityReport {
|
||||
export function fromJson(model: Model, data: any) {
|
||||
const info = PropertyWrapper.createInfo();
|
||||
const issueMap = createIssueMapFromJson(model, data);
|
||||
return { info, data: issueMap }
|
||||
return { info, data: issueMap };
|
||||
}
|
||||
|
||||
export async function fromServer(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): Promise<StructureQualityReport> {
|
||||
const url = getEntryUrl(model.entryId, props.serverUrl)
|
||||
const json = await ctx.fetch({ url, type: 'json' }).runInContext(ctx.runtime)
|
||||
const data = json[model.entryId.toLowerCase()];
|
||||
export async function fromServer(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): Promise<CustomProperty.Data<StructureQualityReport>> {
|
||||
const url = Asset.getUrlAsset(ctx.assetManager, getEntryUrl(model.entryId, props.serverUrl));
|
||||
const json = await ctx.assetManager.resolve(url, 'json').runInContext(ctx.runtime);
|
||||
const data = json.data[model.entryId.toLowerCase()];
|
||||
if (!data) throw new Error('missing data');
|
||||
return fromJson(model, data)
|
||||
return { value: fromJson(model, data), assets: [json] };
|
||||
}
|
||||
|
||||
export function fromCif(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): StructureQualityReport | undefined {
|
||||
let info = PropertyWrapper.tryGetInfoFromCif('pdbe_structure_quality_report', model);
|
||||
if (!info) return
|
||||
if (!info) return;
|
||||
const data = getCifData(model);
|
||||
const issueMap = createIssueMapFromCif(model, data.residues, data.groups);
|
||||
return { info, data: issueMap }
|
||||
return { info, data: issueMap };
|
||||
}
|
||||
|
||||
export async function fromCifOrServer(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): Promise<StructureQualityReport> {
|
||||
return fromCif(ctx, model, props) || fromServer(ctx, model, props)
|
||||
export async function fromCifOrServer(ctx: CustomProperty.Context, model: Model, props: StructureQualityReportProps): Promise<CustomProperty.Data<StructureQualityReport>> {
|
||||
const cif = fromCif(ctx, model, props);
|
||||
return cif ? { value: cif } : fromServer(ctx, model, props);
|
||||
}
|
||||
|
||||
const _emptyArray: string[] = [];
|
||||
@@ -108,13 +105,13 @@ namespace StructureQualityReport {
|
||||
return {
|
||||
residues: toTable(Schema.pdbe_structure_quality_report_issues, model.sourceData.data.frame.categories.pdbe_structure_quality_report_issues),
|
||||
groups: toTable(Schema.pdbe_structure_quality_report_issue_types, model.sourceData.data.frame.categories.pdbe_structure_quality_report_issue_types),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const StructureQualityReportParams = {
|
||||
serverUrl: PD.Text(StructureQualityReport.DefaultServerUrl, { description: 'JSON API Server URL' })
|
||||
}
|
||||
};
|
||||
export type StructureQualityReportParams = typeof StructureQualityReportParams
|
||||
export type StructureQualityReportProps = PD.Values<StructureQualityReportParams>
|
||||
|
||||
@@ -135,7 +132,7 @@ export const StructureQualityReportProvider: CustomModelProperty.Provider<Struct
|
||||
return {
|
||||
fields: _structure_quality_report_issues_fields,
|
||||
source: ctx.models.map(data => ({ data, rowCount: data.elements.length }))
|
||||
}
|
||||
};
|
||||
}
|
||||
}, {
|
||||
name: 'pdbe_structure_quality_report_issue_types',
|
||||
@@ -155,10 +152,10 @@ export const StructureQualityReportProvider: CustomModelProperty.Provider<Struct
|
||||
getParams: (data: Model) => StructureQualityReportParams,
|
||||
isApplicable: (data: Model) => StructureQualityReport.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<StructureQualityReportProps>) => {
|
||||
const p = { ...PD.getDefaultValues(StructureQualityReportParams), ...props }
|
||||
return await StructureQualityReport.fromCifOrServer(ctx, data, p)
|
||||
const p = { ...PD.getDefaultValues(StructureQualityReportParams), ...props };
|
||||
return await StructureQualityReport.fromCifOrServer(ctx, data, p);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const _structure_quality_report_issues_fields = CifWriter.fields<number, ReportExportContext['models'][0]>()
|
||||
.index('id')
|
||||
@@ -210,7 +207,7 @@ function createExportContext(ctx: CifExportContext): ReportExportContext {
|
||||
info,
|
||||
models,
|
||||
issueTypes: Table.ofArrays(StructureQualityReport.Schema.pdbe_structure_quality_report_issue_types, { group_id, issue_type })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createIssueMapFromJson(modelData: Model, data: any): StructureQualityReport['data'] | undefined {
|
||||
@@ -266,7 +263,7 @@ function createIssueMapFromCif(modelData: Model,
|
||||
for (const t of issues) {
|
||||
arraySetAdd(issueTypes, t);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return {
|
||||
issues: IndexedCustomProperty.fromResidueMap(ret),
|
||||
4
src/extensions/rcsb/README.md
Normal file
4
src/extensions/rcsb/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
### Code generation
|
||||
**GraphQL schemas**
|
||||
|
||||
./node_modules/.bin/graphql-codegen -c ./src/extensions/rcsb/graphql/codegen.yml
|
||||
@@ -4,20 +4,20 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../../../mol-util/param-definition'
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry } from '../../../../../mol-model-props/rcsb/assembly-symmetry';
|
||||
import { PluginBehavior } from '../../../behavior';
|
||||
import { AssemblySymmetryParams, AssemblySymmetryRepresentation } from '../../../../../mol-model-props/rcsb/representations/assembly-symmetry';
|
||||
import { AssemblySymmetryClusterColorThemeProvider } from '../../../../../mol-model-props/rcsb/themes/assembly-symmetry-cluster';
|
||||
import { PluginStateTransform, PluginStateObject } from '../../../../../mol-plugin-state/objects';
|
||||
import { Task } from '../../../../../mol-task';
|
||||
import { PluginContext } from '../../../../context';
|
||||
import { StateTransformer, StateAction, StateObject, StateTransform, StateObjectRef } from '../../../../../mol-state';
|
||||
import { GenericRepresentationRef } from '../../../../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
import { AssemblySymmetryControls } from './ui/assembly-symmetry';
|
||||
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry, AssemblySymmetryDataProvider } from './prop';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
|
||||
import { AssemblySymmetryParams, AssemblySymmetryRepresentation } from './representation';
|
||||
import { AssemblySymmetryClusterColorThemeProvider } from './color';
|
||||
import { PluginStateTransform, PluginStateObject } from '../../../mol-plugin-state/objects';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { StateTransformer, StateAction, StateObject, StateTransform, StateObjectRef } from '../../../mol-state';
|
||||
import { GenericRepresentationRef } from '../../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
import { AssemblySymmetryControls } from './ui';
|
||||
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
|
||||
const Tag = AssemblySymmetry.Tag
|
||||
const Tag = AssemblySymmetry.Tag;
|
||||
|
||||
export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean }>({
|
||||
name: 'rcsb-assembly-symmetry-prop',
|
||||
@@ -30,37 +30,37 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
|
||||
private provider = AssemblySymmetryProvider
|
||||
|
||||
register(): void {
|
||||
this.ctx.state.data.actions.add(InitAssemblySymmetry3D)
|
||||
this.ctx.state.data.actions.add(InitAssemblySymmetry3D);
|
||||
this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(AssemblySymmetryClusterColorThemeProvider)
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(AssemblySymmetryClusterColorThemeProvider);
|
||||
|
||||
this.ctx.genericRepresentationControls.set(Tag.Representation, selection => {
|
||||
const refs: GenericRepresentationRef[] = []
|
||||
const refs: GenericRepresentationRef[] = [];
|
||||
selection.structures.forEach(structure => {
|
||||
const symmRepr = structure.genericRepresentations?.filter(r => r.cell.transform.transformer.id === AssemblySymmetry3D.id)[0]
|
||||
if (symmRepr) refs.push(symmRepr)
|
||||
})
|
||||
return [refs, 'Symmetries']
|
||||
})
|
||||
this.ctx.customStructureControls.set(Tag.Representation, AssemblySymmetryControls as any)
|
||||
this.ctx.builders.structure.representation.registerPreset(AssemblySymmetryPreset)
|
||||
const symmRepr = structure.genericRepresentations?.filter(r => r.cell.transform.transformer.id === AssemblySymmetry3D.id)[0];
|
||||
if (symmRepr) refs.push(symmRepr);
|
||||
});
|
||||
return [refs, 'Symmetries'];
|
||||
});
|
||||
this.ctx.customStructureControls.set(Tag.Representation, AssemblySymmetryControls as any);
|
||||
this.ctx.builders.structure.representation.registerPreset(AssemblySymmetryPreset);
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean }) {
|
||||
let updated = this.params.autoAttach !== p.autoAttach
|
||||
let updated = this.params.autoAttach !== p.autoAttach;
|
||||
this.params.autoAttach = p.autoAttach;
|
||||
this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
|
||||
return updated;
|
||||
}
|
||||
|
||||
unregister() {
|
||||
this.ctx.state.data.actions.remove(InitAssemblySymmetry3D)
|
||||
this.ctx.state.data.actions.remove(InitAssemblySymmetry3D);
|
||||
this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(AssemblySymmetryClusterColorThemeProvider)
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(AssemblySymmetryClusterColorThemeProvider);
|
||||
|
||||
this.ctx.genericRepresentationControls.delete(Tag.Representation)
|
||||
this.ctx.customStructureControls.delete(Tag.Representation)
|
||||
this.ctx.builders.structure.representation.unregisterPreset(AssemblySymmetryPreset)
|
||||
this.ctx.genericRepresentationControls.delete(Tag.Representation);
|
||||
this.ctx.customStructureControls.delete(Tag.Representation);
|
||||
this.ctx.builders.structure.representation.unregisterPreset(AssemblySymmetryPreset);
|
||||
}
|
||||
},
|
||||
params: () => ({
|
||||
@@ -80,16 +80,21 @@ export const InitAssemblySymmetry3D = StateAction.build({
|
||||
isApplicable: (a) => AssemblySymmetry.isApplicable(a.data)
|
||||
})(({ a, ref, state }, plugin: PluginContext) => Task.create('Init Assembly Symmetry', async ctx => {
|
||||
try {
|
||||
await AssemblySymmetryProvider.attach({ runtime: ctx, fetch: plugin.fetch }, a.data)
|
||||
const propCtx = { runtime: ctx, assetManager: plugin.managers.asset };
|
||||
await AssemblySymmetryDataProvider.attach(propCtx, a.data);
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(a.data).value;
|
||||
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
|
||||
await AssemblySymmetryProvider.attach(propCtx, a.data, { symmetryIndex });
|
||||
} catch(e) {
|
||||
plugin.log.error(`Assembly Symmetry: ${e}`)
|
||||
return
|
||||
plugin.log.error(`Assembly Symmetry: ${e}`);
|
||||
return;
|
||||
}
|
||||
const tree = state.build().to(ref).apply(AssemblySymmetry3D);
|
||||
const tree = state.build().to(ref)
|
||||
.applyOrUpdateTagged(AssemblySymmetry.Tag.Representation, AssemblySymmetry3D);
|
||||
await state.updateTree(tree).runInContext(ctx);
|
||||
}));
|
||||
|
||||
export { AssemblySymmetry3D }
|
||||
export { AssemblySymmetry3D };
|
||||
|
||||
type AssemblySymmetry3D = typeof AssemblySymmetry3D
|
||||
const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
|
||||
@@ -103,7 +108,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
|
||||
params: (a) => {
|
||||
return {
|
||||
...AssemblySymmetryParams,
|
||||
}
|
||||
};
|
||||
}
|
||||
})({
|
||||
canAutoUpdate({ oldParams, newParams }) {
|
||||
@@ -111,43 +116,44 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
|
||||
},
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Assembly Symmetry', async ctx => {
|
||||
await AssemblySymmetryProvider.attach({ runtime: ctx, fetch: plugin.fetch }, a.data)
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(a.data).value
|
||||
await AssemblySymmetryProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(a.data).value;
|
||||
if (!assemblySymmetry || assemblySymmetry.symbol === 'C1') {
|
||||
return StateObject.Null;
|
||||
}
|
||||
const repr = AssemblySymmetryRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => AssemblySymmetryParams)
|
||||
const repr = AssemblySymmetryRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => AssemblySymmetryParams);
|
||||
await repr.createOrUpdate(params, a.data).runInContext(ctx);
|
||||
const { type, kind, symbol } = assemblySymmetry
|
||||
const { type, kind, symbol } = assemblySymmetry;
|
||||
return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: kind, description: `${type} (${symbol})` });
|
||||
});
|
||||
},
|
||||
update({ a, b, newParams }, plugin: PluginContext) {
|
||||
return Task.create('Assembly Symmetry', async ctx => {
|
||||
await AssemblySymmetryProvider.attach({ runtime: ctx, fetch: plugin.fetch }, a.data)
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(a.data).value
|
||||
await AssemblySymmetryProvider.attach({ runtime: ctx, assetManager: plugin.managers.asset }, a.data);
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(a.data).value;
|
||||
if (!assemblySymmetry || assemblySymmetry.symbol === 'C1') {
|
||||
return StateTransformer.UpdateResult.Recreate
|
||||
// this should NOT be StateTransformer.UpdateResult.Null
|
||||
// because that keeps the old object
|
||||
return StateTransformer.UpdateResult.Recreate;
|
||||
}
|
||||
const props = { ...b.data.repr.props, ...newParams }
|
||||
const props = { ...b.data.repr.props, ...newParams };
|
||||
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
|
||||
const { type, kind, symbol } = assemblySymmetry
|
||||
b.label = kind
|
||||
b.description = `${type} (${symbol})`
|
||||
const { type, kind, symbol } = assemblySymmetry;
|
||||
b.label = kind;
|
||||
b.description = `${type} (${symbol})`;
|
||||
return StateTransformer.UpdateResult.Updated;
|
||||
});
|
||||
},
|
||||
isApplicable(a) {
|
||||
return AssemblySymmetry.isApplicable(a.data)
|
||||
return AssemblySymmetry.isApplicable(a.data);
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
const AssemblySymmetryPresetParams = {
|
||||
export const AssemblySymmetryPresetParams = {
|
||||
...StructureRepresentationPresetProvider.CommonParams,
|
||||
symmetryIndex: PD.Numeric(0)
|
||||
}
|
||||
};
|
||||
|
||||
export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
|
||||
id: 'preset-structure-representation-rcsb-assembly-symmetry',
|
||||
@@ -156,7 +162,7 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
|
||||
description: 'Shows Assembly Symmetry axes and cage; colors structure according to assembly symmetry cluster membership. Data calculated with BioJava, obtained via RCSB PDB.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return AssemblySymmetry.isApplicable(a.data)
|
||||
return AssemblySymmetry.isApplicable(a.data);
|
||||
},
|
||||
params: () => AssemblySymmetryPresetParams,
|
||||
async apply(ref, params, plugin) {
|
||||
@@ -164,21 +170,27 @@ export const AssemblySymmetryPreset = StructureRepresentationPresetProvider({
|
||||
const structure = structureCell?.obj?.data;
|
||||
if (!structureCell || !structure) return {};
|
||||
|
||||
await plugin.runTask(Task.create('Assembly Symmetry', async runtime => {
|
||||
await AssemblySymmetryProvider.attach({ fetch: plugin.fetch, runtime }, structure, { symmetryIndex: params.symmetryIndex })
|
||||
}))
|
||||
if (!AssemblySymmetryDataProvider.get(structure).value) {
|
||||
await plugin.runTask(Task.create('Assembly Symmetry', async runtime => {
|
||||
const propCtx = { runtime, assetManager: plugin.managers.asset };
|
||||
await AssemblySymmetryDataProvider.attach(propCtx, structure);
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value;
|
||||
const symmetryIndex = assemblySymmetryData ? AssemblySymmetry.firstNonC1(assemblySymmetryData) : -1;
|
||||
await AssemblySymmetryProvider.attach(propCtx, structure, { symmetryIndex });
|
||||
}));
|
||||
}
|
||||
|
||||
const assemblySymmetry = await tryCreateAssemblySymmetry(plugin, structureCell);
|
||||
const preset = await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: Tag.Cluster as any }, plugin);
|
||||
const globalThemeName = assemblySymmetry.isOk ? Tag.Cluster as any : undefined;
|
||||
const preset = await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName }, plugin);
|
||||
|
||||
return { components: preset.components, representations: { ...preset.representations, assemblySymmetry } };
|
||||
}
|
||||
});
|
||||
|
||||
async function tryCreateAssemblySymmetry(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, params?: StateTransformer.Params<AssemblySymmetry3D>, initialState?: Partial<StateTransform.State>) {
|
||||
export function tryCreateAssemblySymmetry(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, params?: StateTransformer.Params<AssemblySymmetry3D>, initialState?: Partial<StateTransform.State>) {
|
||||
const state = plugin.state.data;
|
||||
const assemblySymmetry = state.build().to(structure)
|
||||
.apply(AssemblySymmetry3D, params, { state: initialState });
|
||||
await plugin.updateDataState(assemblySymmetry, { revertOnError: true });
|
||||
return assemblySymmetry.selector
|
||||
.applyOrUpdateTagged(AssemblySymmetry.Tag.Representation, AssemblySymmetry3D, params, { state: initialState });
|
||||
return assemblySymmetry.commit({ revertOnError: true });
|
||||
}
|
||||
@@ -6,76 +6,76 @@
|
||||
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry } from '../assembly-symmetry';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetry } from './prop';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { Unit, StructureElement, StructureProperties } from '../../../mol-model/structure';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { ScaleLegend, TableLegend } from '../../../mol-util/legend';
|
||||
import { getPalette, getPaletteParams } from '../../../mol-util/color/palette';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC)
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
|
||||
function getAsymId(unit: Unit): StructureElement.Property<string> {
|
||||
switch (unit.kind) {
|
||||
case Unit.Kind.Atomic:
|
||||
return StructureProperties.chain.label_asym_id
|
||||
return StructureProperties.chain.label_asym_id;
|
||||
case Unit.Kind.Spheres:
|
||||
case Unit.Kind.Gaussians:
|
||||
return StructureProperties.coarse.asym_id
|
||||
return StructureProperties.coarse.asym_id;
|
||||
}
|
||||
}
|
||||
|
||||
function clusterMemberKey(asymId: string, operList: string[]) {
|
||||
return `${asymId}-${operList.join('|')}`
|
||||
return `${asymId}-${operList.join('|')}`;
|
||||
}
|
||||
|
||||
export const AssemblySymmetryClusterColorThemeParams = {
|
||||
...getPaletteParams({ colorList: 'red-yellow-blue' }),
|
||||
}
|
||||
};
|
||||
export type AssemblySymmetryClusterColorThemeParams = typeof AssemblySymmetryClusterColorThemeParams
|
||||
export function getAssemblySymmetryClusterColorThemeParams(ctx: ThemeDataContext) {
|
||||
const params = PD.clone(AssemblySymmetryClusterColorThemeParams)
|
||||
return params
|
||||
const params = PD.clone(AssemblySymmetryClusterColorThemeParams);
|
||||
return params;
|
||||
}
|
||||
|
||||
export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props: PD.Values<AssemblySymmetryClusterColorThemeParams>): ColorTheme<AssemblySymmetryClusterColorThemeParams> {
|
||||
let color: LocationColor = () => DefaultColor
|
||||
let legend: ScaleLegend | TableLegend | undefined
|
||||
let color: LocationColor = () => DefaultColor;
|
||||
let legend: ScaleLegend | TableLegend | undefined;
|
||||
|
||||
const assemblySymmetry = ctx.structure && AssemblySymmetryProvider.get(ctx.structure)
|
||||
const contextHash = assemblySymmetry?.version
|
||||
const assemblySymmetry = ctx.structure && AssemblySymmetryProvider.get(ctx.structure);
|
||||
const contextHash = assemblySymmetry?.version;
|
||||
|
||||
const clusters = assemblySymmetry?.value?.clusters
|
||||
const clusters = assemblySymmetry?.value?.clusters;
|
||||
|
||||
if (clusters?.length && ctx.structure) {
|
||||
const clusterByMember = new Map<string, number>()
|
||||
const clusterByMember = new Map<string, number>();
|
||||
for (let i = 0, il = clusters.length; i < il; ++i) {
|
||||
const { members } = clusters[i]!
|
||||
const { members } = clusters[i]!;
|
||||
for (let j = 0, jl = members.length; j < jl; ++j) {
|
||||
const asymId = members[j]!.asym_id
|
||||
const operList = [...members[j]!.pdbx_struct_oper_list_ids || []] as string[]
|
||||
clusterByMember.set(clusterMemberKey(asymId, operList), i)
|
||||
const asymId = members[j]!.asym_id;
|
||||
const operList = [...members[j]!.pdbx_struct_oper_list_ids || []] as string[];
|
||||
clusterByMember.set(clusterMemberKey(asymId, operList), i);
|
||||
if (operList.length === 0) {
|
||||
operList.push('1') // TODO hack assuming '1' is the id of the identity operator
|
||||
clusterByMember.set(clusterMemberKey(asymId, operList), i)
|
||||
operList.push('1'); // TODO hack assuming '1' is the id of the identity operator
|
||||
clusterByMember.set(clusterMemberKey(asymId, operList), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
const palette = getPalette(clusters.length, props)
|
||||
legend = palette.legend
|
||||
const palette = getPalette(clusters.length, props);
|
||||
legend = palette.legend;
|
||||
|
||||
const _emptyList: any[] = [];
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location)) {
|
||||
const { assembly } = location.unit.conformation.operator
|
||||
const asymId = getAsymId(location.unit)(location)
|
||||
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly?.operList || _emptyList))
|
||||
return cluster !== undefined ? palette.color(cluster) : DefaultColor
|
||||
const { assembly } = location.unit.conformation.operator;
|
||||
const asymId = getAsymId(location.unit)(location);
|
||||
const cluster = clusterByMember.get(clusterMemberKey(asymId, assembly?.operList || _emptyList));
|
||||
return cluster !== undefined ? palette.color(cluster) : DefaultColor;
|
||||
}
|
||||
return DefaultColor
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -86,7 +86,7 @@ export function AssemblySymmetryClusterColorTheme(ctx: ThemeDataContext, props:
|
||||
contextHash,
|
||||
description: 'Assigns chain colors according to assembly symmetry cluster membership calculated with BioJava and obtained via RCSB PDB.',
|
||||
legend
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<AssemblySymmetryClusterColorThemeParams, AssemblySymmetry.Tag.Cluster> = {
|
||||
@@ -101,4 +101,4 @@ export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<Asse
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? AssemblySymmetryProvider.attach(ctx, data.structure, void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.customPropertyDescriptors.reference(AssemblySymmetryProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -4,18 +4,21 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { AssemblySymmetryQuery, AssemblySymmetryQueryVariables } from './graphql/types';
|
||||
import query from './graphql/symmetry.gql';
|
||||
import { AssemblySymmetryQuery, AssemblySymmetryQueryVariables } from '../graphql/types';
|
||||
import query from '../graphql/symmetry.gql';
|
||||
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition'
|
||||
import { CustomPropertyDescriptor, Structure, Model } from '../../mol-model/structure';
|
||||
import { Database as _Database, Column } from '../../mol-data/db'
|
||||
import { GraphQLClient } from '../../mol-util/graphql-client';
|
||||
import { CustomProperty } from '../common/custom-property';
|
||||
import { NonNullableArray } from '../../mol-util/type-helpers';
|
||||
import { CustomStructureProperty } from '../common/custom-structure-property';
|
||||
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { ReadonlyVec3 } from '../../mol-math/linear-algebra/3d/vec3';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { CustomPropertyDescriptor, Structure, Model, StructureSelection, QueryContext } from '../../../mol-model/structure';
|
||||
import { Database as _Database, Column } from '../../../mol-data/db';
|
||||
import { GraphQLClient } from '../../../mol-util/graphql-client';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { NonNullableArray } from '../../../mol-util/type-helpers';
|
||||
import { CustomStructureProperty } from '../../../mol-model-props/common/custom-structure-property';
|
||||
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
import { ReadonlyVec3 } from '../../../mol-math/linear-algebra/3d/vec3';
|
||||
import { SetUtils } from '../../../mol-util/set';
|
||||
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
|
||||
import { compile } from '../../../mol-script/runtime/query/compiler';
|
||||
|
||||
const BiologicalAssemblyNames = new Set([
|
||||
'author_and_software_defined_assembly',
|
||||
@@ -24,18 +27,18 @@ const BiologicalAssemblyNames = new Set([
|
||||
'complete point assembly',
|
||||
'representative helical assembly',
|
||||
'software_defined_assembly'
|
||||
])
|
||||
]);
|
||||
|
||||
export function isBiologicalAssembly(structure: Structure): boolean {
|
||||
if (!MmcifFormat.is(structure.models[0].sourceData)) return false
|
||||
const mmcif = structure.models[0].sourceData.data.db
|
||||
if (!mmcif.pdbx_struct_assembly.details.isDefined) return false
|
||||
const id = structure.units[0].conformation.operator.assembly?.id || ''
|
||||
if (id === '' || id === 'deposited') return true
|
||||
const indices = Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id)
|
||||
if (indices.length !== 1) return false
|
||||
const details = mmcif.pdbx_struct_assembly.details.value(indices[0])
|
||||
return BiologicalAssemblyNames.has(details)
|
||||
if (!MmcifFormat.is(structure.models[0].sourceData)) return false;
|
||||
const mmcif = structure.models[0].sourceData.data.db;
|
||||
if (!mmcif.pdbx_struct_assembly.details.isDefined) return false;
|
||||
const id = structure.units[0].conformation.operator.assembly?.id || '';
|
||||
if (id === '' || id === 'deposited') return true;
|
||||
const indices = Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id);
|
||||
if (indices.length !== 1) return false;
|
||||
const details = mmcif.pdbx_struct_assembly.details.value(indices[0]);
|
||||
return BiologicalAssemblyNames.has(details);
|
||||
}
|
||||
|
||||
export namespace AssemblySymmetry {
|
||||
@@ -44,65 +47,103 @@ export namespace AssemblySymmetry {
|
||||
Representation = 'rcsb-assembly-symmetry-3d'
|
||||
}
|
||||
|
||||
export const DefaultServerUrl = 'https://data-beta.rcsb.org/graphql'
|
||||
export const DefaultServerUrl = 'https://data.rcsb.org/graphql';
|
||||
|
||||
export function isApplicable(structure?: Structure): boolean {
|
||||
return (
|
||||
!!structure && structure.models.length === 1 &&
|
||||
Model.isFromPdbArchive(structure.models[0]) &&
|
||||
isBiologicalAssembly(structure)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<AssemblySymmetryDataValue> {
|
||||
if (!isApplicable(structure)) return []
|
||||
export async function fetch(ctx: CustomProperty.Context, structure: Structure, props: AssemblySymmetryDataProps): Promise<CustomProperty.Data<AssemblySymmetryDataValue>> {
|
||||
if (!isApplicable(structure)) return { value: [] };
|
||||
|
||||
const client = new GraphQLClient(props.serverUrl, ctx.fetch)
|
||||
const client = new GraphQLClient(props.serverUrl, ctx.assetManager);
|
||||
const variables: AssemblySymmetryQueryVariables = {
|
||||
assembly_id: structure.units[0].conformation.operator.assembly?.id || 'deposited',
|
||||
entry_id: structure.units[0].model.entryId
|
||||
}
|
||||
const result = await client.request<AssemblySymmetryQuery>(ctx.runtime, query, variables)
|
||||
};
|
||||
const result = await client.request(ctx.runtime, query, variables);
|
||||
let value: AssemblySymmetryDataValue = [];
|
||||
|
||||
if (!result.assembly?.rcsb_struct_symmetry) {
|
||||
console.error('expected `rcsb_struct_symmetry` field')
|
||||
return []
|
||||
if (!result.data.assembly?.rcsb_struct_symmetry) {
|
||||
console.error('expected `rcsb_struct_symmetry` field');
|
||||
} else {
|
||||
value = result.data.assembly.rcsb_struct_symmetry as AssemblySymmetryDataValue;
|
||||
}
|
||||
return result.assembly.rcsb_struct_symmetry as AssemblySymmetryDataValue
|
||||
return { value, assets: [result] };
|
||||
}
|
||||
|
||||
/** Returns the index of the first non C1 symmetry or -1 */
|
||||
export function firstNonC1(assemblySymmetryData: AssemblySymmetryDataValue) {
|
||||
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
|
||||
if (assemblySymmetryData[i].symbol !== 'C1') return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
export type RotationAxes = ReadonlyArray<{ order: number, start: ReadonlyVec3, end: ReadonlyVec3 }>
|
||||
export function isRotationAxes(x: AssemblySymmetryValue['rotation_axes']): x is RotationAxes {
|
||||
return !!x && x.length > 0
|
||||
return !!x && x.length > 0;
|
||||
}
|
||||
|
||||
export function getAsymIds(assemblySymmetry: AssemblySymmetryValue) {
|
||||
const asymIds = new Set<string>();
|
||||
for (const c of assemblySymmetry.clusters) {
|
||||
if (!c?.members) continue;
|
||||
for (const m of c.members) {
|
||||
if (m?.asym_id) asymIds.add(m.asym_id);
|
||||
}
|
||||
}
|
||||
return SetUtils.toArray(asymIds);
|
||||
}
|
||||
|
||||
function getAsymIdsStructure(structure: Structure, asymIds: string[]) {
|
||||
const query = MS.struct.modifier.union([
|
||||
MS.struct.generator.atomGroups({
|
||||
'chain-test': MS.core.set.has([MS.set(...asymIds), MS.ammp('label_asym_id')])
|
||||
})
|
||||
]);
|
||||
const compiled = compile<StructureSelection>(query);
|
||||
const result = compiled(new QueryContext(structure));
|
||||
return StructureSelection.unionStructure(result);
|
||||
}
|
||||
|
||||
/** Returns structure limited to all cluster member chains */
|
||||
export function getStructure(structure: Structure, assemblySymmetry: AssemblySymmetryValue) {
|
||||
const asymIds = AssemblySymmetry.getAsymIds(assemblySymmetry);
|
||||
return asymIds.length > 0 ? getAsymIdsStructure(structure, asymIds) : structure;
|
||||
}
|
||||
}
|
||||
|
||||
export function getSymmetrySelectParam(structure?: Structure) {
|
||||
const param = PD.Select<number>(0, [[0, 'First Symmetry']])
|
||||
const param = PD.Select<number>(0, [[0, 'First Symmetry']]);
|
||||
if (structure) {
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(structure).value;
|
||||
if (assemblySymmetryData) {
|
||||
const options: [number, string][] = []
|
||||
const options: [number, string][] = [];
|
||||
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
|
||||
const { symbol, kind } = assemblySymmetryData[i]
|
||||
const { symbol, kind } = assemblySymmetryData[i];
|
||||
if (symbol !== 'C1') {
|
||||
options.push([ i, `${i + 1}: ${symbol} ${kind}` ])
|
||||
options.push([ i, `${i + 1}: ${symbol} ${kind}` ]);
|
||||
}
|
||||
}
|
||||
if (options.length) {
|
||||
param.options = options
|
||||
param.defaultValue = options[0][0]
|
||||
param.options = options;
|
||||
param.defaultValue = options[0][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
return param
|
||||
return param;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export const AssemblySymmetryDataParams = {
|
||||
serverUrl: PD.Text(AssemblySymmetry.DefaultServerUrl, { description: 'GraphQL endpoint URL' })
|
||||
}
|
||||
};
|
||||
export type AssemblySymmetryDataParams = typeof AssemblySymmetryDataParams
|
||||
export type AssemblySymmetryDataProps = PD.Values<AssemblySymmetryDataParams>
|
||||
|
||||
@@ -119,10 +160,10 @@ export const AssemblySymmetryDataProvider: CustomStructureProperty.Provider<Asse
|
||||
getParams: (data: Structure) => AssemblySymmetryDataParams,
|
||||
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryDataProps>) => {
|
||||
const p = { ...PD.getDefaultValues(AssemblySymmetryDataParams), ...props }
|
||||
return await AssemblySymmetry.fetch(ctx, data, p)
|
||||
const p = { ...PD.getDefaultValues(AssemblySymmetryDataParams), ...props };
|
||||
return await AssemblySymmetry.fetch(ctx, data, p);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
@@ -130,10 +171,10 @@ function getAssemblySymmetryParams(data?: Structure) {
|
||||
return {
|
||||
... AssemblySymmetryDataParams,
|
||||
symmetryIndex: getSymmetrySelectParam(data)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const AssemblySymmetryParams = getAssemblySymmetryParams()
|
||||
export const AssemblySymmetryParams = getAssemblySymmetryParams();
|
||||
export type AssemblySymmetryParams = typeof AssemblySymmetryParams
|
||||
export type AssemblySymmetryProps = PD.Values<AssemblySymmetryParams>
|
||||
|
||||
@@ -150,11 +191,11 @@ export const AssemblySymmetryProvider: CustomStructureProperty.Provider<Assembly
|
||||
getParams: getAssemblySymmetryParams,
|
||||
isApplicable: (data: Structure) => AssemblySymmetry.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure, props: Partial<AssemblySymmetryProps>) => {
|
||||
const p = { ...PD.getDefaultValues(getAssemblySymmetryParams(data)), ...props }
|
||||
await AssemblySymmetryDataProvider.attach(ctx, data, p)
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(data).value
|
||||
const assemblySymmetry = assemblySymmetryData?.[p.symmetryIndex]
|
||||
if (!assemblySymmetry) new Error(`No assembly symmetry found for index ${p.symmetryIndex}`)
|
||||
return assemblySymmetry
|
||||
const p = { ...PD.getDefaultValues(getAssemblySymmetryParams(data)), ...props };
|
||||
await AssemblySymmetryDataProvider.attach(ctx, data, p);
|
||||
const assemblySymmetryData = AssemblySymmetryDataProvider.get(data).value;
|
||||
const assemblySymmetry = assemblySymmetryData?.[p.symmetryIndex];
|
||||
if (!assemblySymmetry) new Error(`No assembly symmetry found for index ${p.symmetryIndex}`);
|
||||
return { value: assemblySymmetry };
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -5,9 +5,9 @@
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { AssemblySymmetryValue, AssemblySymmetryProvider, AssemblySymmetry } from '../assembly-symmetry';
|
||||
import { AssemblySymmetryValue, AssemblySymmetryProvider, AssemblySymmetry } from './prop';
|
||||
import { MeshBuilder } from '../../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
|
||||
import { Vec3, Mat4, Mat3 } from '../../../mol-math/linear-algebra';
|
||||
import { addCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
|
||||
import { Mesh } from '../../../mol-geo/geometry/mesh/mesh';
|
||||
import { RuntimeContext } from '../../../mol-task';
|
||||
@@ -32,26 +32,27 @@ import { Mutable } from '../../../mol-util/type-helpers';
|
||||
import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
|
||||
import { Structure } from '../../../mol-model/structure';
|
||||
import { isInteger } from '../../../mol-util/number';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
|
||||
const OrderColors = ColorMap({
|
||||
'2': ColorNames.deepskyblue,
|
||||
'3': ColorNames.lime,
|
||||
'N': ColorNames.red,
|
||||
})
|
||||
});
|
||||
const OrderColorsLegend = TableLegend(Object.keys(OrderColors).map(name => {
|
||||
return [name, (OrderColors as any)[name] as Color] as [string, Color]
|
||||
}))
|
||||
return [name, (OrderColors as any)[name] as Color] as [string, Color];
|
||||
}));
|
||||
|
||||
function axesColorHelp(value: { name: string, params: {} }) {
|
||||
return value.name === 'byOrder'
|
||||
? { description: 'Color axes by their order', legend: OrderColorsLegend }
|
||||
: {}
|
||||
: {};
|
||||
}
|
||||
|
||||
const SharedParams = {
|
||||
...Mesh.Params,
|
||||
scale: PD.Numeric(2, { min: 0.1, max: 5, step: 0.1 }),
|
||||
}
|
||||
};
|
||||
|
||||
const AxesParams = {
|
||||
...SharedParams,
|
||||
@@ -61,121 +62,122 @@ const AxesParams = {
|
||||
colorValue: PD.Color(ColorNames.orange),
|
||||
}, { isFlat: true })
|
||||
}, { help: axesColorHelp }),
|
||||
}
|
||||
};
|
||||
type AxesParams = typeof AxesParams
|
||||
|
||||
const CageParams = {
|
||||
...SharedParams,
|
||||
cageColor: PD.Color(ColorNames.orange),
|
||||
}
|
||||
};
|
||||
type CageParams = typeof CageParams
|
||||
|
||||
const AssemblySymmetryVisuals = {
|
||||
'axes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, AxesParams>) => ShapeRepresentation(getAxesShape, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
|
||||
// cage should come before 'axes' so that the representative loci uses the cage shape
|
||||
'cage': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, CageParams>) => ShapeRepresentation(getCageShape, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
|
||||
}
|
||||
'axes': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, AxesParams>) => ShapeRepresentation(getAxesShape, Mesh.Utils, { modifyState: s => ({ ...s, markerActions: MarkerActions.Highlighting }) }),
|
||||
};
|
||||
|
||||
export const AssemblySymmetryParams = {
|
||||
...AxesParams,
|
||||
...CageParams,
|
||||
visuals: PD.MultiSelect(['axes', 'cage'], PD.objectToOptions(AssemblySymmetryVisuals)),
|
||||
}
|
||||
};
|
||||
export type AssemblySymmetryParams = typeof AssemblySymmetryParams
|
||||
export type AssemblySymmetryProps = PD.Values<AssemblySymmetryParams>
|
||||
|
||||
//
|
||||
|
||||
function getAssemblyName(s: Structure) {
|
||||
const id = s.units[0].conformation.operator.assembly?.id || ''
|
||||
return isInteger(id) ? `Assembly ${id}` : id
|
||||
const id = s.units[0].conformation.operator.assembly?.id || '';
|
||||
return isInteger(id) ? `Assembly ${id}` : id;
|
||||
}
|
||||
|
||||
const t = Mat4.identity()
|
||||
const tmpV = Vec3()
|
||||
const tmpCenter = Vec3()
|
||||
const tmpScale = Vec3()
|
||||
const t = Mat4.identity();
|
||||
const tmpV = Vec3();
|
||||
const tmpCenter = Vec3();
|
||||
const tmpScale = Vec3();
|
||||
|
||||
const getOrderPrimitive = memoize1((order: number): Primitive | undefined => {
|
||||
if (order < 2) {
|
||||
return Prism(polygon(48, false))
|
||||
return Prism(polygon(48, false));
|
||||
} else if (order === 2) {
|
||||
const lens = Prism(polygon(48, false))
|
||||
const m = Mat4.identity()
|
||||
Mat4.scale(m, m, Vec3.create(1, 0.35, 1))
|
||||
transformPrimitive(lens, m)
|
||||
return lens
|
||||
const lens = Prism(polygon(48, false));
|
||||
const m = Mat4.identity();
|
||||
Mat4.scale(m, m, Vec3.create(1, 0.35, 1));
|
||||
transformPrimitive(lens, m);
|
||||
return lens;
|
||||
} else if (order === 3) {
|
||||
return Wedge()
|
||||
return Wedge();
|
||||
} else {
|
||||
return Prism(polygon(order, false))
|
||||
return Prism(polygon(order, false));
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function getAxesMesh(data: AssemblySymmetryValue, props: PD.Values<AxesParams>, mesh?: Mesh) {
|
||||
const { scale } = props
|
||||
const { scale } = props;
|
||||
|
||||
const { rotation_axes } = data
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
|
||||
const { rotation_axes } = data;
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh);
|
||||
|
||||
const { start, end } = rotation_axes[0]
|
||||
const radius = (Vec3.distance(start, end) / 500) * scale
|
||||
const { start, end } = rotation_axes[0];
|
||||
const radius = (Vec3.distance(start, end) / 500) * scale;
|
||||
|
||||
Vec3.set(tmpScale, radius * 7, radius * 7, radius * 0.4)
|
||||
Vec3.set(tmpScale, radius * 7, radius * 7, radius * 0.4);
|
||||
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius }
|
||||
const builderState = MeshBuilder.createState(256, 128, mesh)
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius };
|
||||
const builderState = MeshBuilder.createState(256, 128, mesh);
|
||||
|
||||
builderState.currentGroup = 0
|
||||
Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5)
|
||||
builderState.currentGroup = 0;
|
||||
Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5);
|
||||
|
||||
for (let i = 0, il = rotation_axes.length; i < il; ++i) {
|
||||
const { order, start, end } = rotation_axes[i]
|
||||
builderState.currentGroup = i
|
||||
addCylinder(builderState, start, end, 1, cylinderProps)
|
||||
const { order, start, end } = rotation_axes[i];
|
||||
builderState.currentGroup = i;
|
||||
addCylinder(builderState, start, end, 1, cylinderProps);
|
||||
|
||||
const primitive = getOrderPrimitive(order)
|
||||
const primitive = getOrderPrimitive(order);
|
||||
if (primitive) {
|
||||
Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5)
|
||||
Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5);
|
||||
if (Vec3.dot(Vec3.unitY, Vec3.sub(tmpV, start, tmpCenter)) === 0) {
|
||||
Mat4.targetTo(t, start, tmpCenter, Vec3.unitY)
|
||||
Mat4.targetTo(t, start, tmpCenter, Vec3.unitY);
|
||||
} else {
|
||||
Mat4.targetTo(t, start, tmpCenter, Vec3.unitX)
|
||||
Mat4.targetTo(t, start, tmpCenter, Vec3.unitX);
|
||||
}
|
||||
Mat4.scale(t, t, tmpScale)
|
||||
Mat4.scale(t, t, tmpScale);
|
||||
|
||||
Mat4.setTranslation(t, start)
|
||||
MeshBuilder.addPrimitive(builderState, t, primitive)
|
||||
Mat4.setTranslation(t, end)
|
||||
MeshBuilder.addPrimitive(builderState, t, primitive)
|
||||
Mat4.setTranslation(t, start);
|
||||
MeshBuilder.addPrimitive(builderState, t, primitive);
|
||||
Mat4.setTranslation(t, end);
|
||||
MeshBuilder.addPrimitive(builderState, t, primitive);
|
||||
}
|
||||
}
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
return MeshBuilder.getMesh(builderState);
|
||||
}
|
||||
|
||||
function getAxesShape(ctx: RuntimeContext, data: Structure, props: AssemblySymmetryProps, shape?: Shape<Mesh>) {
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!;
|
||||
const geo = getAxesMesh(assemblySymmetry, props, shape && shape.geometry);
|
||||
const getColor = (groupId: number) => {
|
||||
if (props.axesColor.name === 'byOrder') {
|
||||
const { rotation_axes } = assemblySymmetry
|
||||
const order = rotation_axes![groupId]?.order
|
||||
if (order === 2) return OrderColors[2]
|
||||
else if (order === 3) return OrderColors[3]
|
||||
else return OrderColors.N
|
||||
const { rotation_axes } = assemblySymmetry;
|
||||
const order = rotation_axes![groupId]?.order;
|
||||
if (order === 2) return OrderColors[2];
|
||||
else if (order === 3) return OrderColors[3];
|
||||
else return OrderColors.N;
|
||||
} else {
|
||||
return props.axesColor.params.colorValue
|
||||
return props.axesColor.params.colorValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
const getLabel = (groupId: number) => {
|
||||
const { type, symbol, kind, rotation_axes } = assemblySymmetry
|
||||
const order = rotation_axes![groupId]?.order
|
||||
const { type, symbol, kind, rotation_axes } = assemblySymmetry;
|
||||
const order = rotation_axes![groupId]?.order;
|
||||
return [
|
||||
`<small>${data.model.entryId}</small>`,
|
||||
`<small>${getAssemblyName(data)}</small>`,
|
||||
`Axis ${groupId + 1} with Order ${order} of ${type} ${kind} (${symbol})`
|
||||
].join(' | ')
|
||||
}
|
||||
return Shape.create('Axes', data, geo, getColor, () => 1, getLabel)
|
||||
].join(' | ');
|
||||
};
|
||||
return Shape.create('Axes', data, geo, getColor, () => 1, getLabel);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -183,144 +185,178 @@ function getAxesShape(ctx: RuntimeContext, data: Structure, props: AssemblySymme
|
||||
const getSymbolCage = memoize1((symbol: string): Cage | undefined => {
|
||||
if (symbol.startsWith('D') || symbol.startsWith('C')) {
|
||||
// z axis is prism axis, x/y axes cut through edge midpoints
|
||||
const fold = parseInt(symbol.substr(1))
|
||||
const fold = parseInt(symbol.substr(1));
|
||||
if (fold === 2) {
|
||||
return PrismCage(polygon(4, false))
|
||||
return PrismCage(polygon(4, false));
|
||||
} else if (fold === 3) {
|
||||
return WedgeCage()
|
||||
return WedgeCage();
|
||||
} else if (fold > 3) {
|
||||
return PrismCage(polygon(fold, false))
|
||||
return PrismCage(polygon(fold, false));
|
||||
}
|
||||
} else if (symbol === 'O') {
|
||||
// x/y/z axes cut through order 4 vertices
|
||||
return OctahedronCage()
|
||||
return OctahedronCage();
|
||||
} else if (symbol === 'I') {
|
||||
// z axis cut through order 5 vertex
|
||||
// x axis cut through edge midpoint
|
||||
const cage = IcosahedronCage()
|
||||
const m = Mat4.identity()
|
||||
Mat4.rotate(m, m, degToRad(31.7), Vec3.unitX)
|
||||
return transformCage(cloneCage(cage), m)
|
||||
const cage = IcosahedronCage();
|
||||
const m = Mat4.identity();
|
||||
Mat4.rotate(m, m, degToRad(31.7), Vec3.unitX);
|
||||
return transformCage(cloneCage(cage), m);
|
||||
} else if (symbol === 'T') {
|
||||
// x/y/z axes cut through edge midpoints
|
||||
return TetrahedronCage()
|
||||
return TetrahedronCage();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function getSymbolScale(symbol: string) {
|
||||
if (symbol.startsWith('D') || symbol.startsWith('C')) {
|
||||
return 0.75
|
||||
return 0.75;
|
||||
} else if (symbol === 'O') {
|
||||
return 1.2
|
||||
return 1.2;
|
||||
} else if (symbol === 'I') {
|
||||
return 0.25
|
||||
return 0.25;
|
||||
} else if (symbol === 'T') {
|
||||
return 0.8
|
||||
return 0.8;
|
||||
}
|
||||
return 1
|
||||
return 1;
|
||||
}
|
||||
|
||||
function setSymbolTransform(t: Mat4, symbol: string, axes: AssemblySymmetry.RotationAxes, size: number, structure: Structure) {
|
||||
const eye = Vec3()
|
||||
const target = Vec3()
|
||||
const up = Vec3()
|
||||
let pair: Mutable<AssemblySymmetry.RotationAxes> | undefined = undefined
|
||||
const eye = Vec3();
|
||||
const target = Vec3();
|
||||
const up = Vec3();
|
||||
let pair: Mutable<AssemblySymmetry.RotationAxes> | undefined = undefined;
|
||||
|
||||
if (symbol.startsWith('C')) {
|
||||
pair = [axes[0]]
|
||||
pair = [axes[0]];
|
||||
} else if (symbol.startsWith('D')) {
|
||||
const fold = parseInt(symbol.substr(1))
|
||||
const fold = parseInt(symbol.substr(1));
|
||||
if (fold === 2) {
|
||||
pair = axes.filter(a => a.order === 2)
|
||||
pair = axes.filter(a => a.order === 2);
|
||||
} else if (fold >= 3) {
|
||||
const aN = axes.filter(a => a.order === fold)[0]
|
||||
const a2 = axes.filter(a => a.order === 2)[0]
|
||||
pair = [aN, a2]
|
||||
const aN = axes.filter(a => a.order === fold)[0];
|
||||
const a2 = axes.filter(a => a.order === 2)[0];
|
||||
pair = [aN, a2];
|
||||
}
|
||||
} else if (symbol === 'O') {
|
||||
pair = axes.filter(a => a.order === 4)
|
||||
pair = axes.filter(a => a.order === 4);
|
||||
} else if (symbol === 'I') {
|
||||
const a5 = axes.filter(a => a.order === 5)[0]
|
||||
const a5dir = Vec3.sub(Vec3(), a5.end, a5.start)
|
||||
pair = [a5]
|
||||
const a5 = axes.filter(a => a.order === 5)[0];
|
||||
const a5dir = Vec3.sub(Vec3(), a5.end, a5.start);
|
||||
pair = [a5];
|
||||
for (const a of axes.filter(a => a.order === 3)) {
|
||||
let d = radToDeg(Vec3.angle(Vec3.sub(up, a.end, a.start), a5dir))
|
||||
if (equalEps(d, 100.81, 0.1)) {
|
||||
pair[1] = a
|
||||
break
|
||||
let d = radToDeg(Vec3.angle(Vec3.sub(up, a.end, a.start), a5dir));
|
||||
if (equalEps(d, 100.81, 0.1) || equalEps(d, 79.19, 0.1)) {
|
||||
pair[1] = a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (symbol === 'T') {
|
||||
pair = axes.filter(a => a.order === 2)
|
||||
pair = axes.filter(a => a.order === 2);
|
||||
}
|
||||
|
||||
Mat4.setIdentity(t)
|
||||
Mat4.setIdentity(t);
|
||||
if (pair) {
|
||||
const [aA, aB] = pair
|
||||
Vec3.scale(eye, Vec3.add(eye, aA.end, aA.start), 0.5)
|
||||
Vec3.copy(target, aA.end)
|
||||
const [aA, aB] = pair;
|
||||
Vec3.scale(eye, Vec3.add(eye, aA.end, aA.start), 0.5);
|
||||
Vec3.copy(target, aA.end);
|
||||
if (aB) {
|
||||
Vec3.sub(up, aB.end, aB.start)
|
||||
Mat4.targetTo(t, eye, target, up)
|
||||
Mat4.scaleUniformly(t, t, size * getSymbolScale(symbol))
|
||||
Vec3.sub(up, aB.end, aB.start);
|
||||
const d = Vec3.dot(eye, up);
|
||||
if (d < 0) Vec3.negate(up, up);
|
||||
Mat4.targetTo(t, eye, target, up);
|
||||
Mat4.scaleUniformly(t, t, size * getSymbolScale(symbol));
|
||||
} else {
|
||||
if (Vec3.dot(Vec3.unitY, Vec3.sub(tmpV, aA.end, aA.start)) === 0) {
|
||||
Vec3.copy(up, Vec3.unitY)
|
||||
Vec3.copy(up, Vec3.unitY);
|
||||
} else {
|
||||
Vec3.copy(up, Vec3.unitX)
|
||||
Vec3.copy(up, Vec3.unitX);
|
||||
}
|
||||
const sizeXY = (structure.lookup3d.boundary.sphere.radius * 2) * 0.8
|
||||
Mat4.targetTo(t, eye, target, up)
|
||||
Mat4.scale(t, t, Vec3.create(sizeXY, sizeXY, size))
|
||||
Mat4.targetTo(t, eye, target, up);
|
||||
|
||||
const { sphere } = structure.lookup3d.boundary;
|
||||
let sizeXY = (sphere.radius * 2) * 0.8; // fallback for missing extrema
|
||||
if (Sphere3D.hasExtrema(sphere)) {
|
||||
const n = Mat3.directionTransform(Mat3(), t);
|
||||
const dirs = unitCircleDirections.map(d => Vec3.transformMat3(Vec3(), d, n));
|
||||
sizeXY = getMaxProjectedDistance(sphere.extrema, dirs, sphere.center);
|
||||
}
|
||||
|
||||
Mat4.scale(t, t, Vec3.create(sizeXY, sizeXY, size * 0.9));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unitCircleDirections = (function() {
|
||||
const dirs: Vec3[] = [];
|
||||
const circle = polygon(12, false, 1);
|
||||
for (let i = 0, il = circle.length; i < il; i += 3) {
|
||||
dirs.push(Vec3.fromArray(Vec3(), circle, i));
|
||||
}
|
||||
return dirs;
|
||||
})();
|
||||
const tmpProj = Vec3();
|
||||
|
||||
function getMaxProjectedDistance(points: Vec3[], directions: Vec3[], center: Vec3) {
|
||||
let maxDist = 0;
|
||||
for (const p of points) {
|
||||
for (const d of directions) {
|
||||
Vec3.projectPointOnVector(tmpProj, p, d, center);
|
||||
const dist = Vec3.distance(tmpProj, center);
|
||||
if (dist > maxDist) maxDist = dist;
|
||||
}
|
||||
}
|
||||
return maxDist;
|
||||
}
|
||||
|
||||
function getCageMesh(data: Structure, props: PD.Values<CageParams>, mesh?: Mesh) {
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!
|
||||
const { scale } = props
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!;
|
||||
const { scale } = props;
|
||||
|
||||
const { rotation_axes, symbol } = assemblySymmetry
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh)
|
||||
const { rotation_axes, symbol } = assemblySymmetry;
|
||||
if (!AssemblySymmetry.isRotationAxes(rotation_axes)) return Mesh.createEmpty(mesh);
|
||||
|
||||
const cage = getSymbolCage(symbol)
|
||||
if (!cage) return Mesh.createEmpty(mesh)
|
||||
const structure = AssemblySymmetry.getStructure(data, assemblySymmetry);
|
||||
|
||||
const { start, end } = rotation_axes[0]
|
||||
const size = Vec3.distance(start, end)
|
||||
const radius = (size / 500) * scale
|
||||
const cage = getSymbolCage(symbol);
|
||||
if (!cage) return Mesh.createEmpty(mesh);
|
||||
|
||||
const builderState = MeshBuilder.createState(256, 128, mesh)
|
||||
builderState.currentGroup = 0
|
||||
setSymbolTransform(t, symbol, rotation_axes, size, data)
|
||||
Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5)
|
||||
Mat4.setTranslation(t, tmpCenter)
|
||||
MeshBuilder.addCage(builderState, t, cage, radius, 1, 8)
|
||||
const { start, end } = rotation_axes[0];
|
||||
const size = Vec3.distance(start, end);
|
||||
const radius = (size / 500) * scale;
|
||||
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
const builderState = MeshBuilder.createState(256, 128, mesh);
|
||||
builderState.currentGroup = 0;
|
||||
setSymbolTransform(t, symbol, rotation_axes, size, structure);
|
||||
Vec3.scale(tmpCenter, Vec3.add(tmpCenter, start, end), 0.5);
|
||||
Mat4.setTranslation(t, tmpCenter);
|
||||
MeshBuilder.addCage(builderState, t, cage, radius, 1, 8);
|
||||
|
||||
return MeshBuilder.getMesh(builderState);
|
||||
}
|
||||
|
||||
function getCageShape(ctx: RuntimeContext, data: Structure, props: AssemblySymmetryProps, shape?: Shape<Mesh>) {
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!
|
||||
const assemblySymmetry = AssemblySymmetryProvider.get(data).value!;
|
||||
const geo = getCageMesh(data, props, shape && shape.geometry);
|
||||
const getColor = (groupId: number) => {
|
||||
return props.cageColor
|
||||
}
|
||||
return props.cageColor;
|
||||
};
|
||||
const getLabel = (groupId: number) => {
|
||||
const { type, symbol, kind } = assemblySymmetry
|
||||
data.model.entryId
|
||||
const { type, symbol, kind } = assemblySymmetry;
|
||||
data.model.entryId;
|
||||
return [
|
||||
`<small>${data.model.entryId}</small>`,
|
||||
`<small>${getAssemblyName(data)}</small>`,
|
||||
`Cage of ${type} ${kind} (${symbol})`
|
||||
].join(' | ')
|
||||
}
|
||||
return Shape.create('Cage', data, geo, getColor, () => 1, getLabel)
|
||||
].join(' | ');
|
||||
};
|
||||
return Shape.create('Cage', data, geo, getColor, () => 1, getLabel);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export type AssemblySymmetryRepresentation = Representation<Structure, AssemblySymmetryParams>
|
||||
export function AssemblySymmetryRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, AssemblySymmetryParams>): AssemblySymmetryRepresentation {
|
||||
return Representation.createMulti('Assembly Symmetry', ctx, getParams, Representation.StateBuilder, AssemblySymmetryVisuals as unknown as Representation.Def<Structure, AssemblySymmetryParams>)
|
||||
return Representation.createMulti('Assembly Symmetry', ctx, getParams, Representation.StateBuilder, AssemblySymmetryVisuals as unknown as Representation.Def<Structure, AssemblySymmetryParams>);
|
||||
}
|
||||
@@ -5,13 +5,19 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { CollapsableState, CollapsableControls } from '../../../../../../mol-plugin-ui/base';
|
||||
import { ApplyActionControl } from '../../../../../../mol-plugin-ui/state/apply-action';
|
||||
import { InitAssemblySymmetry3D, AssemblySymmetry3D } from '../assembly-symmetry';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetryProps, AssemblySymmetryDataProvider } from '../../../../../../mol-model-props/rcsb/assembly-symmetry';
|
||||
import { ParameterControls } from '../../../../../../mol-plugin-ui/controls/parameters';
|
||||
import { ParamDefinition as PD } from '../../../../../../mol-util/param-definition';
|
||||
import { StructureHierarchyManager } from '../../../../../../mol-plugin-state/manager/structure/hierarchy';
|
||||
import { CollapsableState, CollapsableControls } from '../../../mol-plugin-ui/base';
|
||||
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
|
||||
import { InitAssemblySymmetry3D, AssemblySymmetry3D, AssemblySymmetryPreset, tryCreateAssemblySymmetry } from './behavior';
|
||||
import { AssemblySymmetryProvider, AssemblySymmetryProps, AssemblySymmetryDataProvider, AssemblySymmetry } from './prop';
|
||||
import { ParameterControls } from '../../../mol-plugin-ui/controls/parameters';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { StructureHierarchyManager } from '../../../mol-plugin-state/manager/structure/hierarchy';
|
||||
import { StateAction, StateSelection } from '../../../mol-state';
|
||||
import { PluginStateObject } from '../../../mol-plugin-state/objects';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { Task } from '../../../mol-task';
|
||||
import Check from '@material-ui/icons/Check';
|
||||
import Extension from '@material-ui/icons/Extension';
|
||||
|
||||
interface AssemblySymmetryControlState extends CollapsableState {
|
||||
isBusy: boolean
|
||||
@@ -24,7 +30,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
|
||||
isCollapsed: false,
|
||||
isBusy: false,
|
||||
isHidden: true,
|
||||
brand: { name: 'Sym', accent: 'cyan' }
|
||||
brand: { accent: 'cyan', svg: Extension }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -33,9 +39,9 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
|
||||
this.setState({
|
||||
isHidden: !this.canEnable(),
|
||||
description: StructureHierarchyManager.getSelectedStructuresDescription(this.plugin)
|
||||
})
|
||||
});
|
||||
});
|
||||
this.subscribe(this.plugin.events.state.cell.stateUpdated, e => {
|
||||
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
|
||||
if (e.cell.transform.transformer === AssemblySymmetry3D) this.forceUpdate();
|
||||
});
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, v => this.setState({ isBusy: v }));
|
||||
@@ -56,7 +62,7 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
|
||||
renderEnable() {
|
||||
const pivot = this.pivot;
|
||||
if (!pivot.cell.parent) return null;
|
||||
return <ApplyActionControl state={pivot.cell.parent} action={InitAssemblySymmetry3D} initiallyCollapsed={true} nodeRef={pivot.cell.transform.ref} simpleApply={{ header: 'Enable', icon: 'check' }} />;
|
||||
return <ApplyActionControl state={pivot.cell.parent} action={EnableAssemblySymmetry3D} initiallyCollapsed={true} nodeRef={pivot.cell.transform.ref} simpleApply={{ header: 'Enable', icon: Check }} />;
|
||||
}
|
||||
|
||||
renderNoSymmetries() {
|
||||
@@ -66,25 +72,25 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
|
||||
}
|
||||
|
||||
get params() {
|
||||
const structure = this.pivot.cell.obj?.data
|
||||
const params = PD.clone(structure ? AssemblySymmetryProvider.getParams(structure) : AssemblySymmetryProvider.defaultParams)
|
||||
params.serverUrl.isHidden = true
|
||||
params.symmetryIndex.options = [[-1, 'Off'], ...params.symmetryIndex.options]
|
||||
return params
|
||||
const structure = this.pivot.cell.obj?.data;
|
||||
const params = PD.clone(structure ? AssemblySymmetryProvider.getParams(structure) : AssemblySymmetryProvider.defaultParams);
|
||||
params.serverUrl.isHidden = true;
|
||||
params.symmetryIndex.options = [[-1, 'Off'], ...params.symmetryIndex.options];
|
||||
return params;
|
||||
}
|
||||
|
||||
get values() {
|
||||
const structure = this.pivot.cell.obj?.data
|
||||
const structure = this.pivot.cell.obj?.data;
|
||||
if (structure) {
|
||||
return AssemblySymmetryProvider.props(structure)
|
||||
return AssemblySymmetryProvider.props(structure);
|
||||
} else {
|
||||
return { ...PD.getDefaultValues(AssemblySymmetryProvider.defaultParams), symmetryIndex: -1 }
|
||||
return { ...PD.getDefaultValues(AssemblySymmetryProvider.defaultParams), symmetryIndex: -1 };
|
||||
}
|
||||
}
|
||||
|
||||
async updateAssemblySymmetry(values: AssemblySymmetryProps) {
|
||||
const s = this.pivot
|
||||
const currValues = AssemblySymmetryProvider.props(s.cell.obj!.data)
|
||||
const s = this.pivot;
|
||||
const currValues = AssemblySymmetryProvider.props(s.cell.obj!.data);
|
||||
if (PD.areEqual(AssemblySymmetryProvider.defaultParams, currValues, values)) return;
|
||||
|
||||
if (s.properties) {
|
||||
@@ -92,32 +98,43 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
|
||||
b.to(s.properties.cell).update(old => {
|
||||
old.properties[AssemblySymmetryProvider.descriptor.name] = values;
|
||||
});
|
||||
await this.plugin.updateDataState(b);
|
||||
await b.commit();
|
||||
} else {
|
||||
const pd = this.plugin.customStructureProperties.getParams(s.cell.obj?.data);
|
||||
const params = PD.getDefaultValues(pd);
|
||||
params.properties[AssemblySymmetryProvider.descriptor.name] = values;
|
||||
await this.plugin.builders.structure.insertStructureProperties(s.cell, params);
|
||||
}
|
||||
this.forceUpdate()
|
||||
|
||||
for (const components of this.plugin.managers.structure.hierarchy.currentComponentGroups) {
|
||||
if (values.symmetryIndex === -1) {
|
||||
const name = components[0]?.representations[0]?.cell.transform.params?.colorTheme.name;
|
||||
if (name === AssemblySymmetry.Tag.Cluster) {
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: 'default' });
|
||||
}
|
||||
} else {
|
||||
tryCreateAssemblySymmetry(this.plugin, s.cell);
|
||||
await this.plugin.managers.structure.component.updateRepresentationsTheme(components, { color: AssemblySymmetry.Tag.Cluster as any });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paramsOnChange = (options: AssemblySymmetryProps) => {
|
||||
this.updateAssemblySymmetry(options)
|
||||
this.updateAssemblySymmetry(options);
|
||||
}
|
||||
|
||||
get hasAssemblySymmetry3D() {
|
||||
return !!this.pivot.genericRepresentations?.filter(r => r.cell.transform.transformer.id === AssemblySymmetry3D.id)[0]
|
||||
return !this.pivot.cell.parent || !!StateSelection.findTagInSubtree(this.pivot.cell.parent.tree, this.pivot.cell.transform.ref, AssemblySymmetry.Tag.Representation);
|
||||
}
|
||||
|
||||
get enable() {
|
||||
return !this.hasAssemblySymmetry3D && this.values.symmetryIndex !== -1
|
||||
return !this.hasAssemblySymmetry3D && this.values.symmetryIndex !== -1;
|
||||
}
|
||||
|
||||
get noSymmetries() {
|
||||
const structure = this.pivot.cell.obj?.data
|
||||
const data = structure && AssemblySymmetryDataProvider.get(structure).value
|
||||
return data && data.filter(sym => sym.symbol !== 'C1').length === 0
|
||||
const structure = this.pivot.cell.obj?.data;
|
||||
const data = structure && AssemblySymmetryDataProvider.get(structure).value;
|
||||
return data && data.filter(sym => sym.symbol !== 'C1').length === 0;
|
||||
}
|
||||
|
||||
renderParams() {
|
||||
@@ -132,4 +149,10 @@ export class AssemblySymmetryControls extends CollapsableControls<{}, AssemblySy
|
||||
if (this.enable) return this.renderEnable();
|
||||
return this.renderParams();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const EnableAssemblySymmetry3D = StateAction.build({
|
||||
from: PluginStateObject.Molecule.Structure,
|
||||
})(({ a, ref, state }, plugin: PluginContext) => Task.create('Enable Assembly Symmetry', async ctx => {
|
||||
await AssemblySymmetryPreset.apply(ref, Object.create(null), plugin);
|
||||
}));
|
||||
@@ -1,7 +1,7 @@
|
||||
schema: https://data-beta.rcsb.org/graphql
|
||||
documents: './src/mol-model-props/rcsb/graphql/symmetry.gql.ts'
|
||||
schema: https://data.rcsb.org/graphql
|
||||
documents: './src/extensions/rcsb/graphql/symmetry.gql.ts'
|
||||
generates:
|
||||
'./src/mol-model-props/rcsb/graphql/types.ts':
|
||||
'./src/extensions/rcsb/graphql/types.ts':
|
||||
plugins:
|
||||
- add: '/* eslint-disable */'
|
||||
- time
|
||||
@@ -22,4 +22,4 @@ query AssemblySymmetry($assembly_id: String!, $entry_id: String!) {
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`;
|
||||
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable */
|
||||
export type Maybe<T> = T | null;
|
||||
|
||||
// Generated in 2020-03-30T11:30:30-07:00
|
||||
// Generated in 2020-04-14T16:35:00-07:00
|
||||
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
@@ -800,6 +800,8 @@ export type PdbxAuditRevisionCategory = {
|
||||
|
||||
export type PdbxAuditRevisionDetails = {
|
||||
readonly data_content_type: Scalars['String'];
|
||||
readonly description?: Maybe<Scalars['String']>;
|
||||
readonly details?: Maybe<Scalars['String']>;
|
||||
readonly ordinal: Scalars['Int'];
|
||||
readonly provider?: Maybe<Scalars['String']>;
|
||||
readonly revision_ordinal: Scalars['Int'];
|
||||
8
src/extensions/rcsb/index.ts
Normal file
8
src/extensions/rcsb/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
export { RCSBAssemblySymmetry } from './assembly-symmetry/behavior';
|
||||
export { RCSBValidationReport } from './validation-report/behavior';
|
||||
@@ -4,23 +4,23 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../../../mol-util/param-definition'
|
||||
import { PluginBehavior } from '../../../behavior';
|
||||
import { ValidationReport, ValidationReportProvider } from '../../../../../mol-model-props/rcsb/validation-report';
|
||||
import { RandomCoilIndexColorThemeProvider } from '../../../../../mol-model-props/rcsb/themes/random-coil-index';
|
||||
import { GeometryQualityColorThemeProvider } from '../../../../../mol-model-props/rcsb/themes/geometry-quality';
|
||||
import { Loci } from '../../../../../mol-model/loci';
|
||||
import { OrderedSet } from '../../../../../mol-data/int';
|
||||
import { ClashesRepresentationProvider } from '../../../../../mol-model-props/rcsb/representations/validation-report-clashes';
|
||||
import { DensityFitColorThemeProvider } from '../../../../../mol-model-props/rcsb/themes/density-fit';
|
||||
import { cantorPairing } from '../../../../../mol-data/util';
|
||||
import { DefaultQueryRuntimeTable } from '../../../../../mol-script/runtime/query/compiler';
|
||||
import { StructureSelectionQuery, StructureSelectionCategory } from '../../../../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { MolScriptBuilder as MS } from '../../../../../mol-script/language/builder';
|
||||
import { Task } from '../../../../../mol-task';
|
||||
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StateObjectRef } from '../../../../../mol-state';
|
||||
import { Model } from '../../../../../mol-model/structure';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { PluginBehavior } from '../../../mol-plugin/behavior/behavior';
|
||||
import { ValidationReport, ValidationReportProvider } from './prop';
|
||||
import { RandomCoilIndexColorThemeProvider } from './color/random-coil-index';
|
||||
import { GeometryQualityColorThemeProvider } from './color/geometry-quality';
|
||||
import { Loci } from '../../../mol-model/loci';
|
||||
import { OrderedSet } from '../../../mol-data/int';
|
||||
import { ClashesRepresentationProvider } from './representation';
|
||||
import { DensityFitColorThemeProvider } from './color/density-fit';
|
||||
import { cantorPairing } from '../../../mol-data/util';
|
||||
import { DefaultQueryRuntimeTable } from '../../../mol-script/runtime/query/compiler';
|
||||
import { StructureSelectionQuery, StructureSelectionCategory } from '../../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { MolScriptBuilder as MS } from '../../../mol-script/language/builder';
|
||||
import { Task } from '../../../mol-task';
|
||||
import { StructureRepresentationPresetProvider, PresetStructureRepresentations } from '../../../mol-plugin-state/builder/structure/representation-preset';
|
||||
import { StateObjectRef } from '../../../mol-state';
|
||||
import { Model } from '../../../mol-model/structure';
|
||||
|
||||
export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
|
||||
name: 'rcsb-validation-report-prop',
|
||||
@@ -34,12 +34,12 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean,
|
||||
|
||||
private labelProvider = {
|
||||
label: (loci: Loci): string | undefined => {
|
||||
if (!this.params.showTooltip) return
|
||||
if (!this.params.showTooltip) return;
|
||||
return [
|
||||
geometryQualityLabel(loci),
|
||||
densityFitLabel(loci),
|
||||
randomCoilIndexLabel(loci)
|
||||
].filter(l => !!l).join('</br>')
|
||||
].filter(l => !!l).join('</br>');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,20 +50,20 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean,
|
||||
|
||||
this.ctx.managers.lociLabels.addProvider(this.labelProvider);
|
||||
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(DensityFitColorThemeProvider)
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(GeometryQualityColorThemeProvider)
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(RandomCoilIndexColorThemeProvider)
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(DensityFitColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(GeometryQualityColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.add(RandomCoilIndexColorThemeProvider);
|
||||
|
||||
this.ctx.representation.structure.registry.add(ClashesRepresentationProvider)
|
||||
this.ctx.query.structure.registry.add(hasClash)
|
||||
this.ctx.representation.structure.registry.add(ClashesRepresentationProvider);
|
||||
this.ctx.query.structure.registry.add(hasClash);
|
||||
|
||||
this.ctx.builders.structure.representation.registerPreset(ValidationReportGeometryQualityPreset)
|
||||
this.ctx.builders.structure.representation.registerPreset(ValidationReportDensityFitPreset)
|
||||
this.ctx.builders.structure.representation.registerPreset(ValidationReportRandomCoilIndexPreset)
|
||||
this.ctx.builders.structure.representation.registerPreset(ValidationReportGeometryQualityPreset);
|
||||
this.ctx.builders.structure.representation.registerPreset(ValidationReportDensityFitPreset);
|
||||
this.ctx.builders.structure.representation.registerPreset(ValidationReportRandomCoilIndexPreset);
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean, showTooltip: boolean }) {
|
||||
let updated = this.params.autoAttach !== p.autoAttach
|
||||
let updated = this.params.autoAttach !== p.autoAttach;
|
||||
this.params.autoAttach = p.autoAttach;
|
||||
this.params.showTooltip = p.showTooltip;
|
||||
this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
|
||||
@@ -78,16 +78,16 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean,
|
||||
|
||||
this.ctx.managers.lociLabels.removeProvider(this.labelProvider);
|
||||
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(DensityFitColorThemeProvider)
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(GeometryQualityColorThemeProvider)
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(RandomCoilIndexColorThemeProvider)
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(DensityFitColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(GeometryQualityColorThemeProvider);
|
||||
this.ctx.representation.structure.themes.colorThemeRegistry.remove(RandomCoilIndexColorThemeProvider);
|
||||
|
||||
this.ctx.representation.structure.registry.remove(ClashesRepresentationProvider)
|
||||
this.ctx.query.structure.registry.remove(hasClash)
|
||||
this.ctx.representation.structure.registry.remove(ClashesRepresentationProvider);
|
||||
this.ctx.query.structure.registry.remove(hasClash);
|
||||
|
||||
this.ctx.builders.structure.representation.unregisterPreset(ValidationReportGeometryQualityPreset)
|
||||
this.ctx.builders.structure.representation.unregisterPreset(ValidationReportDensityFitPreset)
|
||||
this.ctx.builders.structure.representation.unregisterPreset(ValidationReportRandomCoilIndexPreset)
|
||||
this.ctx.builders.structure.representation.unregisterPreset(ValidationReportGeometryQualityPreset);
|
||||
this.ctx.builders.structure.representation.unregisterPreset(ValidationReportDensityFitPreset);
|
||||
this.ctx.builders.structure.representation.unregisterPreset(ValidationReportRandomCoilIndexPreset);
|
||||
}
|
||||
},
|
||||
params: () => ({
|
||||
@@ -101,78 +101,78 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean,
|
||||
|
||||
function geometryQualityLabel(loci: Loci): string | undefined {
|
||||
if (loci.kind === 'element-loci') {
|
||||
if (loci.elements.length === 0) return
|
||||
if (loci.elements.length === 0) return;
|
||||
|
||||
if (loci.elements.length === 1 && OrderedSet.size(loci.elements[0].indices) === 1) {
|
||||
const { unit, indices } = loci.elements[0]
|
||||
const { unit, indices } = loci.elements[0];
|
||||
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value
|
||||
if (!validationReport) return
|
||||
if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) return
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value;
|
||||
if (!validationReport) return;
|
||||
if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) return;
|
||||
|
||||
const { bondOutliers, angleOutliers } = validationReport
|
||||
const eI = unit.elements[OrderedSet.start(indices)]
|
||||
const issues = new Set<string>()
|
||||
const { bondOutliers, angleOutliers } = validationReport;
|
||||
const eI = unit.elements[OrderedSet.start(indices)];
|
||||
const issues = new Set<string>();
|
||||
|
||||
const bonds = bondOutliers.index.get(eI)
|
||||
if (bonds) bonds.forEach(b => issues.add(bondOutliers.data[b].tag))
|
||||
const bonds = bondOutliers.index.get(eI);
|
||||
if (bonds) bonds.forEach(b => issues.add(bondOutliers.data[b].tag));
|
||||
|
||||
const angles = angleOutliers.index.get(eI)
|
||||
if (angles) angles.forEach(a => issues.add(angleOutliers.data[a].tag))
|
||||
const angles = angleOutliers.index.get(eI);
|
||||
if (angles) angles.forEach(a => issues.add(angleOutliers.data[a].tag));
|
||||
|
||||
if (issues.size === 0) {
|
||||
return `Geometry Quality <small>(1 Atom)</small>: no issues`;
|
||||
}
|
||||
|
||||
const summary: string[] = []
|
||||
issues.forEach(name => summary.push(name))
|
||||
const summary: string[] = [];
|
||||
issues.forEach(name => summary.push(name));
|
||||
return `Geometry Quality <small>(1 Atom)</small>: ${summary.join(', ')}`;
|
||||
}
|
||||
|
||||
let hasValidationReport = false
|
||||
const seen = new Set<number>()
|
||||
const cummulativeIssues = new Map<string, number>()
|
||||
let hasValidationReport = false;
|
||||
const seen = new Set<number>();
|
||||
const cummulativeIssues = new Map<string, number>();
|
||||
|
||||
for (const { indices, unit } of loci.elements) {
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value
|
||||
if (!validationReport) continue
|
||||
if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue
|
||||
hasValidationReport = true
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value;
|
||||
if (!validationReport) continue;
|
||||
if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue;
|
||||
hasValidationReport = true;
|
||||
|
||||
const { geometryIssues } = validationReport
|
||||
const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index
|
||||
const { elements } = unit
|
||||
const { geometryIssues } = validationReport;
|
||||
const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index;
|
||||
const { elements } = unit;
|
||||
|
||||
OrderedSet.forEach(indices, idx => {
|
||||
const eI = elements[idx]
|
||||
const eI = elements[idx];
|
||||
|
||||
const rI = residueIndex[eI]
|
||||
const residueKey = cantorPairing(rI, unit.id)
|
||||
const rI = residueIndex[eI];
|
||||
const residueKey = cantorPairing(rI, unit.id);
|
||||
if (!seen.has(residueKey)) {
|
||||
const issues = geometryIssues.get(rI)
|
||||
const issues = geometryIssues.get(rI);
|
||||
if (issues) {
|
||||
issues.forEach(name => {
|
||||
const count = cummulativeIssues.get(name) || 0
|
||||
cummulativeIssues.set(name, count + 1)
|
||||
})
|
||||
const count = cummulativeIssues.get(name) || 0;
|
||||
cummulativeIssues.set(name, count + 1);
|
||||
});
|
||||
}
|
||||
seen.add(residueKey)
|
||||
seen.add(residueKey);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasValidationReport) return
|
||||
if (!hasValidationReport) return;
|
||||
|
||||
const residueCount = `<small>(${seen.size} ${seen.size > 1 ? 'Residues' : 'Residue'})</small>`
|
||||
const residueCount = `<small>(${seen.size} ${seen.size > 1 ? 'Residues' : 'Residue'})</small>`;
|
||||
|
||||
if (cummulativeIssues.size === 0) {
|
||||
return `Geometry Quality ${residueCount}: no issues`;
|
||||
}
|
||||
|
||||
const summary: string[] = []
|
||||
const summary: string[] = [];
|
||||
cummulativeIssues.forEach((count, name) => {
|
||||
summary.push(`${name}${count > 1 ? ` \u00D7 ${count}` : ''}`)
|
||||
})
|
||||
summary.push(`${name}${count > 1 ? ` \u00D7 ${count}` : ''}`);
|
||||
});
|
||||
return `Geometry Quality ${residueCount}: ${summary.join(', ')}`;
|
||||
}
|
||||
}
|
||||
@@ -181,58 +181,58 @@ function densityFitLabel(loci: Loci): string | undefined {
|
||||
if (loci.kind === 'element-loci') {
|
||||
if (loci.elements.length === 0) return;
|
||||
|
||||
const seen = new Set<number>()
|
||||
const rsrzSeen = new Set<number>()
|
||||
const rsccSeen = new Set<number>()
|
||||
let rsrzSum = 0
|
||||
let rsccSum = 0
|
||||
const seen = new Set<number>();
|
||||
const rsrzSeen = new Set<number>();
|
||||
const rsccSeen = new Set<number>();
|
||||
let rsrzSum = 0;
|
||||
let rsccSum = 0;
|
||||
|
||||
for (const { indices, unit } of loci.elements) {
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value
|
||||
if (!validationReport) continue
|
||||
if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value;
|
||||
if (!validationReport) continue;
|
||||
if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue;
|
||||
|
||||
const { rsrz, rscc } = validationReport
|
||||
const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index
|
||||
const { elements } = unit
|
||||
const { rsrz, rscc } = validationReport;
|
||||
const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index;
|
||||
const { elements } = unit;
|
||||
|
||||
OrderedSet.forEach(indices, idx => {
|
||||
const eI = elements[idx]
|
||||
const rI = residueIndex[eI]
|
||||
const eI = elements[idx];
|
||||
const rI = residueIndex[eI];
|
||||
|
||||
const residueKey = cantorPairing(rI, unit.id)
|
||||
const residueKey = cantorPairing(rI, unit.id);
|
||||
if (!seen.has(residueKey)) {
|
||||
const rsrzValue = rsrz.get(rI)
|
||||
const rsccValue = rscc.get(rI)
|
||||
const rsrzValue = rsrz.get(rI);
|
||||
const rsccValue = rscc.get(rI);
|
||||
if (rsrzValue !== undefined) {
|
||||
rsrzSum += rsrzValue
|
||||
rsrzSeen.add(residueKey)
|
||||
rsrzSum += rsrzValue;
|
||||
rsrzSeen.add(residueKey);
|
||||
} else if (rsccValue !== undefined) {
|
||||
rsccSum += rsccValue
|
||||
rsccSeen.add(residueKey)
|
||||
rsccSum += rsccValue;
|
||||
rsccSeen.add(residueKey);
|
||||
}
|
||||
seen.add(residueKey)
|
||||
seen.add(residueKey);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (seen.size === 0) return
|
||||
if (seen.size === 0) return;
|
||||
|
||||
const summary: string[] = []
|
||||
const summary: string[] = [];
|
||||
|
||||
if (rsrzSeen.size) {
|
||||
const rsrzCount = `<small>(${rsrzSeen.size} ${rsrzSeen.size > 1 ? 'Residues avg.' : 'Residue'})</small>`
|
||||
const rsrzAvg = rsrzSum / rsrzSeen.size
|
||||
summary.push(`Real Space R ${rsrzCount}: ${rsrzAvg.toFixed(2)}`)
|
||||
const rsrzCount = `<small>(${rsrzSeen.size} ${rsrzSeen.size > 1 ? 'Residues avg.' : 'Residue'})</small>`;
|
||||
const rsrzAvg = rsrzSum / rsrzSeen.size;
|
||||
summary.push(`Real Space R ${rsrzCount}: ${rsrzAvg.toFixed(2)}`);
|
||||
}
|
||||
if (rsccSeen.size) {
|
||||
const rsccCount = `<small>(${rsccSeen.size} ${rsccSeen.size > 1 ? 'Residues avg.' : 'Residue'})</small>`
|
||||
const rsccAvg = rsccSum / rsccSeen.size
|
||||
summary.push(`Real Space Correlation Coefficient ${rsccCount}: ${rsccAvg.toFixed(2)}`)
|
||||
const rsccCount = `<small>(${rsccSeen.size} ${rsccSeen.size > 1 ? 'Residues avg.' : 'Residue'})</small>`;
|
||||
const rsccAvg = rsccSum / rsccSeen.size;
|
||||
summary.push(`Real Space Correlation Coefficient ${rsccCount}: ${rsccAvg.toFixed(2)}`);
|
||||
}
|
||||
|
||||
if (summary.length) {
|
||||
return summary.join('</br>')
|
||||
return summary.join('</br>');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,39 +241,39 @@ function randomCoilIndexLabel(loci: Loci): string | undefined {
|
||||
if (loci.kind === 'element-loci') {
|
||||
if (loci.elements.length === 0) return;
|
||||
|
||||
const seen = new Set<number>()
|
||||
let sum = 0
|
||||
const seen = new Set<number>();
|
||||
let sum = 0;
|
||||
|
||||
for (const { indices, unit } of loci.elements) {
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value
|
||||
if (!validationReport) continue
|
||||
if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value;
|
||||
if (!validationReport) continue;
|
||||
if (!unit.model.customProperties.hasReference(ValidationReportProvider.descriptor)) continue;
|
||||
|
||||
const { rci } = validationReport
|
||||
const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index
|
||||
const { elements } = unit
|
||||
const { rci } = validationReport;
|
||||
const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index;
|
||||
const { elements } = unit;
|
||||
|
||||
OrderedSet.forEach(indices, idx => {
|
||||
const eI = elements[idx]
|
||||
const rI = residueIndex[eI]
|
||||
const eI = elements[idx];
|
||||
const rI = residueIndex[eI];
|
||||
|
||||
const residueKey = cantorPairing(rI, unit.id)
|
||||
const residueKey = cantorPairing(rI, unit.id);
|
||||
if (!seen.has(residueKey)) {
|
||||
const rciValue = rci.get(rI)
|
||||
const rciValue = rci.get(rI);
|
||||
if (rciValue !== undefined) {
|
||||
sum += rciValue
|
||||
seen.add(residueKey)
|
||||
sum += rciValue;
|
||||
seen.add(residueKey);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if (seen.size === 0) return
|
||||
if (seen.size === 0) return;
|
||||
|
||||
const residueCount = `<small>(${seen.size} ${seen.size > 1 ? 'Residues avg.' : 'Residue'})</small>`
|
||||
const rciAvg = sum / seen.size
|
||||
const residueCount = `<small>(${seen.size} ${seen.size > 1 ? 'Residues avg.' : 'Residue'})</small>`;
|
||||
const rciAvg = sum / seen.size;
|
||||
|
||||
return `Random Coil Index ${residueCount}: ${rciAvg.toFixed(2)}`
|
||||
return `Random Coil Index ${residueCount}: ${rciAvg.toFixed(2)}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,9 +292,9 @@ const hasClash = StructureSelectionQuery('Residues with Clashes', MS.struct.modi
|
||||
description: 'Select residues with clashes in the wwPDB validation report.',
|
||||
category: StructureSelectionCategory.Residue,
|
||||
ensureCustomProperties: (ctx, structure) => {
|
||||
return ValidationReportProvider.attach(ctx, structure.models[0])
|
||||
return ValidationReportProvider.attach(ctx, structure.models[0]);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
@@ -305,22 +305,22 @@ export const ValidationReportGeometryQualityPreset = StructureRepresentationPres
|
||||
description: 'Color structure based on geometry quality; show geometry clashes. Data from wwPDB Validation Report, obtained via RCSB PDB.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0])
|
||||
return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const model = structureCell?.obj?.data.model
|
||||
const model = structureCell?.obj?.data.model;
|
||||
if (!structureCell || !model) return {};
|
||||
|
||||
await plugin.runTask(Task.create('Validation Report', async runtime => {
|
||||
await ValidationReportProvider.attach({ fetch: plugin.fetch, runtime }, model)
|
||||
}))
|
||||
await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
|
||||
}));
|
||||
|
||||
const colorTheme = GeometryQualityColorThemeProvider.name as any
|
||||
const { components, representations } = await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin)
|
||||
const colorTheme = GeometryQualityColorThemeProvider.name as any;
|
||||
const { components, representations } = await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin);
|
||||
|
||||
const clashes = await plugin.builders.structure.tryCreateComponentFromExpression(structureCell, hasClash.expression, 'clashes', { label: 'Clashes' })
|
||||
const clashes = await plugin.builders.structure.tryCreateComponentFromExpression(structureCell, hasClash.expression, 'clashes', { label: 'Clashes' });
|
||||
|
||||
const { update, builder, typeParams, color } = StructureRepresentationPresetProvider.reprBuilder(plugin, params);
|
||||
let clashesBallAndStick, clashesSnfg3d;
|
||||
@@ -329,7 +329,7 @@ export const ValidationReportGeometryQualityPreset = StructureRepresentationPres
|
||||
clashesSnfg3d = builder.buildRepresentation<any>(update, clashes, { type: ClashesRepresentationProvider.name, typeParams, color }, { tag: 'clashes-snfg-3d' });
|
||||
}
|
||||
|
||||
await plugin.updateDataState(update, { revertOnError: false });
|
||||
await update.commit({ revertOnError: true });
|
||||
return { components: { ...components, clashes }, representations: { ...representations, clashesBallAndStick, clashesSnfg3d } };
|
||||
}
|
||||
});
|
||||
@@ -341,20 +341,20 @@ export const ValidationReportDensityFitPreset = StructureRepresentationPresetPro
|
||||
description: 'Color structure based on density fit. Data from wwPDB Validation Report, obtained via RCSB PDB.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]) && Model.hasXrayMap(a.data.models[0])
|
||||
return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]) && Model.hasXrayMap(a.data.models[0]);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const model = structureCell?.obj?.data.model
|
||||
const model = structureCell?.obj?.data.model;
|
||||
if (!structureCell || !model) return {};
|
||||
|
||||
await plugin.runTask(Task.create('Validation Report', async runtime => {
|
||||
await ValidationReportProvider.attach({ fetch: plugin.fetch, runtime }, model)
|
||||
}))
|
||||
await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
|
||||
}));
|
||||
|
||||
const colorTheme = DensityFitColorThemeProvider.name as any
|
||||
return await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin)
|
||||
const colorTheme = DensityFitColorThemeProvider.name as any;
|
||||
return await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -365,19 +365,19 @@ export const ValidationReportRandomCoilIndexPreset = StructureRepresentationPres
|
||||
description: 'Color structure based on Random Coil Index. Data from wwPDB Validation Report, obtained via RCSB PDB.'
|
||||
},
|
||||
isApplicable(a) {
|
||||
return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]) && Model.isFromNmr(a.data.models[0])
|
||||
return a.data.models.length === 1 && ValidationReport.isApplicable(a.data.models[0]) && Model.isFromNmr(a.data.models[0]);
|
||||
},
|
||||
params: () => StructureRepresentationPresetProvider.CommonParams,
|
||||
async apply(ref, params, plugin) {
|
||||
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
|
||||
const model = structureCell?.obj?.data.model
|
||||
const model = structureCell?.obj?.data.model;
|
||||
if (!structureCell || !model) return {};
|
||||
|
||||
await plugin.runTask(Task.create('Validation Report', async runtime => {
|
||||
await ValidationReportProvider.attach({ fetch: plugin.fetch, runtime }, model)
|
||||
}))
|
||||
await ValidationReportProvider.attach({ runtime, assetManager: plugin.managers.asset }, model);
|
||||
}));
|
||||
|
||||
const colorTheme = RandomCoilIndexColorThemeProvider.name as any
|
||||
return await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin)
|
||||
const colorTheme = RandomCoilIndexColorThemeProvider.name as any;
|
||||
return await PresetStructureRepresentations.auto.apply(ref, { ...params, globalThemeName: colorTheme }, plugin);
|
||||
}
|
||||
});
|
||||
@@ -4,49 +4,49 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
|
||||
import { Color, ColorScale } from '../../../mol-util/color';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { ValidationReportProvider, ValidationReport } from '../validation-report';
|
||||
import { ThemeDataContext } from '../../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Color, ColorScale } from '../../../../mol-util/color';
|
||||
import { StructureElement, Model } from '../../../../mol-model/structure';
|
||||
import { Location } from '../../../../mol-model/location';
|
||||
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
|
||||
import { ValidationReportProvider, ValidationReport } from '../prop';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC)
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
|
||||
export function DensityFitColorTheme(ctx: ThemeDataContext, props: {}): ColorTheme<{}> {
|
||||
let color: LocationColor = () => DefaultColor
|
||||
let color: LocationColor = () => DefaultColor;
|
||||
const scaleRsrz = ColorScale.create({
|
||||
minLabel: 'Poor',
|
||||
maxLabel: 'Better',
|
||||
domain: [2, 0],
|
||||
listOrName: 'red-yellow-blue',
|
||||
})
|
||||
});
|
||||
const scaleRscc = ColorScale.create({
|
||||
minLabel: 'Poor',
|
||||
maxLabel: 'Better',
|
||||
domain: [0.678, 1.0],
|
||||
listOrName: 'red-yellow-blue',
|
||||
})
|
||||
});
|
||||
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0])
|
||||
const contextHash = validationReport?.version
|
||||
const model = ctx.structure?.models[0]
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]);
|
||||
const contextHash = validationReport?.version;
|
||||
const model = ctx.structure?.models[0];
|
||||
|
||||
if (validationReport?.value && model) {
|
||||
const { rsrz, rscc } = validationReport.value
|
||||
const residueIndex = model.atomicHierarchy.residueAtomSegments.index
|
||||
const { rsrz, rscc } = validationReport.value;
|
||||
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location) && location.unit.model === model) {
|
||||
const rsrzValue = rsrz.get(residueIndex[location.element])
|
||||
if (rsrzValue !== undefined) return scaleRsrz.color(rsrzValue)
|
||||
const rsccValue = rscc.get(residueIndex[location.element])
|
||||
if (rsccValue !== undefined) return scaleRscc.color(rsccValue)
|
||||
return DefaultColor
|
||||
const rsrzValue = rsrz.get(residueIndex[location.element]);
|
||||
if (rsrzValue !== undefined) return scaleRsrz.color(rsrzValue);
|
||||
const rsccValue = rscc.get(residueIndex[location.element]);
|
||||
if (rsccValue !== undefined) return scaleRscc.color(rsccValue);
|
||||
return DefaultColor;
|
||||
}
|
||||
return DefaultColor
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -57,7 +57,7 @@ export function DensityFitColorTheme(ctx: ThemeDataContext, props: {}): ColorThe
|
||||
contextHash,
|
||||
description: 'Assigns residue colors according to the density fit using normalized Real Space R (RSRZ) for polymer residues and real space correlation coefficient (RSCC) for ligands. Colors range from poor (RSRZ = 2 or RSCC = 0.678) - to better (RSRZ = 0 or RSCC = 1.0). Data from wwPDB Validation Report, obtained via RCSB PDB.',
|
||||
legend: scaleRsrz.legend
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationReport.Tag.DensityFit> = {
|
||||
@@ -67,9 +67,9 @@ export const DensityFitColorThemeProvider: ColorTheme.Provider<{}, ValidationRep
|
||||
factory: DensityFitColorTheme,
|
||||
getParams: () => ({}),
|
||||
defaultValues: PD.getDefaultValues({}),
|
||||
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.hasXrayMap(ctx.structure.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -4,24 +4,24 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { ValidationReportProvider, ValidationReport } from '../validation-report';
|
||||
import { TableLegend } from '../../../mol-util/legend';
|
||||
import { PolymerType } from '../../../mol-model/structure/model/types';
|
||||
import { SetUtils } from '../../../mol-util/set';
|
||||
import { ThemeDataContext } from '../../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { StructureElement } from '../../../../mol-model/structure';
|
||||
import { Location } from '../../../../mol-model/location';
|
||||
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
|
||||
import { ValidationReportProvider, ValidationReport } from '../prop';
|
||||
import { TableLegend } from '../../../../mol-util/legend';
|
||||
import { PolymerType } from '../../../../mol-model/structure/model/types';
|
||||
import { SetUtils } from '../../../../mol-util/set';
|
||||
|
||||
const DefaultColor = Color(0x909090)
|
||||
const DefaultColor = Color(0x909090);
|
||||
|
||||
const NoIssuesColor = Color(0x2166ac)
|
||||
const OneIssueColor = Color(0xfee08b)
|
||||
const TwoIssuesColor = Color(0xf46d43)
|
||||
const ThreeOrMoreIssuesColor = Color(0xa50026)
|
||||
const NoIssuesColor = Color(0x2166ac);
|
||||
const OneIssueColor = Color(0xfee08b);
|
||||
const TwoIssuesColor = Color(0xf46d43);
|
||||
const ThreeOrMoreIssuesColor = Color(0xa50026);
|
||||
|
||||
const ColorLegend = TableLegend([
|
||||
['Data unavailable', DefaultColor],
|
||||
@@ -29,64 +29,64 @@ const ColorLegend = TableLegend([
|
||||
['One issue', OneIssueColor],
|
||||
['Two issues', TwoIssuesColor],
|
||||
['Three or more issues', ThreeOrMoreIssuesColor],
|
||||
])
|
||||
]);
|
||||
|
||||
export function getGeometricQualityColorThemeParams(ctx: ThemeDataContext) {
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]).value
|
||||
const options: [string, string][] = []
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]).value;
|
||||
const options: [string, string][] = [];
|
||||
if (validationReport) {
|
||||
const kinds = new Set<string>()
|
||||
validationReport.geometryIssues.forEach(v => v.forEach(k => kinds.add(k)))
|
||||
kinds.forEach(k => options.push([k, k]))
|
||||
const kinds = new Set<string>();
|
||||
validationReport.geometryIssues.forEach(v => v.forEach(k => kinds.add(k)));
|
||||
kinds.forEach(k => options.push([k, k]));
|
||||
}
|
||||
return {
|
||||
ignore: PD.MultiSelect([] as string[], options)
|
||||
}
|
||||
};
|
||||
}
|
||||
export type GeometricQualityColorThemeParams = ReturnType<typeof getGeometricQualityColorThemeParams>
|
||||
|
||||
export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Values<GeometricQualityColorThemeParams>): ColorTheme<GeometricQualityColorThemeParams> {
|
||||
let color: LocationColor = () => DefaultColor
|
||||
let color: LocationColor = () => DefaultColor;
|
||||
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0])
|
||||
const contextHash = validationReport?.version
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]);
|
||||
const contextHash = validationReport?.version;
|
||||
|
||||
const value = validationReport?.value
|
||||
const model = ctx.structure?.models[0]
|
||||
const value = validationReport?.value;
|
||||
const model = ctx.structure?.models[0];
|
||||
|
||||
if (value && model) {
|
||||
const { geometryIssues, clashes, bondOutliers, angleOutliers } = value
|
||||
const residueIndex = model.atomicHierarchy.residueAtomSegments.index
|
||||
const { polymerType } = model.atomicHierarchy.derived.residue
|
||||
const ignore = new Set(props.ignore)
|
||||
const { geometryIssues, clashes, bondOutliers, angleOutliers } = value;
|
||||
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
|
||||
const { polymerType } = model.atomicHierarchy.derived.residue;
|
||||
const ignore = new Set(props.ignore);
|
||||
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location) && location.unit.model === model) {
|
||||
const { element } = location
|
||||
const rI = residueIndex[element]
|
||||
const { element } = location;
|
||||
const rI = residueIndex[element];
|
||||
|
||||
const value = geometryIssues.get(rI)
|
||||
if (value === undefined) return DefaultColor
|
||||
const value = geometryIssues.get(rI);
|
||||
if (value === undefined) return DefaultColor;
|
||||
|
||||
let count = SetUtils.differenceSize(value, ignore)
|
||||
let count = SetUtils.differenceSize(value, ignore);
|
||||
|
||||
if (count > 0 && polymerType[rI] === PolymerType.NA) {
|
||||
count = 0
|
||||
if (!ignore.has('clash') && clashes.getVertexEdgeCount(element) > 0) count += 1
|
||||
if (!ignore.has('mog-bond-outlier') && bondOutliers.index.has(element)) count += 1
|
||||
if (!ignore.has('mog-angle-outlier') && angleOutliers.index.has(element)) count += 1
|
||||
count = 0;
|
||||
if (!ignore.has('clash') && clashes.getVertexEdgeCount(element) > 0) count += 1;
|
||||
if (!ignore.has('mog-bond-outlier') && bondOutliers.index.has(element)) count += 1;
|
||||
if (!ignore.has('mog-angle-outlier') && angleOutliers.index.has(element)) count += 1;
|
||||
}
|
||||
|
||||
switch (count) {
|
||||
case undefined: return DefaultColor
|
||||
case 0: return NoIssuesColor
|
||||
case 1: return OneIssueColor
|
||||
case 2: return TwoIssuesColor
|
||||
default: return ThreeOrMoreIssuesColor
|
||||
case undefined: return DefaultColor;
|
||||
case 0: return NoIssuesColor;
|
||||
case 1: return OneIssueColor;
|
||||
case 2: return TwoIssuesColor;
|
||||
default: return ThreeOrMoreIssuesColor;
|
||||
}
|
||||
}
|
||||
return DefaultColor
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -97,7 +97,7 @@ export function GeometryQualityColorTheme(ctx: ThemeDataContext, props: PD.Value
|
||||
contextHash,
|
||||
description: 'Assigns residue colors according to the number of (filtered) geometry issues. Data from wwPDB Validation Report, obtained via RCSB PDB.',
|
||||
legend: ColorLegend
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQualityColorThemeParams, ValidationReport.Tag.GeometryQuality> = {
|
||||
@@ -112,4 +112,4 @@ export const GeometryQualityColorThemeProvider: ColorTheme.Provider<GeometricQua
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -4,40 +4,40 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ThemeDataContext } from '../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition'
|
||||
import { Color, ColorScale } from '../../../mol-util/color';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
import { Location } from '../../../mol-model/location';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { ValidationReportProvider, ValidationReport } from '../validation-report';
|
||||
import { ThemeDataContext } from '../../../../mol-theme/theme';
|
||||
import { ColorTheme, LocationColor } from '../../../../mol-theme/color';
|
||||
import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
|
||||
import { Color, ColorScale } from '../../../../mol-util/color';
|
||||
import { StructureElement, Model } from '../../../../mol-model/structure';
|
||||
import { Location } from '../../../../mol-model/location';
|
||||
import { CustomProperty } from '../../../../mol-model-props/common/custom-property';
|
||||
import { ValidationReportProvider, ValidationReport } from '../prop';
|
||||
|
||||
const DefaultColor = Color(0xCCCCCC)
|
||||
const DefaultColor = Color(0xCCCCCC);
|
||||
|
||||
export function RandomCoilIndexColorTheme(ctx: ThemeDataContext, props: {}): ColorTheme<{}> {
|
||||
let color: LocationColor = () => DefaultColor
|
||||
let color: LocationColor = () => DefaultColor;
|
||||
const scale = ColorScale.create({
|
||||
reverse: true,
|
||||
domain: [0, 0.6],
|
||||
listOrName: 'red-yellow-blue',
|
||||
})
|
||||
});
|
||||
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0])
|
||||
const contextHash = validationReport?.version
|
||||
const validationReport = ctx.structure && ValidationReportProvider.get(ctx.structure.models[0]);
|
||||
const contextHash = validationReport?.version;
|
||||
|
||||
const rci = validationReport?.value?.rci
|
||||
const model = ctx.structure?.models[0]
|
||||
const rci = validationReport?.value?.rci;
|
||||
const model = ctx.structure?.models[0];
|
||||
|
||||
if (rci && model) {
|
||||
const residueIndex = model.atomicHierarchy.residueAtomSegments.index
|
||||
const residueIndex = model.atomicHierarchy.residueAtomSegments.index;
|
||||
color = (location: Location): Color => {
|
||||
if (StructureElement.Location.is(location) && location.unit.model === model) {
|
||||
const value = rci.get(residueIndex[location.element])
|
||||
return value === undefined ? DefaultColor : scale.color(value)
|
||||
const value = rci.get(residueIndex[location.element]);
|
||||
return value === undefined ? DefaultColor : scale.color(value);
|
||||
}
|
||||
return DefaultColor
|
||||
}
|
||||
return DefaultColor;
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -48,7 +48,7 @@ export function RandomCoilIndexColorTheme(ctx: ThemeDataContext, props: {}): Col
|
||||
contextHash,
|
||||
description: 'Assigns residue colors according to the Random Coil Index value. Data from wwPDB Validation Report, obtained via RCSB PDB.',
|
||||
legend: scale.legend
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}, ValidationReport.Tag.RandomCoilIndex> = {
|
||||
@@ -58,9 +58,9 @@ export const RandomCoilIndexColorThemeProvider: ColorTheme.Provider<{}, Validati
|
||||
factory: RandomCoilIndexColorTheme,
|
||||
getParams: () => ({}),
|
||||
defaultValues: PD.getDefaultValues({}),
|
||||
isApplicable: (ctx: ThemeDataContext) => ValidationReport.isApplicable(ctx.structure?.models[0]),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ValidationReport.isApplicable(ctx.structure.models[0]) && Model.isFromNmr(ctx.structure.models[0]),
|
||||
ensureCustomProperties: {
|
||||
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) => data.structure ? ValidationReportProvider.attach(ctx, data.structure.models[0], void 0, true) : Promise.resolve(),
|
||||
detach: (data) => data.structure && data.structure.models[0].customProperties.reference(ValidationReportProvider.descriptor, false)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -4,26 +4,25 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition'
|
||||
import { CustomPropertyDescriptor, Structure, Unit } from '../../mol-model/structure';
|
||||
import { CustomProperty } from '../common/custom-property';
|
||||
import { CustomModelProperty } from '../common/custom-model-property';
|
||||
import { Model, ElementIndex, ResidueIndex } from '../../mol-model/structure/model';
|
||||
import { IntAdjacencyGraph } from '../../mol-math/graph';
|
||||
import { readFromFile } from '../../mol-util/data-source';
|
||||
import { CustomStructureProperty } from '../common/custom-structure-property';
|
||||
import { InterUnitGraph } from '../../mol-math/graph/inter-unit-graph';
|
||||
import { UnitIndex } from '../../mol-model/structure/structure/element/element';
|
||||
import { IntMap, SortedArray } from '../../mol-data/int';
|
||||
import { arrayMax } from '../../mol-util/array';
|
||||
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
|
||||
import { QuerySymbolRuntime } from '../../mol-script/runtime/query/compiler';
|
||||
import { CustomPropSymbol } from '../../mol-script/language/symbol';
|
||||
import Type from '../../mol-script/language/type';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { CustomPropertyDescriptor, Structure, Unit } from '../../../mol-model/structure';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
|
||||
import { Model, ElementIndex, ResidueIndex } from '../../../mol-model/structure/model';
|
||||
import { IntAdjacencyGraph } from '../../../mol-math/graph';
|
||||
import { CustomStructureProperty } from '../../../mol-model-props/common/custom-structure-property';
|
||||
import { InterUnitGraph } from '../../../mol-math/graph/inter-unit-graph';
|
||||
import { UnitIndex } from '../../../mol-model/structure/structure/element/element';
|
||||
import { IntMap, SortedArray } from '../../../mol-data/int';
|
||||
import { arrayMax } from '../../../mol-util/array';
|
||||
import { equalEps } from '../../../mol-math/linear-algebra/3d/common';
|
||||
import { Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { QuerySymbolRuntime } from '../../../mol-script/runtime/query/compiler';
|
||||
import { CustomPropSymbol } from '../../../mol-script/language/symbol';
|
||||
import Type from '../../../mol-script/language/type';
|
||||
import { Asset } from '../../../mol-util/assets';
|
||||
|
||||
export { ValidationReport }
|
||||
export { ValidationReport };
|
||||
|
||||
interface ValidationReport {
|
||||
/**
|
||||
@@ -85,71 +84,67 @@ namespace ValidationReport {
|
||||
Clashes = 'rcsb-clashes',
|
||||
}
|
||||
|
||||
export const DefaultBaseUrl = '//ftp.rcsb.org/pub/pdb/validation_reports'
|
||||
export const DefaultBaseUrl = '//ftp.rcsb.org/pub/pdb/validation_reports';
|
||||
export function getEntryUrl(pdbId: string, baseUrl: string) {
|
||||
const id = pdbId.toLowerCase()
|
||||
return `${baseUrl}/${id.substr(1, 2)}/${id}/${id}_validation.xml.gz`
|
||||
const id = pdbId.toLowerCase();
|
||||
return `${baseUrl}/${id.substr(1, 2)}/${id}/${id}_validation.xml.gz`;
|
||||
}
|
||||
|
||||
export function isApplicable(model?: Model): boolean {
|
||||
return (
|
||||
!!model &&
|
||||
MmcifFormat.is(model.sourceData) &&
|
||||
(model.sourceData.data.db.database_2.database_id.isDefined ||
|
||||
model.entryId.length === 4)
|
||||
)
|
||||
return !!model && Model.isFromPdbArchive(model);
|
||||
}
|
||||
|
||||
export function fromXml(xml: XMLDocument, model: Model): ValidationReport {
|
||||
return parseValidationReportXml(xml, model)
|
||||
return parseValidationReportXml(xml, model);
|
||||
}
|
||||
|
||||
export async function fetch(ctx: CustomProperty.Context, model: Model, props: ServerSourceProps): Promise<ValidationReport> {
|
||||
const url = getEntryUrl(model.entryId, props.baseUrl)
|
||||
const xml = await ctx.fetch({ url, type: 'xml' }).runInContext(ctx.runtime)
|
||||
return fromXml(xml, model)
|
||||
export async function fetch(ctx: CustomProperty.Context, model: Model, props: ServerSourceProps): Promise<CustomProperty.Data<ValidationReport>> {
|
||||
const url = Asset.getUrlAsset(ctx.assetManager, getEntryUrl(model.entryId, props.baseUrl));
|
||||
const xml = await ctx.assetManager.resolve(url, 'xml').runInContext(ctx.runtime);
|
||||
return { value: fromXml(xml.data, model), assets: [xml] };
|
||||
}
|
||||
|
||||
export async function open(ctx: CustomProperty.Context, model: Model, props: FileSourceProps): Promise<ValidationReport> {
|
||||
const xml = await readFromFile(props.input, 'xml').runInContext(ctx.runtime)
|
||||
return fromXml(xml, model)
|
||||
export async function open(ctx: CustomProperty.Context, model: Model, props: FileSourceProps): Promise<CustomProperty.Data<ValidationReport>> {
|
||||
if (props.input === null) throw new Error('No file given');
|
||||
const xml = await ctx.assetManager.resolve(props.input, 'xml').runInContext(ctx.runtime);
|
||||
return { value: fromXml(xml.data, model), assets: [xml] };
|
||||
}
|
||||
|
||||
export async function obtain(ctx: CustomProperty.Context, model: Model, props: ValidationReportProps): Promise<ValidationReport> {
|
||||
export async function obtain(ctx: CustomProperty.Context, model: Model, props: ValidationReportProps): Promise<CustomProperty.Data<ValidationReport>> {
|
||||
switch(props.source.name) {
|
||||
case 'file': return open(ctx, model, props.source.params)
|
||||
case 'server': return fetch(ctx, model, props.source.params)
|
||||
case 'file': return open(ctx, model, props.source.params);
|
||||
case 'server': return fetch(ctx, model, props.source.params);
|
||||
}
|
||||
}
|
||||
|
||||
export const symbols = {
|
||||
hasClash: QuerySymbolRuntime.Dynamic(CustomPropSymbol('rcsb', 'validation-report.has-clash', Type.Bool),
|
||||
ctx => {
|
||||
const { unit, element } = ctx.element
|
||||
if (!Unit.isAtomic(unit)) return 0
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value
|
||||
return validationReport && validationReport.clashes.getVertexEdgeCount(element) > 0
|
||||
const { unit, element } = ctx.element;
|
||||
if (!Unit.isAtomic(unit)) return 0;
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value;
|
||||
return validationReport && validationReport.clashes.getVertexEdgeCount(element) > 0;
|
||||
}
|
||||
),
|
||||
issueCount: QuerySymbolRuntime.Dynamic(CustomPropSymbol('rcsb', 'validation-report.issue-count', Type.Num),
|
||||
ctx => {
|
||||
const { unit, element } = ctx.element
|
||||
if (!Unit.isAtomic(unit)) return 0
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value
|
||||
return validationReport?.geometryIssues.get(unit.residueIndex[element])?.size || 0
|
||||
const { unit, element } = ctx.element;
|
||||
if (!Unit.isAtomic(unit)) return 0;
|
||||
const validationReport = ValidationReportProvider.get(unit.model).value;
|
||||
return validationReport?.geometryIssues.get(unit.residueIndex[element])?.size || 0;
|
||||
}
|
||||
),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const FileSourceParams = {
|
||||
input: PD.File({ accept: '.xml,.gz,.zip' })
|
||||
}
|
||||
};
|
||||
type FileSourceProps = PD.Values<typeof FileSourceParams>
|
||||
|
||||
const ServerSourceParams = {
|
||||
baseUrl: PD.Text(ValidationReport.DefaultBaseUrl, { description: 'Base URL to directory tree' })
|
||||
}
|
||||
};
|
||||
type ServerSourceProps = PD.Values<typeof ServerSourceParams>
|
||||
|
||||
export const ValidationReportParams = {
|
||||
@@ -157,7 +152,7 @@ export const ValidationReportParams = {
|
||||
'file': PD.Group(FileSourceParams, { label: 'File', isFlat: true }),
|
||||
'server': PD.Group(ServerSourceParams, { label: 'Server', isFlat: true }),
|
||||
}, { options: [['file', 'File'], ['server', 'Server']] })
|
||||
}
|
||||
};
|
||||
export type ValidationReportParams = typeof ValidationReportParams
|
||||
export type ValidationReportProps = PD.Values<ValidationReportParams>
|
||||
|
||||
@@ -172,10 +167,10 @@ export const ValidationReportProvider: CustomModelProperty.Provider<ValidationRe
|
||||
getParams: (data: Model) => ValidationReportParams,
|
||||
isApplicable: (data: Model) => ValidationReport.isApplicable(data),
|
||||
obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<ValidationReportProps>) => {
|
||||
const p = { ...PD.getDefaultValues(ValidationReportParams), ...props }
|
||||
return await ValidationReport.obtain(ctx, data, p)
|
||||
const p = { ...PD.getDefaultValues(ValidationReportParams), ...props };
|
||||
return await ValidationReport.obtain(ctx, data, p);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
@@ -199,25 +194,25 @@ export interface Clashes {
|
||||
}
|
||||
|
||||
function createInterUnitClashes(structure: Structure, clashes: ValidationReport['clashes']) {
|
||||
const builder = new InterUnitGraph.Builder<Unit.Atomic, UnitIndex, InterUnitClashesProps>()
|
||||
const { a, b, edgeProps: { id, magnitude, distance } } = clashes
|
||||
const builder = new InterUnitGraph.Builder<Unit.Atomic, UnitIndex, InterUnitClashesProps>();
|
||||
const { a, b, edgeProps: { id, magnitude, distance } } = clashes;
|
||||
|
||||
const pA = Vec3(), pB = Vec3()
|
||||
const pA = Vec3(), pB = Vec3();
|
||||
|
||||
Structure.eachUnitPair(structure, (unitA: Unit, unitB: Unit) => {
|
||||
const elementsA = unitA.elements
|
||||
const elementsB = unitB.elements
|
||||
const elementsA = unitA.elements;
|
||||
const elementsB = unitB.elements;
|
||||
|
||||
builder.startUnitPair(unitA as Unit.Atomic, unitB as Unit.Atomic)
|
||||
builder.startUnitPair(unitA as Unit.Atomic, unitB as Unit.Atomic);
|
||||
|
||||
for (let i = 0, il = clashes.edgeCount * 2; i < il; ++i) {
|
||||
// TODO create lookup
|
||||
let indexA = SortedArray.indexOf(elementsA, a[i])
|
||||
let indexB = SortedArray.indexOf(elementsB, b[i])
|
||||
let indexA = SortedArray.indexOf(elementsA, a[i]);
|
||||
let indexB = SortedArray.indexOf(elementsB, b[i]);
|
||||
|
||||
if (indexA !== -1 && indexB !== -1) {
|
||||
unitA.conformation.position(a[i], pA)
|
||||
unitB.conformation.position(b[i], pB)
|
||||
unitA.conformation.position(a[i], pA);
|
||||
unitB.conformation.position(b[i], pB);
|
||||
|
||||
// check actual distance to avoid clashes between unrelated chain instances
|
||||
if (equalEps(distance[i], Vec3.distance(pA, pB), 0.1)) {
|
||||
@@ -225,83 +220,83 @@ function createInterUnitClashes(structure: Structure, clashes: ValidationReport[
|
||||
id: id[i],
|
||||
magnitude: magnitude[i],
|
||||
distance: distance[i],
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.finishUnitPair()
|
||||
builder.finishUnitPair();
|
||||
}, {
|
||||
maxRadius: arrayMax(clashes.edgeProps.distance),
|
||||
validUnit: (unit: Unit) => Unit.isAtomic(unit),
|
||||
validUnitPair: (unitA: Unit, unitB: Unit) => unitA.model === unitB.model
|
||||
})
|
||||
});
|
||||
|
||||
return new InterUnitGraph(builder.getMap())
|
||||
return new InterUnitGraph(builder.getMap());
|
||||
}
|
||||
|
||||
function createIntraUnitClashes(unit: Unit.Atomic, clashes: ValidationReport['clashes']): IntraUnitClashes {
|
||||
const aIndices: UnitIndex[] = []
|
||||
const bIndices: UnitIndex[] = []
|
||||
const ids: number[] = []
|
||||
const magnitudes: number[] = []
|
||||
const distances: number[] = []
|
||||
const aIndices: UnitIndex[] = [];
|
||||
const bIndices: UnitIndex[] = [];
|
||||
const ids: number[] = [];
|
||||
const magnitudes: number[] = [];
|
||||
const distances: number[] = [];
|
||||
|
||||
const pA = Vec3(), pB = Vec3()
|
||||
const pA = Vec3(), pB = Vec3();
|
||||
|
||||
const { elements } = unit
|
||||
const { a, b, edgeCount, edgeProps } = clashes
|
||||
const { elements } = unit;
|
||||
const { a, b, edgeCount, edgeProps } = clashes;
|
||||
|
||||
for (let i = 0, il = edgeCount * 2; i < il; ++i) {
|
||||
// TODO create lookup
|
||||
let indexA = SortedArray.indexOf(elements, a[i])
|
||||
let indexB = SortedArray.indexOf(elements, b[i])
|
||||
let indexA = SortedArray.indexOf(elements, a[i]);
|
||||
let indexB = SortedArray.indexOf(elements, b[i]);
|
||||
|
||||
if (indexA !== -1 && indexB !== -1) {
|
||||
unit.conformation.position(a[i], pA)
|
||||
unit.conformation.position(b[i], pB)
|
||||
unit.conformation.position(a[i], pA);
|
||||
unit.conformation.position(b[i], pB);
|
||||
|
||||
// check actual distance to avoid clashes between unrelated chain instances
|
||||
if (equalEps(edgeProps.distance[i], Vec3.distance(pA, pB), 0.1)) {
|
||||
aIndices.push(indexA as UnitIndex)
|
||||
bIndices.push(indexB as UnitIndex)
|
||||
ids.push(edgeProps.id[i])
|
||||
magnitudes.push(edgeProps.magnitude[i])
|
||||
distances.push(edgeProps.distance[i])
|
||||
aIndices.push(indexA as UnitIndex);
|
||||
bIndices.push(indexB as UnitIndex);
|
||||
ids.push(edgeProps.id[i]);
|
||||
magnitudes.push(edgeProps.magnitude[i]);
|
||||
distances.push(edgeProps.distance[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const builder = new IntAdjacencyGraph.EdgeBuilder(elements.length, aIndices, bIndices)
|
||||
const id = new Int32Array(builder.slotCount)
|
||||
const magnitude = new Float32Array(builder.slotCount)
|
||||
const distance = new Float32Array(builder.slotCount)
|
||||
const builder = new IntAdjacencyGraph.EdgeBuilder(elements.length, aIndices, bIndices);
|
||||
const id = new Int32Array(builder.slotCount);
|
||||
const magnitude = new Float32Array(builder.slotCount);
|
||||
const distance = new Float32Array(builder.slotCount);
|
||||
for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
|
||||
builder.addNextEdge()
|
||||
builder.assignProperty(id, ids[i])
|
||||
builder.assignProperty(magnitude, magnitudes[i])
|
||||
builder.assignProperty(distance, distances[i])
|
||||
builder.addNextEdge();
|
||||
builder.assignProperty(id, ids[i]);
|
||||
builder.assignProperty(magnitude, magnitudes[i]);
|
||||
builder.assignProperty(distance, distances[i]);
|
||||
}
|
||||
return builder.createGraph({ id, magnitude, distance })
|
||||
return builder.createGraph({ id, magnitude, distance });
|
||||
}
|
||||
|
||||
function createClashes(structure: Structure, clashes: ValidationReport['clashes']): Clashes {
|
||||
const intraUnit = IntMap.Mutable<IntraUnitClashes>()
|
||||
const intraUnit = IntMap.Mutable<IntraUnitClashes>();
|
||||
|
||||
for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
|
||||
const group = structure.unitSymmetryGroups[i]
|
||||
if (!Unit.isAtomic(group.units[0])) continue
|
||||
const group = structure.unitSymmetryGroups[i];
|
||||
if (!Unit.isAtomic(group.units[0])) continue;
|
||||
|
||||
const intraClashes = createIntraUnitClashes(group.units[0], clashes)
|
||||
const intraClashes = createIntraUnitClashes(group.units[0], clashes);
|
||||
for (let j = 0, jl = group.units.length; j < jl; ++j) {
|
||||
intraUnit.set(group.units[j].id, intraClashes)
|
||||
intraUnit.set(group.units[j].id, intraClashes);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
interUnit: createInterUnitClashes(structure, clashes),
|
||||
intraUnit
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const ClashesProvider: CustomStructureProperty.Provider<{}, Clashes> = CustomStructureProperty.createProvider({
|
||||
@@ -315,22 +310,24 @@ export const ClashesProvider: CustomStructureProperty.Provider<{}, Clashes> = Cu
|
||||
getParams: (data: Structure) => ({}),
|
||||
isApplicable: (data: Structure) => true,
|
||||
obtain: async (ctx: CustomProperty.Context, data: Structure) => {
|
||||
await ValidationReportProvider.attach(ctx, data.models[0])
|
||||
const validationReport = ValidationReportProvider.get(data.models[0]).value!
|
||||
return createClashes(data, validationReport.clashes)
|
||||
await ValidationReportProvider.attach(ctx, data.models[0]);
|
||||
const validationReport = ValidationReportProvider.get(data.models[0]).value!;
|
||||
return {
|
||||
value: createClashes(data, validationReport.clashes)
|
||||
};
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
//
|
||||
|
||||
function getItem(a: NamedNodeMap, name: string) {
|
||||
const item = a.getNamedItem(name)
|
||||
return item !== null ? item.value : ''
|
||||
const item = a.getNamedItem(name);
|
||||
return item !== null ? item.value : '';
|
||||
}
|
||||
|
||||
function hasAttr(a: NamedNodeMap, name: string, value: string) {
|
||||
const item = a.getNamedItem(name)
|
||||
return item !== null && item.value === value
|
||||
const item = a.getNamedItem(name);
|
||||
return item !== null && item.value === value;
|
||||
}
|
||||
|
||||
function getMogInfo(a: NamedNodeMap) {
|
||||
@@ -339,7 +336,7 @@ function getMogInfo(a: NamedNodeMap) {
|
||||
obs: parseFloat(getItem(a, 'obsval')),
|
||||
stdev: parseFloat(getItem(a, 'stdev')),
|
||||
z: parseFloat(getItem(a, 'Zscore')),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getMolInfo(a: NamedNodeMap) {
|
||||
@@ -348,229 +345,229 @@ function getMolInfo(a: NamedNodeMap) {
|
||||
obs: parseFloat(getItem(a, 'obs')),
|
||||
stdev: parseFloat(getItem(a, 'stdev')),
|
||||
z: parseInt(getItem(a, 'z')),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function addIndex(index: number, element: ElementIndex, map: Map<ElementIndex, number[]>) {
|
||||
if (map.has(element)) map.get(element)!.push(index)
|
||||
else map.set(element, [index])
|
||||
if (map.has(element)) map.get(element)!.push(index);
|
||||
else map.set(element, [index]);
|
||||
}
|
||||
|
||||
function ClashesBuilder(elementsCount: number) {
|
||||
const aIndices: ElementIndex[] = []
|
||||
const bIndices: ElementIndex[] = []
|
||||
const ids: number[] = []
|
||||
const magnitudes: number[] = []
|
||||
const distances: number[] = []
|
||||
const aIndices: ElementIndex[] = [];
|
||||
const bIndices: ElementIndex[] = [];
|
||||
const ids: number[] = [];
|
||||
const magnitudes: number[] = [];
|
||||
const distances: number[] = [];
|
||||
|
||||
const seen = new Map<string, ElementIndex>()
|
||||
const seen = new Map<string, ElementIndex>();
|
||||
|
||||
return {
|
||||
add(element: ElementIndex, id: number, magnitude: number, distance: number, isSymop: boolean) {
|
||||
const hash = `${id}|${isSymop ? 's' : ''}`
|
||||
const other = seen.get(hash)
|
||||
const hash = `${id}|${isSymop ? 's' : ''}`;
|
||||
const other = seen.get(hash);
|
||||
if (other !== undefined) {
|
||||
aIndices[aIndices.length] = element
|
||||
bIndices[bIndices.length] = other
|
||||
ids[ids.length] = id
|
||||
magnitudes[magnitudes.length] = magnitude
|
||||
distances[distances.length] = distance
|
||||
aIndices[aIndices.length] = element;
|
||||
bIndices[bIndices.length] = other;
|
||||
ids[ids.length] = id;
|
||||
magnitudes[magnitudes.length] = magnitude;
|
||||
distances[distances.length] = distance;
|
||||
} else {
|
||||
seen.set(hash, element)
|
||||
seen.set(hash, element);
|
||||
}
|
||||
},
|
||||
get() {
|
||||
const builder = new IntAdjacencyGraph.EdgeBuilder(elementsCount, aIndices, bIndices)
|
||||
const id = new Int32Array(builder.slotCount)
|
||||
const magnitude = new Float32Array(builder.slotCount)
|
||||
const distance = new Float32Array(builder.slotCount)
|
||||
const builder = new IntAdjacencyGraph.EdgeBuilder(elementsCount, aIndices, bIndices);
|
||||
const id = new Int32Array(builder.slotCount);
|
||||
const magnitude = new Float32Array(builder.slotCount);
|
||||
const distance = new Float32Array(builder.slotCount);
|
||||
for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
|
||||
builder.addNextEdge()
|
||||
builder.assignProperty(id, ids[i])
|
||||
builder.assignProperty(magnitude, magnitudes[i])
|
||||
builder.assignProperty(distance, distances[i])
|
||||
builder.addNextEdge();
|
||||
builder.assignProperty(id, ids[i]);
|
||||
builder.assignProperty(magnitude, magnitudes[i]);
|
||||
builder.assignProperty(distance, distances[i]);
|
||||
}
|
||||
return builder.createGraph({ id, magnitude, distance })
|
||||
return builder.createGraph({ id, magnitude, distance });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function parseValidationReportXml(xml: XMLDocument, model: Model): ValidationReport {
|
||||
const rsrz = new Map<ResidueIndex, number>()
|
||||
const rscc = new Map<ResidueIndex, number>()
|
||||
const rci = new Map<ResidueIndex, number>()
|
||||
const geometryIssues = new Map<ResidueIndex, Set<string>>()
|
||||
const rsrz = new Map<ResidueIndex, number>();
|
||||
const rscc = new Map<ResidueIndex, number>();
|
||||
const rci = new Map<ResidueIndex, number>();
|
||||
const geometryIssues = new Map<ResidueIndex, Set<string>>();
|
||||
|
||||
const bondOutliers = {
|
||||
index: new Map<ElementIndex, number[]>(),
|
||||
data: [] as ValidationReport['bondOutliers']['data']
|
||||
}
|
||||
};
|
||||
const angleOutliers = {
|
||||
index: new Map<ElementIndex, number[]>(),
|
||||
data: [] as ValidationReport['angleOutliers']['data']
|
||||
}
|
||||
};
|
||||
|
||||
const clashesBuilder = ClashesBuilder(model.atomicHierarchy.atoms._rowCount)
|
||||
const clashesBuilder = ClashesBuilder(model.atomicHierarchy.atoms._rowCount);
|
||||
|
||||
const { index } = model.atomicHierarchy
|
||||
const { index } = model.atomicHierarchy;
|
||||
|
||||
const entries = xml.getElementsByTagName('Entry')
|
||||
const entries = xml.getElementsByTagName('Entry');
|
||||
if (entries.length === 1) {
|
||||
const chemicalShiftLists = entries[0].getElementsByTagName('chemical_shift_list')
|
||||
const chemicalShiftLists = entries[0].getElementsByTagName('chemical_shift_list');
|
||||
if (chemicalShiftLists.length === 1) {
|
||||
const randomCoilIndices = chemicalShiftLists[0].getElementsByTagName('random_coil_index')
|
||||
const randomCoilIndices = chemicalShiftLists[0].getElementsByTagName('random_coil_index');
|
||||
for (let j = 0, jl = randomCoilIndices.length; j < jl; ++j) {
|
||||
const { attributes } = randomCoilIndices[j]
|
||||
const value = parseFloat(getItem(attributes, 'value'))
|
||||
const auth_asym_id = getItem(attributes, 'chain')
|
||||
const auth_comp_id = getItem(attributes, 'rescode')
|
||||
const auth_seq_id = parseInt(getItem(attributes, 'resnum'))
|
||||
const rI = index.findResidueAuth({ auth_asym_id, auth_comp_id, auth_seq_id })
|
||||
if (rI !== -1) rci.set(rI, value)
|
||||
const { attributes } = randomCoilIndices[j];
|
||||
const value = parseFloat(getItem(attributes, 'value'));
|
||||
const auth_asym_id = getItem(attributes, 'chain');
|
||||
const auth_comp_id = getItem(attributes, 'rescode');
|
||||
const auth_seq_id = parseInt(getItem(attributes, 'resnum'));
|
||||
const rI = index.findResidueAuth({ auth_asym_id, auth_comp_id, auth_seq_id });
|
||||
if (rI !== -1) rci.set(rI, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const groups = xml.getElementsByTagName('ModelledSubgroup')
|
||||
const groups = xml.getElementsByTagName('ModelledSubgroup');
|
||||
for (let i = 0, il = groups.length; i < il; ++i) {
|
||||
const g = groups[ i ]
|
||||
const ga = g.attributes
|
||||
const g = groups[ i ];
|
||||
const ga = g.attributes;
|
||||
|
||||
const pdbx_PDB_model_num = parseInt(getItem(ga, 'model'))
|
||||
if (model.modelNum !== pdbx_PDB_model_num) continue
|
||||
const pdbx_PDB_model_num = parseInt(getItem(ga, 'model'));
|
||||
if (model.modelNum !== pdbx_PDB_model_num) continue;
|
||||
|
||||
const auth_asym_id = getItem(ga, 'chain')
|
||||
const auth_comp_id = getItem(ga, 'resname')
|
||||
const auth_seq_id = parseInt(getItem(ga, 'resnum'))
|
||||
const pdbx_PDB_ins_code = getItem(ga, 'icode').trim() || undefined
|
||||
const label_alt_id = getItem(ga, 'altcode').trim() || undefined
|
||||
const auth_asym_id = getItem(ga, 'chain');
|
||||
const auth_comp_id = getItem(ga, 'resname');
|
||||
const auth_seq_id = parseInt(getItem(ga, 'resnum'));
|
||||
const pdbx_PDB_ins_code = getItem(ga, 'icode').trim() || undefined;
|
||||
const label_alt_id = getItem(ga, 'altcode').trim() || undefined;
|
||||
|
||||
const rI = index.findResidueAuth({ auth_asym_id, auth_comp_id, auth_seq_id, pdbx_PDB_ins_code })
|
||||
const rI = index.findResidueAuth({ auth_asym_id, auth_comp_id, auth_seq_id, pdbx_PDB_ins_code });
|
||||
|
||||
// continue if no residue index is found
|
||||
if (rI === -1) continue
|
||||
if (rI === -1) continue;
|
||||
|
||||
if (ga.getNamedItem('rsrz') !== null) rsrz.set(rI, parseFloat(getItem(ga, 'rsrz')))
|
||||
if (ga.getNamedItem('rscc') !== null) rscc.set(rI, parseFloat(getItem(ga, 'rscc')))
|
||||
if (ga.getNamedItem('rsrz') !== null) rsrz.set(rI, parseFloat(getItem(ga, 'rsrz')));
|
||||
if (ga.getNamedItem('rscc') !== null) rscc.set(rI, parseFloat(getItem(ga, 'rscc')));
|
||||
|
||||
const isPolymer = getItem(ga, 'seq') !== '.'
|
||||
const issues = new Set<string>()
|
||||
const isPolymer = getItem(ga, 'seq') !== '.';
|
||||
const issues = new Set<string>();
|
||||
|
||||
if (isPolymer) {
|
||||
const molBondOutliers = g.getElementsByTagName('bond-outlier')
|
||||
if (molBondOutliers.length) issues.add('bond-outlier')
|
||||
const molBondOutliers = g.getElementsByTagName('bond-outlier');
|
||||
if (molBondOutliers.length) issues.add('bond-outlier');
|
||||
|
||||
for (let j = 0, jl = molBondOutliers.length; j < jl; ++j) {
|
||||
const bo = molBondOutliers[j].attributes
|
||||
const idx = bondOutliers.data.length
|
||||
const atomA = index.findAtomOnResidue(rI, getItem(bo, 'atom0'))
|
||||
const atomB = index.findAtomOnResidue(rI, getItem(bo, 'atom1'))
|
||||
addIndex(idx, atomA, bondOutliers.index)
|
||||
addIndex(idx, atomB, bondOutliers.index)
|
||||
const bo = molBondOutliers[j].attributes;
|
||||
const idx = bondOutliers.data.length;
|
||||
const atomA = index.findAtomOnResidue(rI, getItem(bo, 'atom0'));
|
||||
const atomB = index.findAtomOnResidue(rI, getItem(bo, 'atom1'));
|
||||
addIndex(idx, atomA, bondOutliers.index);
|
||||
addIndex(idx, atomB, bondOutliers.index);
|
||||
bondOutliers.data.push({
|
||||
tag: 'bond-outlier', atomA, atomB, ...getMolInfo(bo)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const molAngleOutliers = g.getElementsByTagName('angle-outlier')
|
||||
if (molAngleOutliers.length) issues.add('angle-outlier')
|
||||
const molAngleOutliers = g.getElementsByTagName('angle-outlier');
|
||||
if (molAngleOutliers.length) issues.add('angle-outlier');
|
||||
|
||||
for (let j = 0, jl = molAngleOutliers.length; j < jl; ++j) {
|
||||
const ao = molAngleOutliers[j].attributes
|
||||
const idx = bondOutliers.data.length
|
||||
const atomA = index.findAtomOnResidue(rI, getItem(ao, 'atom0'))
|
||||
const atomB = index.findAtomOnResidue(rI, getItem(ao, 'atom1'))
|
||||
const atomC = index.findAtomOnResidue(rI, getItem(ao, 'atom2'))
|
||||
addIndex(idx, atomA, angleOutliers.index)
|
||||
addIndex(idx, atomB, angleOutliers.index)
|
||||
addIndex(idx, atomC, angleOutliers.index)
|
||||
const ao = molAngleOutliers[j].attributes;
|
||||
const idx = bondOutliers.data.length;
|
||||
const atomA = index.findAtomOnResidue(rI, getItem(ao, 'atom0'));
|
||||
const atomB = index.findAtomOnResidue(rI, getItem(ao, 'atom1'));
|
||||
const atomC = index.findAtomOnResidue(rI, getItem(ao, 'atom2'));
|
||||
addIndex(idx, atomA, angleOutliers.index);
|
||||
addIndex(idx, atomB, angleOutliers.index);
|
||||
addIndex(idx, atomC, angleOutliers.index);
|
||||
angleOutliers.data.push({
|
||||
tag: 'angle-outlier', atomA, atomB, atomC, ...getMolInfo(ao)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const planeOutliers = g.getElementsByTagName('plane-outlier')
|
||||
if (planeOutliers.length) issues.add('plane-outlier')
|
||||
const planeOutliers = g.getElementsByTagName('plane-outlier');
|
||||
if (planeOutliers.length) issues.add('plane-outlier');
|
||||
|
||||
if (hasAttr(ga, 'rota', 'OUTLIER')) issues.add('rotamer-outlier')
|
||||
if (hasAttr(ga, 'rama', 'OUTLIER')) issues.add('ramachandran-outlier')
|
||||
if (hasAttr(ga, 'RNApucker', 'outlier')) issues.add('RNApucker-outlier')
|
||||
if (hasAttr(ga, 'rota', 'OUTLIER')) issues.add('rotamer-outlier');
|
||||
if (hasAttr(ga, 'rama', 'OUTLIER')) issues.add('ramachandran-outlier');
|
||||
if (hasAttr(ga, 'RNApucker', 'outlier')) issues.add('RNApucker-outlier');
|
||||
} else {
|
||||
const mogBondOutliers = g.getElementsByTagName('mog-bond-outlier')
|
||||
if (mogBondOutliers.length) issues.add('mog-bond-outlier')
|
||||
const mogBondOutliers = g.getElementsByTagName('mog-bond-outlier');
|
||||
if (mogBondOutliers.length) issues.add('mog-bond-outlier');
|
||||
|
||||
for (let j = 0, jl = mogBondOutliers.length; j < jl; ++j) {
|
||||
const mbo = mogBondOutliers[j].attributes
|
||||
const atoms = getItem(mbo, 'atoms').split(',')
|
||||
const idx = bondOutliers.data.length
|
||||
const atomA = index.findAtomOnResidue(rI, atoms[0])
|
||||
const atomB = index.findAtomOnResidue(rI, atoms[1])
|
||||
addIndex(idx, atomA, bondOutliers.index)
|
||||
addIndex(idx, atomB, bondOutliers.index)
|
||||
const mbo = mogBondOutliers[j].attributes;
|
||||
const atoms = getItem(mbo, 'atoms').split(',');
|
||||
const idx = bondOutliers.data.length;
|
||||
const atomA = index.findAtomOnResidue(rI, atoms[0]);
|
||||
const atomB = index.findAtomOnResidue(rI, atoms[1]);
|
||||
addIndex(idx, atomA, bondOutliers.index);
|
||||
addIndex(idx, atomB, bondOutliers.index);
|
||||
bondOutliers.data.push({
|
||||
tag: 'mog-bond-outlier', atomA, atomB, ...getMogInfo(mbo)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
const mogAngleOutliers = g.getElementsByTagName('mog-angle-outlier')
|
||||
if (mogAngleOutliers.length) issues.add('mog-angle-outlier')
|
||||
const mogAngleOutliers = g.getElementsByTagName('mog-angle-outlier');
|
||||
if (mogAngleOutliers.length) issues.add('mog-angle-outlier');
|
||||
|
||||
for (let j = 0, jl = mogAngleOutliers.length; j < jl; ++j) {
|
||||
const mao = mogAngleOutliers[j].attributes
|
||||
const atoms = getItem(mao, 'atoms').split(',')
|
||||
const idx = angleOutliers.data.length
|
||||
const atomA = index.findAtomOnResidue(rI, atoms[0])
|
||||
const atomB = index.findAtomOnResidue(rI, atoms[1])
|
||||
const atomC = index.findAtomOnResidue(rI, atoms[2])
|
||||
addIndex(idx, atomA, angleOutliers.index)
|
||||
addIndex(idx, atomB, angleOutliers.index)
|
||||
addIndex(idx, atomC, angleOutliers.index)
|
||||
const mao = mogAngleOutliers[j].attributes;
|
||||
const atoms = getItem(mao, 'atoms').split(',');
|
||||
const idx = angleOutliers.data.length;
|
||||
const atomA = index.findAtomOnResidue(rI, atoms[0]);
|
||||
const atomB = index.findAtomOnResidue(rI, atoms[1]);
|
||||
const atomC = index.findAtomOnResidue(rI, atoms[2]);
|
||||
addIndex(idx, atomA, angleOutliers.index);
|
||||
addIndex(idx, atomB, angleOutliers.index);
|
||||
addIndex(idx, atomC, angleOutliers.index);
|
||||
angleOutliers.data.push({
|
||||
tag: 'mog-angle-outlier', atomA, atomB, atomC, ...getMogInfo(mao)
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const clashes = g.getElementsByTagName('clash')
|
||||
if (clashes.length) issues.add('clash')
|
||||
const clashes = g.getElementsByTagName('clash');
|
||||
if (clashes.length) issues.add('clash');
|
||||
|
||||
for (let j = 0, jl = clashes.length; j < jl; ++j) {
|
||||
const ca = clashes[j].attributes
|
||||
const id = parseInt(getItem(ca, 'cid'))
|
||||
const magnitude = parseFloat(getItem(ca, 'clashmag'))
|
||||
const distance = parseFloat(getItem(ca, 'dist'))
|
||||
const label_atom_id = getItem(ca, 'atom')
|
||||
const element = index.findAtomOnResidue(rI, label_atom_id, label_alt_id)
|
||||
const ca = clashes[j].attributes;
|
||||
const id = parseInt(getItem(ca, 'cid'));
|
||||
const magnitude = parseFloat(getItem(ca, 'clashmag'));
|
||||
const distance = parseFloat(getItem(ca, 'dist'));
|
||||
const label_atom_id = getItem(ca, 'atom');
|
||||
const element = index.findAtomOnResidue(rI, label_atom_id, label_alt_id);
|
||||
if (element !== -1) {
|
||||
clashesBuilder.add(element, id, magnitude, distance, false)
|
||||
clashesBuilder.add(element, id, magnitude, distance, false);
|
||||
}
|
||||
}
|
||||
|
||||
const symmClashes = g.getElementsByTagName('symm-clash')
|
||||
if (symmClashes.length) issues.add('symm-clash')
|
||||
const symmClashes = g.getElementsByTagName('symm-clash');
|
||||
if (symmClashes.length) issues.add('symm-clash');
|
||||
|
||||
for (let j = 0, jl = symmClashes.length; j < jl; ++j) {
|
||||
const sca = symmClashes[j].attributes
|
||||
const id = parseInt(getItem(sca, 'scid'))
|
||||
const magnitude = parseFloat(getItem(sca, 'clashmag'))
|
||||
const distance = parseFloat(getItem(sca, 'dist'))
|
||||
const label_atom_id = getItem(sca, 'atom')
|
||||
const element = index.findAtomOnResidue(rI, label_atom_id, label_alt_id)
|
||||
const sca = symmClashes[j].attributes;
|
||||
const id = parseInt(getItem(sca, 'scid'));
|
||||
const magnitude = parseFloat(getItem(sca, 'clashmag'));
|
||||
const distance = parseFloat(getItem(sca, 'dist'));
|
||||
const label_atom_id = getItem(sca, 'atom');
|
||||
const element = index.findAtomOnResidue(rI, label_atom_id, label_alt_id);
|
||||
if (element !== -1) {
|
||||
clashesBuilder.add(element, id, magnitude, distance, true)
|
||||
clashesBuilder.add(element, id, magnitude, distance, true);
|
||||
}
|
||||
}
|
||||
|
||||
geometryIssues.set(rI, issues)
|
||||
geometryIssues.set(rI, issues);
|
||||
}
|
||||
|
||||
const clashes = clashesBuilder.get()
|
||||
const clashes = clashesBuilder.get();
|
||||
|
||||
const validationReport = {
|
||||
rsrz, rscc, rci, geometryIssues,
|
||||
bondOutliers, angleOutliers,
|
||||
clashes
|
||||
}
|
||||
};
|
||||
|
||||
return validationReport
|
||||
return validationReport;
|
||||
}
|
||||
@@ -13,47 +13,48 @@ import { PickingId } from '../../../mol-geo/geometry/picking';
|
||||
import { EmptyLoci, Loci, DataLoci } from '../../../mol-model/loci';
|
||||
import { Interval } from '../../../mol-data/int';
|
||||
import { RepresentationContext, RepresentationParamsGetter, Representation } from '../../../mol-repr/representation';
|
||||
import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation, getUnitKindsParam } from '../../../mol-repr/structure/representation';
|
||||
import { UnitsRepresentation, StructureRepresentation, StructureRepresentationStateBuilder, StructureRepresentationProvider, ComplexRepresentation } from '../../../mol-repr/structure/representation';
|
||||
import { VisualContext } from '../../../mol-repr/visual';
|
||||
import { createLinkCylinderMesh, LinkCylinderParams, LinkCylinderStyle } from '../../../mol-repr/structure/visual/util/link';
|
||||
import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual, StructureGroup } from '../../../mol-repr/structure/units-visual';
|
||||
import { VisualUpdateState } from '../../../mol-repr/util';
|
||||
import { LocationIterator } from '../../../mol-geo/util/location-iterator';
|
||||
import { ClashesProvider, IntraUnitClashes, InterUnitClashes, ValidationReport } from '../validation-report';
|
||||
import { CustomProperty } from '../../common/custom-property';
|
||||
import { ClashesProvider, IntraUnitClashes, InterUnitClashes, ValidationReport } from './prop';
|
||||
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
|
||||
import { ComplexMeshParams, ComplexVisual, ComplexMeshVisual } from '../../../mol-repr/structure/complex-visual';
|
||||
import { Color } from '../../../mol-util/color';
|
||||
import { MarkerActions } from '../../../mol-util/marker-action';
|
||||
import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { bondLabel } from '../../../mol-theme/label';
|
||||
import { getUnitKindsParam } from '../../../mol-repr/structure/params';
|
||||
|
||||
//
|
||||
|
||||
function createIntraUnitClashCylinderMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<IntraUnitClashParams>, mesh?: Mesh) {
|
||||
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh)
|
||||
if (!Unit.isAtomic(unit)) return Mesh.createEmpty(mesh);
|
||||
|
||||
const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id)
|
||||
const { edgeCount, a, b, edgeProps } = clashes
|
||||
const { magnitude } = edgeProps
|
||||
const { sizeFactor } = props
|
||||
const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id);
|
||||
const { edgeCount, a, b, edgeProps } = clashes;
|
||||
const { magnitude } = edgeProps;
|
||||
const { sizeFactor } = props;
|
||||
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh)
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh);
|
||||
|
||||
const { elements } = unit
|
||||
const pos = unit.conformation.invariantPosition
|
||||
const { elements } = unit;
|
||||
const pos = unit.conformation.invariantPosition;
|
||||
|
||||
const builderProps = {
|
||||
linkCount: edgeCount * 2,
|
||||
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
|
||||
pos(elements[a[edgeIndex]], posA)
|
||||
pos(elements[b[edgeIndex]], posB)
|
||||
pos(elements[a[edgeIndex]], posA);
|
||||
pos(elements[b[edgeIndex]], posB);
|
||||
},
|
||||
style: (edgeIndex: number) => LinkCylinderStyle.Disk,
|
||||
radius: (edgeIndex: number) => magnitude[edgeIndex] * sizeFactor,
|
||||
}
|
||||
};
|
||||
|
||||
return createLinkCylinderMesh(ctx, builderProps, props, mesh)
|
||||
return createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
}
|
||||
|
||||
export const IntraUnitClashParams = {
|
||||
@@ -61,7 +62,7 @@ export const IntraUnitClashParams = {
|
||||
...LinkCylinderParams,
|
||||
linkCap: PD.Boolean(true),
|
||||
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.01 }),
|
||||
}
|
||||
};
|
||||
export type IntraUnitClashParams = typeof IntraUnitClashParams
|
||||
|
||||
export function IntraUnitClashVisual(materialId: number): UnitsVisual<IntraUnitClashParams> {
|
||||
@@ -78,95 +79,95 @@ export function IntraUnitClashVisual(materialId: number): UnitsVisual<IntraUnitC
|
||||
newProps.linkScale !== currentProps.linkScale ||
|
||||
newProps.linkSpacing !== currentProps.linkSpacing ||
|
||||
newProps.linkCap !== currentProps.linkCap
|
||||
)
|
||||
);
|
||||
}
|
||||
}, materialId)
|
||||
}, materialId);
|
||||
}
|
||||
|
||||
function getIntraClashBoundingSphere(unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[], boundingSphere: Sphere3D) {
|
||||
return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
|
||||
unit.conformation.position(unit.elements[clashes.a[elements[i]]], pA)
|
||||
unit.conformation.position(unit.elements[clashes.b[elements[i]]], pB)
|
||||
}, boundingSphere)
|
||||
unit.conformation.position(unit.elements[clashes.a[elements[i]]], pA);
|
||||
unit.conformation.position(unit.elements[clashes.b[elements[i]]], pB);
|
||||
}, boundingSphere);
|
||||
}
|
||||
|
||||
function getIntraClashLabel(structure: Structure, unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) {
|
||||
const idx = elements[0]
|
||||
if (idx === undefined) return ''
|
||||
const { edgeProps: { id, magnitude, distance } } = clashes
|
||||
const mag = magnitude[idx].toFixed(2)
|
||||
const dist = distance[idx].toFixed(2)
|
||||
const idx = elements[0];
|
||||
if (idx === undefined) return '';
|
||||
const { edgeProps: { id, magnitude, distance } } = clashes;
|
||||
const mag = magnitude[idx].toFixed(2);
|
||||
const dist = distance[idx].toFixed(2);
|
||||
|
||||
return [
|
||||
`Clash id: ${id[idx]} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`,
|
||||
bondLabel(Bond.Location(structure, unit, clashes.a[idx], structure, unit, clashes.b[idx]))
|
||||
].join('</br>')
|
||||
].join('</br>');
|
||||
}
|
||||
|
||||
function IntraClashLoci(structure: Structure, unit: Unit.Atomic, clashes: IntraUnitClashes, elements: number[]) {
|
||||
return DataLoci('intra-clashes', { unit, clashes }, elements,
|
||||
(boundingSphere: Sphere3D) => getIntraClashBoundingSphere(unit, clashes, elements, boundingSphere),
|
||||
() => getIntraClashLabel(structure, unit, clashes, elements))
|
||||
() => getIntraClashLabel(structure, unit, clashes, elements));
|
||||
}
|
||||
|
||||
function getIntraClashLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number) {
|
||||
const { objectId, instanceId, groupId } = pickingId
|
||||
const { objectId, instanceId, groupId } = pickingId;
|
||||
if (id === objectId) {
|
||||
const { structure, group } = structureGroup
|
||||
const unit = group.units[instanceId]
|
||||
const { structure, group } = structureGroup;
|
||||
const unit = group.units[instanceId];
|
||||
if (Unit.isAtomic(unit)) {
|
||||
const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id)
|
||||
return IntraClashLoci(structure, unit, clashes, [groupId])
|
||||
const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id);
|
||||
return IntraClashLoci(structure, unit, clashes, [groupId]);
|
||||
}
|
||||
}
|
||||
return EmptyLoci
|
||||
return EmptyLoci;
|
||||
}
|
||||
|
||||
function eachIntraClash(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
|
||||
let changed = false
|
||||
let changed = false;
|
||||
// TODO
|
||||
return changed
|
||||
return changed;
|
||||
}
|
||||
|
||||
function createIntraClashIterator(structureGroup: StructureGroup): LocationIterator {
|
||||
const { structure, group } = structureGroup
|
||||
const unit = group.units[0]
|
||||
const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id)
|
||||
const { a } = clashes
|
||||
const groupCount = clashes.edgeCount * 2
|
||||
const instanceCount = group.units.length
|
||||
const location = StructureElement.Location.create(structure)
|
||||
const { structure, group } = structureGroup;
|
||||
const unit = group.units[0];
|
||||
const clashes = ClashesProvider.get(structure).value!.intraUnit.get(unit.id);
|
||||
const { a } = clashes;
|
||||
const groupCount = clashes.edgeCount * 2;
|
||||
const instanceCount = group.units.length;
|
||||
const location = StructureElement.Location.create(structure);
|
||||
const getLocation = (groupIndex: number, instanceIndex: number) => {
|
||||
const unit = group.units[instanceIndex]
|
||||
location.unit = unit
|
||||
location.element = unit.elements[a[groupIndex]]
|
||||
return location
|
||||
}
|
||||
return LocationIterator(groupCount, instanceCount, getLocation)
|
||||
const unit = group.units[instanceIndex];
|
||||
location.unit = unit;
|
||||
location.element = unit.elements[a[groupIndex]];
|
||||
return location;
|
||||
};
|
||||
return LocationIterator(groupCount, instanceCount, getLocation);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
function createInterUnitClashCylinderMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<InterUnitClashParams>, mesh?: Mesh) {
|
||||
const clashes = ClashesProvider.get(structure).value!.interUnit
|
||||
const { edges, edgeCount } = clashes
|
||||
const { sizeFactor } = props
|
||||
const clashes = ClashesProvider.get(structure).value!.interUnit;
|
||||
const { edges, edgeCount } = clashes;
|
||||
const { sizeFactor } = props;
|
||||
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh)
|
||||
if (!edgeCount) return Mesh.createEmpty(mesh);
|
||||
|
||||
const builderProps = {
|
||||
linkCount: edgeCount,
|
||||
position: (posA: Vec3, posB: Vec3, edgeIndex: number) => {
|
||||
const b = edges[edgeIndex]
|
||||
const uA = b.unitA, uB = b.unitB
|
||||
uA.conformation.position(uA.elements[b.indexA], posA)
|
||||
uB.conformation.position(uB.elements[b.indexB], posB)
|
||||
const b = edges[edgeIndex];
|
||||
const uA = b.unitA, uB = b.unitB;
|
||||
uA.conformation.position(uA.elements[b.indexA], posA);
|
||||
uB.conformation.position(uB.elements[b.indexB], posB);
|
||||
},
|
||||
style: (edgeIndex: number) => LinkCylinderStyle.Disk,
|
||||
radius: (edgeIndex: number) => edges[edgeIndex].props.magnitude * sizeFactor
|
||||
}
|
||||
};
|
||||
|
||||
return createLinkCylinderMesh(ctx, builderProps, props, mesh)
|
||||
return createLinkCylinderMesh(ctx, builderProps, props, mesh);
|
||||
}
|
||||
|
||||
export const InterUnitClashParams = {
|
||||
@@ -174,7 +175,7 @@ export const InterUnitClashParams = {
|
||||
...LinkCylinderParams,
|
||||
linkCap: PD.Boolean(true),
|
||||
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.01 }),
|
||||
}
|
||||
};
|
||||
export type InterUnitClashParams = typeof InterUnitClashParams
|
||||
|
||||
export function InterUnitClashVisual(materialId: number): ComplexVisual<InterUnitClashParams> {
|
||||
@@ -191,65 +192,65 @@ export function InterUnitClashVisual(materialId: number): ComplexVisual<InterUni
|
||||
newProps.linkScale !== currentProps.linkScale ||
|
||||
newProps.linkSpacing !== currentProps.linkSpacing ||
|
||||
newProps.linkCap !== currentProps.linkCap
|
||||
)
|
||||
);
|
||||
}
|
||||
}, materialId)
|
||||
}, materialId);
|
||||
}
|
||||
|
||||
function getInterClashBoundingSphere(clashes: InterUnitClashes, elements: number[], boundingSphere: Sphere3D) {
|
||||
return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
|
||||
const c = clashes.edges[elements[i]]
|
||||
c.unitA.conformation.position(c.unitA.elements[c.indexA], pA)
|
||||
c.unitB.conformation.position(c.unitB.elements[c.indexB], pB)
|
||||
}, boundingSphere)
|
||||
const c = clashes.edges[elements[i]];
|
||||
c.unitA.conformation.position(c.unitA.elements[c.indexA], pA);
|
||||
c.unitB.conformation.position(c.unitB.elements[c.indexB], pB);
|
||||
}, boundingSphere);
|
||||
}
|
||||
|
||||
function getInterClashLabel(structure: Structure, clashes: InterUnitClashes, elements: number[]) {
|
||||
const idx = elements[0]
|
||||
if (idx === undefined) return ''
|
||||
const c = clashes.edges[idx]
|
||||
const mag = c.props.magnitude.toFixed(2)
|
||||
const dist = c.props.distance.toFixed(2)
|
||||
const idx = elements[0];
|
||||
if (idx === undefined) return '';
|
||||
const c = clashes.edges[idx];
|
||||
const mag = c.props.magnitude.toFixed(2);
|
||||
const dist = c.props.distance.toFixed(2);
|
||||
|
||||
return [
|
||||
`Clash id: ${c.props.id} | Magnitude: ${mag} \u212B | Distance: ${dist} \u212B`,
|
||||
bondLabel(Bond.Location(structure, c.unitA, c.indexA, structure, c.unitB, c.indexB))
|
||||
].join('</br>')
|
||||
].join('</br>');
|
||||
}
|
||||
|
||||
function InterClashLoci(structure: Structure, clashes: InterUnitClashes, elements: number[]) {
|
||||
return DataLoci('inter-clashes', clashes, elements,
|
||||
(boundingSphere: Sphere3D) => getInterClashBoundingSphere(clashes, elements, boundingSphere),
|
||||
() => getInterClashLabel(structure, clashes, elements))
|
||||
() => getInterClashLabel(structure, clashes, elements));
|
||||
}
|
||||
|
||||
function getInterClashLoci(pickingId: PickingId, structure: Structure, id: number) {
|
||||
const { objectId, groupId } = pickingId
|
||||
const { objectId, groupId } = pickingId;
|
||||
if (id === objectId) {
|
||||
const clashes = ClashesProvider.get(structure).value!.interUnit
|
||||
return InterClashLoci(structure, clashes, [groupId])
|
||||
const clashes = ClashesProvider.get(structure).value!.interUnit;
|
||||
return InterClashLoci(structure, clashes, [groupId]);
|
||||
}
|
||||
return EmptyLoci
|
||||
return EmptyLoci;
|
||||
}
|
||||
|
||||
function eachInterClash(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
|
||||
let changed = false
|
||||
let changed = false;
|
||||
// TODO
|
||||
return changed
|
||||
return changed;
|
||||
}
|
||||
|
||||
function createInterClashIterator(structure: Structure): LocationIterator {
|
||||
const clashes = ClashesProvider.get(structure).value!.interUnit
|
||||
const groupCount = clashes.edgeCount
|
||||
const instanceCount = 1
|
||||
const location = StructureElement.Location.create(structure)
|
||||
const clashes = ClashesProvider.get(structure).value!.interUnit;
|
||||
const groupCount = clashes.edgeCount;
|
||||
const instanceCount = 1;
|
||||
const location = StructureElement.Location.create(structure);
|
||||
const getLocation = (groupIndex: number) => {
|
||||
const clash = clashes.edges[groupIndex]
|
||||
location.unit = clash.unitA
|
||||
location.element = clash.unitA.elements[clash.indexA]
|
||||
return location
|
||||
}
|
||||
return LocationIterator(groupCount, instanceCount, getLocation, true)
|
||||
const clash = clashes.edges[groupIndex];
|
||||
location.unit = clash.unitA;
|
||||
location.element = clash.unitA.elements[clash.indexA];
|
||||
return location;
|
||||
};
|
||||
return LocationIterator(groupCount, instanceCount, getLocation, true);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -257,24 +258,24 @@ function createInterClashIterator(structure: Structure): LocationIterator {
|
||||
const ClashesVisuals = {
|
||||
'intra-clash': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, IntraUnitClashParams>) => UnitsRepresentation('Intra-unit clash cylinder', ctx, getParams, IntraUnitClashVisual),
|
||||
'inter-clash': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, InterUnitClashParams>) => ComplexRepresentation('Inter-unit clash cylinder', ctx, getParams, InterUnitClashVisual),
|
||||
}
|
||||
};
|
||||
|
||||
export const ClashesParams = {
|
||||
...IntraUnitClashParams,
|
||||
...InterUnitClashParams,
|
||||
unitKinds: getUnitKindsParam(['atomic']),
|
||||
visuals: PD.MultiSelect(['intra-clash', 'inter-clash'], PD.objectToOptions(ClashesVisuals))
|
||||
}
|
||||
};
|
||||
export type ClashesParams = typeof ClashesParams
|
||||
export function getClashesParams(ctx: ThemeRegistryContext, structure: Structure) {
|
||||
return PD.clone(ClashesParams)
|
||||
return PD.clone(ClashesParams);
|
||||
}
|
||||
|
||||
export type ClashesRepresentation = StructureRepresentation<ClashesParams>
|
||||
export function ClashesRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, ClashesParams>): ClashesRepresentation {
|
||||
const repr = Representation.createMulti('Clashes', ctx, getParams, StructureRepresentationStateBuilder, ClashesVisuals as unknown as Representation.Def<Structure, ClashesParams>)
|
||||
repr.setState({ markerActions: MarkerActions.Highlighting })
|
||||
return repr
|
||||
const repr = Representation.createMulti('Clashes', ctx, getParams, StructureRepresentationStateBuilder, ClashesVisuals as unknown as Representation.Def<Structure, ClashesParams>);
|
||||
repr.setState({ markerActions: MarkerActions.Highlighting });
|
||||
return repr;
|
||||
}
|
||||
|
||||
export const ClashesRepresentationProvider = StructureRepresentationProvider({
|
||||
@@ -291,4 +292,4 @@ export const ClashesRepresentationProvider = StructureRepresentationProvider({
|
||||
attach: (ctx: CustomProperty.Context, structure: Structure) => ClashesProvider.attach(ctx, structure, void 0, true),
|
||||
detach: (data) => ClashesProvider.ref(data, false)
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -5,12 +5,12 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra'
|
||||
import { Mat4, Vec3, Vec4, EPSILON } from '../mol-math/linear-algebra';
|
||||
import { Viewport, cameraProject, cameraUnproject } from './camera/util';
|
||||
import { CameraTransitionManager } from './camera/transition';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
export { Camera }
|
||||
export { Camera };
|
||||
|
||||
class Camera {
|
||||
readonly view: Mat4 = Mat4.identity();
|
||||
@@ -66,8 +66,8 @@ class Camera {
|
||||
const changed = !Mat4.areEqual(this.projection, this.prevProjection, EPSILON) || !Mat4.areEqual(this.view, this.prevView, EPSILON);
|
||||
|
||||
if (changed) {
|
||||
Mat4.mul(this.projectionView, this.projection, this.view)
|
||||
Mat4.invert(this.inverseProjectionView, this.projectionView)
|
||||
Mat4.mul(this.projectionView, this.projection, this.view);
|
||||
Mat4.invert(this.inverseProjectionView, this.projectionView);
|
||||
|
||||
Mat4.copy(this.prevView, this.view);
|
||||
Mat4.copy(this.prevProjection, this.projection);
|
||||
@@ -86,30 +86,30 @@ class Camera {
|
||||
}
|
||||
|
||||
getTargetDistance(radius: number) {
|
||||
const r = Math.max(radius, 0.01)
|
||||
const { fov } = this.state
|
||||
const { width, height } = this.viewport
|
||||
const aspect = width / height
|
||||
const aspectFactor = (height < width ? 1 : aspect)
|
||||
return Math.abs((r / aspectFactor) / Math.sin(fov / 2))
|
||||
const r = Math.max(radius, 0.01);
|
||||
const { fov } = this.state;
|
||||
const { width, height } = this.viewport;
|
||||
const aspect = width / height;
|
||||
const aspectFactor = (height < width ? 1 : aspect);
|
||||
return Math.abs((r / aspectFactor) / Math.sin(fov / 2));
|
||||
}
|
||||
|
||||
getFocus(target: Vec3, radius: number, up?: Vec3, dir?: Vec3): Partial<Camera.Snapshot> {
|
||||
const r = Math.max(radius, 0.01)
|
||||
const targetDistance = this.getTargetDistance(r)
|
||||
const r = Math.max(radius, 0.01);
|
||||
const targetDistance = this.getTargetDistance(r);
|
||||
|
||||
Vec3.sub(this.deltaDirection, this.target, this.position)
|
||||
if (dir) Vec3.matchDirection(this.deltaDirection, dir, this.deltaDirection)
|
||||
Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance)
|
||||
Vec3.sub(this.newPosition, target, this.deltaDirection)
|
||||
Vec3.sub(this.deltaDirection, this.target, this.position);
|
||||
if (dir) Vec3.matchDirection(this.deltaDirection, dir, this.deltaDirection);
|
||||
Vec3.setMagnitude(this.deltaDirection, this.deltaDirection, targetDistance);
|
||||
Vec3.sub(this.newPosition, target, this.deltaDirection);
|
||||
|
||||
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state)
|
||||
state.target = Vec3.clone(target)
|
||||
state.radius = r
|
||||
state.position = Vec3.clone(this.newPosition)
|
||||
if (up) Vec3.matchDirection(state.up, up, state.up)
|
||||
const state = Camera.copySnapshot(Camera.createDefaultSnapshot(), this.state);
|
||||
state.target = Vec3.clone(target);
|
||||
state.radius = r;
|
||||
state.position = Vec3.clone(this.newPosition);
|
||||
if (up) Vec3.matchDirection(state.up, up, state.up);
|
||||
|
||||
return state
|
||||
return state;
|
||||
}
|
||||
|
||||
focus(target: Vec3, radius: number, durationMs?: number, up?: Vec3, dir?: Vec3) {
|
||||
@@ -119,11 +119,11 @@ class Camera {
|
||||
}
|
||||
|
||||
project(out: Vec4, point: Vec3) {
|
||||
return cameraProject(out, point, this.viewport, this.projectionView)
|
||||
return cameraProject(out, point, this.viewport, this.projectionView);
|
||||
}
|
||||
|
||||
unproject(out: Vec3, point: Vec3) {
|
||||
return cameraUnproject(out, point, this.viewport, this.inverseProjectionView)
|
||||
return cameraUnproject(out, point, this.viewport, this.inverseProjectionView);
|
||||
}
|
||||
|
||||
constructor(state?: Partial<Camera.Snapshot>, viewport = Viewport.create(-1, -1, 1, 1)) {
|
||||
@@ -151,12 +151,12 @@ namespace Camera {
|
||||
}
|
||||
|
||||
export function setViewOffset(out: ViewOffset, fullWidth: number, fullHeight: number, offsetX: number, offsetY: number, width: number, height: number) {
|
||||
out.fullWidth = fullWidth
|
||||
out.fullHeight = fullHeight
|
||||
out.offsetX = offsetX
|
||||
out.offsetY = offsetY
|
||||
out.width = width
|
||||
out.height = height
|
||||
out.fullWidth = fullWidth;
|
||||
out.fullHeight = fullHeight;
|
||||
out.offsetX = offsetX;
|
||||
out.offsetY = offsetY;
|
||||
out.width = width;
|
||||
out.height = height;
|
||||
}
|
||||
|
||||
export function createDefaultSnapshot(): Snapshot {
|
||||
@@ -209,90 +209,90 @@ namespace Camera {
|
||||
}
|
||||
|
||||
function updateOrtho(camera: Camera) {
|
||||
const { viewport, zoom, near, far, viewOffset } = camera
|
||||
const { viewport, zoom, near, far, viewOffset } = camera;
|
||||
|
||||
const fullLeft = -(viewport.width - viewport.x) / 2
|
||||
const fullRight = (viewport.width - viewport.x) / 2
|
||||
const fullTop = (viewport.height - viewport.y) / 2
|
||||
const fullBottom = -(viewport.height - viewport.y) / 2
|
||||
const fullLeft = -(viewport.width - viewport.x) / 2;
|
||||
const fullRight = (viewport.width - viewport.x) / 2;
|
||||
const fullTop = (viewport.height - viewport.y) / 2;
|
||||
const fullBottom = -(viewport.height - viewport.y) / 2;
|
||||
|
||||
const dx = (fullRight - fullLeft) / (2 * zoom)
|
||||
const dy = (fullTop - fullBottom) / (2 * zoom)
|
||||
const cx = (fullRight + fullLeft) / 2
|
||||
const cy = (fullTop + fullBottom) / 2
|
||||
const dx = (fullRight - fullLeft) / (2 * zoom);
|
||||
const dy = (fullTop - fullBottom) / (2 * zoom);
|
||||
const cx = (fullRight + fullLeft) / 2;
|
||||
const cy = (fullTop + fullBottom) / 2;
|
||||
|
||||
let left = cx - dx
|
||||
let right = cx + dx
|
||||
let top = cy + dy
|
||||
let bottom = cy - dy
|
||||
let left = cx - dx;
|
||||
let right = cx + dx;
|
||||
let top = cy + dy;
|
||||
let bottom = cy - dy;
|
||||
|
||||
if (viewOffset.enabled) {
|
||||
const zoomW = zoom / (viewOffset.width / viewOffset.fullWidth)
|
||||
const zoomH = zoom / (viewOffset.height / viewOffset.fullHeight)
|
||||
const scaleW = (fullRight - fullLeft) / viewOffset.width
|
||||
const scaleH = (fullTop - fullBottom) / viewOffset.height
|
||||
left += scaleW * (viewOffset.offsetX / zoomW)
|
||||
right = left + scaleW * (viewOffset.width / zoomW)
|
||||
top -= scaleH * (viewOffset.offsetY / zoomH)
|
||||
bottom = top - scaleH * (viewOffset.height / zoomH)
|
||||
const zoomW = zoom / (viewOffset.width / viewOffset.fullWidth);
|
||||
const zoomH = zoom / (viewOffset.height / viewOffset.fullHeight);
|
||||
const scaleW = (fullRight - fullLeft) / viewOffset.width;
|
||||
const scaleH = (fullTop - fullBottom) / viewOffset.height;
|
||||
left += scaleW * (viewOffset.offsetX / zoomW);
|
||||
right = left + scaleW * (viewOffset.width / zoomW);
|
||||
top -= scaleH * (viewOffset.offsetY / zoomH);
|
||||
bottom = top - scaleH * (viewOffset.height / zoomH);
|
||||
}
|
||||
|
||||
// build projection matrix
|
||||
Mat4.ortho(camera.projection, left, right, top, bottom, near, far)
|
||||
Mat4.ortho(camera.projection, left, right, top, bottom, near, far);
|
||||
|
||||
// build view matrix
|
||||
Mat4.lookAt(camera.view, camera.position, camera.target, camera.up)
|
||||
Mat4.lookAt(camera.view, camera.position, camera.target, camera.up);
|
||||
}
|
||||
|
||||
function updatePers(camera: Camera) {
|
||||
const aspect = camera.viewport.width / camera.viewport.height
|
||||
const aspect = camera.viewport.width / camera.viewport.height;
|
||||
|
||||
const { near, far, viewOffset } = camera
|
||||
const { near, far, viewOffset } = camera;
|
||||
|
||||
let top = near * Math.tan(0.5 * camera.state.fov)
|
||||
let height = 2 * top
|
||||
let width = aspect * height
|
||||
let left = -0.5 * width
|
||||
let top = near * Math.tan(0.5 * camera.state.fov);
|
||||
let height = 2 * top;
|
||||
let width = aspect * height;
|
||||
let left = -0.5 * width;
|
||||
|
||||
if (viewOffset.enabled) {
|
||||
left += viewOffset.offsetX * width / viewOffset.fullWidth
|
||||
top -= viewOffset.offsetY * height / viewOffset.fullHeight
|
||||
width *= viewOffset.width / viewOffset.fullWidth
|
||||
height *= viewOffset.height / viewOffset.fullHeight
|
||||
left += viewOffset.offsetX * width / viewOffset.fullWidth;
|
||||
top -= viewOffset.offsetY * height / viewOffset.fullHeight;
|
||||
width *= viewOffset.width / viewOffset.fullWidth;
|
||||
height *= viewOffset.height / viewOffset.fullHeight;
|
||||
}
|
||||
|
||||
// build projection matrix
|
||||
Mat4.perspective(camera.projection, left, left + width, top, top - height, near, far)
|
||||
Mat4.perspective(camera.projection, left, left + width, top, top - height, near, far);
|
||||
|
||||
// build view matrix
|
||||
Mat4.lookAt(camera.view, camera.position, camera.target, camera.up)
|
||||
Mat4.lookAt(camera.view, camera.position, camera.target, camera.up);
|
||||
}
|
||||
|
||||
function updateClip(camera: Camera) {
|
||||
let { radius, radiusMax, mode, fog, clipFar } = camera.state
|
||||
if (radius < 0.01) radius = 0.01
|
||||
let { radius, radiusMax, mode, fog, clipFar } = camera.state;
|
||||
if (radius < 0.01) radius = 0.01;
|
||||
|
||||
const normalizedFar = clipFar ? radius : radiusMax
|
||||
const cameraDistance = Vec3.distance(camera.position, camera.target)
|
||||
let near = cameraDistance - radius
|
||||
let far = cameraDistance + normalizedFar
|
||||
const normalizedFar = clipFar ? radius : radiusMax;
|
||||
const cameraDistance = Vec3.distance(camera.position, camera.target);
|
||||
let near = cameraDistance - radius;
|
||||
let far = cameraDistance + normalizedFar;
|
||||
|
||||
const fogNearFactor = -(50 - fog) / 50
|
||||
let fogNear = cameraDistance - (normalizedFar * fogNearFactor)
|
||||
let fogFar = far
|
||||
const fogNearFactor = -(50 - fog) / 50;
|
||||
let fogNear = cameraDistance - (normalizedFar * fogNearFactor);
|
||||
let fogFar = far;
|
||||
|
||||
if (mode === 'perspective') {
|
||||
// set at least to 5 to avoid slow sphere impostor rendering
|
||||
near = Math.max(5, near)
|
||||
far = Math.max(5, far)
|
||||
near = Math.max(5, near);
|
||||
far = Math.max(5, far);
|
||||
} else {
|
||||
near = Math.max(0, near)
|
||||
far = Math.max(0, far)
|
||||
near = Math.max(0, near);
|
||||
far = Math.max(0, far);
|
||||
}
|
||||
|
||||
if (near === far) {
|
||||
// make sure near and far are not identical to avoid Infinity in the projection matrix
|
||||
far = near + 0.01
|
||||
far = near + 0.01;
|
||||
}
|
||||
|
||||
camera.near = near;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Camera } from '../camera';
|
||||
import { Quat, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { lerp } from '../../mol-math/interpolate';
|
||||
|
||||
export { CameraTransitionManager }
|
||||
export { CameraTransitionManager };
|
||||
|
||||
class CameraTransitionManager {
|
||||
private t = 0;
|
||||
@@ -21,8 +21,8 @@ class CameraTransitionManager {
|
||||
private _target: Camera.Snapshot = Camera.createDefaultSnapshot();
|
||||
private _current = Camera.createDefaultSnapshot();
|
||||
|
||||
get source(): Readonly<Camera.Snapshot> { return this._source }
|
||||
get target(): Readonly<Camera.Snapshot> { return this._target }
|
||||
get source(): Readonly<Camera.Snapshot> { return this._source; }
|
||||
get target(): Readonly<Camera.Snapshot> { return this._target; }
|
||||
|
||||
apply(to: Partial<Camera.Snapshot>, durationMs: number = 0, transition?: CameraTransitionManager.TransitionFunc) {
|
||||
if (!this.inTransition || durationMs > 0) {
|
||||
@@ -36,7 +36,7 @@ class CameraTransitionManager {
|
||||
Camera.copySnapshot(this._target, to);
|
||||
|
||||
if (this._target.radius > this._target.radiusMax) {
|
||||
this._target.radius = this._target.radiusMax
|
||||
this._target.radius = this._target.radiusMax;
|
||||
}
|
||||
|
||||
if (!this.inTransition && durationMs <= 0 || (typeof to.mode !== 'undefined' && to.mode !== this.camera.state.mode)) {
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { Mat4, Vec3, Vec4 } from '../../mol-math/linear-algebra'
|
||||
import { Mat4, Vec3, Vec4 } from '../../mol-math/linear-algebra';
|
||||
|
||||
export { Viewport }
|
||||
export { Viewport };
|
||||
|
||||
type Viewport = {
|
||||
x: number
|
||||
@@ -16,74 +16,74 @@ type Viewport = {
|
||||
}
|
||||
|
||||
function Viewport() {
|
||||
return Viewport.zero()
|
||||
return Viewport.zero();
|
||||
}
|
||||
|
||||
namespace Viewport {
|
||||
export function zero(): Viewport {
|
||||
return { x: 0, y: 0, width: 0, height: 0 }
|
||||
return { x: 0, y: 0, width: 0, height: 0 };
|
||||
}
|
||||
export function create(x: number, y: number, width: number, height: number): Viewport {
|
||||
return { x, y, width, height }
|
||||
return { x, y, width, height };
|
||||
}
|
||||
export function clone(viewport: Viewport): Viewport {
|
||||
return { ...viewport }
|
||||
return { ...viewport };
|
||||
}
|
||||
export function copy(target: Viewport, source: Viewport): Viewport {
|
||||
return Object.assign(target, source)
|
||||
return Object.assign(target, source);
|
||||
}
|
||||
export function set(viewport: Viewport, x: number, y: number, width: number, height: number): Viewport {
|
||||
viewport.x = x
|
||||
viewport.y = y
|
||||
viewport.width = width
|
||||
viewport.height = height
|
||||
return viewport
|
||||
viewport.x = x;
|
||||
viewport.y = y;
|
||||
viewport.width = width;
|
||||
viewport.height = height;
|
||||
return viewport;
|
||||
}
|
||||
|
||||
export function toVec4(v4: Vec4, viewport: Viewport): Vec4 {
|
||||
v4[0] = viewport.x
|
||||
v4[1] = viewport.y
|
||||
v4[2] = viewport.width
|
||||
v4[3] = viewport.height
|
||||
return v4
|
||||
v4[0] = viewport.x;
|
||||
v4[1] = viewport.y;
|
||||
v4[2] = viewport.width;
|
||||
v4[3] = viewport.height;
|
||||
return v4;
|
||||
}
|
||||
|
||||
export function equals(a: Viewport, b: Viewport) {
|
||||
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height
|
||||
return a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
const NEAR_RANGE = 0
|
||||
const FAR_RANGE = 1
|
||||
const NEAR_RANGE = 0;
|
||||
const FAR_RANGE = 1;
|
||||
|
||||
const tmpVec4 = Vec4()
|
||||
const tmpVec4 = Vec4();
|
||||
|
||||
/** Transform point into 2D window coordinates. */
|
||||
export function cameraProject (out: Vec4, point: Vec3, viewport: Viewport, projectionView: Mat4) {
|
||||
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport
|
||||
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport;
|
||||
|
||||
// clip space -> NDC -> window coordinates, implicit 1.0 for w component
|
||||
Vec4.set(tmpVec4, point[0], point[1], point[2], 1.0)
|
||||
Vec4.set(tmpVec4, point[0], point[1], point[2], 1.0);
|
||||
|
||||
// transform into clip space
|
||||
Vec4.transformMat4(tmpVec4, tmpVec4, projectionView)
|
||||
Vec4.transformMat4(tmpVec4, tmpVec4, projectionView);
|
||||
|
||||
// transform into NDC
|
||||
const w = tmpVec4[3]
|
||||
const w = tmpVec4[3];
|
||||
if (w !== 0) {
|
||||
tmpVec4[0] /= w
|
||||
tmpVec4[1] /= w
|
||||
tmpVec4[2] /= w
|
||||
tmpVec4[0] /= w;
|
||||
tmpVec4[1] /= w;
|
||||
tmpVec4[2] /= w;
|
||||
}
|
||||
|
||||
// transform into window coordinates, set fourth component is (1/clip.w) as in gl_FragCoord.w
|
||||
out[0] = vX + vWidth / 2 * tmpVec4[0] + (0 + vWidth / 2)
|
||||
out[1] = vY + vHeight / 2 * tmpVec4[1] + (0 + vHeight / 2)
|
||||
out[2] = (FAR_RANGE - NEAR_RANGE) / 2 * tmpVec4[2] + (FAR_RANGE + NEAR_RANGE) / 2
|
||||
out[3] = w === 0 ? 0 : 1 / w
|
||||
return out
|
||||
out[0] = vX + vWidth / 2 * tmpVec4[0] + (0 + vWidth / 2);
|
||||
out[1] = vY + vHeight / 2 * tmpVec4[1] + (0 + vHeight / 2);
|
||||
out[2] = (FAR_RANGE - NEAR_RANGE) / 2 * tmpVec4[2] + (FAR_RANGE + NEAR_RANGE) / 2;
|
||||
out[3] = w === 0 ? 0 : 1 / w;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,14 +91,14 @@ export function cameraProject (out: Vec4, point: Vec3, viewport: Viewport, proje
|
||||
* The point must have x and y set to 2D window coordinates and z between 0 (near) and 1 (far).
|
||||
*/
|
||||
export function cameraUnproject (out: Vec3, point: Vec3, viewport: Viewport, inverseProjectionView: Mat4) {
|
||||
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport
|
||||
const { x: vX, y: vY, width: vWidth, height: vHeight } = viewport;
|
||||
|
||||
const x = point[0] - vX
|
||||
const y = (vHeight - point[1] - 1) - vY
|
||||
const z = point[2]
|
||||
const x = point[0] - vX;
|
||||
const y = (vHeight - point[1] - 1) - vY;
|
||||
const z = point[2];
|
||||
|
||||
out[0] = (2 * x) / vWidth - 1
|
||||
out[1] = (2 * y) / vHeight - 1
|
||||
out[2] = 2 * z - 1
|
||||
return Vec3.transformMat4(out, out, inverseProjectionView)
|
||||
out[0] = (2 * x) / vWidth - 1;
|
||||
out[1] = (2 * y) / vHeight - 1;
|
||||
out[2] = 2 * z - 1;
|
||||
return Vec3.transformMat4(out, out, inverseProjectionView);
|
||||
}
|
||||
@@ -7,12 +7,12 @@
|
||||
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { now } from '../mol-util/now';
|
||||
import { Vec3 } from '../mol-math/linear-algebra'
|
||||
import InputObserver, { ModifiersKeys, ButtonsType } from '../mol-util/input/input-observer'
|
||||
import Renderer, { RendererStats, RendererParams } from '../mol-gl/renderer'
|
||||
import { GraphicsRenderObject } from '../mol-gl/render-object'
|
||||
import { TrackballControls, TrackballControlsParams } from './controls/trackball'
|
||||
import { Viewport } from './camera/util'
|
||||
import { Vec3 } from '../mol-math/linear-algebra';
|
||||
import InputObserver, { ModifiersKeys, ButtonsType } from '../mol-util/input/input-observer';
|
||||
import Renderer, { RendererStats, RendererParams } from '../mol-gl/renderer';
|
||||
import { GraphicsRenderObject } from '../mol-gl/render-object';
|
||||
import { TrackballControls, TrackballControlsParams } from './controls/trackball';
|
||||
import { Viewport } from './camera/util';
|
||||
import { createContext, WebGLContext, getGLContext } from '../mol-gl/webgl/context';
|
||||
import { Representation } from '../mol-repr/representation';
|
||||
import Scene from '../mol-gl/scene';
|
||||
@@ -35,6 +35,7 @@ import { ImagePass, ImageProps } from './passes/image';
|
||||
import { Sphere3D } from '../mol-math/geometry';
|
||||
import { isDebugMode } from '../mol-util/debug';
|
||||
import { CameraHelperParams } from './helper/camera-helper';
|
||||
import { produce } from 'immer';
|
||||
|
||||
export const Canvas3DParams = {
|
||||
camera: PD.Group({
|
||||
@@ -60,11 +61,11 @@ export const Canvas3DParams = {
|
||||
renderer: PD.Group(RendererParams),
|
||||
trackball: PD.Group(TrackballControlsParams),
|
||||
debug: PD.Group(DebugHelperParams)
|
||||
}
|
||||
};
|
||||
export const DefaultCanvas3DParams = PD.getDefaultValues(Canvas3DParams);
|
||||
export type Canvas3DProps = PD.Values<typeof Canvas3DParams>
|
||||
|
||||
export { Canvas3D }
|
||||
export { Canvas3D };
|
||||
|
||||
interface Canvas3D {
|
||||
readonly webgl: WebGLContext,
|
||||
@@ -93,9 +94,8 @@ interface Canvas3D {
|
||||
requestCameraReset(options?: { durationMs?: number, snapshot?: Partial<Camera.Snapshot> }): void
|
||||
readonly camera: Camera
|
||||
readonly boundingSphere: Readonly<Sphere3D>
|
||||
downloadScreenshot(): void
|
||||
getPixelData(variant: GraphicsRenderVariant): PixelData
|
||||
setProps(props: Partial<Canvas3DProps>): void
|
||||
setProps(props: Partial<Canvas3DProps> | ((old: Canvas3DProps) => Partial<Canvas3DProps> | void)): void
|
||||
getImagePass(props: Partial<ImageProps>): ImagePass
|
||||
|
||||
/** Returns a copy of the current Canvas3D instance props */
|
||||
@@ -107,7 +107,7 @@ interface Canvas3D {
|
||||
dispose(): void
|
||||
}
|
||||
|
||||
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()))
|
||||
const requestAnimationFrame = typeof window !== 'undefined' ? window.requestAnimationFrame : (f: (time: number) => void) => setImmediate(()=>f(Date.now()));
|
||||
|
||||
namespace Canvas3D {
|
||||
export interface HoverEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys }
|
||||
@@ -120,144 +120,139 @@ namespace Canvas3D {
|
||||
depth: true,
|
||||
preserveDrawingBuffer: true,
|
||||
premultipliedAlpha: false,
|
||||
})
|
||||
if (gl === null) throw new Error('Could not create a WebGL rendering context')
|
||||
const input = InputObserver.fromElement(canvas)
|
||||
const webgl = createContext(gl)
|
||||
});
|
||||
if (gl === null) throw new Error('Could not create a WebGL rendering context');
|
||||
const input = InputObserver.fromElement(canvas);
|
||||
const webgl = createContext(gl);
|
||||
|
||||
if (isDebugMode) {
|
||||
const loseContextExt = gl.getExtension('WEBGL_lose_context')
|
||||
const loseContextExt = gl.getExtension('WEBGL_lose_context');
|
||||
if (loseContextExt) {
|
||||
canvas.addEventListener('mousedown', e => {
|
||||
if (webgl.isContextLost) return
|
||||
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return
|
||||
if (webgl.isContextLost) return;
|
||||
if (!e.shiftKey || !e.ctrlKey || !e.altKey) return;
|
||||
|
||||
console.log('lose context')
|
||||
loseContextExt.loseContext()
|
||||
console.log('lose context');
|
||||
loseContextExt.loseContext();
|
||||
|
||||
setTimeout(() => {
|
||||
if (!webgl.isContextLost) return
|
||||
console.log('restore context')
|
||||
loseContextExt.restoreContext()
|
||||
}, 1000)
|
||||
}, false)
|
||||
if (!webgl.isContextLost) return;
|
||||
console.log('restore context');
|
||||
loseContextExt.restoreContext();
|
||||
}, 1000);
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.khronos.org/webgl/wiki/HandlingContextLost
|
||||
|
||||
canvas.addEventListener('webglcontextlost', e => {
|
||||
webgl.setContextLost()
|
||||
e.preventDefault()
|
||||
if (isDebugMode) console.log('context lost')
|
||||
}, false)
|
||||
webgl.setContextLost();
|
||||
e.preventDefault();
|
||||
if (isDebugMode) console.log('context lost');
|
||||
}, false);
|
||||
|
||||
canvas.addEventListener('webglcontextrestored', () => {
|
||||
if (!webgl.isContextLost) return
|
||||
webgl.handleContextRestored()
|
||||
if (isDebugMode) console.log('context restored')
|
||||
}, false)
|
||||
if (!webgl.isContextLost) return;
|
||||
webgl.handleContextRestored();
|
||||
if (isDebugMode) console.log('context restored');
|
||||
}, false);
|
||||
|
||||
return Canvas3D.create(webgl, input, props)
|
||||
return Canvas3D.create(webgl, input, props);
|
||||
}
|
||||
|
||||
export function create(webgl: WebGLContext, input: InputObserver, props: Partial<Canvas3DProps> = {}): Canvas3D {
|
||||
const p = { ...DefaultCanvas3DParams, ...props }
|
||||
const p = { ...DefaultCanvas3DParams, ...props };
|
||||
|
||||
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>()
|
||||
const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>()
|
||||
const reprCount = new BehaviorSubject(0)
|
||||
const reprRenderObjects = new Map<Representation.Any, Set<GraphicsRenderObject>>();
|
||||
const reprUpdatedSubscriptions = new Map<Representation.Any, Subscription>();
|
||||
const reprCount = new BehaviorSubject(0);
|
||||
|
||||
const startTime = now()
|
||||
const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp)
|
||||
const startTime = now();
|
||||
const didDraw = new BehaviorSubject<now.Timestamp>(0 as now.Timestamp);
|
||||
|
||||
const { gl, contextRestored } = webgl
|
||||
const { gl, contextRestored } = webgl;
|
||||
|
||||
let width = gl.drawingBufferWidth
|
||||
let height = gl.drawingBufferHeight
|
||||
let width = gl.drawingBufferWidth;
|
||||
let height = gl.drawingBufferHeight;
|
||||
|
||||
const scene = Scene.create(webgl)
|
||||
const scene = Scene.create(webgl);
|
||||
|
||||
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
|
||||
})
|
||||
});
|
||||
|
||||
const controls = TrackballControls.create(input, camera, p.trackball)
|
||||
const renderer = Renderer.create(webgl, p.renderer)
|
||||
const controls = TrackballControls.create(input, camera, p.trackball);
|
||||
const renderer = Renderer.create(webgl, p.renderer);
|
||||
const debugHelper = new BoundingSphereHelper(webgl, scene, p.debug);
|
||||
const interactionHelper = new Canvas3dInteractionHelper(identify, getLoci, input);
|
||||
|
||||
const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, {
|
||||
cameraHelper: p.camera.helper
|
||||
})
|
||||
const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5)
|
||||
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing)
|
||||
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample)
|
||||
});
|
||||
const pickPass = new PickPass(webgl, renderer, scene, camera, 0.5);
|
||||
const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing);
|
||||
const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample);
|
||||
|
||||
const contextRestoredSub = contextRestored.subscribe(() => {
|
||||
pickPass.pickDirty = true
|
||||
draw(true)
|
||||
})
|
||||
|
||||
let drawPending = false
|
||||
let cameraResetRequested = false
|
||||
let nextCameraResetDuration: number | undefined = void 0
|
||||
let nextCameraResetSnapshot: Partial<Camera.Snapshot> | undefined = void 0
|
||||
let drawPending = false;
|
||||
let cameraResetRequested = false;
|
||||
let nextCameraResetDuration: number | undefined = void 0;
|
||||
let nextCameraResetSnapshot: Partial<Camera.Snapshot> | undefined = void 0;
|
||||
|
||||
function getLoci(pickingId: PickingId) {
|
||||
let loci: Loci = EmptyLoci
|
||||
let repr: Representation.Any = Representation.Empty
|
||||
let loci: Loci = EmptyLoci;
|
||||
let repr: Representation.Any = Representation.Empty;
|
||||
reprRenderObjects.forEach((_, _repr) => {
|
||||
const _loci = _repr.getLoci(pickingId)
|
||||
const _loci = _repr.getLoci(pickingId);
|
||||
if (!isEmptyLoci(_loci)) {
|
||||
if (!isEmptyLoci(loci)) {
|
||||
console.warn('found another loci, this should not happen')
|
||||
console.warn('found another loci, this should not happen');
|
||||
}
|
||||
loci = _loci
|
||||
repr = _repr
|
||||
loci = _loci;
|
||||
repr = _repr;
|
||||
}
|
||||
})
|
||||
return { loci, repr }
|
||||
});
|
||||
return { loci, repr };
|
||||
}
|
||||
|
||||
function mark(reprLoci: Representation.Loci, action: MarkerAction) {
|
||||
const { repr, loci } = reprLoci
|
||||
let changed = false
|
||||
const { repr, loci } = reprLoci;
|
||||
let changed = false;
|
||||
if (repr) {
|
||||
changed = repr.mark(loci, action)
|
||||
changed = repr.mark(loci, action);
|
||||
} else {
|
||||
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed })
|
||||
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
|
||||
}
|
||||
if (changed) {
|
||||
scene.update(void 0, true)
|
||||
const prevPickDirty = pickPass.pickDirty
|
||||
draw(true)
|
||||
pickPass.pickDirty = prevPickDirty // marking does not change picking buffers
|
||||
scene.update(void 0, true);
|
||||
const prevPickDirty = pickPass.pickDirty;
|
||||
draw(true);
|
||||
pickPass.pickDirty = prevPickDirty; // marking does not change picking buffers
|
||||
}
|
||||
}
|
||||
|
||||
function render(force: boolean) {
|
||||
if (webgl.isContextLost) return false
|
||||
if (webgl.isContextLost) return false;
|
||||
|
||||
let didRender = false
|
||||
controls.update(currentTime)
|
||||
Viewport.set(camera.viewport, 0, 0, width, height)
|
||||
const cameraChanged = camera.update()
|
||||
multiSample.update(force || cameraChanged, currentTime)
|
||||
let didRender = false;
|
||||
controls.update(currentTime);
|
||||
Viewport.set(camera.viewport, 0, 0, width, height);
|
||||
const cameraChanged = camera.update();
|
||||
multiSample.update(force || cameraChanged, currentTime);
|
||||
|
||||
if (force || cameraChanged || multiSample.enabled) {
|
||||
renderer.setViewport(0, 0, width, height)
|
||||
renderer.setViewport(0, 0, width, height);
|
||||
if (multiSample.enabled) {
|
||||
multiSample.render(true, p.transparentBackground)
|
||||
multiSample.render(true, p.transparentBackground);
|
||||
} else {
|
||||
drawPass.render(!postprocessing.enabled, p.transparentBackground)
|
||||
if (postprocessing.enabled) postprocessing.render(true)
|
||||
drawPass.render(!postprocessing.enabled, p.transparentBackground);
|
||||
if (postprocessing.enabled) postprocessing.render(true);
|
||||
}
|
||||
pickPass.pickDirty = true
|
||||
didRender = true
|
||||
pickPass.pickDirty = true;
|
||||
didRender = true;
|
||||
}
|
||||
|
||||
return didRender;
|
||||
@@ -268,15 +263,15 @@ namespace Canvas3D {
|
||||
|
||||
function draw(force?: boolean) {
|
||||
if (render(!!force || forceNextDraw)) {
|
||||
didDraw.next(now() - startTime as now.Timestamp)
|
||||
didDraw.next(now() - startTime as now.Timestamp);
|
||||
}
|
||||
forceNextDraw = false;
|
||||
drawPending = false
|
||||
drawPending = false;
|
||||
}
|
||||
|
||||
function requestDraw(force?: boolean) {
|
||||
if (drawPending) return
|
||||
drawPending = true
|
||||
if (drawPending) return;
|
||||
drawPending = true;
|
||||
forceNextDraw = !!force;
|
||||
}
|
||||
|
||||
@@ -289,11 +284,11 @@ namespace Canvas3D {
|
||||
if (!camera.transition.inTransition && !webgl.isContextLost) {
|
||||
interactionHelper.tick(currentTime);
|
||||
}
|
||||
requestAnimationFrame(animate)
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
function identify(x: number, y: number): PickingId | undefined {
|
||||
return webgl.isContextLost ? undefined : pickPass.identify(x, y)
|
||||
return webgl.isContextLost ? undefined : pickPass.identify(x, y);
|
||||
}
|
||||
|
||||
function commit(isSynchronous: boolean = false) {
|
||||
@@ -307,7 +302,7 @@ namespace Canvas3D {
|
||||
|
||||
const { center, radius } = scene.boundingSphereVisible;
|
||||
if (radius > 0) {
|
||||
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration
|
||||
const duration = nextCameraResetDuration === undefined ? p.cameraResetDurationMs : nextCameraResetDuration;
|
||||
const focus = camera.getFocus(center, radius);
|
||||
const snapshot = nextCameraResetSnapshot ? { ...focus, ...nextCameraResetSnapshot } : focus;
|
||||
camera.setState(snapshot, duration);
|
||||
@@ -326,8 +321,8 @@ namespace Canvas3D {
|
||||
|
||||
if (camera.transition.inTransition || nextCameraResetSnapshot) return false;
|
||||
|
||||
let cameraSphereOverlapsNone = true
|
||||
Sphere3D.set(cameraSphere, camera.state.target, camera.state.radius)
|
||||
let cameraSphereOverlapsNone = true;
|
||||
Sphere3D.set(cameraSphere, camera.state.target, camera.state.radius);
|
||||
|
||||
// check if any renderable has moved outside of the old bounding sphere
|
||||
// and if no renderable is overlapping with the camera sphere
|
||||
@@ -359,7 +354,7 @@ namespace Canvas3D {
|
||||
}
|
||||
if (oldBoundingSphereVisible.radius === 0) nextCameraResetDuration = 0;
|
||||
|
||||
camera.setState({ radiusMax: scene.boundingSphere.radius }, 0)
|
||||
camera.setState({ radiusMax: scene.boundingSphere.radius }, 0);
|
||||
reprCount.next(reprRenderObjects.size);
|
||||
|
||||
return true;
|
||||
@@ -368,31 +363,31 @@ namespace Canvas3D {
|
||||
function add(repr: Representation.Any) {
|
||||
registerAutoUpdate(repr);
|
||||
|
||||
const oldRO = reprRenderObjects.get(repr)
|
||||
const newRO = new Set<GraphicsRenderObject>()
|
||||
repr.renderObjects.forEach(o => newRO.add(o))
|
||||
const oldRO = reprRenderObjects.get(repr);
|
||||
const newRO = new Set<GraphicsRenderObject>();
|
||||
repr.renderObjects.forEach(o => newRO.add(o));
|
||||
|
||||
if (oldRO) {
|
||||
if (!SetUtils.areEqual(newRO, oldRO)) {
|
||||
newRO.forEach(o => { if (!oldRO.has(o)) scene.add(o) })
|
||||
oldRO.forEach(o => { if (!newRO.has(o)) scene.remove(o) })
|
||||
newRO.forEach(o => { if (!oldRO.has(o)) scene.add(o); });
|
||||
oldRO.forEach(o => { if (!newRO.has(o)) scene.remove(o); });
|
||||
}
|
||||
} else {
|
||||
repr.renderObjects.forEach(o => scene.add(o))
|
||||
repr.renderObjects.forEach(o => scene.add(o));
|
||||
}
|
||||
reprRenderObjects.set(repr, newRO)
|
||||
reprRenderObjects.set(repr, newRO);
|
||||
|
||||
scene.update(repr.renderObjects, false)
|
||||
scene.update(repr.renderObjects, false);
|
||||
}
|
||||
|
||||
function remove(repr: Representation.Any) {
|
||||
unregisterAutoUpdate(repr);
|
||||
|
||||
const renderObjects = reprRenderObjects.get(repr)
|
||||
const renderObjects = reprRenderObjects.get(repr);
|
||||
if (renderObjects) {
|
||||
renderObjects.forEach(o => scene.remove(o))
|
||||
reprRenderObjects.delete(repr)
|
||||
scene.update(repr.renderObjects, false, true)
|
||||
renderObjects.forEach(o => scene.remove(o));
|
||||
reprRenderObjects.delete(repr);
|
||||
scene.update(repr.renderObjects, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,7 +396,7 @@ namespace Canvas3D {
|
||||
|
||||
reprUpdatedSubscriptions.set(repr, repr.updated.subscribe(_ => {
|
||||
if (!repr.state.syncManually) add(repr);
|
||||
}))
|
||||
}));
|
||||
}
|
||||
|
||||
function unregisterAutoUpdate(repr: Representation.Any) {
|
||||
@@ -412,7 +407,37 @@ namespace Canvas3D {
|
||||
}
|
||||
}
|
||||
|
||||
handleResize()
|
||||
function getProps(): Canvas3DProps {
|
||||
const radius = scene.boundingSphere.radius > 0
|
||||
? 100 - Math.round((camera.transition.target.radius / scene.boundingSphere.radius) * 100)
|
||||
: 0;
|
||||
|
||||
return {
|
||||
camera: {
|
||||
mode: camera.state.mode,
|
||||
helper: { ...drawPass.props.cameraHelper }
|
||||
},
|
||||
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 },
|
||||
cameraResetDurationMs: p.cameraResetDurationMs,
|
||||
transparentBackground: p.transparentBackground,
|
||||
|
||||
postprocessing: { ...postprocessing.props },
|
||||
multiSample: { ...multiSample.props },
|
||||
renderer: { ...renderer.props },
|
||||
trackball: { ...controls.props },
|
||||
debug: { ...debugHelper.props }
|
||||
};
|
||||
}
|
||||
|
||||
handleResize();
|
||||
|
||||
const contextRestoredSub = contextRestored.subscribe(() => {
|
||||
pickPass.pickDirty = true;
|
||||
draw(true);
|
||||
});
|
||||
|
||||
return {
|
||||
webgl,
|
||||
@@ -425,26 +450,26 @@ namespace Canvas3D {
|
||||
if (!reprRenderObjects.has(repr)) return;
|
||||
scene.update(repr.renderObjects, !!keepSphere);
|
||||
} else {
|
||||
scene.update(void 0, !!keepSphere)
|
||||
scene.update(void 0, !!keepSphere);
|
||||
}
|
||||
},
|
||||
clear: () => {
|
||||
reprUpdatedSubscriptions.forEach(v => v.unsubscribe())
|
||||
reprUpdatedSubscriptions.clear()
|
||||
reprRenderObjects.clear()
|
||||
scene.clear()
|
||||
debugHelper.clear()
|
||||
requestDraw(true)
|
||||
reprCount.next(reprRenderObjects.size)
|
||||
reprUpdatedSubscriptions.forEach(v => v.unsubscribe());
|
||||
reprUpdatedSubscriptions.clear();
|
||||
reprRenderObjects.clear();
|
||||
scene.clear();
|
||||
debugHelper.clear();
|
||||
requestDraw(true);
|
||||
reprCount.next(reprRenderObjects.size);
|
||||
},
|
||||
syncVisibility: () => {
|
||||
if (camera.state.radiusMax === 0) {
|
||||
cameraResetRequested = true
|
||||
nextCameraResetDuration = 0
|
||||
cameraResetRequested = true;
|
||||
nextCameraResetDuration = 0;
|
||||
}
|
||||
|
||||
if (scene.syncVisibility()) {
|
||||
if (debugHelper.isEnabled) debugHelper.update()
|
||||
if (debugHelper.isEnabled) debugHelper.update();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -463,63 +488,64 @@ namespace Canvas3D {
|
||||
},
|
||||
camera,
|
||||
boundingSphere: scene.boundingSphere,
|
||||
downloadScreenshot: () => {
|
||||
// TODO
|
||||
},
|
||||
getPixelData: (variant: GraphicsRenderVariant) => {
|
||||
switch (variant) {
|
||||
case 'color': return webgl.getDrawingBufferPixelData()
|
||||
case 'pickObject': return pickPass.objectPickTarget.getPixelData()
|
||||
case 'pickInstance': return pickPass.instancePickTarget.getPixelData()
|
||||
case 'pickGroup': return pickPass.groupPickTarget.getPixelData()
|
||||
case 'depth': return readTexture(webgl, drawPass.depthTexture) as PixelData
|
||||
case 'color': return webgl.getDrawingBufferPixelData();
|
||||
case 'pickObject': return pickPass.objectPickTarget.getPixelData();
|
||||
case 'pickInstance': return pickPass.instancePickTarget.getPixelData();
|
||||
case 'pickGroup': return pickPass.groupPickTarget.getPixelData();
|
||||
case 'depth': return readTexture(webgl, drawPass.depthTexture) as PixelData;
|
||||
}
|
||||
},
|
||||
didDraw,
|
||||
reprCount,
|
||||
setProps: (props: Partial<Canvas3DProps>) => {
|
||||
const cameraState: Partial<Camera.Snapshot> = Object.create(null)
|
||||
setProps: (properties) => {
|
||||
const props: Partial<Canvas3DProps> = typeof properties === 'function'
|
||||
? produce(getProps(), properties)
|
||||
: properties;
|
||||
|
||||
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
|
||||
cameraState.mode = props.camera.mode;
|
||||
}
|
||||
if (props.cameraFog !== undefined) {
|
||||
const newFog = props.cameraFog.name === 'on' ? props.cameraFog.params.intensity : 0
|
||||
if (newFog !== camera.state.fog) cameraState.fog = newFog
|
||||
const newFog = props.cameraFog.name === 'on' ? props.cameraFog.params.intensity : 0;
|
||||
if (newFog !== camera.state.fog) cameraState.fog = newFog;
|
||||
}
|
||||
if (props.cameraClipping !== undefined) {
|
||||
if (props.cameraClipping.far !== undefined && props.cameraClipping.far !== camera.state.clipFar) {
|
||||
cameraState.clipFar = props.cameraClipping.far
|
||||
cameraState.clipFar = props.cameraClipping.far;
|
||||
}
|
||||
if (props.cameraClipping.radius !== undefined) {
|
||||
const radius = (scene.boundingSphere.radius / 100) * (100 - props.cameraClipping.radius)
|
||||
const radius = (scene.boundingSphere.radius / 100) * (100 - props.cameraClipping.radius);
|
||||
if (radius > 0 && radius !== cameraState.radius) {
|
||||
// if radius = 0, NaNs happen
|
||||
cameraState.radius = Math.max(radius, 0.01)
|
||||
cameraState.radius = Math.max(radius, 0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Object.keys(cameraState).length > 0) camera.setState(cameraState)
|
||||
if (Object.keys(cameraState).length > 0) camera.setState(cameraState);
|
||||
|
||||
if (props.camera?.helper) drawPass.setProps({ cameraHelper: props.camera.helper })
|
||||
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs
|
||||
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground
|
||||
if (props.camera?.helper) drawPass.setProps({ cameraHelper: props.camera.helper });
|
||||
if (props.cameraResetDurationMs !== undefined) p.cameraResetDurationMs = props.cameraResetDurationMs;
|
||||
if (props.transparentBackground !== undefined) p.transparentBackground = props.transparentBackground;
|
||||
|
||||
if (props.postprocessing) postprocessing.setProps(props.postprocessing)
|
||||
if (props.multiSample) multiSample.setProps(props.multiSample)
|
||||
if (props.renderer) renderer.setProps(props.renderer)
|
||||
if (props.trackball) controls.setProps(props.trackball)
|
||||
if (props.debug) debugHelper.setProps(props.debug)
|
||||
if (props.postprocessing) postprocessing.setProps(props.postprocessing);
|
||||
if (props.multiSample) multiSample.setProps(props.multiSample);
|
||||
if (props.renderer) renderer.setProps(props.renderer);
|
||||
if (props.trackball) controls.setProps(props.trackball);
|
||||
if (props.debug) debugHelper.setProps(props.debug);
|
||||
|
||||
requestDraw(true)
|
||||
requestDraw(true);
|
||||
},
|
||||
getImagePass: (props: Partial<ImageProps> = {}) => {
|
||||
return new ImagePass(webgl, renderer, scene, camera, debugHelper, props)
|
||||
return new ImagePass(webgl, renderer, scene, camera, debugHelper, props);
|
||||
},
|
||||
|
||||
get props() {
|
||||
const radius = scene.boundingSphere.radius > 0
|
||||
? 100 - Math.round((camera.transition.target.radius / scene.boundingSphere.radius) * 100)
|
||||
: 0
|
||||
: 0;
|
||||
|
||||
return {
|
||||
camera: {
|
||||
@@ -538,43 +564,43 @@ namespace Canvas3D {
|
||||
renderer: { ...renderer.props },
|
||||
trackball: { ...controls.props },
|
||||
debug: { ...debugHelper.props }
|
||||
}
|
||||
};
|
||||
},
|
||||
get input() {
|
||||
return input
|
||||
return input;
|
||||
},
|
||||
get stats() {
|
||||
return renderer.stats
|
||||
return renderer.stats;
|
||||
},
|
||||
get interaction() {
|
||||
return interactionHelper.events
|
||||
return interactionHelper.events;
|
||||
},
|
||||
dispose: () => {
|
||||
contextRestoredSub.unsubscribe()
|
||||
contextRestoredSub.unsubscribe();
|
||||
|
||||
scene.clear()
|
||||
debugHelper.clear()
|
||||
input.dispose()
|
||||
controls.dispose()
|
||||
renderer.dispose()
|
||||
interactionHelper.dispose()
|
||||
scene.clear();
|
||||
debugHelper.clear();
|
||||
input.dispose();
|
||||
controls.dispose();
|
||||
renderer.dispose();
|
||||
interactionHelper.dispose();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function handleResize() {
|
||||
width = gl.drawingBufferWidth
|
||||
height = gl.drawingBufferHeight
|
||||
width = gl.drawingBufferWidth;
|
||||
height = gl.drawingBufferHeight;
|
||||
|
||||
renderer.setViewport(0, 0, width, height)
|
||||
Viewport.set(camera.viewport, 0, 0, width, height)
|
||||
Viewport.set(controls.viewport, 0, 0, width, height)
|
||||
renderer.setViewport(0, 0, width, height);
|
||||
Viewport.set(camera.viewport, 0, 0, width, height);
|
||||
Viewport.set(controls.viewport, 0, 0, width, height);
|
||||
|
||||
drawPass.setSize(width, height)
|
||||
pickPass.setSize(width, height)
|
||||
postprocessing.setSize(width, height)
|
||||
multiSample.setSize(width, height)
|
||||
drawPass.setSize(width, height);
|
||||
pickPass.setSize(width, height);
|
||||
postprocessing.setSize(width, height);
|
||||
multiSample.setSize(width, height);
|
||||
|
||||
requestDraw(true)
|
||||
requestDraw(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,9 @@ import { Camera } from '../camera';
|
||||
import { absMax } from '../../mol-math/misc';
|
||||
import { Binding } from '../../mol-util/binding';
|
||||
|
||||
const B = ButtonsType
|
||||
const M = ModifiersKeys
|
||||
const Trigger = Binding.Trigger
|
||||
const B = ButtonsType;
|
||||
const M = ModifiersKeys;
|
||||
const Trigger = Binding.Trigger;
|
||||
|
||||
export const DefaultTrackballBindings = {
|
||||
dragRotate: Binding([Trigger(B.Flag.Primary, M.create())], 'Rotate', 'Drag using ${triggers}'),
|
||||
@@ -31,7 +31,7 @@ export const DefaultTrackballBindings = {
|
||||
scrollZoom: Binding([Trigger(B.Flag.Auxilary, M.create())], 'Zoom', 'Scroll using ${triggers}'),
|
||||
scrollFocus: Binding([Trigger(B.Flag.Auxilary, M.create({ shift: true }))], 'Clip', 'Scroll using ${triggers}'),
|
||||
scrollFocusZoom: Binding.Empty,
|
||||
}
|
||||
};
|
||||
|
||||
export const TrackballControlsParams = {
|
||||
noScroll: PD.Boolean(true, { isHidden: true }),
|
||||
@@ -50,10 +50,10 @@ export const TrackballControlsParams = {
|
||||
maxDistance: PD.Numeric(1e150, {}, { isHidden: true }),
|
||||
|
||||
bindings: PD.Value(DefaultTrackballBindings, { isHidden: true })
|
||||
}
|
||||
};
|
||||
export type TrackballControlsProps = PD.Values<typeof TrackballControlsParams>
|
||||
|
||||
export { TrackballControls }
|
||||
export { TrackballControls };
|
||||
interface TrackballControls {
|
||||
viewport: Viewport
|
||||
|
||||
@@ -66,48 +66,48 @@ interface TrackballControls {
|
||||
}
|
||||
namespace TrackballControls {
|
||||
export function create(input: InputObserver, camera: Camera, props: Partial<TrackballControlsProps> = {}): TrackballControls {
|
||||
const p = { ...PD.getDefaultValues(TrackballControlsParams), ...props }
|
||||
const p = { ...PD.getDefaultValues(TrackballControlsParams), ...props };
|
||||
|
||||
const viewport = Viewport()
|
||||
const viewport = Viewport();
|
||||
|
||||
let disposed = false
|
||||
let disposed = false;
|
||||
|
||||
const dragSub = input.drag.subscribe(onDrag)
|
||||
const interactionEndSub = input.interactionEnd.subscribe(onInteractionEnd)
|
||||
const wheelSub = input.wheel.subscribe(onWheel)
|
||||
const pinchSub = input.pinch.subscribe(onPinch)
|
||||
const dragSub = input.drag.subscribe(onDrag);
|
||||
const interactionEndSub = input.interactionEnd.subscribe(onInteractionEnd);
|
||||
const wheelSub = input.wheel.subscribe(onWheel);
|
||||
const pinchSub = input.pinch.subscribe(onPinch);
|
||||
|
||||
let _isInteracting = false;
|
||||
|
||||
// For internal use
|
||||
const lastPosition = Vec3()
|
||||
const lastPosition = Vec3();
|
||||
|
||||
const _eye = Vec3()
|
||||
const _eye = Vec3();
|
||||
|
||||
const _rotPrev = Vec2()
|
||||
const _rotCurr = Vec2()
|
||||
const _rotLastAxis = Vec3()
|
||||
let _rotLastAngle = 0
|
||||
const _rotPrev = Vec2();
|
||||
const _rotCurr = Vec2();
|
||||
const _rotLastAxis = Vec3();
|
||||
let _rotLastAngle = 0;
|
||||
|
||||
const _zRotPrev = Vec2()
|
||||
const _zRotCurr = Vec2()
|
||||
let _zRotLastAngle = 0
|
||||
const _zRotPrev = Vec2();
|
||||
const _zRotCurr = Vec2();
|
||||
let _zRotLastAngle = 0;
|
||||
|
||||
const _zoomStart = Vec2()
|
||||
const _zoomEnd = Vec2()
|
||||
const _zoomStart = Vec2();
|
||||
const _zoomEnd = Vec2();
|
||||
|
||||
const _focusStart = Vec2()
|
||||
const _focusEnd = Vec2()
|
||||
const _focusStart = Vec2();
|
||||
const _focusEnd = Vec2();
|
||||
|
||||
const _panStart = Vec2()
|
||||
const _panEnd = Vec2()
|
||||
const _panStart = Vec2();
|
||||
const _panEnd = Vec2();
|
||||
|
||||
// Initial values for reseting
|
||||
const target0 = Vec3.clone(camera.target)
|
||||
const position0 = Vec3.clone(camera.position)
|
||||
const up0 = Vec3.clone(camera.up)
|
||||
const target0 = Vec3.clone(camera.target);
|
||||
const position0 = Vec3.clone(camera.position);
|
||||
const up0 = Vec3.clone(camera.up);
|
||||
|
||||
const mouseOnScreenVec2 = Vec2()
|
||||
const mouseOnScreenVec2 = Vec2();
|
||||
function getMouseOnScreen(pageX: number, pageY: number) {
|
||||
return Vec2.set(
|
||||
mouseOnScreenVec2,
|
||||
@@ -116,7 +116,7 @@ namespace TrackballControls {
|
||||
);
|
||||
}
|
||||
|
||||
const mouseOnCircleVec2 = Vec2()
|
||||
const mouseOnCircleVec2 = Vec2();
|
||||
function getMouseOnCircle(pageX: number, pageY: number) {
|
||||
return Vec2.set(
|
||||
mouseOnCircleVec2,
|
||||
@@ -125,125 +125,125 @@ namespace TrackballControls {
|
||||
);
|
||||
}
|
||||
|
||||
const rotAxis = Vec3()
|
||||
const rotQuat = Quat()
|
||||
const rotEyeDir = Vec3()
|
||||
const rotObjUpDir = Vec3()
|
||||
const rotObjSideDir = Vec3()
|
||||
const rotMoveDir = Vec3()
|
||||
const rotAxis = Vec3();
|
||||
const rotQuat = Quat();
|
||||
const rotEyeDir = Vec3();
|
||||
const rotObjUpDir = Vec3();
|
||||
const rotObjSideDir = Vec3();
|
||||
const rotMoveDir = Vec3();
|
||||
|
||||
function rotateCamera() {
|
||||
const dx = _rotCurr[0] - _rotPrev[0]
|
||||
const dy = _rotCurr[1] - _rotPrev[1]
|
||||
const dx = _rotCurr[0] - _rotPrev[0];
|
||||
const dy = _rotCurr[1] - _rotPrev[1];
|
||||
Vec3.set(rotMoveDir, dx, dy, 0);
|
||||
|
||||
const angle = Vec3.magnitude(rotMoveDir) * p.rotateSpeed;
|
||||
|
||||
if (angle) {
|
||||
Vec3.sub(_eye, camera.position, camera.target)
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
|
||||
Vec3.normalize(rotEyeDir, _eye)
|
||||
Vec3.normalize(rotObjUpDir, camera.up)
|
||||
Vec3.normalize(rotObjSideDir, Vec3.cross(rotObjSideDir, rotObjUpDir, rotEyeDir))
|
||||
Vec3.normalize(rotEyeDir, _eye);
|
||||
Vec3.normalize(rotObjUpDir, camera.up);
|
||||
Vec3.normalize(rotObjSideDir, Vec3.cross(rotObjSideDir, rotObjUpDir, rotEyeDir));
|
||||
|
||||
Vec3.setMagnitude(rotObjUpDir, rotObjUpDir, dy)
|
||||
Vec3.setMagnitude(rotObjSideDir, rotObjSideDir, dx)
|
||||
Vec3.setMagnitude(rotObjUpDir, rotObjUpDir, dy);
|
||||
Vec3.setMagnitude(rotObjSideDir, rotObjSideDir, dx);
|
||||
|
||||
Vec3.add(rotMoveDir, rotObjUpDir, rotObjSideDir)
|
||||
Vec3.normalize(rotAxis, Vec3.cross(rotAxis, rotMoveDir, _eye))
|
||||
Quat.setAxisAngle(rotQuat, rotAxis, angle)
|
||||
Vec3.add(rotMoveDir, rotObjUpDir, rotObjSideDir);
|
||||
Vec3.normalize(rotAxis, Vec3.cross(rotAxis, rotMoveDir, _eye));
|
||||
Quat.setAxisAngle(rotQuat, rotAxis, angle);
|
||||
|
||||
Vec3.transformQuat(_eye, _eye, rotQuat)
|
||||
Vec3.transformQuat(camera.up, camera.up, rotQuat)
|
||||
Vec3.transformQuat(_eye, _eye, rotQuat);
|
||||
Vec3.transformQuat(camera.up, camera.up, rotQuat);
|
||||
|
||||
Vec3.copy(_rotLastAxis, rotAxis)
|
||||
Vec3.copy(_rotLastAxis, rotAxis);
|
||||
_rotLastAngle = angle;
|
||||
} else if (!p.staticMoving && _rotLastAngle) {
|
||||
_rotLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
|
||||
Vec3.sub(_eye, camera.position, camera.target)
|
||||
Quat.setAxisAngle(rotQuat, _rotLastAxis, _rotLastAngle)
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
Quat.setAxisAngle(rotQuat, _rotLastAxis, _rotLastAngle);
|
||||
|
||||
Vec3.transformQuat(_eye, _eye, rotQuat)
|
||||
Vec3.transformQuat(camera.up, camera.up, rotQuat)
|
||||
Vec3.transformQuat(_eye, _eye, rotQuat);
|
||||
Vec3.transformQuat(camera.up, camera.up, rotQuat);
|
||||
}
|
||||
|
||||
Vec2.copy(_rotPrev, _rotCurr)
|
||||
Vec2.copy(_rotPrev, _rotCurr);
|
||||
}
|
||||
|
||||
const zRotQuat = Quat()
|
||||
const zRotQuat = Quat();
|
||||
|
||||
function zRotateCamera() {
|
||||
const dx = _zRotCurr[0] - _zRotPrev[0]
|
||||
const dy = _zRotCurr[1] - _zRotPrev[1]
|
||||
const angle = p.rotateSpeed * (-dx + dy) * -0.05
|
||||
const dx = _zRotCurr[0] - _zRotPrev[0];
|
||||
const dy = _zRotCurr[1] - _zRotPrev[1];
|
||||
const angle = p.rotateSpeed * (-dx + dy) * -0.05;
|
||||
|
||||
if (angle) {
|
||||
Vec3.sub(_eye, camera.position, camera.target)
|
||||
Quat.setAxisAngle(zRotQuat, _eye, angle)
|
||||
Vec3.transformQuat(camera.up, camera.up, zRotQuat)
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
Quat.setAxisAngle(zRotQuat, _eye, angle);
|
||||
Vec3.transformQuat(camera.up, camera.up, zRotQuat);
|
||||
_zRotLastAngle = angle;
|
||||
} else if (!p.staticMoving && _zRotLastAngle) {
|
||||
_zRotLastAngle *= Math.sqrt(1.0 - p.dynamicDampingFactor);
|
||||
Vec3.sub(_eye, camera.position, camera.target)
|
||||
Quat.setAxisAngle(zRotQuat, _eye, _zRotLastAngle)
|
||||
Vec3.transformQuat(camera.up, camera.up, zRotQuat)
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
Quat.setAxisAngle(zRotQuat, _eye, _zRotLastAngle);
|
||||
Vec3.transformQuat(camera.up, camera.up, zRotQuat);
|
||||
}
|
||||
|
||||
Vec2.copy(_zRotPrev, _zRotCurr)
|
||||
Vec2.copy(_zRotPrev, _zRotCurr);
|
||||
}
|
||||
|
||||
function zoomCamera() {
|
||||
const factor = 1.0 + (_zoomEnd[1] - _zoomStart[1]) * p.zoomSpeed
|
||||
const factor = 1.0 + (_zoomEnd[1] - _zoomStart[1]) * p.zoomSpeed;
|
||||
if (factor !== 1.0 && factor > 0.0) {
|
||||
Vec3.scale(_eye, _eye, factor)
|
||||
Vec3.scale(_eye, _eye, factor);
|
||||
}
|
||||
|
||||
if (p.staticMoving) {
|
||||
Vec2.copy(_zoomStart, _zoomEnd)
|
||||
Vec2.copy(_zoomStart, _zoomEnd);
|
||||
} else {
|
||||
_zoomStart[1] += (_zoomEnd[1] - _zoomStart[1]) * p.dynamicDampingFactor
|
||||
_zoomStart[1] += (_zoomEnd[1] - _zoomStart[1]) * p.dynamicDampingFactor;
|
||||
}
|
||||
}
|
||||
|
||||
function focusCamera() {
|
||||
const factor = (_focusEnd[1] - _focusStart[1]) * p.zoomSpeed
|
||||
const factor = (_focusEnd[1] - _focusStart[1]) * p.zoomSpeed;
|
||||
if (factor !== 0.0) {
|
||||
const radius = Math.max(1, camera.state.radius + camera.state.radius * factor)
|
||||
camera.setState({ radius })
|
||||
const radius = Math.max(1, camera.state.radius + camera.state.radius * factor);
|
||||
camera.setState({ radius });
|
||||
}
|
||||
|
||||
if (p.staticMoving) {
|
||||
Vec2.copy(_focusStart, _focusEnd)
|
||||
Vec2.copy(_focusStart, _focusEnd);
|
||||
} else {
|
||||
_focusStart[1] += (_focusEnd[1] - _focusStart[1]) * p.dynamicDampingFactor
|
||||
_focusStart[1] += (_focusEnd[1] - _focusStart[1]) * p.dynamicDampingFactor;
|
||||
}
|
||||
}
|
||||
|
||||
const panMouseChange = Vec2()
|
||||
const panObjUp = Vec3()
|
||||
const panOffset = Vec3()
|
||||
const panMouseChange = Vec2();
|
||||
const panObjUp = Vec3();
|
||||
const panOffset = Vec3();
|
||||
|
||||
function panCamera() {
|
||||
Vec2.sub(panMouseChange, Vec2.copy(panMouseChange, _panEnd), _panStart)
|
||||
Vec2.sub(panMouseChange, Vec2.copy(panMouseChange, _panEnd), _panStart);
|
||||
|
||||
if (Vec2.squaredMagnitude(panMouseChange)) {
|
||||
Vec2.scale(panMouseChange, panMouseChange, Vec3.magnitude(_eye) * p.panSpeed)
|
||||
Vec2.scale(panMouseChange, panMouseChange, Vec3.magnitude(_eye) * p.panSpeed);
|
||||
|
||||
Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), camera.up)
|
||||
Vec3.setMagnitude(panOffset, panOffset, panMouseChange[0])
|
||||
Vec3.cross(panOffset, Vec3.copy(panOffset, _eye), camera.up);
|
||||
Vec3.setMagnitude(panOffset, panOffset, panMouseChange[0]);
|
||||
|
||||
Vec3.setMagnitude(panObjUp, camera.up, panMouseChange[1])
|
||||
Vec3.add(panOffset, panOffset, panObjUp)
|
||||
Vec3.setMagnitude(panObjUp, camera.up, panMouseChange[1]);
|
||||
Vec3.add(panOffset, panOffset, panObjUp);
|
||||
|
||||
Vec3.add(camera.position, camera.position, panOffset)
|
||||
Vec3.add(camera.target, camera.target, panOffset)
|
||||
Vec3.add(camera.position, camera.position, panOffset);
|
||||
Vec3.add(camera.target, camera.target, panOffset);
|
||||
|
||||
if (p.staticMoving) {
|
||||
Vec2.copy(_panStart, _panEnd)
|
||||
Vec2.copy(_panStart, _panEnd);
|
||||
} else {
|
||||
Vec2.sub(panMouseChange, _panEnd, _panStart)
|
||||
Vec2.scale(panMouseChange, panMouseChange, p.dynamicDampingFactor)
|
||||
Vec2.add(_panStart, _panStart, panMouseChange)
|
||||
Vec2.sub(panMouseChange, _panEnd, _panStart);
|
||||
Vec2.scale(panMouseChange, panMouseChange, p.dynamicDampingFactor);
|
||||
Vec2.add(_panStart, _panStart, panMouseChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,19 +253,19 @@ namespace TrackballControls {
|
||||
* and not too large compared to `camera.state.radiusMax`
|
||||
*/
|
||||
function checkDistances() {
|
||||
const maxDistance = Math.min(Math.max(camera.state.radiusMax * 1000, 0.01), p.maxDistance)
|
||||
const maxDistance = Math.min(Math.max(camera.state.radiusMax * 1000, 0.01), p.maxDistance);
|
||||
if (Vec3.squaredMagnitude(_eye) > maxDistance * maxDistance) {
|
||||
Vec3.setMagnitude(_eye, _eye, maxDistance)
|
||||
Vec3.add(camera.position, camera.target, _eye)
|
||||
Vec2.copy(_zoomStart, _zoomEnd)
|
||||
Vec2.copy(_focusStart, _focusEnd)
|
||||
Vec3.setMagnitude(_eye, _eye, maxDistance);
|
||||
Vec3.add(camera.position, camera.target, _eye);
|
||||
Vec2.copy(_zoomStart, _zoomEnd);
|
||||
Vec2.copy(_focusStart, _focusEnd);
|
||||
}
|
||||
|
||||
if (Vec3.squaredMagnitude(_eye) < p.minDistance * p.minDistance) {
|
||||
Vec3.setMagnitude(_eye, _eye, p.minDistance)
|
||||
Vec3.add(camera.position, camera.target, _eye)
|
||||
Vec2.copy(_zoomStart, _zoomEnd)
|
||||
Vec2.copy(_focusStart, _focusEnd)
|
||||
Vec3.setMagnitude(_eye, _eye, p.minDistance);
|
||||
Vec3.add(camera.position, camera.target, _eye);
|
||||
Vec2.copy(_zoomStart, _zoomEnd);
|
||||
Vec2.copy(_focusStart, _focusEnd);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,19 +275,19 @@ namespace TrackballControls {
|
||||
if (lastUpdated === t) return;
|
||||
if (p.spin) spin(t - lastUpdated);
|
||||
|
||||
Vec3.sub(_eye, camera.position, camera.target)
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
|
||||
rotateCamera()
|
||||
zRotateCamera()
|
||||
zoomCamera()
|
||||
focusCamera()
|
||||
panCamera()
|
||||
rotateCamera();
|
||||
zRotateCamera();
|
||||
zoomCamera();
|
||||
focusCamera();
|
||||
panCamera();
|
||||
|
||||
Vec3.add(camera.position, camera.target, _eye)
|
||||
checkDistances()
|
||||
Vec3.add(camera.position, camera.target, _eye);
|
||||
checkDistances();
|
||||
|
||||
if (Vec3.squaredDistance(lastPosition, camera.position) > EPSILON) {
|
||||
Vec3.copy(lastPosition, camera.position)
|
||||
Vec3.copy(lastPosition, camera.position);
|
||||
}
|
||||
|
||||
lastUpdated = t;
|
||||
@@ -295,12 +295,12 @@ namespace TrackballControls {
|
||||
|
||||
/** Reset object's vectors and the target vector to their initial values */
|
||||
function reset() {
|
||||
Vec3.copy(camera.target, target0)
|
||||
Vec3.copy(camera.position, position0)
|
||||
Vec3.copy(camera.up, up0)
|
||||
Vec3.copy(camera.target, target0);
|
||||
Vec3.copy(camera.position, position0);
|
||||
Vec3.copy(camera.up, up0);
|
||||
|
||||
Vec3.sub(_eye, camera.position, camera.target)
|
||||
Vec3.copy(lastPosition, camera.position)
|
||||
Vec3.sub(_eye, camera.position, camera.target);
|
||||
Vec3.copy(lastPosition, camera.position);
|
||||
}
|
||||
|
||||
// listeners
|
||||
@@ -308,48 +308,48 @@ namespace TrackballControls {
|
||||
function onDrag({ pageX, pageY, buttons, modifiers, isStart }: DragInput) {
|
||||
_isInteracting = true;
|
||||
|
||||
const dragRotate = Binding.match(p.bindings.dragRotate, buttons, modifiers)
|
||||
const dragRotateZ = Binding.match(p.bindings.dragRotateZ, buttons, modifiers)
|
||||
const dragPan = Binding.match(p.bindings.dragPan, buttons, modifiers)
|
||||
const dragZoom = Binding.match(p.bindings.dragZoom, buttons, modifiers)
|
||||
const dragFocus = Binding.match(p.bindings.dragFocus, buttons, modifiers)
|
||||
const dragFocusZoom = Binding.match(p.bindings.dragFocusZoom, buttons, modifiers)
|
||||
const dragRotate = Binding.match(p.bindings.dragRotate, buttons, modifiers);
|
||||
const dragRotateZ = Binding.match(p.bindings.dragRotateZ, buttons, modifiers);
|
||||
const dragPan = Binding.match(p.bindings.dragPan, buttons, modifiers);
|
||||
const dragZoom = Binding.match(p.bindings.dragZoom, buttons, modifiers);
|
||||
const dragFocus = Binding.match(p.bindings.dragFocus, buttons, modifiers);
|
||||
const dragFocusZoom = Binding.match(p.bindings.dragFocusZoom, buttons, modifiers);
|
||||
|
||||
getMouseOnCircle(pageX, pageY)
|
||||
getMouseOnScreen(pageX, pageY)
|
||||
getMouseOnCircle(pageX, pageY);
|
||||
getMouseOnScreen(pageX, pageY);
|
||||
|
||||
if (isStart) {
|
||||
if (dragRotate) {
|
||||
Vec2.copy(_rotCurr, mouseOnCircleVec2)
|
||||
Vec2.copy(_rotPrev, _rotCurr)
|
||||
Vec2.copy(_rotCurr, mouseOnCircleVec2);
|
||||
Vec2.copy(_rotPrev, _rotCurr);
|
||||
}
|
||||
if (dragRotateZ) {
|
||||
Vec2.copy(_zRotCurr, mouseOnCircleVec2)
|
||||
Vec2.copy(_zRotPrev, _zRotCurr)
|
||||
Vec2.copy(_zRotCurr, mouseOnCircleVec2);
|
||||
Vec2.copy(_zRotPrev, _zRotCurr);
|
||||
}
|
||||
if (dragZoom || dragFocusZoom) {
|
||||
Vec2.copy(_zoomStart, mouseOnScreenVec2)
|
||||
Vec2.copy(_zoomEnd, _zoomStart)
|
||||
Vec2.copy(_zoomStart, mouseOnScreenVec2);
|
||||
Vec2.copy(_zoomEnd, _zoomStart);
|
||||
}
|
||||
if (dragFocus) {
|
||||
Vec2.copy(_focusStart, mouseOnScreenVec2)
|
||||
Vec2.copy(_focusEnd, _focusStart)
|
||||
Vec2.copy(_focusStart, mouseOnScreenVec2);
|
||||
Vec2.copy(_focusEnd, _focusStart);
|
||||
}
|
||||
if (dragPan) {
|
||||
Vec2.copy(_panStart, mouseOnScreenVec2)
|
||||
Vec2.copy(_panEnd, _panStart)
|
||||
Vec2.copy(_panStart, mouseOnScreenVec2);
|
||||
Vec2.copy(_panEnd, _panStart);
|
||||
}
|
||||
}
|
||||
|
||||
if (dragRotate) Vec2.copy(_rotCurr, mouseOnCircleVec2)
|
||||
if (dragRotateZ) Vec2.copy(_zRotCurr, mouseOnCircleVec2)
|
||||
if (dragZoom || dragFocusZoom) Vec2.copy(_zoomEnd, mouseOnScreenVec2)
|
||||
if (dragFocus) Vec2.copy(_focusEnd, mouseOnScreenVec2)
|
||||
if (dragRotate) Vec2.copy(_rotCurr, mouseOnCircleVec2);
|
||||
if (dragRotateZ) Vec2.copy(_zRotCurr, mouseOnCircleVec2);
|
||||
if (dragZoom || dragFocusZoom) Vec2.copy(_zoomEnd, mouseOnScreenVec2);
|
||||
if (dragFocus) Vec2.copy(_focusEnd, mouseOnScreenVec2);
|
||||
if (dragFocusZoom) {
|
||||
const dist = Vec3.distance(camera.state.position, camera.state.target);
|
||||
camera.setState({ radius: dist / 5 })
|
||||
camera.setState({ radius: dist / 5 });
|
||||
}
|
||||
if (dragPan) Vec2.copy(_panEnd, mouseOnScreenVec2)
|
||||
if (dragPan) Vec2.copy(_panEnd, mouseOnScreenVec2);
|
||||
}
|
||||
|
||||
function onInteractionEnd() {
|
||||
@@ -357,30 +357,30 @@ namespace TrackballControls {
|
||||
}
|
||||
|
||||
function onWheel({ dx, dy, dz, buttons, modifiers }: WheelInput) {
|
||||
const delta = absMax(dx, dy, dz)
|
||||
const delta = absMax(dx, dy, dz);
|
||||
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
|
||||
_zoomEnd[1] += delta * 0.0001
|
||||
_zoomEnd[1] += delta * 0.0001;
|
||||
}
|
||||
if (Binding.match(p.bindings.scrollFocus, buttons, modifiers)) {
|
||||
_focusEnd[1] += delta * 0.0001
|
||||
_focusEnd[1] += delta * 0.0001;
|
||||
}
|
||||
}
|
||||
|
||||
function onPinch({ fraction, buttons, modifiers }: PinchInput) {
|
||||
if (Binding.match(p.bindings.scrollZoom, buttons, modifiers)) {
|
||||
_isInteracting = true;
|
||||
_zoomEnd[1] += (fraction - 1) * 0.1
|
||||
_zoomEnd[1] += (fraction - 1) * 0.1;
|
||||
}
|
||||
}
|
||||
|
||||
function dispose() {
|
||||
if (disposed) return
|
||||
disposed = true
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
|
||||
dragSub.unsubscribe()
|
||||
wheelSub.unsubscribe()
|
||||
pinchSub.unsubscribe()
|
||||
interactionEndSub.unsubscribe()
|
||||
dragSub.unsubscribe();
|
||||
wheelSub.unsubscribe();
|
||||
pinchSub.unsubscribe();
|
||||
interactionEndSub.unsubscribe();
|
||||
}
|
||||
|
||||
const _spinSpeed = Vec2.create(0.005, 0);
|
||||
@@ -396,14 +396,14 @@ namespace TrackballControls {
|
||||
return {
|
||||
viewport,
|
||||
|
||||
get props() { return p as Readonly<TrackballControlsProps> },
|
||||
get props() { return p as Readonly<TrackballControlsProps>; },
|
||||
setProps: (props: Partial<TrackballControlsProps>) => {
|
||||
Object.assign(p, props)
|
||||
Object.assign(p, props);
|
||||
},
|
||||
|
||||
update,
|
||||
reset,
|
||||
dispose
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { createRenderObject, GraphicsRenderObject, getNextMaterialId } from '../../mol-gl/render-object'
|
||||
import { createRenderObject, GraphicsRenderObject, getNextMaterialId } from '../../mol-gl/render-object';
|
||||
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
|
||||
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
|
||||
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
|
||||
@@ -24,7 +24,7 @@ export const DebugHelperParams = {
|
||||
visibleSceneBoundingSpheres: PD.Boolean(false, { description: 'Show visible scene bounding spheres.' }),
|
||||
objectBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of visible render objects.' }),
|
||||
instanceBoundingSpheres: PD.Boolean(false, { description: 'Show bounding spheres of visible instances.' }),
|
||||
}
|
||||
};
|
||||
export type DebugHelperParams = typeof DebugHelperParams
|
||||
export type DebugHelperProps = PD.Values<DebugHelperParams>
|
||||
|
||||
@@ -41,24 +41,24 @@ export class BoundingSphereHelper {
|
||||
private visibleSceneData: BoundingSphereData | undefined
|
||||
|
||||
constructor(ctx: WebGLContext, parent: Scene, props: Partial<DebugHelperProps>) {
|
||||
this.scene = Scene.create(ctx)
|
||||
this.parent = parent
|
||||
this._props = { ...PD.getDefaultValues(DebugHelperParams), ...props }
|
||||
this.scene = Scene.create(ctx);
|
||||
this.parent = parent;
|
||||
this._props = { ...PD.getDefaultValues(DebugHelperParams), ...props };
|
||||
}
|
||||
|
||||
update() {
|
||||
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.lightgrey, sceneMaterialId)
|
||||
if (newSceneData) this.sceneData = newSceneData
|
||||
const newSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphere, this.sceneData, ColorNames.lightgrey, sceneMaterialId);
|
||||
if (newSceneData) this.sceneData = newSceneData;
|
||||
|
||||
const newVisibleSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphereVisible, this.visibleSceneData, ColorNames.black, visibleSceneMaterialId)
|
||||
if (newVisibleSceneData) this.visibleSceneData = newVisibleSceneData
|
||||
const newVisibleSceneData = updateBoundingSphereData(this.scene, this.parent.boundingSphereVisible, this.visibleSceneData, ColorNames.black, visibleSceneMaterialId);
|
||||
if (newVisibleSceneData) this.visibleSceneData = newVisibleSceneData;
|
||||
|
||||
this.parent.forEach((r, ro) => {
|
||||
const objectData = this.objectsData.get(ro)
|
||||
const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato, objectMaterialId)
|
||||
if (newObjectData) this.objectsData.set(ro, newObjectData)
|
||||
const objectData = this.objectsData.get(ro);
|
||||
const newObjectData = updateBoundingSphereData(this.scene, r.values.boundingSphere.ref.value, objectData, ColorNames.tomato, objectMaterialId);
|
||||
if (newObjectData) this.objectsData.set(ro, newObjectData);
|
||||
|
||||
const instanceData = this.instancesData.get(ro)
|
||||
const instanceData = this.instancesData.get(ro);
|
||||
const newInstanceData = updateBoundingSphereData(this.scene, r.values.invariantBoundingSphere.ref.value, instanceData, ColorNames.skyblue, instanceMaterialId, {
|
||||
aTransform: ro.values.aTransform,
|
||||
matrix: ro.values.matrix,
|
||||
@@ -67,97 +67,97 @@ export class BoundingSphereHelper {
|
||||
uInstanceCount: ro.values.uInstanceCount,
|
||||
instanceCount: ro.values.instanceCount,
|
||||
aInstance: ro.values.aInstance,
|
||||
})
|
||||
if (newInstanceData) this.instancesData.set(ro, newInstanceData)
|
||||
})
|
||||
});
|
||||
if (newInstanceData) this.instancesData.set(ro, newInstanceData);
|
||||
});
|
||||
|
||||
this.objectsData.forEach((objectData, ro) => {
|
||||
if (!this.parent.has(ro)) {
|
||||
this.scene.remove(objectData.renderObject)
|
||||
this.objectsData.delete(ro)
|
||||
this.scene.remove(objectData.renderObject);
|
||||
this.objectsData.delete(ro);
|
||||
}
|
||||
})
|
||||
});
|
||||
this.instancesData.forEach((instanceData, ro) => {
|
||||
if (!this.parent.has(ro)) {
|
||||
this.scene.remove(instanceData.renderObject)
|
||||
this.instancesData.delete(ro)
|
||||
this.scene.remove(instanceData.renderObject);
|
||||
this.instancesData.delete(ro);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.scene.update(void 0, false)
|
||||
this.scene.commit()
|
||||
this.scene.update(void 0, false);
|
||||
this.scene.commit();
|
||||
}
|
||||
|
||||
syncVisibility() {
|
||||
if (this.sceneData) {
|
||||
this.sceneData.renderObject.state.visible = this._props.sceneBoundingSpheres
|
||||
this.sceneData.renderObject.state.visible = this._props.sceneBoundingSpheres;
|
||||
}
|
||||
|
||||
if (this.visibleSceneData) {
|
||||
this.visibleSceneData.renderObject.state.visible = this._props.visibleSceneBoundingSpheres
|
||||
this.visibleSceneData.renderObject.state.visible = this._props.visibleSceneBoundingSpheres;
|
||||
}
|
||||
|
||||
this.parent.forEach((_, ro) => {
|
||||
const objectData = this.objectsData.get(ro)
|
||||
if (objectData) objectData.renderObject.state.visible = ro.state.visible && this._props.objectBoundingSpheres
|
||||
const objectData = this.objectsData.get(ro);
|
||||
if (objectData) objectData.renderObject.state.visible = ro.state.visible && this._props.objectBoundingSpheres;
|
||||
|
||||
const instanceData = this.instancesData.get(ro)
|
||||
if (instanceData) instanceData.renderObject.state.visible = ro.state.visible && this._props.instanceBoundingSpheres
|
||||
})
|
||||
const instanceData = this.instancesData.get(ro);
|
||||
if (instanceData) instanceData.renderObject.state.visible = ro.state.visible && this._props.instanceBoundingSpheres;
|
||||
});
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.sceneData = undefined
|
||||
this.objectsData.clear()
|
||||
this.scene.clear()
|
||||
this.sceneData = undefined;
|
||||
this.objectsData.clear();
|
||||
this.scene.clear();
|
||||
}
|
||||
|
||||
get isEnabled() {
|
||||
return (
|
||||
this._props.sceneBoundingSpheres || this._props.visibleSceneBoundingSpheres ||
|
||||
this._props.objectBoundingSpheres || this._props.instanceBoundingSpheres
|
||||
)
|
||||
);
|
||||
}
|
||||
get props() { return this._props as Readonly<DebugHelperProps> }
|
||||
get props() { return this._props as Readonly<DebugHelperProps>; }
|
||||
|
||||
setProps (props: Partial<DebugHelperProps>) {
|
||||
Object.assign(this._props, props)
|
||||
if (this.isEnabled) this.update()
|
||||
Object.assign(this._props, props);
|
||||
if (this.isEnabled) this.update();
|
||||
}
|
||||
}
|
||||
|
||||
function updateBoundingSphereData(scene: Scene, boundingSphere: Sphere3D, data: BoundingSphereData | undefined, color: Color, materialId: number, transform?: TransformData) {
|
||||
if (!data || !Sphere3D.equals(data.boundingSphere, boundingSphere)) {
|
||||
const mesh = createBoundingSphereMesh(boundingSphere, data && data.mesh)
|
||||
const renderObject = data ? data.renderObject : createBoundingSphereRenderObject(mesh, color, materialId, transform)
|
||||
const mesh = createBoundingSphereMesh(boundingSphere, data && data.mesh);
|
||||
const renderObject = data ? data.renderObject : createBoundingSphereRenderObject(mesh, color, materialId, transform);
|
||||
if (data) {
|
||||
ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(mesh))
|
||||
ValueCell.update(renderObject.values.drawCount, Geometry.getDrawCount(mesh));
|
||||
} else {
|
||||
scene.add(renderObject)
|
||||
scene.add(renderObject);
|
||||
}
|
||||
return { boundingSphere: Sphere3D.clone(boundingSphere), renderObject, mesh }
|
||||
return { boundingSphere: Sphere3D.clone(boundingSphere), renderObject, mesh };
|
||||
}
|
||||
}
|
||||
|
||||
function createBoundingSphereMesh(boundingSphere: Sphere3D, mesh?: Mesh) {
|
||||
const detail = 2
|
||||
const vertexCount = sphereVertexCount(detail)
|
||||
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh)
|
||||
const detail = 2;
|
||||
const vertexCount = sphereVertexCount(detail);
|
||||
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh);
|
||||
if (boundingSphere.radius) {
|
||||
addSphere(builderState, boundingSphere.center, boundingSphere.radius, detail)
|
||||
addSphere(builderState, boundingSphere.center, boundingSphere.radius, detail);
|
||||
if (Sphere3D.hasExtrema(boundingSphere)) {
|
||||
for (const e of boundingSphere.extrema) addSphere(builderState, e, 1.0, 0)
|
||||
for (const e of boundingSphere.extrema) addSphere(builderState, e, 1.0, 0);
|
||||
}
|
||||
}
|
||||
return MeshBuilder.getMesh(builderState)
|
||||
return MeshBuilder.getMesh(builderState);
|
||||
}
|
||||
|
||||
const sceneMaterialId = getNextMaterialId()
|
||||
const visibleSceneMaterialId = getNextMaterialId()
|
||||
const objectMaterialId = getNextMaterialId()
|
||||
const instanceMaterialId = getNextMaterialId()
|
||||
const sceneMaterialId = getNextMaterialId();
|
||||
const visibleSceneMaterialId = getNextMaterialId();
|
||||
const objectMaterialId = getNextMaterialId();
|
||||
const instanceMaterialId = getNextMaterialId();
|
||||
|
||||
function createBoundingSphereRenderObject(mesh: Mesh, color: Color, materialId: number, transform?: TransformData) {
|
||||
const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform)
|
||||
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, opaque: false }, materialId)
|
||||
const values = Mesh.Utils.createValuesSimple(mesh, { alpha: 0.1, doubleSided: false }, color, 1, transform);
|
||||
return createRenderObject('mesh', values, { visible: true, alphaFactor: 1, pickable: false, opaque: false }, materialId);
|
||||
}
|
||||
@@ -31,7 +31,7 @@ const AxesParams = {
|
||||
colorY: PD.Color(ColorNames.green, { isEssential: true }),
|
||||
colorZ: PD.Color(ColorNames.blue, { isEssential: true }),
|
||||
scale: PD.Numeric(0.33, { min: 0.1, max: 2, step: 0.1 }, { isEssential: true }),
|
||||
}
|
||||
};
|
||||
type AxesParams = typeof AxesParams
|
||||
type AxesProps = PD.Values<AxesParams>
|
||||
|
||||
@@ -40,7 +40,7 @@ export const CameraHelperParams = {
|
||||
on: PD.Group(AxesParams),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Show camera orientation axes' }),
|
||||
}
|
||||
};
|
||||
export type CameraHelperParams = typeof CameraHelperParams
|
||||
export type CameraHelperProps = PD.Values<CameraHelperParams>
|
||||
|
||||
@@ -54,122 +54,122 @@ export class CameraHelper {
|
||||
private renderObject: GraphicsRenderObject | undefined
|
||||
|
||||
constructor(private webgl: WebGLContext, props: Partial<CameraHelperProps> = {}) {
|
||||
this.scene = Scene.create(webgl)
|
||||
this.scene = Scene.create(webgl);
|
||||
|
||||
this.camera = new Camera()
|
||||
Vec3.set(this.camera.up, 0, 1, 0)
|
||||
Vec3.set(this.camera.target, 0, 0, 0)
|
||||
this.camera = new Camera();
|
||||
Vec3.set(this.camera.up, 0, 1, 0);
|
||||
Vec3.set(this.camera.target, 0, 0, 0);
|
||||
|
||||
this.setProps(props)
|
||||
this.setProps(props);
|
||||
}
|
||||
|
||||
setProps(props: Partial<CameraHelperProps>) {
|
||||
this.props = produce(this.props, p => {
|
||||
if (props.axes !== undefined) {
|
||||
p.axes.name = props.axes.name
|
||||
p.axes.name = props.axes.name;
|
||||
if (props.axes.name === 'on') {
|
||||
this.scene.clear()
|
||||
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio }
|
||||
this.renderObject = createAxesRenderObject(params)
|
||||
this.scene.add(this.renderObject)
|
||||
this.scene.commit()
|
||||
this.scene.clear();
|
||||
const params = { ...props.axes.params, scale: props.axes.params.scale * this.webgl.pixelRatio };
|
||||
this.renderObject = createAxesRenderObject(params);
|
||||
this.scene.add(this.renderObject);
|
||||
this.scene.commit();
|
||||
|
||||
Vec3.set(this.camera.position, 0, 0, params.scale * 200)
|
||||
Mat4.lookAt(this.camera.view, this.camera.position, this.camera.target, this.camera.up)
|
||||
Vec3.set(this.camera.position, 0, 0, params.scale * 200);
|
||||
Mat4.lookAt(this.camera.view, this.camera.position, this.camera.target, this.camera.up);
|
||||
|
||||
p.axes.params = { ...props.axes.params }
|
||||
p.axes.params = { ...props.axes.params };
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
get isEnabled() {
|
||||
return this.props.axes.name === 'on'
|
||||
return this.props.axes.name === 'on';
|
||||
}
|
||||
|
||||
update(camera: Camera) {
|
||||
if (!this.renderObject) return
|
||||
if (!this.renderObject) return;
|
||||
|
||||
updateCamera(this.camera, camera.viewport)
|
||||
updateCamera(this.camera, camera.viewport);
|
||||
|
||||
const m = this.renderObject.values.aTransform.ref.value as unknown as Mat4
|
||||
Mat4.extractRotation(m, camera.view)
|
||||
const m = this.renderObject.values.aTransform.ref.value as unknown as Mat4;
|
||||
Mat4.extractRotation(m, camera.view);
|
||||
|
||||
const r = this.renderObject.values.boundingSphere.ref.value.radius
|
||||
const r = this.renderObject.values.boundingSphere.ref.value.radius;
|
||||
Mat4.setTranslation(m, Vec3.create(
|
||||
-camera.viewport.width / 2 + r,
|
||||
-camera.viewport.height / 2 + r,
|
||||
0
|
||||
))
|
||||
));
|
||||
|
||||
ValueCell.update(this.renderObject.values.aTransform, this.renderObject.values.aTransform.ref.value)
|
||||
this.scene.update([this.renderObject], true)
|
||||
ValueCell.update(this.renderObject.values.aTransform, this.renderObject.values.aTransform.ref.value);
|
||||
this.scene.update([this.renderObject], true);
|
||||
}
|
||||
}
|
||||
|
||||
function updateCamera(camera: Camera, viewport: Viewport) {
|
||||
const { near, far } = camera
|
||||
const { near, far } = camera;
|
||||
|
||||
const fullLeft = -(viewport.width - viewport.x) / 2
|
||||
const fullRight = (viewport.width - viewport.x) / 2
|
||||
const fullTop = (viewport.height - viewport.y) / 2
|
||||
const fullBottom = -(viewport.height - viewport.y) / 2
|
||||
const fullLeft = -(viewport.width - viewport.x) / 2;
|
||||
const fullRight = (viewport.width - viewport.x) / 2;
|
||||
const fullTop = (viewport.height - viewport.y) / 2;
|
||||
const fullBottom = -(viewport.height - viewport.y) / 2;
|
||||
|
||||
const dx = (fullRight - fullLeft) / 2
|
||||
const dy = (fullTop - fullBottom) / 2
|
||||
const cx = (fullRight + fullLeft) / 2
|
||||
const cy = (fullTop + fullBottom) / 2
|
||||
const dx = (fullRight - fullLeft) / 2;
|
||||
const dy = (fullTop - fullBottom) / 2;
|
||||
const cx = (fullRight + fullLeft) / 2;
|
||||
const cy = (fullTop + fullBottom) / 2;
|
||||
|
||||
const left = cx - dx
|
||||
const right = cx + dx
|
||||
const top = cy + dy
|
||||
const bottom = cy - dy
|
||||
const left = cx - dx;
|
||||
const right = cx + dx;
|
||||
const top = cy + dy;
|
||||
const bottom = cy - dy;
|
||||
|
||||
Mat4.ortho(camera.projection, left, right, top, bottom, near, far)
|
||||
Mat4.ortho(camera.projection, left, right, top, bottom, near, far);
|
||||
}
|
||||
|
||||
function createAxesMesh(scale: number, mesh?: Mesh) {
|
||||
const state = MeshBuilder.createState(512, 256, mesh)
|
||||
const radius = 0.05 * scale
|
||||
const x = Vec3.scale(Vec3(), Vec3.unitX, scale)
|
||||
const y = Vec3.scale(Vec3(), Vec3.unitY, scale)
|
||||
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale)
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 }
|
||||
const state = MeshBuilder.createState(512, 256, mesh);
|
||||
const radius = 0.05 * scale;
|
||||
const x = Vec3.scale(Vec3(), Vec3.unitX, scale);
|
||||
const y = Vec3.scale(Vec3(), Vec3.unitY, scale);
|
||||
const z = Vec3.scale(Vec3(), Vec3.unitZ, scale);
|
||||
const cylinderProps = { radiusTop: radius, radiusBottom: radius, radialSegments: 32 };
|
||||
|
||||
state.currentGroup = 0
|
||||
addSphere(state, Vec3.origin, radius, 2)
|
||||
state.currentGroup = 0;
|
||||
addSphere(state, Vec3.origin, radius, 2);
|
||||
|
||||
state.currentGroup = 1
|
||||
addSphere(state, x, radius, 2)
|
||||
addCylinder(state, Vec3.origin, x, 1, cylinderProps)
|
||||
state.currentGroup = 1;
|
||||
addSphere(state, x, radius, 2);
|
||||
addCylinder(state, Vec3.origin, x, 1, cylinderProps);
|
||||
|
||||
state.currentGroup = 2
|
||||
addSphere(state, y, radius, 2)
|
||||
addCylinder(state, Vec3.origin, y, 1, cylinderProps)
|
||||
state.currentGroup = 2;
|
||||
addSphere(state, y, radius, 2);
|
||||
addCylinder(state, Vec3.origin, y, 1, cylinderProps);
|
||||
|
||||
state.currentGroup = 3
|
||||
addSphere(state, z, radius, 2)
|
||||
addCylinder(state, Vec3.origin, z, 1, cylinderProps)
|
||||
state.currentGroup = 3;
|
||||
addSphere(state, z, radius, 2);
|
||||
addCylinder(state, Vec3.origin, z, 1, cylinderProps);
|
||||
|
||||
return MeshBuilder.getMesh(state)
|
||||
return MeshBuilder.getMesh(state);
|
||||
}
|
||||
|
||||
function getAxesShape(props: AxesProps, shape?: Shape<Mesh>) {
|
||||
const scale = 100 * props.scale
|
||||
const mesh = createAxesMesh(scale, shape?.geometry)
|
||||
mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4))
|
||||
const scale = 100 * props.scale;
|
||||
const mesh = createAxesMesh(scale, shape?.geometry);
|
||||
mesh.setBoundingSphere(Sphere3D.create(Vec3.create(scale / 2, scale / 2, scale / 2), scale + scale / 4));
|
||||
const getColor = (groupId: number) => {
|
||||
switch (groupId) {
|
||||
case 1: return props.colorX
|
||||
case 2: return props.colorY
|
||||
case 3: return props.colorZ
|
||||
default: return ColorNames.grey
|
||||
case 1: return props.colorX;
|
||||
case 2: return props.colorY;
|
||||
case 3: return props.colorZ;
|
||||
default: return ColorNames.grey;
|
||||
}
|
||||
}
|
||||
return Shape.create('axes', {}, mesh, getColor, () => 1, () => '')
|
||||
};
|
||||
return Shape.create('axes', {}, mesh, getColor, () => 1, () => '');
|
||||
}
|
||||
|
||||
function createAxesRenderObject(props: AxesProps) {
|
||||
const shape = getAxesShape(props)
|
||||
return Shape.createRenderObject(shape, props)
|
||||
const shape = getAxesShape(props);
|
||||
return Shape.createRenderObject(shape, props);
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
|
||||
export const DrawPassParams = {
|
||||
cameraHelper: PD.Group(CameraHelperParams)
|
||||
}
|
||||
};
|
||||
export const DefaultDrawPassProps = PD.getDefaultValues(DrawPassParams);
|
||||
export type DrawPassProps = PD.Values<typeof DrawPassParams>
|
||||
|
||||
@@ -30,74 +30,74 @@ export class DrawPass {
|
||||
private depthTarget: RenderTarget | null
|
||||
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private debugHelper: BoundingSphereHelper, props: Partial<DrawPassProps> = {}) {
|
||||
const { gl, extensions, resources } = webgl
|
||||
const width = gl.drawingBufferWidth
|
||||
const height = gl.drawingBufferHeight
|
||||
this.colorTarget = webgl.createRenderTarget(width, height)
|
||||
this.packedDepth = !extensions.depthTexture
|
||||
this.depthTarget = this.packedDepth ? webgl.createRenderTarget(width, height) : null
|
||||
this.depthTexture = this.depthTarget ? this.depthTarget.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest')
|
||||
const { gl, extensions, resources } = webgl;
|
||||
const width = gl.drawingBufferWidth;
|
||||
const height = gl.drawingBufferHeight;
|
||||
this.colorTarget = webgl.createRenderTarget(width, height);
|
||||
this.packedDepth = !extensions.depthTexture;
|
||||
this.depthTarget = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
|
||||
this.depthTexture = this.depthTarget ? this.depthTarget.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest');
|
||||
if (!this.packedDepth) {
|
||||
this.depthTexture.define(width, height)
|
||||
this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth')
|
||||
this.depthTexture.define(width, height);
|
||||
this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
|
||||
}
|
||||
|
||||
const p = { ...DefaultDrawPassProps, ...props }
|
||||
const p = { ...DefaultDrawPassProps, ...props };
|
||||
this.cameraHelper = new CameraHelper(webgl, p.cameraHelper);
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
this.colorTarget.setSize(width, height)
|
||||
this.colorTarget.setSize(width, height);
|
||||
if (this.depthTarget) {
|
||||
this.depthTarget.setSize(width, height)
|
||||
this.depthTarget.setSize(width, height);
|
||||
} else {
|
||||
this.depthTexture.define(width, height)
|
||||
this.depthTexture.define(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
setProps(props: Partial<DrawPassProps>) {
|
||||
if (props.cameraHelper) this.cameraHelper.setProps(props.cameraHelper)
|
||||
if (props.cameraHelper) this.cameraHelper.setProps(props.cameraHelper);
|
||||
}
|
||||
|
||||
get props(): DrawPassProps {
|
||||
return {
|
||||
cameraHelper: { ...this.cameraHelper.props }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render(toDrawingBuffer: boolean, transparentBackground: boolean) {
|
||||
const { webgl, renderer, colorTarget, depthTarget } = this
|
||||
const { webgl, renderer, colorTarget, depthTarget } = this;
|
||||
if (toDrawingBuffer) {
|
||||
webgl.unbindFramebuffer()
|
||||
webgl.unbindFramebuffer();
|
||||
} else {
|
||||
colorTarget.bind()
|
||||
colorTarget.bind();
|
||||
if (!this.packedDepth) {
|
||||
// TODO unlcear why it is not enough to call `attachFramebuffer` in `Texture.reset`
|
||||
this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth')
|
||||
this.depthTexture.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
|
||||
}
|
||||
}
|
||||
|
||||
renderer.setViewport(0, 0, colorTarget.getWidth(), colorTarget.getHeight())
|
||||
this.renderInternal('color', transparentBackground)
|
||||
renderer.setViewport(0, 0, colorTarget.getWidth(), colorTarget.getHeight());
|
||||
this.renderInternal('color', transparentBackground);
|
||||
|
||||
// do a depth pass if not rendering to drawing buffer and
|
||||
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
|
||||
if (!toDrawingBuffer && depthTarget) {
|
||||
depthTarget.bind()
|
||||
this.renderInternal('depth', transparentBackground)
|
||||
depthTarget.bind();
|
||||
this.renderInternal('depth', transparentBackground);
|
||||
}
|
||||
}
|
||||
|
||||
private renderInternal(variant: 'color' | 'depth', transparentBackground: boolean) {
|
||||
const { renderer, scene, camera, debugHelper, cameraHelper } = this
|
||||
renderer.render(scene, camera, variant, true, transparentBackground)
|
||||
const { renderer, scene, camera, debugHelper, cameraHelper } = this;
|
||||
renderer.render(scene, camera, variant, true, transparentBackground);
|
||||
if (debugHelper.isEnabled) {
|
||||
debugHelper.syncVisibility()
|
||||
renderer.render(debugHelper.scene, camera, variant, false, transparentBackground)
|
||||
debugHelper.syncVisibility();
|
||||
renderer.render(debugHelper.scene, camera, variant, false, transparentBackground);
|
||||
}
|
||||
if (cameraHelper.isEnabled) {
|
||||
cameraHelper.update(camera)
|
||||
renderer.render(cameraHelper.scene, cameraHelper.camera, variant, false, transparentBackground)
|
||||
cameraHelper.update(camera);
|
||||
renderer.render(cameraHelper.scene, cameraHelper.camera, variant, false, transparentBackground);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,9 @@ import Renderer from '../../mol-gl/renderer';
|
||||
import Scene from '../../mol-gl/scene';
|
||||
import { BoundingSphereHelper } from '../helper/bounding-sphere-helper';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { DrawPass, DrawPassParams } from './draw'
|
||||
import { PostprocessingPass, PostprocessingParams } from './postprocessing'
|
||||
import { MultiSamplePass, MultiSampleParams } from './multi-sample'
|
||||
import { DrawPass, DrawPassParams } from './draw';
|
||||
import { PostprocessingPass, PostprocessingParams } from './postprocessing';
|
||||
import { MultiSamplePass, MultiSampleParams } from './multi-sample';
|
||||
import { Camera } from '../camera';
|
||||
import { Viewport } from '../camera/util';
|
||||
|
||||
@@ -21,7 +21,7 @@ export const ImageParams = {
|
||||
multiSample: PD.Group(MultiSampleParams),
|
||||
postprocessing: PD.Group(PostprocessingParams),
|
||||
drawPass: PD.Group(DrawPassParams),
|
||||
}
|
||||
};
|
||||
export type ImageProps = PD.Values<typeof ImageParams>
|
||||
|
||||
export class ImagePass {
|
||||
@@ -31,43 +31,43 @@ export class ImagePass {
|
||||
private _transparentBackground = false
|
||||
|
||||
private _colorTarget: RenderTarget
|
||||
get colorTarget() { return this._colorTarget }
|
||||
get colorTarget() { return this._colorTarget; }
|
||||
|
||||
readonly drawPass: DrawPass
|
||||
private readonly postprocessing: PostprocessingPass
|
||||
private readonly multiSample: MultiSamplePass
|
||||
|
||||
get width() { return this._width }
|
||||
get height() { return this._height }
|
||||
get width() { return this._width; }
|
||||
get height() { return this._height; }
|
||||
|
||||
constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, props: Partial<ImageProps>) {
|
||||
const p = { ...PD.getDefaultValues(ImageParams), ...props }
|
||||
const p = { ...PD.getDefaultValues(ImageParams), ...props };
|
||||
|
||||
this._transparentBackground = p.transparentBackground
|
||||
this._transparentBackground = p.transparentBackground;
|
||||
|
||||
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, p.drawPass)
|
||||
this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing)
|
||||
this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample)
|
||||
this.drawPass = new DrawPass(webgl, renderer, scene, this._camera, debugHelper, p.drawPass);
|
||||
this.postprocessing = new PostprocessingPass(webgl, this._camera, this.drawPass, p.postprocessing);
|
||||
this.multiSample = new MultiSamplePass(webgl, this._camera, this.drawPass, this.postprocessing, p.multiSample);
|
||||
|
||||
this.setSize(this._width, this._height)
|
||||
this.setSize(this._width, this._height);
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
if (width === this._width && height === this._height) return
|
||||
if (width === this._width && height === this._height) return;
|
||||
|
||||
this._width = width
|
||||
this._height = height
|
||||
this._width = width;
|
||||
this._height = height;
|
||||
|
||||
this.drawPass.setSize(width, height)
|
||||
this.postprocessing.setSize(width, height)
|
||||
this.multiSample.setSize(width, height)
|
||||
this.drawPass.setSize(width, height);
|
||||
this.postprocessing.setSize(width, height);
|
||||
this.multiSample.setSize(width, height);
|
||||
}
|
||||
|
||||
setProps(props: Partial<ImageProps> = {}) {
|
||||
if (props.transparentBackground !== undefined) this._transparentBackground = props.transparentBackground
|
||||
if (props.postprocessing) this.postprocessing.setProps(props.postprocessing)
|
||||
if (props.multiSample) this.multiSample.setProps(props.multiSample)
|
||||
if (props.drawPass) this.drawPass.setProps(props.drawPass)
|
||||
if (props.transparentBackground !== undefined) this._transparentBackground = props.transparentBackground;
|
||||
if (props.postprocessing) this.postprocessing.setProps(props.postprocessing);
|
||||
if (props.multiSample) this.multiSample.setProps(props.multiSample);
|
||||
if (props.drawPass) this.drawPass.setProps(props.drawPass);
|
||||
}
|
||||
|
||||
get props(): ImageProps {
|
||||
@@ -76,34 +76,34 @@ export class ImagePass {
|
||||
postprocessing: this.postprocessing.props,
|
||||
multiSample: this.multiSample.props,
|
||||
drawPass: this.drawPass.props
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
Camera.copySnapshot(this._camera.state, this.camera.state)
|
||||
Viewport.set(this._camera.viewport, 0, 0, this._width, this._height)
|
||||
this._camera.update()
|
||||
Camera.copySnapshot(this._camera.state, this.camera.state);
|
||||
Viewport.set(this._camera.viewport, 0, 0, this._width, this._height);
|
||||
this._camera.update();
|
||||
|
||||
this.renderer.setViewport(0, 0, this._width, this._height);
|
||||
|
||||
if (this.multiSample.enabled) {
|
||||
this.multiSample.render(false, this._transparentBackground)
|
||||
this._colorTarget = this.multiSample.colorTarget
|
||||
this.multiSample.render(false, this._transparentBackground);
|
||||
this._colorTarget = this.multiSample.colorTarget;
|
||||
} else {
|
||||
this.drawPass.render(false, this._transparentBackground)
|
||||
this.drawPass.render(false, this._transparentBackground);
|
||||
if (this.postprocessing.enabled) {
|
||||
this.postprocessing.render(false)
|
||||
this._colorTarget = this.postprocessing.target
|
||||
this.postprocessing.render(false);
|
||||
this._colorTarget = this.postprocessing.target;
|
||||
} else {
|
||||
this._colorTarget = this.drawPass.colorTarget
|
||||
this._colorTarget = this.drawPass.colorTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getImageData(width: number, height: number) {
|
||||
this.setSize(width, height)
|
||||
this.render()
|
||||
const pd = this.colorTarget.getPixelData()
|
||||
return new ImageData(new Uint8ClampedArray(pd.array), pd.width, pd.height)
|
||||
this.setSize(width, height);
|
||||
this.render();
|
||||
const pd = this.colorTarget.getPixelData();
|
||||
return new ImageData(new Uint8ClampedArray(pd.array), pd.width, pd.height);
|
||||
}
|
||||
}
|
||||
@@ -19,15 +19,15 @@ import { Camera } from '../../mol-canvas3d/camera';
|
||||
import { PostprocessingPass } from './postprocessing';
|
||||
import { DrawPass } from './draw';
|
||||
|
||||
import quad_vert from '../../mol-gl/shader/quad.vert'
|
||||
import compose_frag from '../../mol-gl/shader/compose.frag'
|
||||
import quad_vert from '../../mol-gl/shader/quad.vert';
|
||||
import compose_frag from '../../mol-gl/shader/compose.frag';
|
||||
|
||||
const ComposeSchema = {
|
||||
...QuadSchema,
|
||||
tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
|
||||
uTexSize: UniformSpec('v2'),
|
||||
uWeight: UniformSpec('f'),
|
||||
}
|
||||
};
|
||||
|
||||
type ComposeRenderable = ComputeRenderable<Values<typeof ComposeSchema>>
|
||||
|
||||
@@ -37,19 +37,19 @@ function getComposeRenderable(ctx: WebGLContext, colorTexture: Texture): Compose
|
||||
tColor: ValueCell.create(colorTexture),
|
||||
uTexSize: ValueCell.create(Vec2.create(colorTexture.getWidth(), colorTexture.getHeight())),
|
||||
uWeight: ValueCell.create(1.0),
|
||||
}
|
||||
};
|
||||
|
||||
const schema = { ...ComposeSchema }
|
||||
const shaderCode = ShaderCode('compose', quad_vert, compose_frag)
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
const schema = { ...ComposeSchema };
|
||||
const shaderCode = ShaderCode('compose', quad_vert, compose_frag);
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values)
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
export const MultiSampleParams = {
|
||||
mode: PD.Select('off', [['off', 'Off'], ['on', 'On'], ['temporal', 'Temporal']]),
|
||||
sampleLevel: PD.Numeric(2, { min: 0, max: 5, step: 1 }),
|
||||
}
|
||||
};
|
||||
export type MultiSampleProps = PD.Values<typeof MultiSampleParams>
|
||||
|
||||
export class MultiSamplePass {
|
||||
@@ -65,229 +65,229 @@ export class MultiSamplePass {
|
||||
private lastRenderTime = 0
|
||||
|
||||
constructor(private webgl: WebGLContext, private camera: Camera, private drawPass: DrawPass, private postprocessing: PostprocessingPass, props: Partial<MultiSampleProps>) {
|
||||
const { gl } = webgl
|
||||
this.colorTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight)
|
||||
this.composeTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight)
|
||||
this.holdTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight)
|
||||
this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture)
|
||||
this.props = { ...PD.getDefaultValues(MultiSampleParams), ...props }
|
||||
const { gl } = webgl;
|
||||
this.colorTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
this.composeTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
this.holdTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture);
|
||||
this.props = { ...PD.getDefaultValues(MultiSampleParams), ...props };
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
if (this.props.mode === 'temporal') {
|
||||
if (this.currentTime - this.lastRenderTime > 200) {
|
||||
return this.sampleIndex !== -1
|
||||
return this.sampleIndex !== -1;
|
||||
} else {
|
||||
this.sampleIndex = 0
|
||||
return false
|
||||
this.sampleIndex = 0;
|
||||
return false;
|
||||
}
|
||||
} else if (this.props.mode === 'on') {
|
||||
return true
|
||||
return true;
|
||||
} else {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
update(changed: boolean, currentTime: number) {
|
||||
if (changed) this.lastRenderTime = currentTime
|
||||
this.currentTime = currentTime
|
||||
if (changed) this.lastRenderTime = currentTime;
|
||||
this.currentTime = currentTime;
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
this.colorTarget.setSize(width, height)
|
||||
this.composeTarget.setSize(width, height)
|
||||
this.holdTarget.setSize(width, height)
|
||||
ValueCell.update(this.compose.values.uTexSize, Vec2.set(this.compose.values.uTexSize.ref.value, width, height))
|
||||
this.colorTarget.setSize(width, height);
|
||||
this.composeTarget.setSize(width, height);
|
||||
this.holdTarget.setSize(width, height);
|
||||
ValueCell.update(this.compose.values.uTexSize, Vec2.set(this.compose.values.uTexSize.ref.value, width, height));
|
||||
}
|
||||
|
||||
setProps(props: Partial<MultiSampleProps>) {
|
||||
if (props.mode !== undefined) this.props.mode = props.mode
|
||||
if (props.sampleLevel !== undefined) this.props.sampleLevel = props.sampleLevel
|
||||
if (props.mode !== undefined) this.props.mode = props.mode;
|
||||
if (props.sampleLevel !== undefined) this.props.sampleLevel = props.sampleLevel;
|
||||
}
|
||||
|
||||
render(toDrawingBuffer: boolean, transparentBackground: boolean) {
|
||||
if (this.props.mode === 'temporal') {
|
||||
this.renderTemporalMultiSample(toDrawingBuffer, transparentBackground)
|
||||
this.renderTemporalMultiSample(toDrawingBuffer, transparentBackground);
|
||||
} else {
|
||||
this.renderMultiSample(toDrawingBuffer, transparentBackground)
|
||||
this.renderMultiSample(toDrawingBuffer, transparentBackground);
|
||||
}
|
||||
}
|
||||
|
||||
private renderMultiSample(toDrawingBuffer: boolean, transparentBackground: boolean) {
|
||||
const { camera, compose, composeTarget, drawPass, postprocessing, webgl } = this
|
||||
const { gl, state } = webgl
|
||||
const { camera, compose, composeTarget, drawPass, postprocessing, webgl } = this;
|
||||
const { gl, state } = webgl;
|
||||
|
||||
// based on the Multisample Anti-Aliasing Render Pass
|
||||
// contributed to three.js by bhouston / http://clara.io/
|
||||
//
|
||||
// This manual approach to MSAA re-renders the scene once for
|
||||
// each sample with camera jitter and accumulates the results.
|
||||
const offsetList = JitterVectors[ Math.max(0, Math.min(this.props.sampleLevel, 5)) ]
|
||||
const offsetList = JitterVectors[ Math.max(0, Math.min(this.props.sampleLevel, 5)) ];
|
||||
|
||||
const baseSampleWeight = 1.0 / offsetList.length
|
||||
const roundingRange = 1 / 32
|
||||
const baseSampleWeight = 1.0 / offsetList.length;
|
||||
const roundingRange = 1 / 32;
|
||||
|
||||
camera.viewOffset.enabled = true
|
||||
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture)
|
||||
compose.update()
|
||||
camera.viewOffset.enabled = true;
|
||||
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
|
||||
compose.update();
|
||||
|
||||
const width = drawPass.colorTarget.getWidth()
|
||||
const height = drawPass.colorTarget.getHeight()
|
||||
const width = drawPass.colorTarget.getWidth();
|
||||
const height = drawPass.colorTarget.getHeight();
|
||||
|
||||
// render the scene multiple times, each slightly jitter offset
|
||||
// from the last and accumulate the results.
|
||||
for (let i = 0; i < offsetList.length; ++i) {
|
||||
const offset = offsetList[i]
|
||||
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height)
|
||||
camera.update()
|
||||
const offset = offsetList[i];
|
||||
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height);
|
||||
camera.update();
|
||||
|
||||
// the theory is that equal weights for each sample lead to an accumulation of rounding
|
||||
// errors. The following equation varies the sampleWeight per sample so that it is uniformly
|
||||
// distributed across a range of values whose rounding errors cancel each other out.
|
||||
const uniformCenteredDistribution = -0.5 + (i + 0.5) / offsetList.length
|
||||
const sampleWeight = baseSampleWeight + roundingRange * uniformCenteredDistribution
|
||||
ValueCell.update(compose.values.uWeight, sampleWeight)
|
||||
const uniformCenteredDistribution = -0.5 + (i + 0.5) / offsetList.length;
|
||||
const sampleWeight = baseSampleWeight + roundingRange * uniformCenteredDistribution;
|
||||
ValueCell.update(compose.values.uWeight, sampleWeight);
|
||||
|
||||
// render scene and optionally postprocess
|
||||
drawPass.render(false, transparentBackground)
|
||||
if (postprocessing.enabled) postprocessing.render(false)
|
||||
drawPass.render(false, transparentBackground);
|
||||
if (postprocessing.enabled) postprocessing.render(false);
|
||||
|
||||
// compose rendered scene with compose target
|
||||
composeTarget.bind()
|
||||
gl.viewport(0, 0, width, height)
|
||||
state.enable(gl.BLEND)
|
||||
state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD)
|
||||
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE)
|
||||
state.disable(gl.DEPTH_TEST)
|
||||
state.disable(gl.SCISSOR_TEST)
|
||||
state.depthMask(false)
|
||||
composeTarget.bind();
|
||||
gl.viewport(0, 0, width, height);
|
||||
state.enable(gl.BLEND);
|
||||
state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
|
||||
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.depthMask(false);
|
||||
if (i === 0) {
|
||||
state.clearColor(0, 0, 0, 0)
|
||||
gl.clear(gl.COLOR_BUFFER_BIT)
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
compose.render()
|
||||
compose.render();
|
||||
}
|
||||
|
||||
ValueCell.update(compose.values.uWeight, 1.0)
|
||||
ValueCell.update(compose.values.tColor, composeTarget.texture)
|
||||
compose.update()
|
||||
ValueCell.update(compose.values.uWeight, 1.0);
|
||||
ValueCell.update(compose.values.tColor, composeTarget.texture);
|
||||
compose.update();
|
||||
|
||||
if (toDrawingBuffer) {
|
||||
webgl.unbindFramebuffer()
|
||||
webgl.unbindFramebuffer();
|
||||
} else {
|
||||
this.colorTarget.bind()
|
||||
this.colorTarget.bind();
|
||||
}
|
||||
gl.viewport(0, 0, width, height)
|
||||
state.disable(gl.BLEND)
|
||||
compose.render()
|
||||
gl.viewport(0, 0, width, height);
|
||||
state.disable(gl.BLEND);
|
||||
compose.render();
|
||||
|
||||
camera.viewOffset.enabled = false
|
||||
camera.update()
|
||||
camera.viewOffset.enabled = false;
|
||||
camera.update();
|
||||
}
|
||||
|
||||
private renderTemporalMultiSample(toDrawingBuffer: boolean, transparentBackground: boolean) {
|
||||
const { camera, compose, composeTarget, holdTarget, postprocessing, drawPass, webgl } = this
|
||||
const { gl, state } = webgl
|
||||
const { camera, compose, composeTarget, holdTarget, postprocessing, drawPass, webgl } = this;
|
||||
const { gl, state } = webgl;
|
||||
|
||||
// based on the Multisample Anti-Aliasing Render Pass
|
||||
// contributed to three.js by bhouston / http://clara.io/
|
||||
//
|
||||
// This manual approach to MSAA re-renders the scene once for
|
||||
// each sample with camera jitter and accumulates the results.
|
||||
const offsetList = JitterVectors[ Math.max(0, Math.min(this.props.sampleLevel, 5)) ]
|
||||
const offsetList = JitterVectors[ Math.max(0, Math.min(this.props.sampleLevel, 5)) ];
|
||||
|
||||
if (this.sampleIndex === -1) return
|
||||
if (this.sampleIndex === -1) return;
|
||||
if (this.sampleIndex >= offsetList.length) {
|
||||
this.sampleIndex = -1
|
||||
return
|
||||
this.sampleIndex = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
const i = this.sampleIndex
|
||||
const i = this.sampleIndex;
|
||||
|
||||
if (i === 0) {
|
||||
drawPass.render(false, transparentBackground)
|
||||
if (postprocessing.enabled) postprocessing.render(false)
|
||||
ValueCell.update(compose.values.uWeight, 1.0)
|
||||
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture)
|
||||
compose.update()
|
||||
drawPass.render(false, transparentBackground);
|
||||
if (postprocessing.enabled) postprocessing.render(false);
|
||||
ValueCell.update(compose.values.uWeight, 1.0);
|
||||
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
|
||||
compose.update();
|
||||
|
||||
holdTarget.bind()
|
||||
state.disable(gl.BLEND)
|
||||
compose.render()
|
||||
holdTarget.bind();
|
||||
state.disable(gl.BLEND);
|
||||
compose.render();
|
||||
}
|
||||
|
||||
const sampleWeight = 1.0 / offsetList.length
|
||||
const sampleWeight = 1.0 / offsetList.length;
|
||||
|
||||
camera.viewOffset.enabled = true
|
||||
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture)
|
||||
ValueCell.update(compose.values.uWeight, sampleWeight)
|
||||
compose.update()
|
||||
camera.viewOffset.enabled = true;
|
||||
ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture);
|
||||
ValueCell.update(compose.values.uWeight, sampleWeight);
|
||||
compose.update();
|
||||
|
||||
const width = drawPass.colorTarget.getWidth()
|
||||
const height = drawPass.colorTarget.getHeight()
|
||||
const width = drawPass.colorTarget.getWidth();
|
||||
const height = drawPass.colorTarget.getHeight();
|
||||
|
||||
// render the scene multiple times, each slightly jitter offset
|
||||
// from the last and accumulate the results.
|
||||
const numSamplesPerFrame = Math.pow(2, this.props.sampleLevel)
|
||||
const numSamplesPerFrame = Math.pow(2, this.props.sampleLevel);
|
||||
for (let i = 0; i < numSamplesPerFrame; ++i) {
|
||||
const offset = offsetList[this.sampleIndex]
|
||||
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height)
|
||||
camera.update()
|
||||
const offset = offsetList[this.sampleIndex];
|
||||
Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height);
|
||||
camera.update();
|
||||
|
||||
// render scene and optionally postprocess
|
||||
drawPass.render(false, transparentBackground)
|
||||
if (postprocessing.enabled) postprocessing.render(false)
|
||||
drawPass.render(false, transparentBackground);
|
||||
if (postprocessing.enabled) postprocessing.render(false);
|
||||
|
||||
// compose rendered scene with compose target
|
||||
composeTarget.bind()
|
||||
state.enable(gl.BLEND)
|
||||
state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD)
|
||||
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE)
|
||||
state.disable(gl.DEPTH_TEST)
|
||||
state.disable(gl.SCISSOR_TEST)
|
||||
state.depthMask(false)
|
||||
composeTarget.bind();
|
||||
state.enable(gl.BLEND);
|
||||
state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
|
||||
state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.depthMask(false);
|
||||
if (this.sampleIndex === 0) {
|
||||
state.clearColor(0, 0, 0, 0)
|
||||
gl.clear(gl.COLOR_BUFFER_BIT)
|
||||
state.clearColor(0, 0, 0, 0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
compose.render()
|
||||
compose.render();
|
||||
|
||||
this.sampleIndex += 1
|
||||
if (this.sampleIndex >= offsetList.length ) break
|
||||
this.sampleIndex += 1;
|
||||
if (this.sampleIndex >= offsetList.length ) break;
|
||||
}
|
||||
|
||||
const accumulationWeight = this.sampleIndex * sampleWeight
|
||||
const accumulationWeight = this.sampleIndex * sampleWeight;
|
||||
if (accumulationWeight > 0) {
|
||||
ValueCell.update(compose.values.uWeight, 1.0)
|
||||
ValueCell.update(compose.values.tColor, composeTarget.texture)
|
||||
compose.update()
|
||||
ValueCell.update(compose.values.uWeight, 1.0);
|
||||
ValueCell.update(compose.values.tColor, composeTarget.texture);
|
||||
compose.update();
|
||||
if (toDrawingBuffer) {
|
||||
webgl.unbindFramebuffer()
|
||||
webgl.unbindFramebuffer();
|
||||
} else {
|
||||
this.colorTarget.bind()
|
||||
this.colorTarget.bind();
|
||||
}
|
||||
gl.viewport(0, 0, width, height)
|
||||
state.disable(gl.BLEND)
|
||||
compose.render()
|
||||
gl.viewport(0, 0, width, height);
|
||||
state.disable(gl.BLEND);
|
||||
compose.render();
|
||||
}
|
||||
if (accumulationWeight < 1.0) {
|
||||
ValueCell.update(compose.values.uWeight, 1.0 - accumulationWeight)
|
||||
ValueCell.update(compose.values.tColor, holdTarget.texture)
|
||||
compose.update()
|
||||
ValueCell.update(compose.values.uWeight, 1.0 - accumulationWeight);
|
||||
ValueCell.update(compose.values.tColor, holdTarget.texture);
|
||||
compose.update();
|
||||
if (toDrawingBuffer) {
|
||||
webgl.unbindFramebuffer()
|
||||
webgl.unbindFramebuffer();
|
||||
} else {
|
||||
this.colorTarget.bind()
|
||||
this.colorTarget.bind();
|
||||
}
|
||||
gl.viewport(0, 0, width, height)
|
||||
if (accumulationWeight === 0) state.disable(gl.BLEND)
|
||||
else state.enable(gl.BLEND)
|
||||
compose.render()
|
||||
gl.viewport(0, 0, width, height);
|
||||
if (accumulationWeight === 0) state.disable(gl.BLEND);
|
||||
else state.enable(gl.BLEND);
|
||||
compose.render();
|
||||
}
|
||||
|
||||
camera.viewOffset.enabled = false
|
||||
camera.update()
|
||||
if (this.sampleIndex >= offsetList.length) this.sampleIndex = -1
|
||||
camera.viewOffset.enabled = false;
|
||||
camera.update();
|
||||
if (this.sampleIndex >= offsetList.length) this.sampleIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,12 +321,12 @@ const JitterVectors = [
|
||||
[ 2, 1 ], [ 6, 2 ], [ 0, 4 ], [ 4, 4 ],
|
||||
[ 2, 5 ], [ 7, 5 ], [ 5, 6 ], [ 3, 7 ]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
JitterVectors.forEach(offsetList => {
|
||||
offsetList.forEach(offset => {
|
||||
// 0.0625 = 1 / 16
|
||||
offset[0] *= 0.0625
|
||||
offset[1] *= 0.0625
|
||||
})
|
||||
})
|
||||
offset[0] *= 0.0625;
|
||||
offset[1] *= 0.0625;
|
||||
});
|
||||
});
|
||||
@@ -28,44 +28,44 @@ export class PickPass {
|
||||
private pickHeight: number
|
||||
|
||||
constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private pickBaseScale: number) {
|
||||
const { gl } = webgl
|
||||
const width = gl.drawingBufferWidth
|
||||
const height = gl.drawingBufferHeight
|
||||
const { gl } = webgl;
|
||||
const width = gl.drawingBufferWidth;
|
||||
const height = gl.drawingBufferHeight;
|
||||
|
||||
this.pickScale = pickBaseScale / webgl.pixelRatio
|
||||
this.pickWidth = Math.round(width * this.pickScale)
|
||||
this.pickHeight = Math.round(height * this.pickScale)
|
||||
this.pickScale = pickBaseScale / webgl.pixelRatio;
|
||||
this.pickWidth = Math.round(width * this.pickScale);
|
||||
this.pickHeight = Math.round(height * this.pickScale);
|
||||
|
||||
this.objectPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight)
|
||||
this.instancePickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight)
|
||||
this.groupPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight)
|
||||
this.objectPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
|
||||
this.instancePickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
|
||||
this.groupPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight);
|
||||
|
||||
this.setupBuffers()
|
||||
this.setupBuffers();
|
||||
}
|
||||
|
||||
private setupBuffers() {
|
||||
const bufferSize = this.pickWidth * this.pickHeight * 4
|
||||
const bufferSize = this.pickWidth * this.pickHeight * 4;
|
||||
if (!this.objectBuffer || this.objectBuffer.length !== bufferSize) {
|
||||
this.objectBuffer = new Uint8Array(bufferSize)
|
||||
this.instanceBuffer = new Uint8Array(bufferSize)
|
||||
this.groupBuffer = new Uint8Array(bufferSize)
|
||||
this.objectBuffer = new Uint8Array(bufferSize);
|
||||
this.instanceBuffer = new Uint8Array(bufferSize);
|
||||
this.groupBuffer = new Uint8Array(bufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
this.pickScale = this.pickBaseScale / this.webgl.pixelRatio
|
||||
this.pickWidth = Math.round(width * this.pickScale)
|
||||
this.pickHeight = Math.round(height * this.pickScale)
|
||||
this.pickScale = this.pickBaseScale / this.webgl.pixelRatio;
|
||||
this.pickWidth = Math.round(width * this.pickScale);
|
||||
this.pickHeight = Math.round(height * this.pickScale);
|
||||
|
||||
this.objectPickTarget.setSize(this.pickWidth, this.pickHeight)
|
||||
this.instancePickTarget.setSize(this.pickWidth, this.pickHeight)
|
||||
this.groupPickTarget.setSize(this.pickWidth, this.pickHeight)
|
||||
this.objectPickTarget.setSize(this.pickWidth, this.pickHeight);
|
||||
this.instancePickTarget.setSize(this.pickWidth, this.pickHeight);
|
||||
this.groupPickTarget.setSize(this.pickWidth, this.pickHeight);
|
||||
|
||||
this.setupBuffers()
|
||||
this.setupBuffers();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { renderer, scene, camera } = this
|
||||
const { renderer, scene, camera } = this;
|
||||
renderer.setViewport(0, 0, this.pickWidth, this.pickHeight);
|
||||
this.objectPickTarget.bind();
|
||||
renderer.render(scene, camera, 'pickObject', true, false);
|
||||
@@ -74,53 +74,53 @@ export class PickPass {
|
||||
this.groupPickTarget.bind();
|
||||
renderer.render(scene, camera, 'pickGroup', true, false);
|
||||
|
||||
this.pickDirty = false
|
||||
this.pickDirty = false;
|
||||
}
|
||||
|
||||
private syncBuffers() {
|
||||
const { webgl } = this
|
||||
const { webgl } = this;
|
||||
|
||||
this.objectPickTarget.bind()
|
||||
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.objectBuffer)
|
||||
this.objectPickTarget.bind();
|
||||
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.objectBuffer);
|
||||
|
||||
this.instancePickTarget.bind()
|
||||
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.instanceBuffer)
|
||||
this.instancePickTarget.bind();
|
||||
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.instanceBuffer);
|
||||
|
||||
this.groupPickTarget.bind()
|
||||
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.groupBuffer)
|
||||
this.groupPickTarget.bind();
|
||||
webgl.readPixels(0, 0, this.pickWidth, this.pickHeight, this.groupBuffer);
|
||||
}
|
||||
|
||||
private getId(x: number, y: number, buffer: Uint8Array) {
|
||||
const idx = (y * this.pickWidth + x) * 4
|
||||
return decodeFloatRGB(buffer[idx], buffer[idx + 1], buffer[idx + 2])
|
||||
const idx = (y * this.pickWidth + x) * 4;
|
||||
return decodeFloatRGB(buffer[idx], buffer[idx + 1], buffer[idx + 2]);
|
||||
}
|
||||
|
||||
identify(x: number, y: number): PickingId | undefined {
|
||||
const { webgl, pickScale } = this
|
||||
if (webgl.isContextLost) return
|
||||
const { webgl, pickScale } = this;
|
||||
if (webgl.isContextLost) return;
|
||||
|
||||
const { gl } = webgl
|
||||
const { gl } = webgl;
|
||||
if (this.pickDirty) {
|
||||
this.render()
|
||||
this.syncBuffers()
|
||||
this.render();
|
||||
this.syncBuffers();
|
||||
}
|
||||
|
||||
x *= webgl.pixelRatio
|
||||
y *= webgl.pixelRatio
|
||||
y = gl.drawingBufferHeight - y // flip y
|
||||
x *= webgl.pixelRatio;
|
||||
y *= webgl.pixelRatio;
|
||||
y = gl.drawingBufferHeight - y; // flip y
|
||||
|
||||
const xp = Math.round(x * pickScale)
|
||||
const yp = Math.round(y * pickScale)
|
||||
const xp = Math.round(x * pickScale);
|
||||
const yp = Math.round(y * pickScale);
|
||||
|
||||
const objectId = this.getId(xp, yp, this.objectBuffer)
|
||||
if (objectId === -1) return
|
||||
const objectId = this.getId(xp, yp, this.objectBuffer);
|
||||
if (objectId === -1) return;
|
||||
|
||||
const instanceId = this.getId(xp, yp, this.instanceBuffer)
|
||||
if (instanceId === -1) return
|
||||
const instanceId = this.getId(xp, yp, this.instanceBuffer);
|
||||
if (instanceId === -1) return;
|
||||
|
||||
const groupId = this.getId(xp, yp, this.groupBuffer)
|
||||
if (groupId === -1) return
|
||||
const groupId = this.getId(xp, yp, this.groupBuffer);
|
||||
if (groupId === -1) return;
|
||||
|
||||
return { objectId, instanceId, groupId }
|
||||
return { objectId, instanceId, groupId };
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,8 @@ import { DrawPass } from './draw';
|
||||
import { Camera } from '../../mol-canvas3d/camera';
|
||||
import { produce } from 'immer';
|
||||
|
||||
import quad_vert from '../../mol-gl/shader/quad.vert'
|
||||
import postprocessing_frag from '../../mol-gl/shader/postprocessing.frag'
|
||||
import quad_vert from '../../mol-gl/shader/quad.vert';
|
||||
import postprocessing_frag from '../../mol-gl/shader/postprocessing.frag';
|
||||
|
||||
const PostprocessingSchema = {
|
||||
...QuadSchema,
|
||||
@@ -45,7 +45,7 @@ const PostprocessingSchema = {
|
||||
uOutlineThreshold: UniformSpec('f'),
|
||||
|
||||
dPackedDepth: DefineSpec('boolean'),
|
||||
}
|
||||
};
|
||||
|
||||
export const PostprocessingParams = {
|
||||
occlusion: PD.MappedStatic('off', {
|
||||
@@ -63,13 +63,13 @@ export const PostprocessingParams = {
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Draw outline around 3D objects' })
|
||||
}
|
||||
};
|
||||
export type PostprocessingProps = PD.Values<typeof PostprocessingParams>
|
||||
|
||||
type PostprocessingRenderable = ComputeRenderable<Values<typeof PostprocessingSchema>>
|
||||
|
||||
function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, depthTexture: Texture, packedDepth: boolean, props: Partial<PostprocessingProps>): PostprocessingRenderable {
|
||||
const p = { ...PD.getDefaultValues(PostprocessingParams), ...props }
|
||||
const p = { ...PD.getDefaultValues(PostprocessingParams), ...props };
|
||||
const values: Values<typeof PostprocessingSchema> = {
|
||||
...QuadValues,
|
||||
tColor: ValueCell.create(colorTexture),
|
||||
@@ -93,13 +93,13 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
|
||||
uOutlineThreshold: ValueCell.create(p.outline.name === 'on' ? p.outline.params.threshold : 0.8),
|
||||
|
||||
dPackedDepth: ValueCell.create(packedDepth),
|
||||
}
|
||||
};
|
||||
|
||||
const schema = { ...PostprocessingSchema }
|
||||
const shaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag)
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values)
|
||||
const schema = { ...PostprocessingSchema };
|
||||
const shaderCode = ShaderCode('postprocessing', quad_vert, postprocessing_frag);
|
||||
const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
|
||||
|
||||
return createComputeRenderable(renderItem, values)
|
||||
return createComputeRenderable(renderItem, values);
|
||||
}
|
||||
|
||||
export class PostprocessingPass {
|
||||
@@ -108,67 +108,67 @@ export class PostprocessingPass {
|
||||
renderable: PostprocessingRenderable
|
||||
|
||||
constructor(private webgl: WebGLContext, private camera: Camera, drawPass: DrawPass, props: Partial<PostprocessingProps>) {
|
||||
const { gl } = webgl
|
||||
this.target = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight)
|
||||
this.props = { ...PD.getDefaultValues(PostprocessingParams), ...props }
|
||||
const { colorTarget, depthTexture, packedDepth } = drawPass
|
||||
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, packedDepth, this.props)
|
||||
const { gl } = webgl;
|
||||
this.target = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
this.props = { ...PD.getDefaultValues(PostprocessingParams), ...props };
|
||||
const { colorTarget, depthTexture, packedDepth } = drawPass;
|
||||
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, packedDepth, this.props);
|
||||
}
|
||||
|
||||
get enabled() {
|
||||
return this.props.occlusion.name === 'on' || this.props.outline.name === 'on'
|
||||
return this.props.occlusion.name === 'on' || this.props.outline.name === 'on';
|
||||
}
|
||||
|
||||
setSize(width: number, height: number) {
|
||||
this.target.setSize(width, height)
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height))
|
||||
this.target.setSize(width, height);
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
}
|
||||
|
||||
setProps(props: Partial<PostprocessingProps>) {
|
||||
this.props = produce(this.props, p => {
|
||||
if (props.occlusion !== undefined) {
|
||||
p.occlusion.name = props.occlusion.name
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, props.occlusion.name === 'on')
|
||||
p.occlusion.name = props.occlusion.name;
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOcclusionEnable, props.occlusion.name === 'on');
|
||||
if (props.occlusion.name === 'on') {
|
||||
p.occlusion.params = { ...props.occlusion.params }
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOcclusionKernelSize, props.occlusion.params.kernelSize)
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOcclusionBias, props.occlusion.params.bias)
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOcclusionRadius, props.occlusion.params.radius)
|
||||
p.occlusion.params = { ...props.occlusion.params };
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOcclusionKernelSize, props.occlusion.params.kernelSize);
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOcclusionBias, props.occlusion.params.bias);
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOcclusionRadius, props.occlusion.params.radius);
|
||||
}
|
||||
}
|
||||
|
||||
if (props.outline !== undefined) {
|
||||
p.outline.name = props.outline.name
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOutlineEnable, props.outline.name === 'on')
|
||||
p.outline.name = props.outline.name;
|
||||
ValueCell.updateIfChanged(this.renderable.values.dOutlineEnable, props.outline.name === 'on');
|
||||
if (props.outline.name === 'on') {
|
||||
p.outline.params = { ...props.outline.params }
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOutlineScale, props.outline.params.scale)
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOutlineThreshold, props.outline.params.threshold)
|
||||
p.outline.params = { ...props.outline.params };
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOutlineScale, props.outline.params.scale);
|
||||
ValueCell.updateIfChanged(this.renderable.values.uOutlineThreshold, props.outline.params.threshold);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.renderable.update()
|
||||
this.renderable.update();
|
||||
}
|
||||
|
||||
render(toDrawingBuffer: boolean) {
|
||||
ValueCell.update(this.renderable.values.uFar, this.camera.far)
|
||||
ValueCell.update(this.renderable.values.uNear, this.camera.near)
|
||||
ValueCell.update(this.renderable.values.uFogFar, this.camera.fogFar)
|
||||
ValueCell.update(this.renderable.values.uFogNear, this.camera.fogNear)
|
||||
ValueCell.update(this.renderable.values.dOrthographic, this.camera.state.mode === 'orthographic' ? 1 : 0)
|
||||
ValueCell.update(this.renderable.values.uFar, this.camera.far);
|
||||
ValueCell.update(this.renderable.values.uNear, this.camera.near);
|
||||
ValueCell.update(this.renderable.values.uFogFar, this.camera.fogFar);
|
||||
ValueCell.update(this.renderable.values.uFogNear, this.camera.fogNear);
|
||||
ValueCell.update(this.renderable.values.dOrthographic, this.camera.state.mode === 'orthographic' ? 1 : 0);
|
||||
|
||||
const { gl, state } = this.webgl
|
||||
const { gl, state } = this.webgl;
|
||||
if (toDrawingBuffer) {
|
||||
this.webgl.unbindFramebuffer()
|
||||
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)
|
||||
this.webgl.unbindFramebuffer();
|
||||
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||
} else {
|
||||
this.target.bind()
|
||||
this.target.bind();
|
||||
}
|
||||
state.disable(gl.SCISSOR_TEST)
|
||||
state.disable(gl.BLEND)
|
||||
state.disable(gl.DEPTH_TEST)
|
||||
state.depthMask(false)
|
||||
this.renderable.render()
|
||||
state.disable(gl.SCISSOR_TEST);
|
||||
state.disable(gl.BLEND);
|
||||
state.disable(gl.DEPTH_TEST);
|
||||
state.depthMask(false);
|
||||
this.renderable.render();
|
||||
}
|
||||
}
|
||||
@@ -6,40 +6,40 @@
|
||||
|
||||
/** Set canvas size taking `devicePixelRatio` into account */
|
||||
export function setCanvasSize(canvas: HTMLCanvasElement, width: number, height: number) {
|
||||
canvas.width = Math.round(window.devicePixelRatio * width)
|
||||
canvas.height = Math.round(window.devicePixelRatio * height)
|
||||
Object.assign(canvas.style, { width: `${width}px`, height: `${height}px` })
|
||||
canvas.width = Math.round(window.devicePixelRatio * width);
|
||||
canvas.height = Math.round(window.devicePixelRatio * height);
|
||||
Object.assign(canvas.style, { width: `${width}px`, height: `${height}px` });
|
||||
}
|
||||
|
||||
/** Resize canvas to container element taking `devicePixelRatio` into account */
|
||||
export function resizeCanvas (canvas: HTMLCanvasElement, container: Element) {
|
||||
let width = window.innerWidth
|
||||
let height = window.innerHeight
|
||||
let width = window.innerWidth;
|
||||
let height = window.innerHeight;
|
||||
if (container !== document.body) {
|
||||
let bounds = container.getBoundingClientRect()
|
||||
width = bounds.right - bounds.left
|
||||
height = bounds.bottom - bounds.top
|
||||
let bounds = container.getBoundingClientRect();
|
||||
width = bounds.right - bounds.left;
|
||||
height = bounds.bottom - bounds.top;
|
||||
}
|
||||
setCanvasSize(canvas, width, height)
|
||||
setCanvasSize(canvas, width, height);
|
||||
}
|
||||
|
||||
function _canvasToBlob(canvas: HTMLCanvasElement, callback: BlobCallback, type?: string, quality?: any) {
|
||||
const bin = atob(canvas.toDataURL(type, quality).split(',')[1])
|
||||
const len = bin.length
|
||||
const len32 = len >> 2
|
||||
const a8 = new Uint8Array(len)
|
||||
const a32 = new Uint32Array(a8.buffer, 0, len32)
|
||||
const bin = atob(canvas.toDataURL(type, quality).split(',')[1]);
|
||||
const len = bin.length;
|
||||
const len32 = len >> 2;
|
||||
const a8 = new Uint8Array(len);
|
||||
const a32 = new Uint32Array(a8.buffer, 0, len32);
|
||||
|
||||
let j = 0
|
||||
let j = 0;
|
||||
for (let i = 0; i < len32; ++i) {
|
||||
a32[i] = bin.charCodeAt(j++) |
|
||||
bin.charCodeAt(j++) << 8 |
|
||||
bin.charCodeAt(j++) << 16 |
|
||||
bin.charCodeAt(j++) << 24
|
||||
bin.charCodeAt(j++) << 24;
|
||||
}
|
||||
|
||||
let tailLength = len & 3;
|
||||
while (tailLength--) a8[j] = bin.charCodeAt(j++)
|
||||
while (tailLength--) a8[j] = bin.charCodeAt(j++);
|
||||
|
||||
callback(new Blob([a8], { type: type || 'image/png' }));
|
||||
}
|
||||
@@ -47,14 +47,14 @@ function _canvasToBlob(canvas: HTMLCanvasElement, callback: BlobCallback, type?:
|
||||
export async function canvasToBlob(canvas: HTMLCanvasElement, type?: string, quality?: any): Promise<Blob> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (blob: Blob | null) => {
|
||||
if (blob) resolve(blob)
|
||||
else reject('no blob returned')
|
||||
}
|
||||
if (blob) resolve(blob);
|
||||
else reject('no blob returned');
|
||||
};
|
||||
|
||||
if (!HTMLCanvasElement.prototype.toBlob) {
|
||||
_canvasToBlob(canvas, callback, type, quality)
|
||||
_canvasToBlob(canvas, callback, type, quality);
|
||||
} else {
|
||||
canvas.toBlob(callback, type, quality)
|
||||
canvas.toBlob(callback, type, quality);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { EquivalenceClasses } from '../util'
|
||||
import { EquivalenceClasses } from '../util';
|
||||
|
||||
describe('equiv-classes', () => {
|
||||
it('integer mod classes', () => {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import Iterator from '../iterator'
|
||||
import Iterator from '../iterator';
|
||||
|
||||
function iteratorToArray<T>(it: Iterator<T>): T[] {
|
||||
const ret = [];
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user