diff --git a/src/apps/viewer/extensions/cellpack/model.ts b/src/apps/viewer/extensions/cellpack/model.ts index faa230b51216f2878272a9c8c473b9f65c664bfa..179e3c8fec205ba19a2047ec0e0bc35cbc636396 100644 --- a/src/apps/viewer/extensions/cellpack/model.ts +++ b/src/apps/viewer/extensions/cellpack/model.ts @@ -71,7 +71,7 @@ function getTransforms(results: Ingredient['results']) { } function getAssembly(transforms: Mat4[], structure: Structure) { - const builder = Structure.Builder(void 0, void 0) + const builder = Structure.Builder() const { units } = structure; for (let i = 0, il = transforms.length; i < il; ++i) { @@ -115,7 +115,7 @@ export function createStructureFromCellPack(ingredients: Packing['ingredients'], if (s) structures.push(s) } - const builder = Structure.Builder(void 0, void 0) + const builder = Structure.Builder() let offsetInvariantId = 0 for (const s of structures) { let maxInvariantId = 0 diff --git a/src/mol-model/structure/query/queries/filters.ts b/src/mol-model/structure/query/queries/filters.ts index f4531fb298f86c152fb17f32095470ecc1b6b31f..3b4c0c7ebd9bf4cef9c384415ec5a38ee2497089 100644 --- a/src/mol-model/structure/query/queries/filters.ts +++ b/src/mol-model/structure/query/queries/filters.ts @@ -39,7 +39,7 @@ export function first(query: StructureQuery): StructureQuery { if (sel.kind === 'singletons') { if (sel.structure.elementCount > 0) { const u = sel.structure.units[0]; - const s = Structure.create([u.getChild(SortedArray.ofSingleton(u.elements[0]))], ctx.inputStructure); + const s = Structure.create([u.getChild(SortedArray.ofSingleton(u.elements[0]))], { parent: ctx.inputStructure }); ret.add(s); } } else { diff --git a/src/mol-model/structure/query/queries/generators.ts b/src/mol-model/structure/query/queries/generators.ts index 3afd35b21d84203de267b585be08e04d9455c9c1..2538462c01d623d9e8f9bdece388065a21ff9a58 100644 --- a/src/mol-model/structure/query/queries/generators.ts +++ b/src/mol-model/structure/query/queries/generators.ts @@ -220,7 +220,7 @@ function atomGroupsGrouped({ unitTest, entityTest, chainTest, residueTest, atomT function getRingStructure(unit: Unit.Atomic, ring: UnitRing, inputStructure: Structure) { const elements = new Int32Array(ring.length) as any as ElementIndex[]; for (let i = 0, _i = ring.length; i < _i; i++) elements[i] = unit.elements[ring[i]]; - return Structure.create([unit.getChild(SortedArray.ofSortedArray(elements))], inputStructure); + return Structure.create([unit.getChild(SortedArray.ofSortedArray(elements))], { parent: inputStructure }); } export function rings(fingerprints?: ArrayLike<UnitRing.Fingerprint>): StructureQuery { diff --git a/src/mol-model/structure/query/selection.ts b/src/mol-model/structure/query/selection.ts index c2d1b828699817f7f95999b31d4dd98a09d6257e..f19064aab761a4da1656ca346dfb3c534a617c98 100644 --- a/src/mol-model/structure/query/selection.ts +++ b/src/mol-model/structure/query/selection.ts @@ -135,7 +135,7 @@ namespace StructureSelection { const { elements } = unit; for (let i = 0, _i = elements.length; i < _i; i++) { // TODO: optimize this somehow??? - const s = Structure.create([unit.getChild(SortedArray.ofSingleton(elements[i]))], sel.source); + const s = Structure.create([unit.getChild(SortedArray.ofSingleton(elements[i]))], { parent: sel.source }); fn(s, idx++); } } diff --git a/src/mol-model/structure/query/utils/structure-set.ts b/src/mol-model/structure/query/utils/structure-set.ts index 1d1b0011d89f02a9e1ada3af3e8bf2cf425b590a..2e1cf2d1aa17d8cf22bdad066779d310c173dc22 100644 --- a/src/mol-model/structure/query/utils/structure-set.ts +++ b/src/mol-model/structure/query/utils/structure-set.ts @@ -80,7 +80,7 @@ export function structureIntersect(sA: Structure, sB: Structure): Structure { } } - return Structure.create(units, sA.parent || sB.parent); + return Structure.create(units, { parent: sA.parent || sB.parent }); } export function structureSubtract(a: Structure, b: Structure): Structure { @@ -103,5 +103,5 @@ export function structureSubtract(a: Structure, b: Structure): Structure { } } - return Structure.create(units, a.parent || b.parent); + return Structure.create(units, { parent: a.parent || b.parent }); } \ No newline at end of file diff --git a/src/mol-model/structure/structure/element.ts b/src/mol-model/structure/structure/element.ts index 25baf1bd57dd31f851ede425e24c9b3a8aed9667..eb0cb0577633f1f545ad8b3267352537ab4ed4e0 100644 --- a/src/mol-model/structure/structure/element.ts +++ b/src/mol-model/structure/structure/element.ts @@ -651,7 +651,7 @@ namespace StructureElement { } } } - return Structure.create(units, parent) + return Structure.create(units, { parent }) } export function areEqual(a: Query, b: Query) { diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index e36e78fac760d072f09d723804fdfd58eb1be9c6..708eee3f3c2486c91dca749130c4438b5cb1aa48 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -44,6 +44,8 @@ class Structure { carbohydrates?: Carbohydrates, models?: ReadonlyArray<Model>, model?: Model, + masterModel?: Model, + representativeModel?: Model, uniqueResidueNames?: Set<string>, entityIndices?: ReadonlyArray<EntityIndex>, uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>, @@ -205,15 +207,30 @@ class Structure { || (this._props.uniqueAtomicResidueIndices = getUniqueAtomicResidueIndices(this)); } - /** If the structure is based on a single model, return it. Otherwise throw an exception. */ + /** + * If the structure is based on a single model or has a master-/representative-model, return it. + * Otherwise throw an exception. + */ get model(): Model { if (this._props.model) return this._props.model; + if (this._props.representativeModel) return this._props.representativeModel; + if (this._props.masterModel) return this._props.masterModel; const models = this.models; - if (models.length > 1) throw new Error('The structre is based on multiple models.'); + if (models.length > 1) { + throw new Error('The structure is based on multiple models and has neither a master- nor a representative-model.'); + } this._props.model = models[0]; return this._props.model; } + get masterModel(): Model | undefined { + return this._props.masterModel + } + + get representativeModel(): Model | undefined { + return this._props.representativeModel + } + hasElement(e: StructureElement) { if (!this.unitMap.has(e.unit.id)) return false; return SortedArray.has(this.unitMap.get(e.unit.id).elements, e.element); @@ -243,12 +260,19 @@ class Structure { return map; } - constructor(units: ArrayLike<Unit>, parent: Structure | undefined, coordinateSystem?: SymmetryOperator) { + constructor(units: ArrayLike<Unit>, props: Structure.Props = {}) { this.unitMap = this.initUnits(units); this.units = units as ReadonlyArray<Unit>; - if (parent) this._props.parent = parent.parent || parent; - if (coordinateSystem) this._props.coordinateSystem = coordinateSystem; - else if (parent) this._props.coordinateSystem = parent.coordinateSystem; + if (props.parent) this._props.parent = props.parent.parent || props.parent; + + if (props.coordinateSystem) this._props.coordinateSystem = props.coordinateSystem; + else if (props.parent) this._props.coordinateSystem = props.parent.coordinateSystem; + + if (props.masterModel) this._props.masterModel = props.masterModel; + else if (props.parent) this._props.masterModel = props.parent.masterModel; + + if (props.representativeModel) this._props.representativeModel = props.representativeModel; + else if (props.parent) this._props.representativeModel = props.parent.representativeModel; } } @@ -339,7 +363,16 @@ function getUniqueAtomicResidueIndices(structure: Structure): ReadonlyMap<UUID, } namespace Structure { - export const Empty = new Structure([], void 0, void 0); + export const Empty = new Structure([]); + + export interface Props { + parent?: Structure + coordinateSystem?: SymmetryOperator + /** Master model for structures of a protein model and multiple ligand models */ + masterModel?: Model + /** Representative model for structures of a model trajectory */ + representativeModel?: Model + } /** Represents a single structure */ export interface Loci { @@ -366,8 +399,28 @@ namespace Structure { return a.structure === b.structure } - export function create(units: ReadonlyArray<Unit>, parent: Structure | undefined, coordinateSystem?: SymmetryOperator): Structure { - return new Structure(units, parent, coordinateSystem); + export function create(units: ReadonlyArray<Unit>, props?: Props): Structure { + return new Structure(units, props); + } + + export function ofTrajectory(trajectory: ReadonlyArray<Model>): Structure { + if (trajectory.length === 0) return Empty + + const units: Unit[] = []; + + let count = 0 + for (let i = 0, il = trajectory.length; i < il; ++i) { + const structure = ofModel(trajectory[i]) + for (let j = 0, jl = structure.units.length; j < jl; ++j) { + const u = structure.units[j] + const invariantId = u.invariantId + count + const newUnit = Unit.create(units.length, invariantId, u.kind, u.model, u.conformation.operator, u.elements) + units.push(newUnit) + } + count = units.length + } + + return create(units, { representativeModel: trajectory[0] }); } /** @@ -378,7 +431,7 @@ namespace Structure { */ export function ofModel(model: Model): Structure { const chains = model.atomicHierarchy.chainAtomSegments; - const builder = new StructureBuilder(void 0, void 0); + const builder = new StructureBuilder(); for (let c = 0 as ChainIndex; c < chains.count; c++) { const start = chains.offsets[c]; @@ -496,7 +549,7 @@ namespace Structure { const cs = s.coordinateSystem; const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs.assembly, cs.ncsId, cs.hkl), cs); - return new Structure(units, s, newCS); + return new Structure(units, { parent: s, coordinateSystem: newCS }); } export class StructureBuilder { @@ -517,20 +570,20 @@ namespace Structure { } getStructure(): Structure { - return create(this.units, this.parent, this.coordinateSystem); + return create(this.units, this.props); } get isEmpty() { return this.units.length === 0; } - constructor(private parent: Structure | undefined, private coordinateSystem: SymmetryOperator | undefined) { + constructor(private props: Props = {}) { } } - export function Builder(parent: Structure | undefined, coordinateSystem: SymmetryOperator | undefined) { - return new StructureBuilder(parent, coordinateSystem); + export function Builder(props: Props = {}) { + return new StructureBuilder(props); } export function hashCode(s: Structure) { diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts index 5379e19368273a32d207bbd2ff17e9fc6dcc28f8..c636da1e5738366fa263ea44c6a74621803715f0 100644 --- a/src/mol-model/structure/structure/symmetry.ts +++ b/src/mol-model/structure/structure/symmetry.ts @@ -24,7 +24,8 @@ namespace StructureSymmetry { const assembly = ModelSymmetry.findAssembly(models[0], asmName); if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`); - const assembler = Structure.Builder(void 0, SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] })); + const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] }) + const assembler = Structure.Builder({ coordinateSystem }); const queryCtx = new QueryContext(structure); @@ -137,7 +138,7 @@ function getOperatorsCached333(symmetry: ModelSymmetry) { } function assembleOperators(structure: Structure, operators: ReadonlyArray<SymmetryOperator>) { - const assembler = Structure.Builder(void 0, void 0); + const assembler = Structure.Builder(); const { units } = structure; for (const oper of operators) { for (const unit of units) { @@ -179,7 +180,7 @@ async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius const operators = getOperatorsCached333(symmetry); const lookup = structure.lookup3d; - const assembler = Structure.Builder(void 0, void 0); + const assembler = Structure.Builder(); const { units } = structure; const center = Vec3.zero(); diff --git a/src/mol-model/structure/structure/unit/links/inter-compute.ts b/src/mol-model/structure/structure/unit/links/inter-compute.ts index b1cbcc140548ee0fd7b020f9e191b411bbdacbe6..250113aae0ad3aa28d8f8916bb8a8e3a6cd7be9a 100644 --- a/src/mol-model/structure/structure/unit/links/inter-compute.ts +++ b/src/mol-model/structure/structure/unit/links/inter-compute.ts @@ -1,7 +1,8 @@ /** - * Copyright (c) 2017 Mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { LinkType } from '../../../model/types'; @@ -152,10 +153,15 @@ function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, params: LinkCompu return bondCount; } -function findLinks(structure: Structure, params: LinkComputationParameters) { +export interface InterLinkComputationParameters extends LinkComputationParameters { + validUnitPair: (unitA: Unit, unitB: Unit) => boolean +} + +function findLinks(structure: Structure, params: InterLinkComputationParameters) { const map = new Map<number, InterUnitBonds.UnitPairBonds[]>(); if (!structure.units.some(u => Unit.isAtomic(u))) return new InterUnitBonds(map); + const { validUnitPair } = params; const lookup = structure.lookup3d; const imageCenter = Vec3.zero(); @@ -167,7 +173,7 @@ function findLinks(structure: Structure, params: LinkComputationParameters) { const closeUnits = lookup.findUnitIndices(imageCenter[0], imageCenter[1], imageCenter[2], bs.radius + MAX_RADIUS); for (let i = 0; i < closeUnits.count; i++) { const other = structure.units[closeUnits.indices[i]]; - if (!Unit.isAtomic(other) || unit.id >= other.id) continue; + if (!Unit.isAtomic(other) || unit.id >= other.id || !validUnitPair(unit, other)) continue; if (other.elements.length >= unit.elements.length) findPairLinks(unit, other, params, map); else findPairLinks(other, unit, params, map); @@ -177,10 +183,20 @@ function findLinks(structure: Structure, params: LinkComputationParameters) { return new InterUnitBonds(map); } -function computeInterUnitBonds(structure: Structure, params?: Partial<LinkComputationParameters>): InterUnitBonds { +function ValidUnitPair(structure: Structure) { + const { masterModel } = structure + if (masterModel) { + return (a: Unit, b: Unit) => a.model === b.model || a.model === masterModel || b.model === masterModel + } else { + return (a: Unit, b: Unit) => a.model === b.model + } +} + +function computeInterUnitBonds(structure: Structure, params?: Partial<InterLinkComputationParameters>): InterUnitBonds { return findLinks(structure, { maxHbondLength: (params && params.maxHbondLength) || 1.15, forceCompute: !!(params && params.forceCompute), + validUnitPair: (params && params.validUnitPair) || ValidUnitPair(structure), }); } diff --git a/src/mol-model/structure/structure/util/subset-builder.ts b/src/mol-model/structure/structure/util/subset-builder.ts index de2cc6bf8c6eb45fd41d42ec5c68ae3dae95cf48..75f1127f3e4441badb6fc5b69caca48bed290b52 100644 --- a/src/mol-model/structure/structure/util/subset-builder.ts +++ b/src/mol-model/structure/structure/util/subset-builder.ts @@ -90,7 +90,7 @@ export class StructureSubsetBuilder { newUnits[newUnits.length] = child; } - return Structure.create(newUnits, this.parent); + return Structure.create(newUnits, { parent: this.parent }); } getStructure() { diff --git a/src/mol-model/structure/structure/util/unique-subset-builder.ts b/src/mol-model/structure/structure/util/unique-subset-builder.ts index 13bdd18ccf3a4219bc1d0be91f7d2b7f3471838b..742afb255a41016fdb2f79d3e8ad14cd923b6459 100644 --- a/src/mol-model/structure/structure/util/unique-subset-builder.ts +++ b/src/mol-model/structure/structure/util/unique-subset-builder.ts @@ -85,7 +85,7 @@ export class StructureUniqueSubsetBuilder { newUnits[newUnits.length] = child; } - return Structure.create(newUnits, this.parent, this.parent.coordinateSystem); + return Structure.create(newUnits, { parent: this.parent }); } get isEmpty() { diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 732ec22067933fdfd5b642677df4c8a2a0c6ff13..fc542c0c158ae5d2bd8ebfeceab92d931a492de2 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -44,6 +44,7 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Action(StateTransforms.Model.StructureSymmetryFromModel), PluginSpec.Action(TransformStructureConformation), PluginSpec.Action(StateTransforms.Model.StructureFromModel), + PluginSpec.Action(StateTransforms.Model.StructureFromTrajectory), PluginSpec.Action(StateTransforms.Model.ModelFromTrajectory), PluginSpec.Action(StateTransforms.Model.UserStructureSelection), PluginSpec.Action(StateTransforms.Representation.StructureRepresentation3D), diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts index 76914d041aa58c76de63014c4f3feb82b5e24e8f..3e78a31c5cd39d2ee04a0a9cd6623a7b04223a35 100644 --- a/src/mol-plugin/state/actions/structure.ts +++ b/src/mol-plugin/state/actions/structure.ts @@ -199,9 +199,9 @@ function createSingleTrajectoryModel(sources: StateTransformer.Params<Download>[ .apply(StateTransforms.Data.DownloadBlob, { sources: sources.map((src, i) => ({ id: '' + i, url: src.url, isBinary: src.isBinary })), maxConcurrency: 6 - }).apply(StateTransforms.Data.ParseBlob, { + }, { state: { isGhost: true } }).apply(StateTransforms.Data.ParseBlob, { formats: sources.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' })) - }) + }, { state: { isGhost: true } }) .apply(StateTransforms.Model.TrajectoryFromBlob) .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 }); } @@ -211,13 +211,13 @@ export function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary switch (format) { case 'cif': parsed = b.apply(StateTransforms.Data.ParseCif, void 0, { state: { isGhost: true } }) - .apply(StateTransforms.Model.TrajectoryFromMmCif, void 0, { state: { isGhost: true } }) + .apply(StateTransforms.Model.TrajectoryFromMmCif) break case 'pdb': - parsed = b.apply(StateTransforms.Model.TrajectoryFromPDB, void 0, { state: { isGhost: true } }); + parsed = b.apply(StateTransforms.Model.TrajectoryFromPDB); break case 'gro': - parsed = b.apply(StateTransforms.Model.TrajectoryFromGRO, void 0, { state: { isGhost: true } }); + parsed = b.apply(StateTransforms.Model.TrajectoryFromGRO); break default: throw new Error('unsupported format') diff --git a/src/mol-plugin/state/transforms/helpers.ts b/src/mol-plugin/state/transforms/helpers.ts index a68fe1cd0c5a7ba4fdbfeb577dd096c730a7181d..793336dbeb19ceec4195cb898b548a721b49557a 100644 --- a/src/mol-plugin/state/transforms/helpers.ts +++ b/src/mol-plugin/state/transforms/helpers.ts @@ -42,7 +42,7 @@ export function getStructureTransparency(structure: Structure, script: Script, v * Attaches ComputedSecondaryStructure property when unavailable in sourceData */ export async function ensureSecondaryStructure(s: Structure) { - if (s.model && s.model.sourceData.kind === 'mmCIF') { + if (s.models.length === 1 && s.model && s.model.sourceData.kind === 'mmCIF') { if (!s.model.sourceData.data.struct_conf.id.isDefined && !s.model.sourceData.data.struct_sheet_range.id.isDefined) { await ComputedSecondaryStructure.attachFromCifOrCompute(s) } diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index b2fcb3e0287686e81ff0fa229864efad508629d7..f78a0e94a39c5dd89ca824c87e2a170769168b79 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -33,6 +33,7 @@ export { TrajectoryFromMmCif }; export { TrajectoryFromPDB }; export { TrajectoryFromGRO }; export { ModelFromTrajectory }; +export { StructureFromTrajectory }; export { StructureFromModel }; export { StructureAssemblyFromModel }; export { StructureSymmetryFromModel }; @@ -161,6 +162,22 @@ const ModelFromTrajectory = PluginStateTransform.BuiltIn({ } }); +type StructureFromTrajectory = typeof StructureFromTrajectory +const StructureFromTrajectory = PluginStateTransform.BuiltIn({ + name: 'structure-from-trajectory', + display: { name: 'Structure from Trajectory', description: 'Create a molecular structure from a trajectory.' }, + from: SO.Molecule.Trajectory, + to: SO.Molecule.Structure +})({ + apply({ a }) { + return Task.create('Build Structure', async ctx => { + const s = Structure.ofTrajectory(a.data); + const props = { label: a.data[0].label, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` }; + return new SO.Molecule.Structure(s, props); + }) + } +}); + type StructureFromModel = typeof StructureFromModel const StructureFromModel = PluginStateTransform.BuiltIn({ name: 'structure-from-model',