diff --git a/src/mol-plugin/state/actions/data-format.ts b/src/mol-plugin/state/actions/data-format.ts index 7e4bbd656f0e3d47097f3c7d73f0960571a448b3..c8eff16e37cb84cb2d61fc5174cff5e74f55f7ab 100644 --- a/src/mol-plugin/state/actions/data-format.ts +++ b/src/mol-plugin/state/actions/data-format.ts @@ -13,6 +13,7 @@ import { ParamDefinition as PD } from 'mol-util/param-definition'; import { Ccp4Provider, Dsn6Provider, DscifProvider } from './volume'; import { StateTransforms } from '../transforms'; import { MmcifProvider, PdbProvider, GroProvider } from './structure'; +import msgpackDecode from 'mol-io/common/msgpack/decode' export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> { private _list: { name: string, provider: DataFormatProvider<D> }[] = [] @@ -139,4 +140,18 @@ export const OpenFile = StateAction.build({ const b = state.build().to(data.ref); // need to await the 2nd update the so that the enclosing Task finishes after the update is done. await provider.getDefaultBuilder(ctx, b, state).runInContext(taskCtx) -})); \ No newline at end of file +})); + +// + +type cifVariants = 'dscif' | -1 +export function guessCifVariant(info: FileInfo, data: Uint8Array | string): cifVariants { + if (info.ext === 'bcif') { + try { + if (msgpackDecode(data as Uint8Array).encoder.startsWith('VolumeServer')) return 'dscif' + } catch { } + } else if (info.ext === 'cif') { + if ((data as string).startsWith('data_SERVER\n#\n_density_server_result')) return 'dscif' + } + return -1 +} \ No newline at end of file diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts index 714adecf217632e6c000e5e2344ff76866148005..b9b623d33ce48996797da7efe367930b6235cc22 100644 --- a/src/mol-plugin/state/actions/structure.ts +++ b/src/mol-plugin/state/actions/structure.ts @@ -13,7 +13,7 @@ import { StateTransforms } from '../transforms'; import { Download } from '../transforms/data'; import { StructureRepresentation3DHelpers } from '../transforms/representation'; import { CustomModelProperties, StructureSelection } from '../transforms/model'; -import { DataFormatProvider } from './data-format'; +import { DataFormatProvider, guessCifVariant } from './data-format'; import { FileInfo } from 'mol-util/file-info'; import { Task } from 'mol-task'; import { StructureElement } from 'mol-model/structure'; @@ -24,7 +24,10 @@ export const MmcifProvider: DataFormatProvider<any> = { stringExtensions: ['cif', 'mmcif', 'mcif'], binaryExtensions: ['bcif'], isApplicable: (info: FileInfo, data: Uint8Array | string) => { - return info.ext === 'cif' || info.ext === 'mmcif' || info.ext === 'mcif' || info.ext === 'bcif' + if (info.ext === 'mmcif' || info.ext === 'mcif') return true + // assume cif/bcif files that are not DensityServer CIF are mmCIF + if (info.ext === 'cif' || info.ext === 'bcif') return guessCifVariant(info, data) !== 'dscif' + return false }, getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, state: State) => { return Task.create('mmCIF default builder', async taskCtx => { diff --git a/src/mol-plugin/state/actions/volume.ts b/src/mol-plugin/state/actions/volume.ts index baf6e0ded64d08c380cf907bd4a671302cbeac5a..727114bf9f8b56ec9b7c619130b28e208d79ab06 100644 --- a/src/mol-plugin/state/actions/volume.ts +++ b/src/mol-plugin/state/actions/volume.ts @@ -16,7 +16,7 @@ import { PluginStateObject } from '../objects'; import { StateTransforms } from '../transforms'; import { Download } from '../transforms/data'; import { VolumeRepresentation3DHelpers } from '../transforms/representation'; -import { DataFormatProvider } from './data-format'; +import { DataFormatProvider, guessCifVariant } from './data-format'; export const Ccp4Provider: DataFormatProvider<any> = { label: 'CCP4/MRC/BRIX', @@ -59,10 +59,10 @@ export const DscifProvider: DataFormatProvider<any> = { description: 'DensityServer CIF', stringExtensions: ['cif'], binaryExtensions: ['bcif'], - isApplicable: (info: FileInfo, data: Uint8Array) => { - return info.ext === 'cif' || info.ext === 'bcif' + isApplicable: (info: FileInfo, data: Uint8Array | string) => { + return guessCifVariant(info, data) === 'dscif' ? true : false }, - getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.Binary>, state: State) => { + getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, state: State) => { return Task.create('DensityServer CIF default builder', async taskCtx => { const cifBuilder = data.apply(StateTransforms.Data.ParseCif) const cifStateObject = await state.updateTree(cifBuilder).runInContext(taskCtx) diff --git a/src/mol-repr/structure/visual/util/common.ts b/src/mol-repr/structure/visual/util/common.ts index 1e2f479d22e546a668aab603854caf7841db563f..cd0e2271a17f32e5821ebca489f0f935eb15c4a8 100644 --- a/src/mol-repr/structure/visual/util/common.ts +++ b/src/mol-repr/structure/visual/util/common.ts @@ -11,7 +11,7 @@ import { OrderedSet, SortedArray } from 'mol-data/int'; import { EmptyLoci, Loci } from 'mol-model/loci'; /** Return a Loci for the elements of a whole residue the elementIndex belongs to. */ -export function getResidueLoci(structure: Structure, unit: Unit, elementIndex: ElementIndex): Loci { +export function getResidueLoci(structure: Structure, unit: Unit.Atomic, elementIndex: ElementIndex): Loci { const { elements, model } = unit if (OrderedSet.indexOf(elements, elementIndex) !== -1) { const { index, offsets } = model.atomicHierarchy.residueAtomSegments diff --git a/src/mol-repr/structure/visual/util/polymer.ts b/src/mol-repr/structure/visual/util/polymer.ts index 71b987a3ea8b5ac3954524d393e59875b55b5733..bf0f536b8c15cbbd1f4f98788507fd9fa6807e4e 100644 --- a/src/mol-repr/structure/visual/util/polymer.ts +++ b/src/mol-repr/structure/visual/util/polymer.ts @@ -72,12 +72,25 @@ export function getPolymerElementLoci(pickingId: PickingId, structureGroup: Stru if (id === objectId) { const { structure, group } = structureGroup const unit = group.units[instanceId] - return getResidueLoci(structure, unit, unit.polymerElements[groupId]) + if (Unit.isAtomic(unit)) { + return getResidueLoci(structure, unit, unit.polymerElements[groupId]) + } else { + const { elements } = unit + const elementIndex = unit.polymerElements[groupId] + const unitIndex = OrderedSet.indexOf(elements, elementIndex) as StructureElement.UnitIndex | -1 + if (unitIndex !== -1) { + const indices = OrderedSet.ofSingleton(unitIndex) + return StructureElement.Loci(structure, [{ unit, indices }]) + } + } } return EmptyLoci } -/** Mark a polymer element (e.g. part of a cartoon trace) when all its residue's elements are in a loci. */ +/** + * Mark a polymer element (e.g. part of a cartoon trace) + * - for atomic units mark only when all its residue's elements are in a loci + */ export function eachPolymerElement(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) { let changed = false if (!StructureElement.isLoci(loci)) return false @@ -90,19 +103,32 @@ export function eachPolymerElement(loci: Loci, structureGroup: StructureGroup, a for (const e of loci.elements) { const unitIdx = group.unitIndexMap.get(e.unit.id) if (unitIdx !== undefined) { - // TODO optimized implementation for intervals - OrderedSet.forEach(e.indices, v => { - const rI = index[elements[v]] - const unitIndexMin = OrderedSet.findPredecessorIndex(elements, offsets[rI]) - const unitIndexMax = OrderedSet.findPredecessorIndex(elements, offsets[rI + 1] - 1) - const unitIndexInterval = Interval.ofRange(unitIndexMin, unitIndexMax) - if (!OrderedSet.isSubset(e.indices, unitIndexInterval)) return - const eI = traceElementIndex[rI] - const idx = OrderedSet.indexOf(e.unit.polymerElements, eI) - if (idx !== -1) { - if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true + if (Unit.isAtomic(e.unit)) { + // TODO optimized implementation for intervals + OrderedSet.forEach(e.indices, v => { + const rI = index[elements[v]] + const unitIndexMin = OrderedSet.findPredecessorIndex(elements, offsets[rI]) + const unitIndexMax = OrderedSet.findPredecessorIndex(elements, offsets[rI + 1] - 1) + const unitIndexInterval = Interval.ofRange(unitIndexMin, unitIndexMax) + if (!OrderedSet.isSubset(e.indices, unitIndexInterval)) return + const eI = traceElementIndex[rI] + const idx = OrderedSet.indexOf(e.unit.polymerElements, eI) + if (idx !== -1) { + if (apply(Interval.ofSingleton(unitIdx * groupCount + idx))) changed = true + } + }) + } else { + if (Interval.is(e.indices)) { + const start = unitIdx * groupCount + Interval.start(e.indices); + const end = unitIdx * groupCount + Interval.end(e.indices); + if (apply(Interval.ofBounds(start, end))) changed = true + } else { + for (let i = 0, _i = e.indices.length; i < _i; i++) { + const idx = unitIdx * groupCount + e.indices[i]; + if (apply(Interval.ofSingleton(idx))) changed = true + } } - }) + } } } return changed