mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 22:31:26 +08:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eae7c11c55 | ||
|
|
b8251e1ade | ||
|
|
2ff2b9f348 | ||
|
|
164e3f3343 | ||
|
|
4901a1bd87 | ||
|
|
cd194cca65 | ||
|
|
1748efbc18 | ||
|
|
4d60b40403 | ||
|
|
6e5a41879f | ||
|
|
c5f9eb54da | ||
|
|
aebbfeb061 | ||
|
|
a0a5a6b578 | ||
|
|
6bdafb85d7 | ||
|
|
0dd7debf5d | ||
|
|
962b9ee7af | ||
|
|
15bcc5df88 | ||
|
|
8495d834c8 | ||
|
|
7282399709 | ||
|
|
780bdd6e7e | ||
|
|
ad08e7c67f | ||
|
|
ff089964ca | ||
|
|
990191529a | ||
|
|
ce1bec12b4 | ||
|
|
703ea9af53 | ||
|
|
df0227ae1e | ||
|
|
486d12b6ac | ||
|
|
9c18375ab4 | ||
|
|
e1708aed68 | ||
|
|
a42d778b84 | ||
|
|
7692b59c7c | ||
|
|
82de9b36b3 | ||
|
|
49b3c8f65f | ||
|
|
90ad32d936 | ||
|
|
2509e91f1a | ||
|
|
80dc2219e4 | ||
|
|
931cb0fa7d | ||
|
|
4383f2ea90 | ||
|
|
add79dc242 | ||
|
|
b6e142f04c | ||
|
|
105f6c3041 | ||
|
|
4babbb65c1 | ||
|
|
878159f7ed | ||
|
|
c320386019 | ||
|
|
1e7a0159f0 | ||
|
|
bbf4f1d1d3 | ||
|
|
8cd1c69c76 | ||
|
|
2372a878ac |
23
CHANGELOG.md
23
CHANGELOG.md
@@ -6,6 +6,29 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v3.3.0] - 2022-02-27
|
||||
|
||||
- Fix parsing contour-level from emdb v3 header files
|
||||
- Fix invalid CSS (#376)
|
||||
- Fix "texture not renderable" & "texture not bound" warnings (#319)
|
||||
- Fix visual for bonds between two aromatic rings
|
||||
- Fix visual for delocalized bonds (parsed from mmcif and mol2)
|
||||
- Fix ring computation algorithm
|
||||
- Add ``UnitResonance`` property with info about delocalized triplets
|
||||
- Resolve marking in main renderer loop to improve overall performance
|
||||
- Use ``throttleTime`` instead of ``debounceTime`` in sequence viewer for better responsiveness
|
||||
- Change line geometry default ``scaleFactor`` to 2 (3 is too big after fixing line rendering)
|
||||
- Trajectory animation performance improvements
|
||||
- Reuse ``Model.CoarseGrained`` for coordinate trajectories
|
||||
- Avoid calculating ``InterUnitBonds`` when ``Structure.parent`` ones are empty
|
||||
- Reuse unit boundary if sphere has not changed too much
|
||||
- Don't show 'inter-bond' and 'element-cross' visuals in line representations of polymerAndLigand preset
|
||||
- Fix additional mononucleotides detected as polymer components
|
||||
- Fix and improve ``canRemap`` handling in ``IntraUnitBonds``
|
||||
- Reuse occlusion for secondary passes during multi-sampling
|
||||
- Check if marking passes are needed before doing them
|
||||
- Add ``resolutionScale`` parameter to allow trading quality of occlusion for performance
|
||||
|
||||
## [v3.2.0] - 2022-02-17
|
||||
|
||||
- Rename "best database mapping" to "SIFTS Mapping"
|
||||
|
||||
@@ -34,6 +34,14 @@
|
||||
* ACE (many, e.g. 5AGU, 1E1X)
|
||||
* ACY in 7ABY
|
||||
* NH2 (many, e.g. 6Y13)
|
||||
* Ligands with many rings
|
||||
* STU (e.g. 1U59) - many fused rings
|
||||
* HT (e.g. 127D) - rings connected by a single bond
|
||||
* J2C (e.g. 7EFJ) - rings connected by a single atom
|
||||
* RBF (e.g. 7QF2) - three linearly fused rings
|
||||
* TA1 (e.g. 1JFF) - many fused rings (incl. a 8-member rings)
|
||||
* BPA (e.g. 1JDG) - many fused rings
|
||||
* CLR (e.g. 3GKI) - four fused rings
|
||||
|
||||
Assembly symmetries
|
||||
* 5M30 (Assembly 1, C3 local and pseudo)
|
||||
|
||||
2222
package-lock.json
generated
2222
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.2.0",
|
||||
"version": "3.3.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -92,40 +92,40 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/add": "^3.1.1",
|
||||
"@graphql-codegen/cli": "^2.5.0",
|
||||
"@graphql-codegen/cli": "^2.6.2",
|
||||
"@graphql-codegen/time": "^3.1.1",
|
||||
"@graphql-codegen/typescript": "^2.4.3",
|
||||
"@graphql-codegen/typescript": "^2.4.5",
|
||||
"@graphql-codegen/typescript-graphql-files-modules": "^2.1.1",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.3.4",
|
||||
"@graphql-codegen/typescript-operations": "^2.2.4",
|
||||
"@graphql-codegen/typescript-graphql-request": "^4.3.7",
|
||||
"@graphql-codegen/typescript-operations": "^2.3.2",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/gl": "^4.1.0",
|
||||
"@types/jest": "^27.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||
"@typescript-eslint/parser": "^5.10.2",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.12.1",
|
||||
"@typescript-eslint/parser": "^5.12.1",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^7.0.0",
|
||||
"cpx2": "^4.1.2",
|
||||
"cpx2": "^4.2.0",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^6.6.0",
|
||||
"eslint": "^8.8.0",
|
||||
"eslint": "^8.10.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"fs-extra": "^10.0.1",
|
||||
"graphql": "^16.3.0",
|
||||
"http-server": "^14.1.0",
|
||||
"jest": "^27.5.0",
|
||||
"jest": "^27.5.1",
|
||||
"mini-css-extract-plugin": "^2.5.3",
|
||||
"path-browserify": "^1.0.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sass": "^1.49.7",
|
||||
"sass-loader": "^12.4.0",
|
||||
"simple-git": "^3.1.1",
|
||||
"sass": "^1.49.9",
|
||||
"sass-loader": "^12.6.0",
|
||||
"simple-git": "^3.2.6",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-jest": "^27.1.3",
|
||||
"typescript": "^4.5.5",
|
||||
"webpack": "^5.68.0",
|
||||
"webpack": "^5.69.1",
|
||||
"webpack-cli": "^4.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -133,28 +133,28 @@
|
||||
"@types/benchmark": "^2.1.1",
|
||||
"@types/compression": "1.7.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^16.11.22",
|
||||
"@types/node-fetch": "^2.5.12",
|
||||
"@types/node": "^16.11.26",
|
||||
"@types/node-fetch": "^2.6.1",
|
||||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/swagger-ui-dist": "3.30.1",
|
||||
"argparse": "^2.0.1",
|
||||
"body-parser": "^1.19.1",
|
||||
"body-parser": "^1.19.2",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.2",
|
||||
"express": "^4.17.3",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^9.0.12",
|
||||
"immutable": "^4.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"rxjs": "^7.5.2",
|
||||
"swagger-ui-dist": "^4.5.0",
|
||||
"rxjs": "^7.5.4",
|
||||
"swagger-ui-dist": "^4.5.2",
|
||||
"tslib": "^2.3.1",
|
||||
"util.promisify": "^1.1.1",
|
||||
"xhr2": "^0.2.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react": "^17.0.2 || ^16.14.0",
|
||||
"react-dom": "^17.0.2 || ^16.14.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +44,11 @@ function occlusionStyle(plugin: PluginContext) {
|
||||
postprocessing: {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
occlusion: { name: 'on', params: {
|
||||
samples: 64,
|
||||
radius: 8,
|
||||
bias: 1.0,
|
||||
blurKernelSize: 13
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
radius: 5,
|
||||
samples: 32,
|
||||
resolutionScale: 1
|
||||
} },
|
||||
outline: { name: 'on', params: {
|
||||
scale: 1.0,
|
||||
|
||||
@@ -24,7 +24,7 @@ const Canvas3DPresets = {
|
||||
illustrative: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
|
||||
outline: { name: 'on', params: { scale: 1, threshold: 0.33, color: Color(0x000000) } }
|
||||
},
|
||||
renderer: {
|
||||
@@ -36,7 +36,7 @@ const Canvas3DPresets = {
|
||||
occlusion: {
|
||||
canvas3d: <Preset>{
|
||||
postprocessing: {
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15 } },
|
||||
occlusion: { name: 'on', params: { samples: 32, radius: 6, bias: 1.4, blurKernelSize: 15, resolutionScale: 1 } },
|
||||
outline: { name: 'off', params: {} }
|
||||
},
|
||||
renderer: {
|
||||
|
||||
@@ -4,7 +4,7 @@ export type InputMaybe<T> = Maybe<T>;
|
||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
|
||||
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
|
||||
// Generated on 2022-02-06T15:40:15-08:00
|
||||
// Generated on 2022-02-27T12:49:36-08:00
|
||||
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
@@ -477,6 +477,8 @@ export type ClustersMembers = {
|
||||
export type CoreAssembly = {
|
||||
/** Get PDB entry that includes this assembly. */
|
||||
readonly entry?: Maybe<CoreEntry>;
|
||||
/** Get all pairwise polymer interfaces for this PDB assembly. */
|
||||
readonly interfaces?: Maybe<ReadonlyArray<Maybe<CoreInterface>>>;
|
||||
readonly pdbx_struct_assembly?: Maybe<PdbxStructAssembly>;
|
||||
readonly pdbx_struct_assembly_auth_evidence?: Maybe<ReadonlyArray<Maybe<PdbxStructAssemblyAuthEvidence>>>;
|
||||
readonly pdbx_struct_assembly_gen?: Maybe<ReadonlyArray<Maybe<PdbxStructAssemblyGen>>>;
|
||||
@@ -724,6 +726,16 @@ export type CoreEntry = {
|
||||
readonly symmetry?: Maybe<Symmetry>;
|
||||
};
|
||||
|
||||
export type CoreInterface = {
|
||||
readonly rcsb_id: Scalars['String'];
|
||||
readonly rcsb_interface_container_identifiers: RcsbInterfaceContainerIdentifiers;
|
||||
readonly rcsb_interface_info?: Maybe<RcsbInterfaceInfo>;
|
||||
/** List of operations for each interface partner. */
|
||||
readonly rcsb_interface_operator: ReadonlyArray<Maybe<ReadonlyArray<Maybe<ReadonlyArray<Maybe<Scalars['String']>>>>>>;
|
||||
readonly rcsb_interface_partner: ReadonlyArray<Maybe<RcsbInterfacePartner>>;
|
||||
readonly rcsb_latest_revision?: Maybe<RcsbLatestRevision>;
|
||||
};
|
||||
|
||||
export type CoreNonpolymerEntity = {
|
||||
/** Get PDB entry that contains this non-polymer entity. */
|
||||
readonly entry?: Maybe<CoreEntry>;
|
||||
@@ -3186,6 +3198,28 @@ export type GeneName = {
|
||||
readonly value?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type InterfacePartnerFeatureAdditionalProperties = {
|
||||
/**
|
||||
* The additional property name.
|
||||
*
|
||||
* Allowable values:
|
||||
* TO_BE_DEFINED
|
||||
*
|
||||
*/
|
||||
readonly name?: Maybe<Scalars['String']>;
|
||||
/** The value(s) of the additional property. */
|
||||
readonly values?: Maybe<ReadonlyArray<Maybe<Scalars['ObjectScalar']>>>;
|
||||
};
|
||||
|
||||
export type InterfacePartnerFeatureFeaturePositions = {
|
||||
/** An identifier for the monomer at which this segment of the feature begins. */
|
||||
readonly beg_seq_id: Scalars['Int'];
|
||||
/** An identifier for the monomer at which this segment of the feature ends. */
|
||||
readonly end_seq_id?: Maybe<Scalars['Int']>;
|
||||
/** The value(s) of the feature over the monomer segment. */
|
||||
readonly values?: Maybe<ReadonlyArray<Maybe<Scalars['Float']>>>;
|
||||
};
|
||||
|
||||
export type PdbxAuditRevisionCategory = {
|
||||
/**
|
||||
* The category updated in the pdbx_audit_revision_category record.
|
||||
@@ -6745,6 +6779,10 @@ export type Query = {
|
||||
readonly entries?: Maybe<ReadonlyArray<Maybe<CoreEntry>>>;
|
||||
/** Get PDB entry given the PDB id. */
|
||||
readonly entry?: Maybe<CoreEntry>;
|
||||
/** Get a pairwise polymeric interface given the PDB ID, ASSEMBLY ID and INTERFACE ID. */
|
||||
readonly interface?: Maybe<CoreInterface>;
|
||||
/** Get a list of pairwise polymeric interfaces given a list of INTERFACE IDs. Here INTERFACE ID is a compound identifier that includes entry_id, assembly_id and interface_id e.g. 1XXX-1.1. */
|
||||
readonly interfaces?: Maybe<ReadonlyArray<Maybe<CoreInterface>>>;
|
||||
/** Get a list of PDB non-polymer entities given a list of ENTITY IDs. Here ENTITY ID is a compound identifier that includes entry_id and entity_id separated by '_', e.g. 1XXX_1. */
|
||||
readonly nonpolymer_entities?: Maybe<ReadonlyArray<Maybe<CoreNonpolymerEntity>>>;
|
||||
/** Get a PDB non-polymer entity, given the PDB ID and ENTITY ID. Here ENTITY ID is a '1', '2', '3', etc. */
|
||||
@@ -6831,6 +6869,20 @@ export type QueryEntryArgs = {
|
||||
};
|
||||
|
||||
|
||||
/** Query root */
|
||||
export type QueryInterfaceArgs = {
|
||||
assembly_id: Scalars['String'];
|
||||
entry_id: Scalars['String'];
|
||||
interface_id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
/** Query root */
|
||||
export type QueryInterfacesArgs = {
|
||||
interface_ids: ReadonlyArray<Scalars['String']>;
|
||||
};
|
||||
|
||||
|
||||
/** Query root */
|
||||
export type QueryNonpolymer_EntitiesArgs = {
|
||||
entity_ids: ReadonlyArray<Scalars['String']>;
|
||||
@@ -6952,6 +7004,8 @@ export type RcsbAssemblyContainerIdentifiers = {
|
||||
readonly assembly_id: Scalars['String'];
|
||||
/** Entry identifier for the container. */
|
||||
readonly entry_id: Scalars['String'];
|
||||
/** List of binary interface Ids within the assembly (it points to interface id collection). */
|
||||
readonly interface_ids?: Maybe<ReadonlyArray<Maybe<Scalars['String']>>>;
|
||||
/**
|
||||
* A unique identifier for each object in this assembly container formed by
|
||||
* a dash separated concatenation of entry and assembly identifiers.
|
||||
@@ -7010,6 +7064,24 @@ export type RcsbAssemblyInfo = {
|
||||
* This is the total count of non-polymer entity instances generated in the assembly coordinate data.
|
||||
*/
|
||||
readonly nonpolymer_entity_instance_count?: Maybe<Scalars['Int']>;
|
||||
/** Number of heterologous (both binding sites are different) interface entities */
|
||||
readonly num_heterologous_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of heteromeric (both partners are different polymeric entities) interface entities */
|
||||
readonly num_heteromeric_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of homomeric (both partners are the same polymeric entity) interface entities */
|
||||
readonly num_homomeric_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of polymer-polymer interface entities, grouping equivalent interfaces at the entity level (i.e. same entity_ids on either side, with similar but not identical binding sites) */
|
||||
readonly num_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of geometrically equivalent (i.e. same asym_ids on either side) polymer-polymer interfaces in the assembly */
|
||||
readonly num_interfaces?: Maybe<Scalars['Int']>;
|
||||
/** Number of isologous (both binding sites are same, i.e. interface is symmetric) interface entities */
|
||||
readonly num_isologous_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of nucleic acid-nucleic acid interface entities */
|
||||
readonly num_na_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of protein-nucleic acid interface entities */
|
||||
readonly num_prot_na_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** Number of protein-protein interface entities */
|
||||
readonly num_protein_interface_entities?: Maybe<Scalars['Int']>;
|
||||
/** The assembly non-hydrogen polymer entity atomic coordinate count. */
|
||||
readonly polymer_atom_count?: Maybe<Scalars['Int']>;
|
||||
/**
|
||||
@@ -7085,6 +7157,10 @@ export type RcsbAssemblyInfo = {
|
||||
* This is the total count of solvent entity instances generated in the assembly coordinate data.
|
||||
*/
|
||||
readonly solvent_entity_instance_count?: Maybe<Scalars['Int']>;
|
||||
/** Total buried surface area calculated as the sum of buried surface areas over all interfaces */
|
||||
readonly total_assembly_buried_surface_area?: Maybe<Scalars['Float']>;
|
||||
/** Total number of interfacing residues in the assembly, calculated as the sum of interfacing residues over all interfaces */
|
||||
readonly total_number_interface_residues?: Maybe<Scalars['Int']>;
|
||||
/**
|
||||
* The number of unmodeled polymer monomers in the assembly coordinate data. This is
|
||||
* the total count of monomers with unreported coordinate data for all polymer
|
||||
@@ -8817,6 +8893,99 @@ export type RcsbGenomicLineage = {
|
||||
readonly name?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type RcsbInterfaceContainerIdentifiers = {
|
||||
/** This item references an assembly in pdbx_struct_assembly */
|
||||
readonly assembly_id: Scalars['String'];
|
||||
/** Entry identifier for the container. */
|
||||
readonly entry_id: Scalars['String'];
|
||||
/**
|
||||
* Identifier for NCS-equivalent interfaces within the assembly (same entity_ids on both sides)
|
||||
*
|
||||
* Examples:
|
||||
* 1, 2
|
||||
*
|
||||
*/
|
||||
readonly interface_entity_id?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* Identifier for the geometrically equivalent (same asym_ids on either side) interfaces within the assembly
|
||||
*
|
||||
* Examples:
|
||||
* 1, 2
|
||||
*
|
||||
*/
|
||||
readonly interface_id: Scalars['String'];
|
||||
/**
|
||||
* Unique identifier for the document
|
||||
*
|
||||
* Examples:
|
||||
* 2UZI-1.A.B?1
|
||||
*
|
||||
*/
|
||||
readonly rcsb_id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type RcsbInterfaceInfo = {
|
||||
/** Total interface buried surface area */
|
||||
readonly interface_area?: Maybe<Scalars['Float']>;
|
||||
/** Allowable values: homo, hetero. */
|
||||
readonly interface_character?: Maybe<Scalars['String']>;
|
||||
/** Number of core interface residues, defined as those that bury >90% accessible surface area with respect to the unbound state */
|
||||
readonly num_core_interface_residues?: Maybe<Scalars['Int']>;
|
||||
/** Number of interface residues, defined as those with burial fraction > 0 */
|
||||
readonly num_interface_residues?: Maybe<Scalars['Int']>;
|
||||
/** Allowable values: Nucleic acid (only), Protein (only), Protein/NA. */
|
||||
readonly polymer_composition?: Maybe<Scalars['String']>;
|
||||
/** The Jaccard score (intersection over union) of interface contacts in homomeric interfaces, comparing contact sets left-right vs right-left. High values indicate isologous (symmetric) interfaces, with value=1 if perfectly symmetric (e.g. crystallographic symmetry) */
|
||||
readonly self_jaccard_contact_score?: Maybe<Scalars['Float']>;
|
||||
};
|
||||
|
||||
export type RcsbInterfacePartner = {
|
||||
readonly interface_partner_feature?: Maybe<ReadonlyArray<Maybe<RcsbInterfacePartnerInterfacePartnerFeature>>>;
|
||||
readonly interface_partner_identifier?: Maybe<RcsbInterfacePartnerInterfacePartnerIdentifier>;
|
||||
};
|
||||
|
||||
export type RcsbInterfacePartnerInterfacePartnerFeature = {
|
||||
readonly additional_properties?: Maybe<ReadonlyArray<Maybe<InterfacePartnerFeatureAdditionalProperties>>>;
|
||||
/**
|
||||
* Identifies the version of the feature assignment.
|
||||
*
|
||||
* Examples:
|
||||
* V4_0_2
|
||||
*
|
||||
*/
|
||||
readonly assignment_version?: Maybe<Scalars['String']>;
|
||||
/** A description for the feature. */
|
||||
readonly description?: Maybe<Scalars['String']>;
|
||||
/** An identifier for the feature. */
|
||||
readonly feature_id?: Maybe<Scalars['String']>;
|
||||
readonly feature_positions?: Maybe<ReadonlyArray<Maybe<InterfacePartnerFeatureFeaturePositions>>>;
|
||||
/** A name for the feature. */
|
||||
readonly name?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* Code identifying the individual, organization or program that assigned the feature.
|
||||
*
|
||||
* Examples:
|
||||
* NACCESS
|
||||
*
|
||||
*/
|
||||
readonly provenance_source?: Maybe<Scalars['String']>;
|
||||
/**
|
||||
* A type or category of the feature.
|
||||
*
|
||||
* Allowable values:
|
||||
* ASA_UNBOUND, ASA_BOUND
|
||||
*
|
||||
*/
|
||||
readonly type?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type RcsbInterfacePartnerInterfacePartnerIdentifier = {
|
||||
/** Instance identifier for this container. */
|
||||
readonly asym_id: Scalars['String'];
|
||||
/** Polymer entity identifier for the container. */
|
||||
readonly entity_id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type RcsbLatestRevision = {
|
||||
/** The major version number of the latest revision. */
|
||||
readonly major_revision?: Maybe<Scalars['Int']>;
|
||||
@@ -10263,7 +10432,7 @@ export type RcsbPolymerInstanceFeature = {
|
||||
* A type or category of the feature.
|
||||
*
|
||||
* Allowable values:
|
||||
* ANGLE_OUTLIER, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SCOP, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ
|
||||
* ANGLE_OUTLIER, BINDING_SITE, BOND_OUTLIER, C-MANNOSYLATION_SITE, CATH, CIS-PEPTIDE, ECOD, HELIX_P, MEMBRANE_SEGMENT, MOGUL_ANGLE_OUTLIER, MOGUL_BOND_OUTLIER, N-GLYCOSYLATION_SITE, O-GLYCOSYLATION_SITE, RAMACHANDRAN_OUTLIER, ROTAMER_OUTLIER, RSCC_OUTLIER, RSRZ_OUTLIER, S-GLYCOSYLATION_SITE, SABDAB_ANTIBODY_HEAVY_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_SUBCLASS, SABDAB_ANTIBODY_LIGHT_CHAIN_TYPE, SCOP, SCOP2B_SUPERFAMILY, SCOP2_FAMILY, SCOP2_SUPERFAMILY, SHEET, STEREO_OUTLIER, UNASSIGNED_SEC_STRUCT, UNOBSERVED_ATOM_XYZ, UNOBSERVED_RESIDUE_XYZ, ZERO_OCCUPANCY_ATOM_XYZ, ZERO_OCCUPANCY_RESIDUE_XYZ, ASA
|
||||
*
|
||||
*/
|
||||
readonly type?: Maybe<Scalars['String']>;
|
||||
|
||||
@@ -236,7 +236,7 @@ interface Canvas3D {
|
||||
/** Sets drawPaused = false without starting the built in animation loop */
|
||||
resume(): void
|
||||
identify(x: number, y: number): PickData | undefined
|
||||
mark(loci: Representation.Loci, action: MarkerAction, noDraw?: boolean): void
|
||||
mark(loci: Representation.Loci, action: MarkerAction): void
|
||||
getLoci(pickingId: PickingId | undefined): Representation.Loci
|
||||
|
||||
notifyDidDraw: boolean,
|
||||
@@ -345,7 +345,30 @@ namespace Canvas3D {
|
||||
return { loci, repr };
|
||||
}
|
||||
|
||||
function mark(reprLoci: Representation.Loci, action: MarkerAction, noDraw = false) {
|
||||
let markBuffer: [reprLoci: Representation.Loci, action: MarkerAction][] = [];
|
||||
|
||||
function mark(reprLoci: Representation.Loci, action: MarkerAction) {
|
||||
// NOTE: might try to optimize a case with opposite actions for the
|
||||
// same loci. Tho this might end up being more expensive (and error prone)
|
||||
// then just applying everything "naively".
|
||||
markBuffer.push([reprLoci, action]);
|
||||
}
|
||||
|
||||
function resolveMarking() {
|
||||
let changed = false;
|
||||
for (const [r, l] of markBuffer) {
|
||||
changed = applyMark(r, l) || changed;
|
||||
}
|
||||
markBuffer = [];
|
||||
if (changed) {
|
||||
scene.update(void 0, true);
|
||||
helper.handle.scene.update(void 0, true);
|
||||
helper.camera.scene.update(void 0, true);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function applyMark(reprLoci: Representation.Loci, action: MarkerAction) {
|
||||
const { repr, loci } = reprLoci;
|
||||
let changed = false;
|
||||
if (repr) {
|
||||
@@ -355,24 +378,10 @@ namespace Canvas3D {
|
||||
changed = helper.camera.mark(loci, action) || changed;
|
||||
reprRenderObjects.forEach((_, _repr) => { changed = _repr.mark(loci, action) || changed; });
|
||||
}
|
||||
if (changed) {
|
||||
if (noDraw) {
|
||||
// Even with `noDraw` make sure changes will be rendered.
|
||||
// Note that with this calling mark (with or without `noDraw`) multiple times
|
||||
// during a JS event loop iteration will only result in a single render call.
|
||||
forceNextRender = true;
|
||||
} else {
|
||||
scene.update(void 0, true);
|
||||
helper.handle.scene.update(void 0, true);
|
||||
helper.camera.scene.update(void 0, true);
|
||||
const prevPickDirty = pickHelper.dirty;
|
||||
draw({ force: true, allowMulti: true });
|
||||
pickHelper.dirty = prevPickDirty; // marking does not change picking buffers
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
function render(force: boolean, allowMulti: boolean) {
|
||||
function render(force: boolean) {
|
||||
if (webgl.isContextLost) return false;
|
||||
|
||||
let resized = false;
|
||||
@@ -386,6 +395,8 @@ namespace Canvas3D {
|
||||
y > gl.drawingBufferHeight || y + height < 0
|
||||
) return false;
|
||||
|
||||
const markingUpdated = resolveMarking();
|
||||
|
||||
let didRender = false;
|
||||
controls.update(currentTime);
|
||||
const cameraChanged = camera.update();
|
||||
@@ -393,9 +404,9 @@ namespace Canvas3D {
|
||||
const shouldRender = force || cameraChanged || resized || forceNextRender;
|
||||
forceNextRender = false;
|
||||
|
||||
const multiSampleChanged = multiSampleHelper.update(shouldRender, p.multiSample);
|
||||
const multiSampleChanged = multiSampleHelper.update(markingUpdated || shouldRender, p.multiSample);
|
||||
|
||||
if (shouldRender || multiSampleChanged) {
|
||||
if (shouldRender || multiSampleChanged || markingUpdated) {
|
||||
let cam: Camera | StereoCamera = camera;
|
||||
if (p.camera.stereo.name === 'on') {
|
||||
stereoCamera.update();
|
||||
@@ -404,12 +415,13 @@ namespace Canvas3D {
|
||||
|
||||
const ctx = { renderer, camera: cam, scene, helper };
|
||||
if (MultiSamplePass.isEnabled(p.multiSample)) {
|
||||
const forceOn = !cameraChanged && allowMulti && !controls.isAnimating;
|
||||
const forceOn = !cameraChanged && markingUpdated && !controls.isAnimating;
|
||||
multiSampleHelper.render(ctx, p, true, forceOn);
|
||||
} else {
|
||||
passes.draw.render(ctx, p, true);
|
||||
}
|
||||
pickHelper.dirty = true;
|
||||
// if only marking has updated, do not set the flag to dirty
|
||||
pickHelper.dirty = pickHelper.dirty || shouldRender;
|
||||
didRender = true;
|
||||
}
|
||||
|
||||
@@ -421,9 +433,9 @@ namespace Canvas3D {
|
||||
let currentTime = 0;
|
||||
let drawPaused = false;
|
||||
|
||||
function draw(options?: { force?: boolean, allowMulti?: boolean }) {
|
||||
function draw(options?: { force?: boolean }) {
|
||||
if (drawPaused) return;
|
||||
if (render(!!options?.force, !!options?.allowMulti) && notifyDidDraw) {
|
||||
if (render(!!options?.force) && notifyDidDraw) {
|
||||
didDraw.next(now() - startTime as now.Timestamp);
|
||||
}
|
||||
}
|
||||
@@ -831,6 +843,8 @@ namespace Canvas3D {
|
||||
dispose: () => {
|
||||
contextRestoredSub.unsubscribe();
|
||||
|
||||
markBuffer = [];
|
||||
|
||||
scene.clear();
|
||||
helper.debug.clear();
|
||||
controls.dispose();
|
||||
|
||||
@@ -307,19 +307,22 @@ export class DrawPass {
|
||||
}
|
||||
|
||||
if (markingEnabled) {
|
||||
const markingDepthTest = props.marking.ghostEdgeStrength < 1;
|
||||
if (markingDepthTest) {
|
||||
this.marking.depthTarget.bind();
|
||||
const markerAverage = scene.getMarkerAverage();
|
||||
if (markerAverage > 0) {
|
||||
const markingDepthTest = props.marking.ghostEdgeStrength < 1;
|
||||
if (markingDepthTest && markerAverage !== 1) {
|
||||
this.marking.depthTarget.bind();
|
||||
renderer.clear(false, true);
|
||||
renderer.renderMarkingDepth(scene.primitives, camera, null);
|
||||
}
|
||||
|
||||
this.marking.maskTarget.bind();
|
||||
renderer.clear(false, true);
|
||||
renderer.renderMarkingDepth(scene.primitives, camera, null);
|
||||
renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
|
||||
|
||||
this.marking.update(props.marking);
|
||||
this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget);
|
||||
}
|
||||
|
||||
this.marking.maskTarget.bind();
|
||||
renderer.clear(false, true);
|
||||
renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
|
||||
|
||||
this.marking.update(props.marking);
|
||||
this.marking.render(camera.viewport, postprocessingEnabled ? this.postprocessing.target : this.colorTarget);
|
||||
}
|
||||
|
||||
if (helper.debug.isEnabled) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -157,6 +157,14 @@ export class MultiSamplePass {
|
||||
ValueCell.update(compose.values.uWeight, sampleWeight);
|
||||
|
||||
// render scene
|
||||
if (i === 0) {
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
} else {
|
||||
drawPass.postprocessing.setOcclusionOffset(
|
||||
offset[0] / width,
|
||||
offset[1] / height
|
||||
);
|
||||
}
|
||||
drawPass.render(ctx, props, false);
|
||||
|
||||
// compose rendered scene with compose target
|
||||
@@ -175,6 +183,8 @@ export class MultiSamplePass {
|
||||
compose.render();
|
||||
}
|
||||
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
|
||||
ValueCell.update(compose.values.uWeight, 1.0);
|
||||
ValueCell.update(compose.values.tColor, composeTarget.texture);
|
||||
compose.update();
|
||||
@@ -236,6 +246,14 @@ export class MultiSamplePass {
|
||||
camera.update();
|
||||
|
||||
// render scene
|
||||
if (sampleIndex === 0) {
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
} else {
|
||||
drawPass.postprocessing.setOcclusionOffset(
|
||||
offset[0] / width,
|
||||
offset[1] / height
|
||||
);
|
||||
}
|
||||
drawPass.render(ctx, props, false);
|
||||
|
||||
// compose rendered scene with compose target
|
||||
@@ -258,6 +276,8 @@ export class MultiSamplePass {
|
||||
}
|
||||
}
|
||||
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
|
||||
this.bindOutputTarget(toDrawingBuffer);
|
||||
gl.viewport(x, y, width, height);
|
||||
gl.scissor(x, y, width, height);
|
||||
@@ -291,23 +311,23 @@ const JitterVectors = [
|
||||
[0, 0]
|
||||
],
|
||||
[
|
||||
[4, 4], [-4, -4]
|
||||
[0, 0], [-4, -4]
|
||||
],
|
||||
[
|
||||
[-2, -6], [6, -2], [-6, 2], [2, 6]
|
||||
[0, 0], [6, -2], [-6, 2], [2, 6]
|
||||
],
|
||||
[
|
||||
[1, -3], [-1, 3], [5, 1], [-3, -5],
|
||||
[0, 0], [-1, 3], [5, 1], [-3, -5],
|
||||
[-5, 5], [-7, -1], [3, 7], [7, -7]
|
||||
],
|
||||
[
|
||||
[1, 1], [-1, -3], [-3, 2], [4, -1],
|
||||
[0, 0], [-1, -3], [-3, 2], [4, -1],
|
||||
[-5, -2], [2, 5], [5, 3], [3, -5],
|
||||
[-2, 6], [0, -7], [-4, -6], [-6, 4],
|
||||
[-8, 0], [7, -4], [6, 7], [-7, -8]
|
||||
],
|
||||
[
|
||||
[-4, -7], [-7, -5], [-3, -5], [-5, -4],
|
||||
[0, 0], [-7, -5], [-3, -5], [-5, -4],
|
||||
[-1, -4], [-2, -2], [-6, -1], [-4, 0],
|
||||
[-7, 1], [-1, 2], [-6, 3], [-3, 3],
|
||||
[-7, 6], [-3, 6], [-5, 7], [-1, 7],
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
*/
|
||||
|
||||
import { QuadSchema, QuadValues } from '../../mol-gl/compute/util';
|
||||
import { CopyRenderable, createCopyRenderable, QuadSchema, QuadValues } from '../../mol-gl/compute/util';
|
||||
import { TextureSpec, Values, UniformSpec, DefineSpec } from '../../mol-gl/renderable/schema';
|
||||
import { ShaderCode } from '../../mol-gl/shader-code';
|
||||
import { WebGLContext } from '../../mol-gl/webgl/context';
|
||||
@@ -199,6 +199,7 @@ const PostprocessingSchema = {
|
||||
uMaxPossibleViewZDiff: UniformSpec('f'),
|
||||
|
||||
dOcclusionEnable: DefineSpec('boolean'),
|
||||
uOcclusionOffset: UniformSpec('v2'),
|
||||
|
||||
dOutlineEnable: DefineSpec('boolean'),
|
||||
dOutlineScale: DefineSpec('number'),
|
||||
@@ -227,6 +228,7 @@ function getPostprocessingRenderable(ctx: WebGLContext, colorTexture: Texture, d
|
||||
uMaxPossibleViewZDiff: ValueCell.create(0.5),
|
||||
|
||||
dOcclusionEnable: ValueCell.create(true),
|
||||
uOcclusionOffset: ValueCell.create(Vec2.create(0, 0)),
|
||||
|
||||
dOutlineEnable: ValueCell.create(false),
|
||||
dOutlineScale: ValueCell.create(1),
|
||||
@@ -244,9 +246,10 @@ export const PostprocessingParams = {
|
||||
occlusion: PD.MappedStatic('on', {
|
||||
on: PD.Group({
|
||||
samples: PD.Numeric(32, { min: 1, max: 256, step: 1 }),
|
||||
radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final radius is 2^x.' }),
|
||||
radius: PD.Numeric(5, { min: 0, max: 10, step: 0.1 }, { description: 'Final occlusion radius is 2^x' }),
|
||||
bias: PD.Numeric(0.8, { min: 0, max: 3, step: 0.1 }),
|
||||
blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
|
||||
resolutionScale: PD.Numeric(1, { min: 0.1, max: 1, step: 0.05 }, { description: 'Adjust resolution of occlusion calculation' }),
|
||||
}),
|
||||
off: PD.Group({})
|
||||
}, { cycle: true, description: 'Darken occluded crevices with the ambient occlusion effect' }),
|
||||
@@ -281,6 +284,9 @@ export class PostprocessingPass {
|
||||
private readonly ssaoBlurFirstPassFramebuffer: Framebuffer;
|
||||
private readonly ssaoBlurSecondPassFramebuffer: Framebuffer;
|
||||
|
||||
private readonly downsampledDepthTarget: RenderTarget;
|
||||
private readonly downsampleDepthRenderable: CopyRenderable;
|
||||
|
||||
private readonly ssaoDepthTexture: Texture;
|
||||
private readonly ssaoDepthBlurProxyTexture: Texture;
|
||||
|
||||
@@ -290,24 +296,25 @@ export class PostprocessingPass {
|
||||
|
||||
private nSamples: number;
|
||||
private blurKernelSize: number;
|
||||
private downsampleFactor: number;
|
||||
|
||||
private readonly renderable: PostprocessingRenderable;
|
||||
|
||||
private ssaoScale: number;
|
||||
private calcSsaoScale() {
|
||||
// downscale ssao for high pixel-ratios
|
||||
return Math.min(1, 1 / this.webgl.pixelRatio);
|
||||
return Math.min(1, 1 / this.webgl.pixelRatio) * this.downsampleFactor;
|
||||
}
|
||||
|
||||
constructor(private webgl: WebGLContext, drawPass: DrawPass) {
|
||||
this.ssaoScale = this.calcSsaoScale();
|
||||
|
||||
constructor(private webgl: WebGLContext, private drawPass: DrawPass) {
|
||||
const { colorTarget, depthTexture } = drawPass;
|
||||
const width = colorTarget.getWidth();
|
||||
const height = colorTarget.getHeight();
|
||||
|
||||
this.nSamples = 1;
|
||||
this.blurKernelSize = 1;
|
||||
this.downsampleFactor = 1;
|
||||
this.ssaoScale = this.calcSsaoScale();
|
||||
|
||||
// needs to be linear for anti-aliasing pass
|
||||
this.target = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
|
||||
@@ -332,17 +339,20 @@ export class PostprocessingPass {
|
||||
const sw = Math.floor(width * this.ssaoScale);
|
||||
const sh = Math.floor(height * this.ssaoScale);
|
||||
|
||||
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
this.downsampledDepthTarget = webgl.createRenderTarget(sw, sh, false, 'uint8', 'linear');
|
||||
this.downsampleDepthRenderable = createCopyRenderable(webgl, depthTexture);
|
||||
|
||||
this.ssaoDepthTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthTexture.attachFramebuffer(this.ssaoFramebuffer, 'color0');
|
||||
|
||||
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
|
||||
this.ssaoDepthBlurProxyTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.attachFramebuffer(this.ssaoBlurFirstPassFramebuffer, 'color0');
|
||||
|
||||
this.ssaoDepthTexture.attachFramebuffer(this.ssaoBlurSecondPassFramebuffer, 'color0');
|
||||
|
||||
this.ssaoRenderable = getSsaoRenderable(webgl, depthTexture);
|
||||
this.ssaoRenderable = getSsaoRenderable(webgl, this.downsampleFactor === 1 ? depthTexture : this.downsampledDepthTarget.texture);
|
||||
this.ssaoBlurFirstPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthTexture, 'horizontal');
|
||||
this.ssaoBlurSecondPassRenderable = getSsaoBlurRenderable(webgl, this.ssaoDepthBlurProxyTexture, 'vertical');
|
||||
this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, this.outlinesTarget.texture, this.ssaoDepthTexture);
|
||||
@@ -359,11 +369,13 @@ export class PostprocessingPass {
|
||||
const sh = Math.floor(height * this.ssaoScale);
|
||||
this.target.setSize(width, height);
|
||||
this.outlinesTarget.setSize(width, height);
|
||||
this.downsampledDepthTarget.setSize(sw, sh);
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
|
||||
ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.outlinesRenderable.values.uTexSize, Vec2.set(this.outlinesRenderable.values.uTexSize.ref.value, width, height));
|
||||
ValueCell.update(this.downsampleDepthRenderable.values.uTexSize, Vec2.set(this.downsampleDepthRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
@@ -434,6 +446,30 @@ export class PostprocessingPass {
|
||||
ValueCell.updateIfChanged(this.ssaoBlurSecondPassRenderable.values.dOcclusionKernelSize, this.blurKernelSize);
|
||||
}
|
||||
|
||||
if (this.downsampleFactor !== props.occlusion.params.resolutionScale) {
|
||||
needsUpdateSsao = true;
|
||||
|
||||
this.downsampleFactor = props.occlusion.params.resolutionScale;
|
||||
this.ssaoScale = this.calcSsaoScale();
|
||||
|
||||
const sw = Math.floor(w * this.ssaoScale);
|
||||
const sh = Math.floor(h * this.ssaoScale);
|
||||
|
||||
this.downsampledDepthTarget.setSize(sw, sh);
|
||||
this.ssaoDepthTexture.define(sw, sh);
|
||||
this.ssaoDepthBlurProxyTexture.define(sw, sh);
|
||||
|
||||
if (this.ssaoScale === 1) {
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepth, this.drawPass.depthTexture);
|
||||
} else {
|
||||
ValueCell.update(this.ssaoRenderable.values.tDepth, this.downsampledDepthTarget.texture);
|
||||
}
|
||||
|
||||
ValueCell.update(this.downsampleDepthRenderable.values.uTexSize, Vec2.set(this.downsampleDepthRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoRenderable.values.uTexSize, Vec2.set(this.ssaoRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurFirstPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uTexSize, Vec2.set(this.ssaoBlurSecondPassRenderable.values.uTexSize.ref.value, sw, sh));
|
||||
}
|
||||
}
|
||||
|
||||
if (props.outline.name === 'on') {
|
||||
@@ -494,6 +530,13 @@ export class PostprocessingPass {
|
||||
gl.scissor(x, y, width, height);
|
||||
}
|
||||
|
||||
private occlusionOffset: [x: number, y: number] = [0, 0];
|
||||
setOcclusionOffset(x: number, y: number) {
|
||||
this.occlusionOffset[0] = x;
|
||||
this.occlusionOffset[1] = y;
|
||||
ValueCell.update(this.renderable.values.uOcclusionOffset, Vec2.set(this.renderable.values.uOcclusionOffset.ref.value, x, y));
|
||||
}
|
||||
|
||||
render(camera: ICamera, toDrawingBuffer: boolean, transparentBackground: boolean, backgroundColor: Color, props: PostprocessingProps) {
|
||||
this.updateState(camera, transparentBackground, backgroundColor, props);
|
||||
|
||||
@@ -502,14 +545,13 @@ export class PostprocessingPass {
|
||||
this.outlinesRenderable.render();
|
||||
}
|
||||
|
||||
if (props.occlusion.name === 'on') {
|
||||
const { x, y, width, height } = camera.viewport;
|
||||
const sx = Math.floor(x * this.ssaoScale);
|
||||
const sy = Math.floor(y * this.ssaoScale);
|
||||
const sw = Math.ceil(width * this.ssaoScale);
|
||||
const sh = Math.ceil(height * this.ssaoScale);
|
||||
this.webgl.gl.viewport(sx, sy, sw, sh);
|
||||
this.webgl.gl.scissor(sx, sy, sw, sh);
|
||||
// don't render occlusion if offset is given,
|
||||
// which will reuse the existing occlusion
|
||||
if (props.occlusion.name === 'on' && this.occlusionOffset[0] === 0 && this.occlusionOffset[1] === 0) {
|
||||
if (this.ssaoScale < 1) {
|
||||
this.downsampledDepthTarget.bind();
|
||||
this.downsampleDepthRenderable.render();
|
||||
}
|
||||
|
||||
this.ssaoFramebuffer.bind();
|
||||
this.ssaoRenderable.render();
|
||||
@@ -519,9 +561,6 @@ export class PostprocessingPass {
|
||||
|
||||
this.ssaoBlurSecondPassFramebuffer.bind();
|
||||
this.ssaoBlurSecondPassRenderable.render();
|
||||
|
||||
this.webgl.gl.viewport(x, y, width, height);
|
||||
this.webgl.gl.scissor(x, y, width, height);
|
||||
}
|
||||
|
||||
if (toDrawingBuffer) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -165,7 +165,7 @@ export namespace Lines {
|
||||
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
sizeFactor: PD.Numeric(3, { min: 0, max: 10, step: 0.1 }),
|
||||
sizeFactor: PD.Numeric(2, { min: 0, max: 10, step: 0.1 }),
|
||||
lineSizeAttenuation: PD.Boolean(false),
|
||||
};
|
||||
export type Params = typeof Params
|
||||
|
||||
@@ -31,7 +31,7 @@ describe('renderer', () => {
|
||||
expect(ctx.gl.drawingBufferHeight).toBe(32);
|
||||
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(1);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(0);
|
||||
@@ -52,7 +52,7 @@ describe('renderer', () => {
|
||||
scene.add(points);
|
||||
scene.commit();
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(8);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(9);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(ctx.extensions.vertexArrayObject ? 5 : 0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(5);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(10);
|
||||
@@ -60,7 +60,7 @@ describe('renderer', () => {
|
||||
scene.remove(points);
|
||||
scene.commit();
|
||||
expect(ctx.stats.resourceCounts.attribute).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.texture).toBe(1);
|
||||
expect(ctx.stats.resourceCounts.vertexArray).toBe(0);
|
||||
expect(ctx.stats.resourceCounts.program).toBe(5);
|
||||
expect(ctx.stats.resourceCounts.shader).toBe(10);
|
||||
|
||||
@@ -16,7 +16,7 @@ import { GlobalUniformValues } from './renderable/schema';
|
||||
import { GraphicsRenderVariant } from './webgl/render-item';
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
import { degToRad } from '../mol-math/misc';
|
||||
import { createNullTexture, Texture, Textures } from './webgl/texture';
|
||||
import { Texture, Textures } from './webgl/texture';
|
||||
import { arrayMapUpsert } from '../mol-util/array';
|
||||
import { clamp } from '../mol-math/interpolate';
|
||||
|
||||
@@ -146,9 +146,9 @@ namespace Renderer {
|
||||
|
||||
let transparentBackground = false;
|
||||
|
||||
const nullDepthTexture = createNullTexture(gl);
|
||||
const emptyDepthTexture = ctx.resources.texture('image-depth', 'depth', 'ushort', 'nearest');
|
||||
const sharedTexturesList: Textures = [
|
||||
['tDepth', nullDepthTexture]
|
||||
['tDepth', emptyDepthTexture]
|
||||
];
|
||||
|
||||
const view = Mat4();
|
||||
@@ -309,7 +309,7 @@ namespace Renderer {
|
||||
};
|
||||
|
||||
const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderWboit: boolean, markingDepthTest: boolean) => {
|
||||
arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || nullDepthTexture);
|
||||
arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || emptyDepthTexture);
|
||||
|
||||
ValueCell.update(globalUniforms.uModel, group.view);
|
||||
ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, group.view, camera.view));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -79,6 +79,7 @@ interface Scene extends Object3D {
|
||||
has: (o: GraphicsRenderObject) => boolean
|
||||
clear: () => void
|
||||
forEach: (callbackFn: (value: GraphicsRenderable, key: GraphicsRenderObject) => void) => void
|
||||
getMarkerAverage: () => number
|
||||
}
|
||||
|
||||
namespace Scene {
|
||||
@@ -243,7 +244,18 @@ namespace Scene {
|
||||
visibleHash = computeVisibleHash();
|
||||
}
|
||||
return boundingSphereVisible;
|
||||
}
|
||||
},
|
||||
getMarkerAverage() {
|
||||
if (primitives.length === 0 && volumes.length === 0) return 0;
|
||||
let markerAverage = 0;
|
||||
for (let i = 0, il = primitives.length; i < il; ++i) {
|
||||
markerAverage += primitives[i].values.markerAverage.ref.value;
|
||||
}
|
||||
for (let i = 0, il = volumes.length; i < il; ++i) {
|
||||
markerAverage += volumes[i].values.markerAverage.ref.value;
|
||||
}
|
||||
return markerAverage / (primitives.length + volumes.length);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz>
|
||||
@@ -24,8 +24,7 @@ uniform vec3 uFogColor;
|
||||
uniform vec3 uOutlineColor;
|
||||
uniform bool uTransparentBackground;
|
||||
|
||||
uniform float uOcclusionBias;
|
||||
uniform float uOcclusionRadius;
|
||||
uniform vec2 uOcclusionOffset;
|
||||
|
||||
uniform float uMaxPossibleViewZDiff;
|
||||
|
||||
@@ -102,7 +101,7 @@ void main(void) {
|
||||
if (!isBackground(depth)) {
|
||||
viewDist = abs(getViewZ(depth));
|
||||
fogFactor = smoothstep(uFogNear, uFogFar, viewDist);
|
||||
float occlusionFactor = getSsao(coords);
|
||||
float occlusionFactor = getSsao(coords + uOcclusionOffset);
|
||||
if (!uTransparentBackground) {
|
||||
color.rgb = mix(mix(occlusionColor, uFogColor, fogFactor), color.rgb, occlusionFactor);
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -176,7 +176,7 @@ function isTexture2d(x: TextureImage<any> | TextureVolume<any>, target: number,
|
||||
return target === gl.TEXTURE_2D;
|
||||
}
|
||||
|
||||
function isTexture3d(x: TextureImage<any> | TextureVolume<any>, target: number, gl: WebGL2RenderingContext): x is TextureImage<any> {
|
||||
function isTexture3d(x: TextureImage<any> | TextureVolume<any>, target: number, gl: WebGL2RenderingContext): x is TextureVolume<any> {
|
||||
return target === gl.TEXTURE_3D;
|
||||
}
|
||||
|
||||
@@ -260,6 +260,10 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
let destroyed = false;
|
||||
|
||||
function define(_width: number, _height: number, _depth?: number) {
|
||||
if (_width === 0 || _height === 0 || (isWebGL2(gl) && target === gl.TEXTURE_3D && _depth === 0)) {
|
||||
throw new Error('empty textures are not allowed');
|
||||
}
|
||||
|
||||
if (width === _width && height === _height && depth === (_depth || 0)) return;
|
||||
|
||||
width = _width, height = _height, depth = _depth || 0;
|
||||
@@ -272,14 +276,20 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
|
||||
throw new Error('unknown texture target');
|
||||
}
|
||||
}
|
||||
define(1, 1, isWebGL2(gl) && target === gl.TEXTURE_3D ? 1 : 0);
|
||||
|
||||
function load(data: TextureImage<any> | TextureVolume<any> | HTMLImageElement, sub = false) {
|
||||
if (data.width === 0 || data.height === 0 || (!isImage(data) && isWebGL2(gl) && isTexture3d(data, target, gl) && data.depth === 0)) {
|
||||
throw new Error('empty textures are not allowed');
|
||||
}
|
||||
|
||||
gl.bindTexture(target, texture);
|
||||
// unpack alignment of 1 since we use textures only for data
|
||||
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
||||
gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
|
||||
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
|
||||
if (isImage(data)) {
|
||||
width = data.width, height = data.height;
|
||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, format, type, data);
|
||||
|
||||
28
src/mol-io/common/_spec/encoder.spec.ts
Normal file
28
src/mol-io/common/_spec/encoder.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ArrayEncoding } from '../binary-cif/array-encoder';
|
||||
import { decode } from '../binary-cif/decoder';
|
||||
|
||||
const E = ArrayEncoding;
|
||||
|
||||
test('fixedPoint2', async () => {
|
||||
const fixedPoint2 = E.by(E.fixedPoint(100)).and(E.delta).and(E.integerPacking);
|
||||
|
||||
const x = [1.092, 1.960, 0.666, 0.480, 1.267];
|
||||
const y = [7.428, 7.026, 6.851, 7.524, 8.333];
|
||||
const z = [26.270, 26.561, 25.573, 27.055, 25.881];
|
||||
|
||||
const xEnc = fixedPoint2.encode(new Float32Array(x));
|
||||
const yEnc = fixedPoint2.encode(new Float32Array(y));
|
||||
const zEnc = fixedPoint2.encode(new Float32Array(z));
|
||||
|
||||
expect(xEnc.data.length).toEqual(6);
|
||||
expect(yEnc.data.length).toEqual(5);
|
||||
expect(zEnc.data.length).toEqual(6);
|
||||
|
||||
const xDec = decode(xEnc);
|
||||
const yDec = decode(yEnc);
|
||||
const zDec = decode(zEnc);
|
||||
|
||||
x.forEach((a, i) => expect(xDec[i]).toBeCloseTo(a, 2));
|
||||
y.forEach((a, i) => expect(yDec[i]).toBeCloseTo(a, 2));
|
||||
z.forEach((a, i) => expect(zDec[i]).toBeCloseTo(a, 2));
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.355, IHM 1.17, MA 1.3.4.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.356, IHM 1.17, MA 1.3.5.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.355, IHM 1.17, MA 1.3.4.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.356, IHM 1.17, MA 1.3.5.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.355, IHM 1.17, MA 1.3.4.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.356, IHM 1.17, MA 1.3.5.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -2233,6 +2233,10 @@ export const mmCIF_Schema = {
|
||||
oligomeric_count: int,
|
||||
/**
|
||||
* A description of special aspects of the macromolecular assembly.
|
||||
*
|
||||
* In the PDB, 'representative helical assembly', 'complete point assembly',
|
||||
* 'complete icosahedral assembly', 'software_defined_assembly', 'author_defined_assembly',
|
||||
* and 'author_and_software_defined_assembly' are considered "biologically relevant assemblies.
|
||||
*/
|
||||
details: str,
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -30,7 +30,7 @@ const DnaAtomIdsList = [
|
||||
|
||||
/** Used to reduce false positives for atom name-based type guessing */
|
||||
const NonPolymerNames = new Set([
|
||||
'FMN', 'NCN', 'FNS', 'FMA' // Mononucleotides
|
||||
'FMN', 'NCN', 'FNS', 'FMA', 'ATP', 'ADP', 'AMP', 'GTP', 'GDP', 'GMP' // Mononucleotides
|
||||
]);
|
||||
|
||||
const StandardComponents = (function () {
|
||||
@@ -156,7 +156,7 @@ export class ComponentBuilder {
|
||||
this.set(StandardComponents.get(compId)!);
|
||||
} else if (WaterNames.has(compId)) {
|
||||
this.set({ id: compId, name: 'WATER', type: 'non-polymer' });
|
||||
} else if (NonPolymerNames.has(compId)) {
|
||||
} else if (NonPolymerNames.has(compId.toUpperCase())) {
|
||||
this.set({ id: compId, name: this.namesMap.get(compId) || compId, type: 'non-polymer' });
|
||||
} else {
|
||||
const atomIds = this.getAtomIds(index);
|
||||
|
||||
@@ -103,11 +103,11 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
|
||||
const flag = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => {
|
||||
switch (x) {
|
||||
case 'ar': // aromatic
|
||||
case 'am': // amide
|
||||
return BondType.Flag.Aromatic | BondType.Flag.Covalent;
|
||||
case 'du': // dummy
|
||||
case 'nc': // not connected
|
||||
return BondType.Flag.None;
|
||||
case 'am': // amide
|
||||
case 'un': // unknown
|
||||
default:
|
||||
return BondType.Flag.Covalent;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -56,10 +56,10 @@ export namespace ComponentBond {
|
||||
const entries: Map<string, Entry> = new Map();
|
||||
|
||||
function addEntry(id: string) {
|
||||
// weird behavior when 'PRO' is requested - will report a single bond between N and H because a later operation would override real content
|
||||
if (entries.has(id)) {
|
||||
return entries.get(id)!;
|
||||
}
|
||||
// weird behavior when 'PRO' is requested - will report a single bond
|
||||
// between N and H because a later operation would override real content
|
||||
if (entries.has(id)) return entries.get(id)!;
|
||||
|
||||
const e = new Entry(id);
|
||||
entries.set(id, e);
|
||||
return e;
|
||||
@@ -83,10 +83,8 @@ export namespace ComponentBond {
|
||||
let ord = 1;
|
||||
if (aromatic) flags |= BondType.Flag.Aromatic;
|
||||
switch (order.toLowerCase()) {
|
||||
case 'doub':
|
||||
case 'delo':
|
||||
ord = 2;
|
||||
break;
|
||||
case 'delo': flags |= BondType.Flag.Aromatic; break;
|
||||
case 'doub': ord = 2; break;
|
||||
case 'trip': ord = 3; break;
|
||||
case 'quad': ord = 4; break;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -98,6 +98,7 @@ export namespace Model {
|
||||
const srcIndex = model.atomicHierarchy.atomSourceIndex;
|
||||
const isIdentity = Column.isIdentity(srcIndex);
|
||||
const srcIndexArray = isIdentity ? void 0 : srcIndex.toArray({ array: Int32Array });
|
||||
const coarseGrained = isCoarseGrained(model);
|
||||
|
||||
for (let i = 0, il = frames.length; i < il; ++i) {
|
||||
const f = frames[i];
|
||||
@@ -119,6 +120,7 @@ export namespace Model {
|
||||
}
|
||||
|
||||
TrajectoryInfo.set(m, { index: i, size: frames.length });
|
||||
CoarseGrained.set(m, coarseGrained);
|
||||
|
||||
trajectory.push(m);
|
||||
}
|
||||
@@ -138,11 +140,13 @@ export namespace Model {
|
||||
|
||||
const bondData = { pairs: topology.bonds, count: model.atomicHierarchy.atoms._rowCount };
|
||||
const indexPairBonds = IndexPairBonds.fromData(bondData);
|
||||
const coarseGrained = isCoarseGrained(model);
|
||||
|
||||
let index = 0;
|
||||
for (const m of trajectory) {
|
||||
IndexPairBonds.Provider.set(m, indexPairBonds);
|
||||
TrajectoryInfo.set(m, { index: index++, size: trajectory.length });
|
||||
CoarseGrained.set(m, coarseGrained);
|
||||
}
|
||||
return new ArrayTrajectory(trajectory);
|
||||
});
|
||||
@@ -225,35 +229,44 @@ export namespace Model {
|
||||
};
|
||||
|
||||
const CoarseGrainedProp = '__CoarseGrained__';
|
||||
export const CoarseGrained = {
|
||||
get(model: Model): boolean | undefined {
|
||||
return model._staticPropertyData[CoarseGrainedProp];
|
||||
},
|
||||
set(model: Model, coarseGrained: boolean) {
|
||||
return model._staticPropertyData[CoarseGrainedProp] = coarseGrained;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Has typical coarse grained atom names (BB, SC1) or less than three times as many
|
||||
* atoms as polymer residues (C-alpha only models).
|
||||
*/
|
||||
export function isCoarseGrained(model: Model): boolean {
|
||||
if (model._staticPropertyData[CoarseGrainedProp] !== undefined) return model._staticPropertyData[CoarseGrainedProp];
|
||||
let coarseGrained = CoarseGrained.get(model);
|
||||
if (coarseGrained === undefined) {
|
||||
let polymerResidueCount = 0;
|
||||
const { polymerType } = model.atomicHierarchy.derived.residue;
|
||||
for (let i = 0; i < polymerType.length; ++i) {
|
||||
if (polymerType[i] !== PolymerType.NA) polymerResidueCount += 1;
|
||||
}
|
||||
|
||||
let polymerResidueCount = 0;
|
||||
const { polymerType } = model.atomicHierarchy.derived.residue;
|
||||
for (let i = 0; i < polymerType.length; ++i) {
|
||||
if (polymerType[i] !== PolymerType.NA) polymerResidueCount += 1;
|
||||
// check for coarse grained atom names
|
||||
let hasBB = false, hasSC1 = false;
|
||||
const { label_atom_id, _rowCount: atomCount } = model.atomicHierarchy.atoms;
|
||||
for (let i = 0; i < atomCount; ++i) {
|
||||
const atomName = label_atom_id.value(i);
|
||||
if (!hasBB && atomName === 'BB') hasBB = true;
|
||||
if (!hasSC1 && atomName === 'SC1') hasSC1 = true;
|
||||
if (hasBB && hasSC1) break;
|
||||
}
|
||||
|
||||
coarseGrained = (hasBB && hasSC1) || (
|
||||
polymerResidueCount && atomCount
|
||||
? atomCount / polymerResidueCount < 3
|
||||
: false
|
||||
);
|
||||
CoarseGrained.set(model, coarseGrained);
|
||||
}
|
||||
|
||||
// check for coarse grained atom names
|
||||
let hasBB = false, hasSC1 = false;
|
||||
const { label_atom_id, _rowCount: atomCount } = model.atomicHierarchy.atoms;
|
||||
for (let i = 0; i < atomCount; ++i) {
|
||||
const atomName = label_atom_id.value(i);
|
||||
if (!hasBB && atomName === 'BB') hasBB = true;
|
||||
if (!hasSC1 && atomName === 'SC1') hasSC1 = true;
|
||||
if (hasBB && hasSC1) break;
|
||||
}
|
||||
|
||||
const coarseGrained = (hasBB && hasSC1) || (
|
||||
polymerResidueCount && atomCount
|
||||
? atomCount / polymerResidueCount < 3
|
||||
: false
|
||||
);
|
||||
model._staticPropertyData[CoarseGrainedProp] = coarseGrained;
|
||||
return coarseGrained;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -232,7 +232,14 @@ class Structure {
|
||||
|
||||
get interUnitBonds() {
|
||||
if (this.state.interUnitBonds) return this.state.interUnitBonds;
|
||||
this.state.interUnitBonds = computeInterUnitBonds(this, { ignoreWater: !this.dynamicBonds });
|
||||
if (this.parent && this.state.dynamicBonds === this.parent.state.dynamicBonds &&
|
||||
this.parent.state.interUnitBonds && this.parent.state.interUnitBonds.edgeCount === 0
|
||||
) {
|
||||
// no need to compute InterUnitBonds if parent's ones are empty
|
||||
this.state.interUnitBonds = new InterUnitBonds(new Map());
|
||||
} else {
|
||||
this.state.interUnitBonds = computeInterUnitBonds(this, { ignoreWater: !this.dynamicBonds });
|
||||
}
|
||||
return this.state.interUnitBonds;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -25,6 +25,10 @@ import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
|
||||
import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair';
|
||||
import { ElementSetIntraBondCache } from './unit/bonds/element-set-intra-bond-cache';
|
||||
import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
|
||||
import { getResonance, UnitResonance } from './unit/resonance';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3add = Vec3.add;
|
||||
|
||||
/**
|
||||
* A building block of a structure that corresponds to an atomic or
|
||||
@@ -220,9 +224,32 @@ namespace Unit {
|
||||
|
||||
remapModel(model: Model, dynamicBonds: boolean, props?: AtomicProperties) {
|
||||
if (!props) {
|
||||
props = { ...this.props, bonds: dynamicBonds ? undefined : tryRemapBonds(this, this.props.bonds, model) };
|
||||
props = {
|
||||
...this.props,
|
||||
bonds: dynamicBonds && !this.props.bonds?.props?.canRemap
|
||||
? undefined
|
||||
: tryRemapBonds(this, this.props.bonds, model, dynamicBonds)
|
||||
};
|
||||
if (!Unit.isSameConformation(this, model)) {
|
||||
props.boundary = undefined;
|
||||
const b = props.boundary;
|
||||
if (b) {
|
||||
const { elements } = this;
|
||||
const pos = this.conformation.invariantPosition;
|
||||
const v = Vec3();
|
||||
const center = Vec3();
|
||||
|
||||
for (let i = 0, il = elements.length; i < il; i++) {
|
||||
pos(elements[i], v);
|
||||
v3add(center, center, v);
|
||||
}
|
||||
Vec3.scale(center, center, 1 / elements.length);
|
||||
|
||||
// only invalidate boundary if sphere has changed too much
|
||||
if (Vec3.distance(center, b.sphere.center) / b.sphere.radius >= 1.0) {
|
||||
props.boundary = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
props.lookup3d = undefined;
|
||||
props.principalAxes = undefined;
|
||||
}
|
||||
@@ -282,6 +309,12 @@ namespace Unit {
|
||||
return this.props.rings;
|
||||
}
|
||||
|
||||
get resonance() {
|
||||
if (this.props.resonance) return this.props.resonance;
|
||||
this.props.resonance = getResonance(this);
|
||||
return this.props.resonance;
|
||||
}
|
||||
|
||||
get polymerElements() {
|
||||
if (this.props.polymerElements) return this.props.polymerElements;
|
||||
this.props.polymerElements = getAtomicPolymerElements(this);
|
||||
@@ -342,6 +375,7 @@ namespace Unit {
|
||||
interface AtomicProperties extends BaseProperties {
|
||||
bonds?: IntraUnitBonds
|
||||
rings?: UnitRings
|
||||
resonance?: UnitResonance
|
||||
nucleotideElements?: SortedArray<ElementIndex>
|
||||
proteinElements?: SortedArray<ElementIndex>
|
||||
residueCount?: number
|
||||
@@ -481,7 +515,7 @@ namespace Unit {
|
||||
return isSameConformation(a, b.model);
|
||||
}
|
||||
|
||||
function tryRemapBonds(a: Atomic, old: IntraUnitBonds | undefined, model: Model) {
|
||||
function tryRemapBonds(a: Atomic, old: IntraUnitBonds | undefined, model: Model, dynamicBonds: boolean) {
|
||||
// TODO: should include additional checks?
|
||||
|
||||
if (!old) return void 0;
|
||||
@@ -495,7 +529,7 @@ namespace Unit {
|
||||
return void 0;
|
||||
}
|
||||
|
||||
if (old.props?.canRemap) {
|
||||
if (old.props?.canRemap || !dynamicBonds) {
|
||||
return old;
|
||||
}
|
||||
return isSameConformation(a, model) ? old : void 0;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -12,7 +12,13 @@ import { StructureElement } from '../../element';
|
||||
import { Bond } from '../bonds';
|
||||
import { InterUnitGraph } from '../../../../../mol-math/graph/inter-unit-graph';
|
||||
|
||||
type IntraUnitBonds = IntAdjacencyGraph<StructureElement.UnitIndex, { readonly order: ArrayLike<number>, readonly flags: ArrayLike<BondType.Flag> }, { readonly canRemap?: boolean }>
|
||||
type IntraUnitBonds = IntAdjacencyGraph<StructureElement.UnitIndex, {
|
||||
readonly order: ArrayLike<number>,
|
||||
readonly flags: ArrayLike<BondType.Flag>
|
||||
}, {
|
||||
/** can remap even with dynamicBonds on, e.g., for water molecules */
|
||||
readonly canRemap?: boolean
|
||||
}>
|
||||
|
||||
namespace IntraUnitBonds {
|
||||
export const Empty: IntraUnitBonds = IntAdjacencyGraph.create([], [], [], 0, { flags: [], order: [] });
|
||||
|
||||
@@ -141,7 +141,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
|
||||
const aI = atoms[_aI];
|
||||
|
||||
const elemA = type_symbol.value(aI);
|
||||
if (isWatery && (elemA !== 'H' || elemA !== 'O')) isWatery = false;
|
||||
if (isWatery && (elemA !== 'H' && elemA !== 'O')) isWatery = false;
|
||||
|
||||
const structConnEntries = props.forceCompute ? void 0 : structConn && structConn.byAtomIndex.get(aI);
|
||||
let hasStructConn = false;
|
||||
|
||||
83
src/mol-model/structure/structure/unit/resonance.ts
Normal file
83
src/mol-model/structure/structure/unit/resonance.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { SortedArray } from '../../../../mol-data/int/sorted-array';
|
||||
import { sortedCantorPairing } from '../../../../mol-data/util';
|
||||
import { BondType } from '../../model/types';
|
||||
import { StructureElement } from '../element';
|
||||
import { Unit } from '../unit';
|
||||
|
||||
export type UnitResonance = {
|
||||
/**
|
||||
* Lookup for triplets of atoms in delocalized bonds.
|
||||
*
|
||||
* Does not include triplets that are part of aromatic rings.
|
||||
*/
|
||||
readonly delocalizedTriplets: {
|
||||
/** Return 3rd element in triplet or undefined if `a` and `b` are not part of a triplet */
|
||||
readonly getThirdElement: (a: StructureElement.UnitIndex, b: StructureElement.UnitIndex) => StructureElement.UnitIndex | undefined
|
||||
/** Return index into `triplets` or undefined if `a` is not part of any triplet */
|
||||
readonly getTripletIndices: (a: StructureElement.UnitIndex) => number[] | undefined
|
||||
readonly triplets: SortedArray<StructureElement.UnitIndex>[]
|
||||
}
|
||||
}
|
||||
|
||||
export function getResonance(unit: Unit.Atomic): UnitResonance {
|
||||
return {
|
||||
delocalizedTriplets: getDelocalizedTriplets(unit)
|
||||
};
|
||||
}
|
||||
|
||||
function getDelocalizedTriplets(unit: Unit.Atomic) {
|
||||
const bonds = unit.bonds;
|
||||
const { b, edgeProps, offset } = bonds;
|
||||
const { order: _order, flags: _flags } = edgeProps;
|
||||
const { elementAromaticRingIndices } = unit.rings;
|
||||
|
||||
const triplets: SortedArray<StructureElement.UnitIndex>[] = [];
|
||||
const thirdElementMap = new Map<number, StructureElement.UnitIndex>();
|
||||
const indicesMap = new Map<number, number[]>();
|
||||
|
||||
const add = (a: StructureElement.UnitIndex, b: StructureElement.UnitIndex, c: StructureElement.UnitIndex) => {
|
||||
const index = triplets.length;
|
||||
triplets.push(SortedArray.ofUnsortedArray([a, b, c]));
|
||||
thirdElementMap.set(sortedCantorPairing(a, b), c);
|
||||
if (indicesMap.has(a)) indicesMap.get(a)!.push(index);
|
||||
else indicesMap.set(a, [index]);
|
||||
};
|
||||
|
||||
for (let i = 0 as StructureElement.UnitIndex; i < unit.elements.length; i++) {
|
||||
if (elementAromaticRingIndices.has(i)) continue;
|
||||
|
||||
const count = offset[i + 1] - offset[i] + 1;
|
||||
if (count < 2) continue;
|
||||
|
||||
const deloBonds: StructureElement.UnitIndex[] = [];
|
||||
for (let t = offset[i], _t = offset[i + 1]; t < _t; t++) {
|
||||
const f = _flags[t];
|
||||
if (!BondType.is(f, BondType.Flag.Aromatic)) continue;
|
||||
|
||||
deloBonds.push(b[t]);
|
||||
}
|
||||
|
||||
if (deloBonds.length >= 2) {
|
||||
add(i, deloBonds[0], deloBonds[1]);
|
||||
for (let j = 1, jl = deloBonds.length; j < jl; j++) {
|
||||
add(i, deloBonds[j], deloBonds[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getThirdElement: (a: StructureElement.UnitIndex, b: StructureElement.UnitIndex) => {
|
||||
return thirdElementMap.get(sortedCantorPairing(a, b));
|
||||
},
|
||||
getTripletIndices: (a: StructureElement.UnitIndex) => {
|
||||
return indicesMap.get(a);
|
||||
},
|
||||
triplets,
|
||||
};
|
||||
}
|
||||
@@ -28,17 +28,19 @@ export function computeRings(unit: Unit.Atomic) {
|
||||
}
|
||||
|
||||
const enum Constants {
|
||||
MaxDepth = 4
|
||||
MaxDepth = 5
|
||||
}
|
||||
|
||||
interface State {
|
||||
startVertex: number,
|
||||
endVertex: number,
|
||||
count: number,
|
||||
visited: Int32Array,
|
||||
isRingAtom: Int32Array,
|
||||
marked: Int32Array,
|
||||
queue: Int32Array,
|
||||
color: Int32Array,
|
||||
pred: Int32Array,
|
||||
depth: Int32Array,
|
||||
|
||||
left: Int32Array,
|
||||
right: Int32Array,
|
||||
@@ -59,9 +61,11 @@ function State(unit: Unit.Atomic, capacity: number): State {
|
||||
startVertex: 0,
|
||||
endVertex: 0,
|
||||
count: 0,
|
||||
visited: new Int32Array(capacity),
|
||||
isRingAtom: new Int32Array(capacity),
|
||||
marked: new Int32Array(capacity),
|
||||
queue: new Int32Array(capacity),
|
||||
pred: new Int32Array(capacity),
|
||||
depth: new Int32Array(capacity),
|
||||
left: new Int32Array(Constants.MaxDepth),
|
||||
right: new Int32Array(Constants.MaxDepth),
|
||||
color: new Int32Array(capacity),
|
||||
@@ -78,17 +82,26 @@ function State(unit: Unit.Atomic, capacity: number): State {
|
||||
|
||||
function resetState(state: State) {
|
||||
state.count = state.endVertex - state.startVertex;
|
||||
const { visited, pred, color } = state;
|
||||
const { isRingAtom, pred, color, depth, marked } = state;
|
||||
for (let i = 0; i < state.count; i++) {
|
||||
visited[i] = -1;
|
||||
isRingAtom[i] = 0;
|
||||
pred[i] = -1;
|
||||
marked[i] = -1;
|
||||
color[i] = 0;
|
||||
depth[i] = 0;
|
||||
}
|
||||
state.currentColor = 0;
|
||||
state.currentAltLoc = '';
|
||||
state.hasAltLoc = false;
|
||||
}
|
||||
|
||||
function resetDepth(state: State) {
|
||||
const { depth } = state;
|
||||
for (let i = 0; i < state.count; i++) {
|
||||
depth[i] = state.count + 1;
|
||||
}
|
||||
}
|
||||
|
||||
function largestResidue(unit: Unit.Atomic) {
|
||||
const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
|
||||
let size = 0;
|
||||
@@ -99,8 +112,16 @@ function largestResidue(unit: Unit.Atomic) {
|
||||
return size;
|
||||
}
|
||||
|
||||
function isStartIndex(state: State, i: number) {
|
||||
const bondOffset = state.bonds.offset;
|
||||
const a = state.startVertex + i;
|
||||
const bStart = bondOffset[a], bEnd = bondOffset[a + 1];
|
||||
const bondCount = bEnd - bStart;
|
||||
if (bondCount <= 1 || (state.isRingAtom[i] && bondCount === 2)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function processResidue(state: State, start: number, end: number) {
|
||||
const { visited } = state;
|
||||
state.startVertex = start;
|
||||
state.endVertex = end;
|
||||
|
||||
@@ -117,11 +138,13 @@ function processResidue(state: State, start: number, end: number) {
|
||||
}
|
||||
arraySetRemove(altLocs, '');
|
||||
|
||||
let mark = 1;
|
||||
if (altLocs.length === 0) {
|
||||
resetState(state);
|
||||
for (let i = 0; i < state.count; i++) {
|
||||
if (visited[i] >= 0) continue;
|
||||
findRings(state, i);
|
||||
if (!isStartIndex(state, i)) continue;
|
||||
resetDepth(state);
|
||||
mark = findRings(state, i, mark);
|
||||
}
|
||||
} else {
|
||||
for (let aI = 0; aI < altLocs.length; aI++) {
|
||||
@@ -129,12 +152,13 @@ function processResidue(state: State, start: number, end: number) {
|
||||
state.hasAltLoc = true;
|
||||
state.currentAltLoc = altLocs[aI];
|
||||
for (let i = 0; i < state.count; i++) {
|
||||
if (visited[i] >= 0) continue;
|
||||
if (!isStartIndex(state, i)) continue;
|
||||
const altLoc = state.altLoc.value(elements[state.startVertex + i]);
|
||||
if (altLoc && altLoc !== state.currentAltLoc) {
|
||||
continue;
|
||||
}
|
||||
findRings(state, i);
|
||||
resetDepth(state);
|
||||
mark = findRings(state, i, mark);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,10 +168,10 @@ function processResidue(state: State, start: number, end: number) {
|
||||
}
|
||||
}
|
||||
|
||||
function addRing(state: State, a: number, b: number) {
|
||||
function addRing(state: State, a: number, b: number, isRingAtom: Int32Array) {
|
||||
// only "monotonous" rings
|
||||
if (b < a) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const { pred, color, left, right } = state;
|
||||
@@ -176,7 +200,7 @@ function addRing(state: State, a: number, b: number) {
|
||||
if (current < 0) break;
|
||||
}
|
||||
if (!found) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
current = a;
|
||||
@@ -190,50 +214,50 @@ function addRing(state: State, a: number, b: number) {
|
||||
const len = leftOffset + rightOffset;
|
||||
// rings must have at least three elements
|
||||
if (len < 3) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
const ring = new Int32Array(len);
|
||||
let ringOffset = 0;
|
||||
for (let t = 0; t < leftOffset; t++) ring[ringOffset++] = state.startVertex + left[t];
|
||||
for (let t = rightOffset - 1; t >= 0; t--) ring[ringOffset++] = state.startVertex + right[t];
|
||||
for (let t = 0; t < leftOffset; t++) {
|
||||
ring[ringOffset++] = state.startVertex + left[t];
|
||||
isRingAtom[left[t]] = 1;
|
||||
}
|
||||
for (let t = rightOffset - 1; t >= 0; t--) {
|
||||
ring[ringOffset++] = state.startVertex + right[t];
|
||||
isRingAtom[right[t]] = 1;
|
||||
}
|
||||
|
||||
sortArray(ring);
|
||||
|
||||
if (state.hasAltLoc) {
|
||||
// we need to check if the ring was already added because alt locs are present.
|
||||
// Check if the ring is unique and another one is not it's subset
|
||||
for (let rI = 0, _rI = state.currentRings.length; rI < _rI; rI++) {
|
||||
const r = state.currentRings[rI];
|
||||
|
||||
for (let rI = 0, _rI = state.currentRings.length; rI < _rI; rI++) {
|
||||
const r = state.currentRings[rI];
|
||||
if (ring[0] !== r[0]) continue;
|
||||
if (ring.length !== r.length) continue;
|
||||
|
||||
let areSame = true;
|
||||
for (let aI = 0, _aI = ring.length; aI < _aI; aI++) {
|
||||
if (ring[aI] !== r[aI]) {
|
||||
areSame = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (areSame) {
|
||||
return;
|
||||
}
|
||||
if (ring.length === r.length) {
|
||||
if (SortedArray.areEqual(ring as any, r)) return false;
|
||||
} else if (ring.length > r.length) {
|
||||
if (SortedArray.isSubset(ring as any, r)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
state.currentRings.push(SortedArray.ofSortedArray(ring));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function findRings(state: State, from: number) {
|
||||
const { bonds, startVertex, endVertex, visited, queue, pred } = state;
|
||||
function findRings(state: State, from: number, mark: number) {
|
||||
const { bonds, startVertex, endVertex, isRingAtom, marked, queue, pred, depth } = state;
|
||||
const { elements } = state.unit;
|
||||
const { b: neighbor, edgeProps: { flags: bondFlags }, offset } = bonds;
|
||||
visited[from] = 1;
|
||||
marked[from] = mark;
|
||||
depth[from] = 0;
|
||||
queue[0] = from;
|
||||
let head = 0, size = 1;
|
||||
|
||||
while (head < size) {
|
||||
const top = queue[head++];
|
||||
const d = depth[top];
|
||||
const a = startVertex + top;
|
||||
const start = offset[a], end = offset[a + 1];
|
||||
|
||||
@@ -250,18 +274,25 @@ function findRings(state: State, from: number) {
|
||||
|
||||
const other = b - startVertex;
|
||||
|
||||
if (visited[other] > 0) {
|
||||
if (marked[other] === mark) {
|
||||
if (pred[other] !== top && pred[top] !== other) {
|
||||
addRing(state, top, other);
|
||||
if (addRing(state, top, other, isRingAtom)) {
|
||||
return mark + 1;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
visited[other] = 1;
|
||||
const newDepth = Math.min(depth[other], d + 1);
|
||||
if (newDepth > Constants.MaxDepth) continue;
|
||||
|
||||
depth[other] = newDepth;
|
||||
marked[other] = mark;
|
||||
queue[size++] = other;
|
||||
pred[other] = top;
|
||||
}
|
||||
}
|
||||
return mark + 1;
|
||||
}
|
||||
|
||||
export function getFingerprint(elements: string[]) {
|
||||
|
||||
@@ -185,9 +185,9 @@ const polymerAndLigand = StructureRepresentationPresetProvider({
|
||||
nonStandard: builder.buildRepresentation(update, components.nonStandard, { type: 'ball-and-stick', typeParams, color, colorParams: ballAndStickColor }, { tag: 'non-standard' }),
|
||||
branchedBallAndStick: builder.buildRepresentation(update, components.branched, { type: 'ball-and-stick', typeParams: { ...typeParams, alpha: 0.3 }, color, colorParams: ballAndStickColor }, { tag: 'branched-ball-and-stick' }),
|
||||
branchedSnfg3d: builder.buildRepresentation(update, components.branched, { type: 'carbohydrate', typeParams, color }, { tag: 'branched-snfg-3d' }),
|
||||
water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6 }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'water' }),
|
||||
water: builder.buildRepresentation(update, components.water, { type: waterType, typeParams: { ...typeParams, alpha: 0.6, visuals: waterType === 'line' ? ['intra-bond', 'element-point'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'water' }),
|
||||
ion: builder.buildRepresentation(update, components.ion, { type: 'ball-and-stick', typeParams, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'ion' }),
|
||||
lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6 }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'lipid' }),
|
||||
lipid: builder.buildRepresentation(update, components.lipid, { type: lipidType, typeParams: { ...typeParams, alpha: 0.6, visuals: lipidType === 'line' ? ['intra-bond'] : undefined }, color, colorParams: { carbonColor: { name: 'element-symbol', params: {} } } }, { tag: 'lipid' }),
|
||||
coarse: builder.buildRepresentation(update, components.coarse, { type: 'spacefill', typeParams, color: color || 'chain-id' }, { tag: 'coarse' })
|
||||
};
|
||||
|
||||
|
||||
@@ -76,9 +76,11 @@ namespace InteractivityManager {
|
||||
|
||||
/**
|
||||
* The `noRender` argument indicates that the action should only update the internal
|
||||
* data structure but not render anything user visible. For example 1) no drawing of
|
||||
* the canvas3d scene or 2) no ui update of loci labels. This is useful because some
|
||||
* actions require clearing any markings before they can be applied.
|
||||
* data structure but not render anything user visible. For example, no ui update of
|
||||
* loci labels.
|
||||
*
|
||||
* This is useful because some actions require clearing any markings before
|
||||
* they can be applied.
|
||||
*/
|
||||
export type LociMarkProvider = (loci: Representation.Loci, action: MarkerAction, /* test */ noRender?: boolean) => void
|
||||
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { MarkerAction } from '../../mol-util/marker-action';
|
||||
import { ButtonsType, ModifiersKeys, getButtons, getModifiers, getButton } from '../../mol-util/input/input-observer';
|
||||
import { SequenceWrapper } from './wrapper';
|
||||
import { StructureElement, StructureProperties, Unit } from '../../mol-model/structure';
|
||||
import { Subject } from 'rxjs';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
import { throttleTime } from 'rxjs/operators';
|
||||
import { OrderedSet } from '../../mol-data/int';
|
||||
import { StructureElement, StructureProperties, Unit } from '../../mol-model/structure';
|
||||
import { Representation } from '../../mol-repr/representation';
|
||||
import { ButtonsType, getButton, getButtons, getModifiers, ModifiersKeys } from '../../mol-util/input/input-observer';
|
||||
import { MarkerAction } from '../../mol-util/marker-action';
|
||||
import { PluginUIComponent } from '../base';
|
||||
import { SequenceWrapper } from './wrapper';
|
||||
|
||||
type SequenceProps = {
|
||||
sequenceWrapper: SequenceWrapper.Any,
|
||||
@@ -55,12 +55,10 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
|
||||
this.plugin.managers.interactivity.lociHighlights.addProvider(this.lociHighlightProvider);
|
||||
this.plugin.managers.interactivity.lociSelects.addProvider(this.lociSelectionProvider);
|
||||
|
||||
this.subscribe(debounceTime<{ seqIdx: number, buttons: number, button: number, modifiers: ModifiersKeys }>(15)(this.highlightQueue), (e) => {
|
||||
this.subscribe(this.highlightQueue.pipe(throttleTime(3 * 16.666, void 0, { leading: true, trailing: true })), (e) => {
|
||||
const loci = this.getLoci(e.seqIdx < 0 ? void 0 : e.seqIdx);
|
||||
this.hover(loci, e.buttons, e.button, e.modifiers);
|
||||
});
|
||||
|
||||
// this.updateMarker()
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
||||
@@ -490,7 +490,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.msp-action-menu-options {
|
||||
.msp-action-menu-options {
|
||||
&-no-header, .msp-control-group-children {
|
||||
max-height: 300px;
|
||||
overflow: hidden;
|
||||
@@ -602,10 +602,10 @@
|
||||
}
|
||||
|
||||
.msp-slider {
|
||||
> div:first-child() {
|
||||
> div:first-child {
|
||||
right: 42px;
|
||||
}
|
||||
> div:last-child() {
|
||||
> div:last-child {
|
||||
width: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
-webkit-touch-callout: none;
|
||||
touch-action: manipulation;
|
||||
@@ -71,7 +72,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.msp-semi-transparent-background {
|
||||
.msp-semi-transparent-background {
|
||||
background: $default-background;
|
||||
opacity: 0.5;
|
||||
position: absolute;
|
||||
@@ -94,7 +95,7 @@
|
||||
|
||||
.msp-viewport-controls-panel-controls {
|
||||
overflow-y: auto;
|
||||
max-height: 400px;
|
||||
max-height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ export class QuickStyles extends PurePluginUIComponent {
|
||||
},
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: { bias: 0.9, blurKernelSize: 15, radius: 5, samples: 32 }
|
||||
params: { bias: 0.8, blurKernelSize: 15, radius: 5, samples: 32, resolutionScale: 1 }
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -84,7 +84,7 @@ export class QuickStyles extends PurePluginUIComponent {
|
||||
name: 'on',
|
||||
params: pp.occlusion.name === 'on'
|
||||
? pp.occlusion.params
|
||||
: { bias: 0.9, blurKernelSize: 15, radius: 5, samples: 32 }
|
||||
: { bias: 0.8, blurKernelSize: 15, radius: 5, samples: 32, resolutionScale: 1 }
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
@@ -43,9 +43,9 @@ export const HighlightLoci = PluginBehavior.create({
|
||||
name: 'representation-highlight-loci',
|
||||
category: 'interaction',
|
||||
ctor: class extends PluginBehavior.Handler<HighlightLociProps> {
|
||||
private lociMarkProvider = (interactionLoci: Representation.Loci, action: MarkerAction, noRender?: boolean) => {
|
||||
private lociMarkProvider = (interactionLoci: Representation.Loci, action: MarkerAction) => {
|
||||
if (!this.ctx.canvas3d || !this.params.mark) return;
|
||||
this.ctx.canvas3d.mark(interactionLoci, action, noRender);
|
||||
this.ctx.canvas3d.mark(interactionLoci, action);
|
||||
};
|
||||
private getLoci(loci: Loci) {
|
||||
return this.params.preferAtoms && Bond.isLoci(loci) && loci.bonds.length === 2
|
||||
@@ -113,9 +113,9 @@ export const SelectLoci = PluginBehavior.create({
|
||||
category: 'interaction',
|
||||
ctor: class extends PluginBehavior.Handler<SelectLociProps> {
|
||||
private spine: StateTreeSpine.Impl;
|
||||
private lociMarkProvider = (reprLoci: Representation.Loci, action: MarkerAction, noRender?: boolean) => {
|
||||
private lociMarkProvider = (reprLoci: Representation.Loci, action: MarkerAction) => {
|
||||
if (!this.ctx.canvas3d || !this.params.mark) return;
|
||||
this.ctx.canvas3d.mark({ loci: reprLoci.loci }, action, noRender);
|
||||
this.ctx.canvas3d.mark({ loci: reprLoci.loci }, action);
|
||||
};
|
||||
private getLoci(loci: Loci) {
|
||||
return this.params.preferAtoms && Bond.isLoci(loci) && loci.bonds.length === 2
|
||||
|
||||
@@ -69,8 +69,16 @@ export async function getContourLevel(provider: 'emdb' | 'pdbe', plugin: PluginC
|
||||
export async function getContourLevelEmdb(plugin: PluginContext, taskCtx: RuntimeContext, emdbId: string) {
|
||||
const emdbHeaderServer = plugin.config.get(PluginConfig.VolumeStreaming.EmdbHeaderServer);
|
||||
const header = await plugin.fetch({ url: `${emdbHeaderServer}/${emdbId.toUpperCase()}/header/${emdbId.toLowerCase()}.xml`, type: 'xml' }).runInContext(taskCtx);
|
||||
const map = header.getElementsByTagName('map')[0];
|
||||
const contourLevel = parseFloat(map.getElementsByTagName('contourLevel')[0].textContent!);
|
||||
const contours = header.getElementsByTagName('contour');
|
||||
|
||||
let primaryContour = contours[0];
|
||||
for (let i = 1; i < contours.length; i++) {
|
||||
if (contours[i].getAttribute('primary') === 'true') {
|
||||
primaryContour = contours[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
const contourLevel = parseFloat(primaryContour.getElementsByTagName('level')[0].textContent!);
|
||||
|
||||
return contourLevel;
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ class ViewportScreenshotHelper extends PluginComponent {
|
||||
postprocessing: {
|
||||
...c.props.postprocessing,
|
||||
occlusion: aoProps.name === 'on'
|
||||
? { name: 'on', params: { ...aoProps.params, samples: 128 } }
|
||||
? { name: 'on', params: { ...aoProps.params, samples: 128, resolutionScale: 1 } }
|
||||
: aoProps
|
||||
},
|
||||
marking: { ...c.props.marking }
|
||||
@@ -143,7 +143,7 @@ class ViewportScreenshotHelper extends PluginComponent {
|
||||
postprocessing: {
|
||||
...c.props.postprocessing,
|
||||
occlusion: aoProps.name === 'on'
|
||||
? { name: 'on', params: { ...aoProps.params, samples: 128 } }
|
||||
? { name: 'on', params: { ...aoProps.params, samples: 128, resolutionScale: 1 } }
|
||||
: aoProps
|
||||
},
|
||||
marking: { ...c.props.marking }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -31,7 +31,7 @@ export const LineParams = {
|
||||
...ElementCrossParams,
|
||||
multipleBonds: PD.Select('offset', PD.arrayToOptions(['off', 'symmetric', 'offset'] as const)),
|
||||
includeParent: PD.Boolean(false),
|
||||
sizeFactor: PD.Numeric(3, { min: 0.01, max: 10, step: 0.01 }),
|
||||
sizeFactor: PD.Numeric(2, { min: 0.01, max: 10, step: 0.01 }),
|
||||
unitKinds: getUnitKindsParam(['atomic']),
|
||||
visuals: PD.MultiSelect(['intra-bond', 'inter-bond', 'element-point', 'element-cross'], PD.objectToOptions(LineVisuals))
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -79,12 +79,16 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
|
||||
};
|
||||
|
||||
const { elementRingIndices, elementAromaticRingIndices } = unit.rings;
|
||||
const deloTriplets = aromaticBonds ? unit.resonance.delocalizedTriplets : undefined;
|
||||
|
||||
return {
|
||||
linkCount: edgeCount * 2,
|
||||
referencePosition: (edgeIndex: number) => {
|
||||
let aI = a[edgeIndex], bI = b[edgeIndex];
|
||||
|
||||
const rI = deloTriplets?.getThirdElement(aI, bI);
|
||||
if (rI !== undefined) return pos(elements[rI], vRef);
|
||||
|
||||
if (aI > bI) [aI, bI] = [bI, aI];
|
||||
if (offset[aI + 1] - offset[aI] === 1) [aI, bI] = [bI, aI];
|
||||
|
||||
@@ -145,8 +149,10 @@ function getIntraUnitBondCylinderBuilderProps(unit: Unit.Atomic, structure: Stru
|
||||
if (isBondType(f, BondType.Flag.Aromatic) || (arCount && !ignoreComputedAromatic)) {
|
||||
if (arCount === 2) {
|
||||
return LinkStyle.MirroredAromatic;
|
||||
} else {
|
||||
} else if (arCount === 1 || deloTriplets?.getThirdElement(aI, bI)) {
|
||||
return LinkStyle.Aromatic;
|
||||
} else {
|
||||
// case for bonds between two aromatic rings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -52,12 +52,16 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
|
||||
const pos = unit.conformation.invariantPosition;
|
||||
|
||||
const { elementRingIndices, elementAromaticRingIndices } = unit.rings;
|
||||
const deloTriplets = aromaticBonds ? unit.resonance.delocalizedTriplets : undefined;
|
||||
|
||||
const builderProps: LinkBuilderProps = {
|
||||
linkCount: edgeCount * 2,
|
||||
referencePosition: (edgeIndex: number) => {
|
||||
let aI = a[edgeIndex], bI = b[edgeIndex];
|
||||
|
||||
const rI = deloTriplets?.getThirdElement(aI, bI);
|
||||
if (rI !== undefined) return pos(elements[rI], vRef);
|
||||
|
||||
if (aI > bI) [aI, bI] = [bI, aI];
|
||||
if (offset[aI + 1] - offset[aI] === 1) [aI, bI] = [bI, aI];
|
||||
|
||||
@@ -106,8 +110,10 @@ function createIntraUnitBondLines(ctx: VisualContext, unit: Unit, structure: Str
|
||||
if (isBondType(f, BondType.Flag.Aromatic) || (arCount && !ignoreComputedAromatic)) {
|
||||
if (arCount === 2) {
|
||||
return LinkStyle.MirroredAromatic;
|
||||
} else {
|
||||
} else if (arCount === 1 || deloTriplets?.getThirdElement(aI, bI)) {
|
||||
return LinkStyle.Aromatic;
|
||||
} else {
|
||||
// case for bonds between two aromatic rings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,4 +264,4 @@ export function eachInterBond(loci: Loci, structure: Structure, apply: (interval
|
||||
__unitMap.clear();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -22,9 +22,6 @@ import { SpheresBuilder } from '../../../../mol-geo/geometry/spheres/spheres-bui
|
||||
import { isTrace, isH, StructureGroup } from './common';
|
||||
import { Sphere3D } from '../../../../mol-math/geometry';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3add = Vec3.add;
|
||||
|
||||
type ElementProps = {
|
||||
ignoreHydrogens: boolean,
|
||||
traceOnly: boolean,
|
||||
@@ -73,17 +70,13 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur
|
||||
const ignore = makeElementIgnoreTest(structure, unit, props);
|
||||
const l = StructureElement.Location.create(structure, unit);
|
||||
const themeSize = theme.size.size;
|
||||
const center = Vec3();
|
||||
let maxSize = 0;
|
||||
let count = 0;
|
||||
|
||||
for (let i = 0; i < elementCount; i++) {
|
||||
if (ignore && ignore(elements[i])) continue;
|
||||
|
||||
l.element = elements[i];
|
||||
pos(elements[i], v);
|
||||
v3add(center, center, v);
|
||||
count += 1;
|
||||
|
||||
builderState.currentGroup = i;
|
||||
const size = themeSize(l);
|
||||
@@ -92,17 +85,8 @@ export function createElementSphereMesh(ctx: VisualContext, unit: Unit, structur
|
||||
addSphere(builderState, v, size * sizeFactor, detail);
|
||||
}
|
||||
|
||||
// re-use boundingSphere if it has not changed much
|
||||
let boundingSphere: Sphere3D;
|
||||
Vec3.scale(center, center, 1 / count);
|
||||
if (mesh && Vec3.distance(center, mesh.boundingSphere.center) / mesh.boundingSphere.radius < 1.0) {
|
||||
boundingSphere = Sphere3D.clone(mesh.boundingSphere);
|
||||
} else {
|
||||
boundingSphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * sizeFactor + 0.05);
|
||||
}
|
||||
|
||||
const m = MeshBuilder.getMesh(builderState);
|
||||
m.setBoundingSphere(boundingSphere);
|
||||
m.setBoundingSphere(Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * sizeFactor + 0.05));
|
||||
|
||||
return m;
|
||||
}
|
||||
@@ -126,34 +110,21 @@ export function createElementSphereImpostor(ctx: VisualContext, unit: Unit, stru
|
||||
|
||||
const l = StructureElement.Location.create(structure, unit);
|
||||
const themeSize = theme.size.size;
|
||||
const center = Vec3();
|
||||
let maxSize = 0;
|
||||
let count = 0;
|
||||
|
||||
for (let i = 0; i < elementCount; i++) {
|
||||
if (ignore?.(elements[i])) continue;
|
||||
|
||||
pos(elements[i], v);
|
||||
builder.add(v[0], v[1], v[2], i);
|
||||
v3add(center, center, v);
|
||||
count += 1;
|
||||
|
||||
l.element = elements[i];
|
||||
const size = themeSize(l);
|
||||
if (size > maxSize) maxSize = size;
|
||||
}
|
||||
|
||||
// re-use boundingSphere if it has not changed much
|
||||
let boundingSphere: Sphere3D;
|
||||
Vec3.scale(center, center, 1 / count);
|
||||
if (spheres && Vec3.distance(center, spheres.boundingSphere.center) / spheres.boundingSphere.radius < 1.0) {
|
||||
boundingSphere = Sphere3D.clone(spheres.boundingSphere);
|
||||
} else {
|
||||
boundingSphere = Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * props.sizeFactor + 0.05);
|
||||
}
|
||||
|
||||
const s = builder.getSpheres();
|
||||
s.setBoundingSphere(boundingSphere);
|
||||
s.setBoundingSphere(Sphere3D.expand(Sphere3D(), (childUnit ?? unit).boundary.sphere, maxSize * props.sizeFactor + 0.05));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"sourceMap": true,
|
||||
"sourceMap": false,
|
||||
"noUnusedLocals": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"alwaysStrict": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitThis": true,
|
||||
"sourceMap": true,
|
||||
"sourceMap": false,
|
||||
"noUnusedLocals": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
|
||||
Reference in New Issue
Block a user