Compare commits

...

25 Commits

Author SHA1 Message Date
Alexander Rose
3415fe0847 0.7.1-dev.2 2020-05-09 11:54:33 -07:00
Alexander Rose
1569958a29 debug-mode url param, canvas3d console stats 2020-05-09 11:52:09 -07:00
Alexander Rose
3543faa0c2 fix snapshot loading 2020-05-09 11:03:58 -07:00
Alexander Rose
251dbf3877 0.7.1-dev.1 2020-05-08 15:30:57 -07:00
Alexander Rose
32d35efef0 add grid size to volume transform description and incr version 2020-05-08 15:27:39 -07:00
Alexander Rose
8b6428a61d 0.7.0 2020-05-08 10:45:43 -07:00
Alexander Rose
dda43370cf add setProductionMode, setDebugMode exports 2020-05-08 10:42:38 -07:00
Alexander Rose
92c1e979c0 add version to viewer module export 2020-05-08 10:36:26 -07:00
Alexander Rose
ad38a33943 basic support for aligning coarse structures 2020-05-08 10:20:09 -07:00
Alexander Rose
88c276a4c7 inline option for ToggleSelectionModeButton 2020-05-07 16:16:32 -07:00
Alexander Rose
0a3d19235d 0.7.0-dev.21 2020-05-06 20:16:36 -07:00
Alexander Rose
0d90fd1f06 tweak version script
- so the build contains the correct version number
2020-05-06 20:14:52 -07:00
Alexander Rose
02d3274e83 0.7.0-dev.20 2020-05-06 18:49:10 -07:00
Alexander Rose
2531af2b94 use cursor icon for selection mode 2020-05-06 18:02:53 -07:00
Alexander Rose
850328be4e fix coloring of bonds via overpaint 2020-05-06 17:29:55 -07:00
Alexander Rose
f8ce9cbb65 determine type of sequence for alignment 2020-05-06 15:38:51 -07:00
Alexander Rose
2af9d1cabf limit by chains superposition to polymers 2020-05-06 11:32:31 -07:00
Alexander Rose
e8d1737d40 backbone/sidechain query fixes
- handle non-polymer components in polymers
2020-05-06 10:56:01 -07:00
Alexander Rose
0328e93518 interactivity: selectOnly, only deselect for the structure of the given loci 2020-05-06 10:37:11 -07:00
Alexander Rose
8a4ab9bdb9 more selection helper fixes
- use structure from last decorator as reference
- handle that oldObj is not defined for inserts
2020-05-05 22:10:53 -07:00
Alexander Rose
410cdb193d selection manager fixes
- add removed/updated events to substructure-parent-helper
- remap selections
2020-05-05 17:03:04 -07:00
Alexander Rose
a278337b4c Merge branch 'master' of https://github.com/molstar/molstar 2020-05-05 11:08:46 -07:00
Alexander Rose
b1308de0b9 StructureSelectionManager improvements
- remap/clear referenceLoci onUpdate
- remap/clear history onUpdate
- removed unused prevHighlight
- support group-by-structure in modifyHistory
2020-05-05 11:08:05 -07:00
Alexander Rose
9705078970 set writeDepth specifically for points and text geo 2020-05-05 10:12:51 -07:00
Alexander Rose
b1ca98e945 ignore atoms with zero occupancy for bond computation
- assuming they are not actually atoms
2020-05-05 09:54:47 -07:00
31 changed files with 291 additions and 216 deletions

2
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.7.0-dev.19",
"version": "0.7.1-dev.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "0.7.0-dev.19",
"version": "0.7.1-dev.2",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -34,7 +34,8 @@
"model-server-watch": "nodemon --watch lib lib/servers/servers/model/server.js",
"volume-server-test": "node lib/servers/servers/volume/server.js --idMap em 'test/${id}.mdb' --defaultPort 1336",
"plugin-state": "node lib/servers/servers/plugin-state/index.js",
"preversion": "npm run test && npm run build",
"preversion": "npm run test",
"version": "npm run build",
"postversion": "git push && git push --tags"
},
"files": [

View File

@@ -45,6 +45,9 @@
return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || '');
}
var debugMode = getParam('debug-mode', '[^&]+').trim() === '1';
if (debugMode) molstar.setDebugMode(debugMode);
var hideControls = getParam('hide-controls', '[^&]+').trim() === '1';
var pdbProvider = getParam('pdb-provider', '[^&]+').trim().toLowerCase();
var emdbProvider = getParam('emdb-provider', '[^&]+').trim().toLowerCase();
@@ -59,7 +62,7 @@
if (snapshotId) viewer.setRemoteSnapshot(snapshotId);
var snapshotUrl = getParam('snapshot-url', '[^&]+').trim();
var snapshotUrlType = getParam('snapshot-url-type', '[^&]+').toLowerCase().trim();
var snapshotUrlType = getParam('snapshot-url-type', '[^&]+').toLowerCase().trim() || 'molj';
if (snapshotUrl && snapshotUrlType) viewer.loadSnapshotFromUrl(snapshotUrl, snapshotUrlType);
var structureUrl = getParam('structure-url', '[^&]+').trim();

View File

@@ -23,8 +23,12 @@ import { ObjectKeys } from '../../mol-util/type-helpers';
import { PluginState } from '../../mol-plugin/state';
import { DownloadDensity } from '../../mol-plugin-state/actions/volume';
import { PluginLayoutControlsDisplay } from '../../mol-plugin/layout';
require('mol-plugin-ui/skin/light.scss');
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setProductionMode, setDebugMode } from '../../mol-util/debug';
const Extensions = {
'cellpack': PluginSpec.Behavior(CellPack),
'pdbe-structure-quality-report': PluginSpec.Behavior(PDBeStructureQualityReport),

View File

@@ -362,10 +362,19 @@ namespace Canvas3D {
camera.setState({ radiusMax: scene.boundingSphere.radius }, 0);
reprCount.next(reprRenderObjects.size);
if (isDebugMode) consoleStats();
return true;
}
function consoleStats() {
console.table(scene.renderables.map(r => ({
drawCount: r.values.drawCount.ref.value,
instanceCount: r.values.instanceCount.ref.value,
materialId: r.materialId,
})));
}
function add(repr: Representation.Any) {
registerAutoUpdate(repr);
@@ -384,6 +393,7 @@ namespace Canvas3D {
reprRenderObjects.set(repr, newRO);
scene.update(repr.renderObjects, false);
if (isDebugMode) consoleStats();
}
function remove(repr: Representation.Any) {
@@ -394,6 +404,7 @@ namespace Canvas3D {
renderObjects.forEach(o => scene.remove(o));
reprRenderObjects.delete(repr);
scene.update(repr.renderObjects, false, true);
if (isDebugMode) consoleStats();
}
}

View File

@@ -207,5 +207,6 @@ export namespace Points {
!props.pointFilledCircle ||
(props.pointFilledCircle && props.pointEdgeBleach === 0)
);
state.writeDepth = state.opaque;
}
}

View File

@@ -283,6 +283,7 @@ export namespace Text {
BaseGeometry.updateRenderableState(state, props);
state.pickable = false;
state.opaque = false;
state.writeDepth = true;
}
}

View File

@@ -9,7 +9,7 @@ import { SubstitutionMatrix, SubstitutionMatrices, SubstitutionMatrixData } from
const DefaultAlignmentOptions = {
gapPenalty: -11,
gapExtensionPenalty: -1,
substMatrix: 'blosum62' as SubstitutionMatrix
substMatrix: 'default' as SubstitutionMatrix | 'default'
};
export type AlignmentOptions = typeof DefaultAlignmentOptions;
@@ -22,7 +22,7 @@ export function align(seqA: ArrayLike<string>, seqB: ArrayLike<string>, options:
class Alignment {
readonly gapPenalty: number; readonly gapExtensionPenalty: number
readonly substMatrix: SubstitutionMatrixData
readonly substMatrix: SubstitutionMatrixData | undefined
readonly n: number; readonly m: number
readonly S: number[][] = []; readonly V: number[][] = []; readonly H: number[][] = []
@@ -30,7 +30,7 @@ class Alignment {
constructor (readonly seqA: ArrayLike<string>, readonly seqB: ArrayLike<string>, options: AlignmentOptions) {
this.gapPenalty = options.gapPenalty;
this.gapExtensionPenalty = options.gapExtensionPenalty;
this.substMatrix = SubstitutionMatrices[options.substMatrix];
this.substMatrix = options.substMatrix === 'default' ? undefined : SubstitutionMatrices[options.substMatrix];
this.n = this.seqA.length;
this.m = this.seqB.length;
@@ -61,22 +61,19 @@ class Alignment {
}
private makeScoreFn () {
const seq1 = this.seqA;
const seq2 = this.seqB;
const substMatrix = this.substMatrix;
const { seqA, seqB, substMatrix } = this;
if (substMatrix) {
return function score (i: number, j: number) {
const c1 = seq1[i];
const c2 = seq2[j];
return substMatrix[c1]?.[c2] ?? -4;
const cA = seqA[i];
const cB = seqB[j];
return substMatrix[cA]?.[cB] ?? -4;
};
} else {
return function scoreNoSubstMat (i: number, j: number) {
const c1 = seq1[i];
const c2 = seq2[j];
return c1 === c2 ? 5 : -3;
const cA = seqA[i];
const cB = seqB[j];
return cA === cB ? 5 : -3;
};
}
}

View File

@@ -24,16 +24,26 @@ namespace AlignSequences {
function createSeqIdIndicesMap(element: StructureElement.Loci.Element) {
const seqIds = new Map<number, StructureElement.UnitIndex[]>();
if (!Unit.isAtomic(element.unit)) return seqIds;
const { label_seq_id } = element.unit.model.atomicHierarchy.residues;
const { residueIndex } = element.unit;
for (let i = 0, il = OrderedSet.size(element.indices); i < il; ++i) {
const uI = OrderedSet.getAt(element.indices, i);
const eI = element.unit.elements[uI];
const seqId = label_seq_id.value(residueIndex[eI]);
if (seqIds.has(seqId)) seqIds.get(seqId)!.push(uI);
else seqIds.set(seqId, [uI]);
if (Unit.isAtomic(element.unit)) {
const { label_seq_id } = element.unit.model.atomicHierarchy.residues;
const { residueIndex } = element.unit;
for (let i = 0, il = OrderedSet.size(element.indices); i < il; ++i) {
const uI = OrderedSet.getAt(element.indices, i);
const eI = element.unit.elements[uI];
const seqId = label_seq_id.value(residueIndex[eI]);
if (seqIds.has(seqId)) seqIds.get(seqId)!.push(uI);
else seqIds.set(seqId, [uI]);
}
} else if (Unit.isCoarse(element.unit)) {
const { seq_id_begin } = Unit.isSpheres(element.unit)
? element.unit.model.coarseHierarchy.spheres
: element.unit.model.coarseHierarchy.gaussians;
for (let i = 0, il = OrderedSet.size(element.indices); i < il; ++i) {
const uI = OrderedSet.getAt(element.indices, i);
const eI = element.unit.elements[uI];
const seqId = seq_id_begin.value(eI);
seqIds.set(seqId, [uI]);
}
}
return seqIds;
}

View File

@@ -102,6 +102,10 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
if (added) continue;
}
// ignore atoms with zero occupancy (assuming they are not actually atoms)
const occA = occupancyA.value(aI);
if (hasOccupancy && occA === 0) continue;
const { indices, count, squaredDistances } = lookup3d.find(imageA[0], imageA[1], imageA[2], MAX_RADIUS);
if (count === 0) continue;
@@ -112,7 +116,6 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
const metalA = MetalsSet.has(aeI);
const atomIdA = label_atom_idA.value(aI);
const compIdA = label_comp_idA.value(residueIndexA[aI]);
const occA = occupancyA.value(aI);
for (let ni = 0; ni < count; ni++) {
const _bI = indices[ni] as StructureElement.UnitIndex;

View File

@@ -39,6 +39,7 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
const atomCount = unit.elements.length;
const { elements: atoms, residueIndex, chainIndex } = unit;
const { type_symbol, label_atom_id, label_alt_id } = unit.model.atomicHierarchy.atoms;
const { occupancy } = unit.model.atomicConformation;
const { label_comp_id, label_seq_id } = unit.model.atomicHierarchy.residues;
const { index } = unit.model.atomicHierarchy;
const { byEntityKey } = unit.model.sequence;
@@ -116,6 +117,9 @@ function _computeBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUni
}
lastResidue = raI;
// ignore atoms with zero occupancy (assuming they are not actually atoms)
if (occupancy.isDefined && occupancy.value(aI) === 0) continue;
const aeI = getElementIdx(type_symbol.value(aI));
const atomIdA = label_atom_id.value(aI);
const componentPairs = componentMap ? componentMap.get(atomIdA) : void 0;

View File

@@ -9,6 +9,7 @@ import { MinimizeRmsd } from '../../../../mol-math/linear-algebra/3d/minimize-rm
import StructureElement from '../element';
import { OrderedSet } from '../../../../mol-data/int';
import { AlignSequences } from '../../../sequence/alignment/sequence';
import StructureProperties from '../properties';
export function superpose(xs: StructureElement.Loci[]): MinimizeRmsd.Result[] {
const ret: MinimizeRmsd.Result[] = [];
@@ -31,16 +32,21 @@ export function superpose(xs: StructureElement.Loci[]): MinimizeRmsd.Result[] {
}
type AlignAndSuperposeResult = MinimizeRmsd.Result & { alignmentScore: number };
const reProtein = /(polypeptide|cyclic-pseudo-peptide)/i;
export function alignAndSuperpose(xs: StructureElement.Loci[]): AlignAndSuperposeResult[] {
const ret: AlignAndSuperposeResult[] = [];
if (xs.length <= 0) return ret;
const l = StructureElement.Loci.getFirstLocation(xs[0])!;
const subtype = StructureProperties.entity.subtype(l);
const substMatrix = subtype.match(reProtein) ? 'blosum62' : 'default';
for (let i = 1; i < xs.length; i++) {
const { a, b, score } = AlignSequences.compute({
a: xs[0].elements[0],
b: xs[i].elements[0]
});
b: xs[i].elements[0],
}, { substMatrix });
const lociA = StructureElement.Loci(xs[0].structure, [a]);
const lociB = StructureElement.Loci(xs[i].structure, [b]);

View File

@@ -135,6 +135,17 @@ const _nucleiEntityTest = MS.core.logic.and([
])
]);
/**
* this is to get non-polymer and peptide terminus components in polymer entities,
* - non-polymer, e.g. PXZ in 4HIV or generally ACE
* - carboxy terminus, e.g. FC0 in 4BP9, or ETA in 6DDE
* - amino terminus, e.g. ARF in 3K4V, or 4MM in 3EGV
*/
const _nonPolymerResidueTest = MS.core.str.match([
MS.re('non-polymer|(amino|carboxy) terminus|peptide-like', 'i'),
MS.ammp('chemCompType')
]);
// TODO maybe pre-calculate backbone atom properties
const backbone = StructureSelectionQuery('Backbone', MS.struct.modifier.union([
MS.struct.combinator.merge([
@@ -142,6 +153,7 @@ const backbone = StructureSelectionQuery('Backbone', MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': _proteinEntityTest,
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
'residue-test': MS.core.logic.not([_nonPolymerResidueTest]),
'atom-test': MS.core.set.has([MS.set(...SetUtils.toArray(ProteinBackboneAtoms)), MS.ammp('label_atom_id')])
})
]),
@@ -149,6 +161,7 @@ const backbone = StructureSelectionQuery('Backbone', MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': _nucleiEntityTest,
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
'residue-test': MS.core.logic.not([_nonPolymerResidueTest]),
'atom-test': MS.core.set.has([MS.set(...SetUtils.toArray(NucleicBackboneAtoms)), MS.ammp('label_atom_id')])
})
])
@@ -162,6 +175,7 @@ const sidechain = StructureSelectionQuery('Sidechain', MS.struct.modifier.union(
MS.struct.generator.atomGroups({
'entity-test': _proteinEntityTest,
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
'residue-test': MS.core.logic.not([_nonPolymerResidueTest]),
'atom-test': MS.core.logic.or([
MS.core.logic.not([
MS.core.set.has([MS.set(...SetUtils.toArray(ProteinBackboneAtoms)), MS.ammp('label_atom_id')])
@@ -173,6 +187,7 @@ const sidechain = StructureSelectionQuery('Sidechain', MS.struct.modifier.union(
MS.struct.generator.atomGroups({
'entity-test': _nucleiEntityTest,
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
'residue-test': MS.core.logic.not([_nonPolymerResidueTest]),
'atom-test': MS.core.logic.or([
MS.core.logic.not([
MS.core.set.has([MS.set(...SetUtils.toArray(NucleicBackboneAtoms)), MS.ammp('label_atom_id')])
@@ -190,6 +205,7 @@ const sidechainWithTrace = StructureSelectionQuery('Sidechain with Trace', MS.st
MS.struct.generator.atomGroups({
'entity-test': _proteinEntityTest,
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
'residue-test': MS.core.logic.not([_nonPolymerResidueTest]),
'atom-test': MS.core.logic.or([
MS.core.logic.not([
MS.core.set.has([MS.set(...SetUtils.toArray(ProteinBackboneAtoms)), MS.ammp('label_atom_id')])
@@ -206,6 +222,7 @@ const sidechainWithTrace = StructureSelectionQuery('Sidechain with Trace', MS.st
MS.struct.generator.atomGroups({
'entity-test': _nucleiEntityTest,
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
'residue-test': MS.core.logic.not([_nonPolymerResidueTest]),
'atom-test': MS.core.logic.or([
MS.core.logic.not([
MS.core.set.has([MS.set(...SetUtils.toArray(NucleicBackboneAtoms)), MS.ammp('label_atom_id')])
@@ -296,18 +313,11 @@ const ligand = StructureSelectionQuery('Ligand', MS.struct.modifier.union([
])
})
]),
// this is to get non-polymer and peptide terminus components in polymer entities,
// - non-polymer, e.g. PXZ in 4HIV or generally ACE
// - carboxy terminus, e.g. FC0 in 4BP9, or ETA in 6DDE
// - amino terminus, e.g. ARF in 3K4V, or 4MM in 3EGV
MS.struct.modifier.union([
MS.struct.generator.atomGroups({
'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']),
'chain-test': MS.core.rel.eq([MS.ammp('objectPrimitive'), 'atomistic']),
'residue-test': MS.core.str.match([
MS.re('non-polymer|(amino|carboxy) terminus|peptide-like', 'i'),
MS.ammp('chemCompType')
])
'residue-test': _nonPolymerResidueTest
})
])
]),

View File

@@ -204,9 +204,10 @@ namespace InteractivityManager {
}
selectOnly(current: Representation.Loci, applyGranularity = true) {
this.deselectAll();
const normalized = this.normalizedLoci(current, applyGranularity);
if (StructureElement.Loci.is(normalized.loci)) {
// only deselect for the structure of the given loci
this.deselect({ loci: Structure.toStructureElementLoci(normalized.loci.structure), repr: normalized.repr }, false);
this.sel.modify('set', normalized.loci);
}
this.mark(normalized, MarkerAction.Select);

View File

@@ -231,6 +231,8 @@ class PluginStateSnapshotManager extends StatefulPluginComponent<{
if (PluginStateSnapshotManager.isStateSnapshot(snapshot)) {
return this.setStateSnapshot(snapshot);
} else if (PluginStateSnapshotManager.isStateSnapshot(snapshot.data)) {
return this.setStateSnapshot(snapshot.data);
} else {
this.plugin.state.setSnapshot(snapshot);
}
@@ -330,7 +332,7 @@ namespace PluginStateSnapshotManager {
snapshot: PluginState.Snapshot
}
export function Entry(snapshot: PluginState.Snapshot, params: {name?: string, description?: string }): Entry {
export function Entry(snapshot: PluginState.Snapshot, params: { name?: string, description?: string }): Entry {
return { timestamp: +new Date(), snapshot, ...params };
}

View File

@@ -13,13 +13,13 @@ import { EmptyLoci, Loci } from '../../../mol-model/loci';
import { Structure, StructureElement, StructureSelection } from '../../../mol-model/structure';
import { Boundary } from '../../../mol-model/structure/structure/util/boundary';
import { PluginContext } from '../../../mol-plugin/context';
import { StateObject, StateObjectRef } from '../../../mol-state';
import { StateObjectRef } from '../../../mol-state';
import { Task } from '../../../mol-task';
import { structureElementStatsLabel } from '../../../mol-theme/label';
import { arrayRemoveAtInPlace } from '../../../mol-util/array';
import { StatefulPluginComponent } from '../../component';
import { StructureSelectionQuery } from '../../helpers/structure-selection-query';
import { PluginStateObject } from '../../objects';
import { PluginStateObject as PSO } from '../../objects';
import { UUID } from '../../../mol-util';
import { StructureRef } from './hierarchy-state';
@@ -46,7 +46,7 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
}
}
private referenceLoci: Loci | undefined
private referenceLoci: StructureElement.Loci | undefined
get entries() { return this.state.entries; }
get additionsHistory() { return this.state.additionsHistory; }
@@ -57,7 +57,8 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
}
private getEntry(s: Structure) {
const cell = this.plugin.helpers.substructureParent.get(s);
// ignore decorators to get stable ref
const cell = this.plugin.helpers.substructureParent.get(s, true);
if (!cell) return;
const ref = cell.transform.ref;
if (!this.entries.has(ref)) {
@@ -144,26 +145,34 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
return !StructureElement.Loci.areEqual(sel, entry.selection);
}
modifyHistory(entry: StructureSelectionHistoryEntry, action: 'remove' | 'up' | 'down', modulus?: number) {
const idx = this.additionsHistory.indexOf(entry);
modifyHistory(entry: StructureSelectionHistoryEntry, action: 'remove' | 'up' | 'down', modulus?: number, groupByStructure = false) {
const history = this.additionsHistory;
const idx = history.indexOf(entry);
if (idx < 0) return;
let swapWith: number | undefined = void 0;
switch (action) {
case 'remove': arrayRemoveAtInPlace(this.additionsHistory, idx); break;
case 'remove': arrayRemoveAtInPlace(history, idx); break;
case 'up': swapWith = idx - 1; break;
case 'down': swapWith = idx + 1; break;
}
if (swapWith !== void 0) {
const mod = modulus ? Math.min(this.additionsHistory.length, modulus) : this.additionsHistory.length;
swapWith = swapWith % mod;
if (swapWith < 0) swapWith += mod;
const mod = modulus ? Math.min(history.length, modulus) : history.length;
while (true) {
swapWith = swapWith % mod;
if (swapWith < 0) swapWith += mod;
const t = this.additionsHistory[idx];
this.additionsHistory[idx] = this.additionsHistory[swapWith];
this.additionsHistory[swapWith] = t;
if (!groupByStructure || history[idx].loci.structure === history[swapWith].loci.structure) {
const t = history[idx];
history[idx] = history[swapWith];
history[swapWith] = t;
break;
} else {
swapWith += action === 'up' ? -1 : +1;
}
}
}
this.events.additionsHistoryUpdated.next();
@@ -205,32 +214,81 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
}
}
private onRemove(ref: string) {
if (this.entries.has(ref)) {
this.entries.delete(ref);
this.clearHistory();
this.referenceLoci = undefined;
private clearHistoryForStructure(structure: Structure) {
const historyEntryToRemove: StructureSelectionHistoryEntry[] = [];
for (const e of this.state.additionsHistory) {
if (e.loci.structure.root === structure.root) {
historyEntryToRemove.push(e);
}
}
for (const e of historyEntryToRemove) {
this.modifyHistory(e, 'remove');
}
if (historyEntryToRemove.length !== 0) {
this.events.additionsHistoryUpdated.next();
}
}
private onUpdate(ref: string, oldObj: StateObject | undefined, obj: StateObject) {
if (!PluginStateObject.Molecule.Structure.is(obj)) return;
private onRemove(ref: string, obj: PSO.Molecule.Structure | undefined) {
if (this.entries.has(ref)) {
if (!PluginStateObject.Molecule.Structure.is(oldObj) || oldObj === obj || oldObj.data === obj.data) return;
this.entries.delete(ref);
if (obj?.data) {
this.clearHistoryForStructure(obj.data);
}
if (this.referenceLoci?.structure === obj?.data) {
this.referenceLoci = undefined;
}
this.state.stats = void 0;
this.events.changed.next();
}
}
// TODO: property update the latest loci & reference loci
this.state.additionsHistory = [];
this.referenceLoci = undefined;
private onUpdate(ref: string, oldObj: PSO.Molecule.Structure | undefined, obj: PSO.Molecule.Structure) {
// remap the old selection to be related to the new object if possible.
if (Structure.areUnitAndIndicesEqual(oldObj.data, obj.data)) {
this.entries.set(ref, remapSelectionEntry(this.entries.get(ref)!, obj.data));
return;
// no change to structure
if (oldObj === obj || oldObj?.data === obj.data) return;
// ignore decorators to get stable ref
const cell = this.plugin.helpers.substructureParent.get(obj.data, true);
if (!cell) return;
ref = cell.transform.ref;
if (!this.entries.has(ref)) return;
// use structure from last decorator as reference
const structure = this.plugin.helpers.substructureParent.get(obj.data)?.obj?.data;
if (!structure) return;
// oldObj is not defined for inserts (e.g. TransformStructureConformation)
if (!oldObj || Structure.areUnitAndIndicesEqual(oldObj.data, obj.data)) {
this.entries.set(ref, remapSelectionEntry(this.entries.get(ref)!, structure));
// remap referenceLoci & prevHighlight if needed and possible
if (this.referenceLoci?.structure.root === structure.root) {
this.referenceLoci = StructureElement.Loci.remap(this.referenceLoci, structure);
}
// clear the selection
this.entries.set(ref, new SelectionEntry(StructureElement.Loci(obj.data, [])));
// remap history locis if needed and possible
let changedHistory = false;
for (const e of this.state.additionsHistory) {
if (e.loci.structure.root === structure.root) {
e.loci = StructureElement.Loci.remap(e.loci, structure);
changedHistory = true;
}
}
if (changedHistory) this.events.additionsHistoryUpdated.next();
} else {
// clear the selection for ref
this.entries.set(ref, new SelectionEntry(StructureElement.Loci(structure, [])));
if (this.referenceLoci?.structure.root === structure.root) {
this.referenceLoci = undefined;
}
this.clearHistoryForStructure(structure);
this.state.stats = void 0;
this.events.changed.next();
}
}
@@ -292,7 +350,7 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
if (!xs) return;
const ref = this.referenceLoci;
if (!ref || !StructureElement.Loci.is(ref) || ref.structure.root !== loci.structure.root) return;
if (!ref || !StructureElement.Loci.is(ref) || ref.structure !== loci.structure) return;
let e: StructureElement.Loci['elements'][0] | undefined;
for (const _e of ref.elements) {
@@ -305,26 +363,7 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
if (xs.unit !== e.unit) return;
return getElementRange(loci.structure.root, e, xs);
}
private prevHighlight: StructureElement.Loci | undefined = void 0;
accumulateInteractiveHighlight(loci: Loci) {
if (StructureElement.Loci.is(loci)) {
if (this.prevHighlight) {
this.prevHighlight = StructureElement.Loci.union(this.prevHighlight, loci);
} else {
this.prevHighlight = loci;
}
}
return this.prevHighlight;
}
clearInteractiveHighlight() {
const ret = this.prevHighlight;
this.prevHighlight = void 0;
return ret || EmptyLoci;
return getElementRange(loci.structure, e, xs);
}
/** Count of all selected elements */
@@ -427,11 +466,11 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
}));
}
fromSelections(ref: StateObjectRef<PluginStateObject.Molecule.Structure.Selections>) {
fromSelections(ref: StateObjectRef<PSO.Molecule.Structure.Selections>) {
const cell = StateObjectRef.resolveAndCheck(this.plugin.state.data, ref);
if (!cell || !cell.obj) return;
if (!PluginStateObject.Molecule.Structure.Selections.is(cell.obj)) {
if (!PSO.Molecule.Structure.Selections.is(cell.obj)) {
console.warn('fromSelections applied to wrong object type.', cell.obj);
return;
}
@@ -445,8 +484,9 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
constructor(private plugin: PluginContext) {
super({ entries: new Map(), additionsHistory: [], stats: SelectionStats() });
plugin.state.data.events.object.removed.subscribe(e => this.onRemove(e.ref));
plugin.state.data.events.object.updated.subscribe(e => this.onUpdate(e.ref, e.oldObj, e.obj));
// listen to events from substructureParent helper to ensure it is updated
plugin.helpers.substructureParent.events.removed.subscribe(e => this.onRemove(e.ref, e.obj));
plugin.helpers.substructureParent.events.updated.subscribe(e => this.onUpdate(e.ref, e.oldObj, e.obj));
}
}

View File

@@ -42,7 +42,7 @@ const VolumeFromCcp4 = PluginStateTransform.BuiltIn({
apply({ a, params }) {
return Task.create('Create volume from CCP4/MRC/MAP', async ctx => {
const volume = await volumeFromCcp4(a.data, { ...params, label: a.data.name || a.label }).runInContext(ctx);
const props = { label: volume.label || 'Volume', description: 'Volume' };
const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.NX}\u00D7${a.data.header.NX}\u00D7${a.data.header.NX}` };
return new SO.Volume.Data(volume, props);
});
}
@@ -63,7 +63,7 @@ const VolumeFromDsn6 = PluginStateTransform.BuiltIn({
apply({ a, params }) {
return Task.create('Create volume from DSN6/BRIX', async ctx => {
const volume = await volumeFromDsn6(a.data, { ...params, label: a.data.name || a.label }).runInContext(ctx);
const props = { label: volume.label || 'Volume', description: 'Volume' };
const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.xExtent}\u00D7${a.data.header.yExtent}\u00D7${a.data.header.zExtent}` };
return new SO.Volume.Data(volume, props);
});
}
@@ -85,7 +85,7 @@ const VolumeFromCube = PluginStateTransform.BuiltIn({
apply({ a, params }) {
return Task.create('Create volume from Cube', async ctx => {
const volume = await volumeFromCube(a.data, { ...params, label: a.data.name || a.label }).runInContext(ctx);
const props = { label: volume.label || 'Volume', description: 'Volume' };
const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.dim[0]}\u00D7${a.data.header.dim[1]}\u00D7${a.data.header.dim[2]}` };
return new SO.Volume.Data(volume, props);
});
}
@@ -102,8 +102,7 @@ const VolumeFromDx = PluginStateTransform.BuiltIn({
return Task.create('Parse DX', async ctx => {
console.log(a);
const volume = await volumeFromDx(a.data, { label: a.data.name || a.label }).runInContext(ctx);
console.log(volume);
const props = { label: volume.label || 'Volume', description: 'Volume' };
const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.dim[0]}\u00D7${a.data.header.dim[1]}\u00D7${a.data.header.dim[2]}` };
return new SO.Volume.Data(volume, props);
});
}
@@ -135,7 +134,8 @@ const VolumeFromDensityServerCif = PluginStateTransform.BuiltIn({
if (!block) throw new Error(`Data block '${[header]}' not found.`);
const densityServerCif = CIF.schema.densityServer(block);
const volume = await volumeFromDensityServerData(densityServerCif).runInContext(ctx);
const props = { label: densityServerCif.volume_data_3d_info.name.value(0), description: `${densityServerCif.volume_data_3d_info.name.value(0)}` };
const [x, y, z] = volume.grid.cells.space.dimensions;
const props = { label: densityServerCif.volume_data_3d_info.name.value(0), description: `Volume ${x}\u00D7${y}\u00D7${z}` };
return new SO.Volume.Data(volume, props);
});
}

View File

@@ -21,13 +21,14 @@ export function Icon(props: {
//
export function Union() { return _union; }
export function Subtract() { return _subtract; }
export function Intersect() { return _intersect; }
export function UnionSvg() { return _union; }
export function SubtractSvg() { return _subtract; }
export function IntersectSvg() { return _intersect; }
export function SetSvg() { return _set; }
export function MoleculeSvg() { return _molecule; }
export function RulerSvg() { return _ruler; }
export function CubeSvg() { return _cube; }
export function CursorSvg() { return _cursor; }
const circleLeft = <circle r="6px" id="circle-left" cy="12px" cx="8px" strokeWidth="1" />;
const circleRight = <circle r="6px" id="circle-right" cy="12px" cx="16px" strokeWidth="1" />;
@@ -87,64 +88,25 @@ const _set = <svg width="24px" height="24px" viewBox="0 0 24 24">
const _molecule = <svg width="17px" height="17px" viewBox="0 0 299.463 299.463">
<g>
<path d="M256.851,173.832v-48.201c22.916-4.918,34.151-30.668,22.556-50.771c-11.547-20.004-39.486-23.251-55.242-5.844
l-41.746-24.106C189.618,22.603,172.861,0,149.734,0c-23.132,0-39.881,22.609-32.685,44.911L75.305,69.016
C59.522,51.586,31.597,54.88,20.061,74.863c-11.63,20.163-0.298,45.862,22.557,50.769v48.2
c-22.821,4.898-34.195,30.591-22.556,50.771c11.529,19.972,39.454,23.285,55.242,5.845l41.746,24.106
c-7.199,22.308,9.559,44.911,32.685,44.911c23.132,0,39.88-22.609,32.685-44.911l41.745-24.106
c15.817,17.469,43.73,14.099,55.242-5.844c0,0,0-0.001,0.001-0.002c4.587-7.953,5.805-17.213,3.431-26.076
C279.392,185.657,269.129,176.461,256.851,173.832z M249.62,72.088c20.568,0,27.428,27.191,10.008,37.239
c-0.003,0.002-0.006,0.003-0.009,0.005c-10.04,5.81-22.85,1.762-27.877-8.475C225.206,87.548,234.938,72.088,249.62,72.088z
M149.734,14.4c11.005,0,19.958,8.954,19.958,19.959c0,11.127-9.077,19.958-19.958,19.958c-10.95,0-19.958-8.9-19.958-19.958
C129.776,23.354,138.729,14.4,149.734,14.4z M39.84,109.328c-17.451-10.067-10.534-37.24,10.01-37.24
c15.311,0,24.922,16.653,17.251,29.942C61.681,111.397,49.517,114.925,39.84,109.328z M59.802,224.702
c-9.535,5.503-21.768,2.229-27.268-7.298c-7.639-13.242,1.887-29.945,17.236-29.945c0.013,0,0.027,0,0.04,0
C70.07,187.48,77.49,214.469,59.802,224.702z M149.734,285.062c-11.005,0-19.958-8.954-19.958-19.958
c0-11.127,9.077-19.958,19.958-19.958c10.954,0,19.958,8.903,19.958,19.958C169.693,276.109,160.74,285.062,149.734,285.062z
M216.953,217.982l-41.727,24.095c-13.778-15.22-37.459-14.94-50.983,0l-41.728-24.096c6.196-19.289-5.541-39.835-25.498-44.149
V125.63c19.752-4.268,31.762-24.65,25.498-44.149l41.727-24.095c13.629,15.055,37.32,15.093,50.983,0l41.728,24.096
c-6.196,19.29,5.534,39.835,25.498,44.149v48.202C222.61,178.123,210.721,198.581,216.953,217.982z M266.935,217.404
c-5.501,9.528-17.732,12.802-27.261,7.302c-17.682-10.23-10.301-37.247,10.032-37.247
C264.984,187.459,274.602,204.112,266.935,217.404z"/>
<path d="M256.851,173.832v-48.201c22.916-4.918,34.151-30.668,22.556-50.771c-11.547-20.004-39.486-23.251-55.242-5.844 l-41.746-24.106C189.618,22.603,172.861,0,149.734,0c-23.132,0-39.881,22.609-32.685,44.911L75.305,69.016 C59.522,51.586,31.597,54.88,20.061,74.863c-11.63,20.163-0.298,45.862,22.557,50.769v48.2 c-22.821,4.898-34.195,30.591-22.556,50.771c11.529,19.972,39.454,23.285,55.242,5.845l41.746,24.106 c-7.199,22.308,9.559,44.911,32.685,44.911c23.132,0,39.88-22.609,32.685-44.911l41.745-24.106 c15.817,17.469,43.73,14.099,55.242-5.844c0,0,0-0.001,0.001-0.002c4.587-7.953,5.805-17.213,3.431-26.076 C279.392,185.657,269.129,176.461,256.851,173.832z M249.62,72.088c20.568,0,27.428,27.191,10.008,37.239 c-0.003,0.002-0.006,0.003-0.009,0.005c-10.04,5.81-22.85,1.762-27.877-8.475C225.206,87.548,234.938,72.088,249.62,72.088z M149.734,14.4c11.005,0,19.958,8.954,19.958,19.959c0,11.127-9.077,19.958-19.958,19.958c-10.95,0-19.958-8.9-19.958-19.958 C129.776,23.354,138.729,14.4,149.734,14.4z M39.84,109.328c-17.451-10.067-10.534-37.24,10.01-37.24 c15.311,0,24.922,16.653,17.251,29.942C61.681,111.397,49.517,114.925,39.84,109.328z M59.802,224.702 c-9.535,5.503-21.768,2.229-27.268-7.298c-7.639-13.242,1.887-29.945,17.236-29.945c0.013,0,0.027,0,0.04,0 C70.07,187.48,77.49,214.469,59.802,224.702z M149.734,285.062c-11.005,0-19.958-8.954-19.958-19.958 c0-11.127,9.077-19.958,19.958-19.958c10.954,0,19.958,8.903,19.958,19.958C169.693,276.109,160.74,285.062,149.734,285.062z M216.953,217.982l-41.727,24.095c-13.778-15.22-37.459-14.94-50.983,0l-41.728-24.096c6.196-19.289-5.541-39.835-25.498-44.149 V125.63c19.752-4.268,31.762-24.65,25.498-44.149l41.727-24.095c13.629,15.055,37.32,15.093,50.983,0l41.728,24.096 c-6.196,19.29,5.534,39.835,25.498,44.149v48.202C222.61,178.123,210.721,198.581,216.953,217.982z M266.935,217.404 c-5.501,9.528-17.732,12.802-27.261,7.302c-17.682-10.23-10.301-37.247,10.032-37.247 C264.984,187.459,274.602,204.112,266.935,217.404z"/>
</g>
</svg>;
const _ruler = <svg viewBox="0 0 508.073 508.073" width="17px" height="17px">
<g>
<path d="M470.459,378.925c-0.7-2.1-1.9-4-3.4-5.5l-113.9-113.9l149.8-149.8c10-10,2.6-17.3,0-19.9l-85.7-85.7
c-5.5-5.5-14.4-5.5-19.9,0l-149.8,149.8l-134.7-134.7c-25.5-25.5-67.8-25.6-93.4,0c-25.8,25.8-25.8,67.7,0,93.5l134.6,134.7
l-149.9,149.9c-5.5,5.5-5.5,14.4,0,19.9l85.6,85.7c2.6,2.6,10,10,19.9,0l150-149.9l113.9,113.9c1.5,1.5,3.4,2.7,5.5,3.4
l110.4,36.9c6,2,10.7,0.3,14.4-3.4c3.8-3.8,5.1-9.4,3.4-14.4L470.459,378.925z M276.159,165.225l29.9,29.9
c5.5,5.5,14.4,5.5,19.9,0c5-5,5.5-14.4,0-19.9l-29.9-29.9l23.7-23.7l13,13c5.5,5.5,14.4,5.5,19.9,0c5.2-5.2,6.2-13.7,0-19.9
l-13-13l23.7-23.7l29.4,29.4c5.5,5.5,14.4,5.5,19.9,0c5.6-5.6,5.5-14.4,0-19.9l-29.4-29.5l24-24l65.8,65.7l-139.8,139.9
l-65.8-65.8L276.159,165.225z M39.359,92.825c-14.8-14.8-14.8-38.8,0-53.6c14.1-14.1,38.8-14.7,53.6,0l15.5,15.5l-53.6,53.6
L39.359,92.825z M99.759,473.025l-65.7-65.7l24-24l13.2,13.2c5.5,5.5,14.4,5.6,19.9,0c5.5-5.5,5.5-14.4,0-19.9l-13.1-13.3
l23.7-23.7l29.6,29.6c6.2,6.2,14.7,5.2,19.9,0c5.5-5.5,5.5-14.4,0-19.9l-29.6-29.6l23.7-23.7l13.2,13.2c5.5,5.5,14.9,5,19.9,0
c5.5-5.5,5.5-14.4,0-19.9l-13.2-13.2l8.8-8.8l65.8,65.7L99.759,473.025z M74.759,128.225l53.6-53.6l308.7,308.7l-53.6,53.6
L74.759,128.225z M409.659,450.725l41.3-41.3l20.7,61.9L409.659,450.725z"/>
<path d="M470.459,378.925c-0.7-2.1-1.9-4-3.4-5.5l-113.9-113.9l149.8-149.8c10-10,2.6-17.3,0-19.9l-85.7-85.7 c-5.5-5.5-14.4-5.5-19.9,0l-149.8,149.8l-134.7-134.7c-25.5-25.5-67.8-25.6-93.4,0c-25.8,25.8-25.8,67.7,0,93.5l134.6,134.7 l-149.9,149.9c-5.5,5.5-5.5,14.4,0,19.9l85.6,85.7c2.6,2.6,10,10,19.9,0l150-149.9l113.9,113.9c1.5,1.5,3.4,2.7,5.5,3.4 l110.4,36.9c6,2,10.7,0.3,14.4-3.4c3.8-3.8,5.1-9.4,3.4-14.4L470.459,378.925z M276.159,165.225l29.9,29.9 c5.5,5.5,14.4,5.5,19.9,0c5-5,5.5-14.4,0-19.9l-29.9-29.9l23.7-23.7l13,13c5.5,5.5,14.4,5.5,19.9,0c5.2-5.2,6.2-13.7,0-19.9 l-13-13l23.7-23.7l29.4,29.4c5.5,5.5,14.4,5.5,19.9,0c5.6-5.6,5.5-14.4,0-19.9l-29.4-29.5l24-24l65.8,65.7l-139.8,139.9 l-65.8-65.8L276.159,165.225z M39.359,92.825c-14.8-14.8-14.8-38.8,0-53.6c14.1-14.1,38.8-14.7,53.6,0l15.5,15.5l-53.6,53.6 L39.359,92.825z M99.759,473.025l-65.7-65.7l24-24l13.2,13.2c5.5,5.5,14.4,5.6,19.9,0c5.5-5.5,5.5-14.4,0-19.9l-13.1-13.3 l23.7-23.7l29.6,29.6c6.2,6.2,14.7,5.2,19.9,0c5.5-5.5,5.5-14.4,0-19.9l-29.6-29.6l23.7-23.7l13.2,13.2c5.5,5.5,14.9,5,19.9,0 c5.5-5.5,5.5-14.4,0-19.9l-13.2-13.2l8.8-8.8l65.8,65.7L99.759,473.025z M74.759,128.225l53.6-53.6l308.7,308.7l-53.6,53.6 L74.759,128.225z M409.659,450.725l41.3-41.3l20.7,61.9L409.659,450.725z"/>
</g>
</svg>;
const _cube = <svg viewBox="0 0 270.06 270.06" width="17px" height="17px">
<g>
<path d="M264.898,0.007
c-0.181,0.006-0.362,0.023-0.541,0.049H84.996c-1.326,0-2.598,0.527-3.535,1.465l-80,80C0.525,82.459-0.001,83.73,0,85.056v180
c0,2.761,2.239,5,5,5h180c1.326,0,2.598-0.527,3.535-1.465l80-80c0.938-0.938,1.465-2.209,1.465-3.535V5.819
c0.14-0.893,0.035-1.807-0.303-2.645c-0.016-0.045-0.033-0.09-0.051-0.135c-0.808-1.89-2.69-3.093-4.744-3.033L264.898,0.007z
M87.066,10.056h165.859l-70,70H17.066L87.066,10.056z M259.996,17.126v165.859l-70,70V87.126L259.996,17.126L259.996,17.126z
M84.92,19.985c-2.759,0.042-4.963,2.311-4.924,5.07v40c-0.039,2.761,2.168,5.032,4.929,5.071s5.032-2.168,5.071-4.929
c0.001-0.047,0.001-0.094,0-0.141v-40c0.039-2.761-2.168-5.031-4.93-5.07C85.018,19.984,84.969,19.984,84.92,19.985z M9.996,90.056
h170v170h-170V90.056L9.996,90.056z M84.92,99.985c-2.759,0.042-4.963,2.311-4.924,5.07v30c-0.039,2.761,2.168,5.032,4.929,5.071
s5.032-2.168,5.071-4.929c0.001-0.047,0.001-0.094,0-0.141v-30c0.039-2.761-2.168-5.031-4.93-5.07
C85.018,99.984,84.969,99.984,84.92,99.985z M84.92,159.985c-2.759,0.042-4.963,2.311-4.924,5.07v17.93L61.461,201.52
c-1.992,1.913-2.057,5.078-0.144,7.07c1.913,1.992,5.078,2.057,7.07,0.144c0.049-0.047,0.097-0.095,0.144-0.144l18.535-18.535h17.93
c2.761,0.039,5.032-2.168,5.071-4.929s-2.168-5.032-4.929-5.071c-0.047-0.001-0.094-0.001-0.141,0h-15v-15
c0.039-2.761-2.168-5.031-4.93-5.07C85.018,159.984,84.969,159.984,84.92,159.985z M134.996,180.055
c-2.761-0.039-5.032,2.168-5.071,4.929c-0.039,2.761,2.168,5.032,4.929,5.071c0.047,0.001,0.094,0.001,0.141,0h30
c2.761,0.039,5.032-2.168,5.071-4.929c0.039-2.761-2.168-5.032-4.929-5.071c-0.047-0.001-0.094-0.001-0.141,0H134.996z
M204.996,180.055c-2.761-0.039-5.032,2.168-5.071,4.929c-0.039,2.761,2.168,5.032,4.929,5.071c0.047,0.001,0.094,0.001,0.141,0h40
c2.761,0.039,5.032-2.168,5.071-4.929s-2.168-5.032-4.929-5.071c-0.047-0.001-0.094-0.001-0.141,0H204.996z M44.898,220.007
c-1.299,0.039-2.532,0.582-3.438,1.514l-20,20c-1.992,1.913-2.057,5.078-0.144,7.07c1.913,1.992,5.078,2.057,7.07,0.144
c0.049-0.047,0.097-0.095,0.144-0.144l20-20c1.98-1.925,2.025-5.091,0.1-7.071C47.654,220.514,46.3,219.965,44.898,220.007z"/>
<path d="M264.898,0.007 c-0.181,0.006-0.362,0.023-0.541,0.049H84.996c-1.326,0-2.598,0.527-3.535,1.465l-80,80C0.525,82.459-0.001,83.73,0,85.056v180 c0,2.761,2.239,5,5,5h180c1.326,0,2.598-0.527,3.535-1.465l80-80c0.938-0.938,1.465-2.209,1.465-3.535V5.819 c0.14-0.893,0.035-1.807-0.303-2.645c-0.016-0.045-0.033-0.09-0.051-0.135c-0.808-1.89-2.69-3.093-4.744-3.033L264.898,0.007z M87.066,10.056h165.859l-70,70H17.066L87.066,10.056z M259.996,17.126v165.859l-70,70V87.126L259.996,17.126L259.996,17.126z M84.92,19.985c-2.759,0.042-4.963,2.311-4.924,5.07v40c-0.039,2.761,2.168,5.032,4.929,5.071s5.032-2.168,5.071-4.929 c0.001-0.047,0.001-0.094,0-0.141v-40c0.039-2.761-2.168-5.031-4.93-5.07C85.018,19.984,84.969,19.984,84.92,19.985z M9.996,90.056 h170v170h-170V90.056L9.996,90.056z M84.92,99.985c-2.759,0.042-4.963,2.311-4.924,5.07v30c-0.039,2.761,2.168,5.032,4.929,5.071 s5.032-2.168,5.071-4.929c0.001-0.047,0.001-0.094,0-0.141v-30c0.039-2.761-2.168-5.031-4.93-5.07 C85.018,99.984,84.969,99.984,84.92,99.985z M84.92,159.985c-2.759,0.042-4.963,2.311-4.924,5.07v17.93L61.461,201.52 c-1.992,1.913-2.057,5.078-0.144,7.07c1.913,1.992,5.078,2.057,7.07,0.144c0.049-0.047,0.097-0.095,0.144-0.144l18.535-18.535h17.93 c2.761,0.039,5.032-2.168,5.071-4.929s-2.168-5.032-4.929-5.071c-0.047-0.001-0.094-0.001-0.141,0h-15v-15 c0.039-2.761-2.168-5.031-4.93-5.07C85.018,159.984,84.969,159.984,84.92,159.985z M134.996,180.055 c-2.761-0.039-5.032,2.168-5.071,4.929c-0.039,2.761,2.168,5.032,4.929,5.071c0.047,0.001,0.094,0.001,0.141,0h30 c2.761,0.039,5.032-2.168,5.071-4.929c0.039-2.761-2.168-5.032-4.929-5.071c-0.047-0.001-0.094-0.001-0.141,0H134.996z M204.996,180.055c-2.761-0.039-5.032,2.168-5.071,4.929c-0.039,2.761,2.168,5.032,4.929,5.071c0.047,0.001,0.094,0.001,0.141,0h40 c2.761,0.039,5.032-2.168,5.071-4.929s-2.168-5.032-4.929-5.071c-0.047-0.001-0.094-0.001-0.141,0H204.996z M44.898,220.007 c-1.299,0.039-2.532,0.582-3.438,1.514l-20,20c-1.992,1.913-2.057,5.078-0.144,7.07c1.913,1.992,5.078,2.057,7.07,0.144 c0.049-0.047,0.097-0.095,0.144-0.144l20-20c1.98-1.925,2.025-5.091,0.1-7.071C47.654,220.514,46.3,219.965,44.898,220.007z"/>
</g>
</svg>;
// Icons below are adapted from https://materialdesignicons.com/ and
// licensed with https://github.com/Templarian/MaterialDesign/blob/master/LICENSE
const _cursor = <svg width="24px" height="24px" viewBox="0 0 24 24">
<path d="M10.07,14.27C10.57,14.03 11.16,14.25 11.4,14.75L13.7,19.74L15.5,18.89L13.19,13.91C12.95,13.41 13.17,12.81 13.67,12.58L13.95,12.5L16.25,12.05L8,5.12V15.9L9.82,14.43L10.07,14.27M13.64,21.97C13.14,22.21 12.54,22 12.31,21.5L10.13,16.76L7.62,18.78C7.45,18.92 7.24,19 7,19A1,1 0 0,1 6,18V3A1,1 0 0,1 7,2C7.24,2 7.47,2.09 7.64,2.23L7.65,2.22L19.14,11.86C19.57,12.22 19.62,12.85 19.27,13.27C19.12,13.45 18.91,13.57 18.7,13.61L15.54,14.23L17.74,18.96C18,19.46 17.76,20.05 17.26,20.28L13.64,21.97Z" />
</svg>;

View File

@@ -24,7 +24,7 @@ import { ParamDefinition } from '../../mol-util/param-definition';
import { CollapsableControls, CollapsableState, PurePluginUIComponent } from '../base';
import { ActionMenu } from '../controls/action-menu';
import { Button, ExpandGroup, IconButton, ToggleButton } from '../controls/common';
import { CubeSvg, Intersect, SetSvg, Subtract, Union } from '../controls/icons';
import { CubeSvg, IntersectSvg, SetSvg, SubtractSvg, UnionSvg } from '../controls/icons';
import { ParameterControls } from '../controls/parameters';
import { UpdateTransformControl } from '../state/update-transform';
import { GenericEntryListControls } from './generic';
@@ -267,9 +267,9 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo
if (mng.canBeModified(this.props.group[0])) {
ret.push([
ActionMenu.Header('Modify by Selection'),
ActionMenu.Item('Include', () => mng.modifyByCurrentSelection(this.props.group, 'union'), { icon: Union }),
ActionMenu.Item('Subtract', () => mng.modifyByCurrentSelection(this.props.group, 'subtract'), { icon: Subtract }),
ActionMenu.Item('Intersect', () => mng.modifyByCurrentSelection(this.props.group, 'intersect'), { icon: Intersect })
ActionMenu.Item('Include', () => mng.modifyByCurrentSelection(this.props.group, 'union'), { icon: UnionSvg }),
ActionMenu.Item('Subtract', () => mng.modifyByCurrentSelection(this.props.group, 'subtract'), { icon: SubtractSvg }),
ActionMenu.Item('Intersect', () => mng.modifyByCurrentSelection(this.props.group, 'intersect'), { icon: IntersectSvg })
]);
}

View File

@@ -172,7 +172,7 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo
{entries}
</div>}
{entries.length === 0 && <div className='msp-control-offset msp-help-text'>
<div className='msp-help-description' style={{ padding: 0 }}><Icon svg={HelpOutline} inline />Add one or more selections <ToggleSelectionModeButton /></div>
<div className='msp-help-description'><Icon svg={HelpOutline} inline />Add one or more selections (toggle <ToggleSelectionModeButton inline /> mode)</div>
</div>}
</>;
}

View File

@@ -10,7 +10,6 @@ import CancelOutlined from '@material-ui/icons/CancelOutlined';
import Brush from '@material-ui/icons/Brush';
import Restore from '@material-ui/icons/Restore';
import Remove from '@material-ui/icons/Remove';
import SelectAll from '@material-ui/icons/SelectAll';
import HelpOutline from '@material-ui/icons/HelpOutline';
import * as React from 'react';
import { StructureSelectionQueries, StructureSelectionQuery, getNonStandardResidueQueries, getElementQueries, getPolymerAndBranchedEntityQueries } from '../../mol-plugin-state/helpers/structure-selection-query';
@@ -25,13 +24,13 @@ import { PluginUIComponent, PurePluginUIComponent } from '../base';
import { ActionMenu } from '../controls/action-menu';
import { Button, ControlGroup, IconButton, ToggleButton } from '../controls/common';
import { ParameterControls, ParamOnChange, PureSelectControl } from '../controls/parameters';
import { Union, Subtract, Intersect, SetSvg as SetSvg, CubeSvg, Icon } from '../controls/icons';
import { UnionSvg, SubtractSvg, IntersectSvg, SetSvg, CubeSvg, Icon, CursorSvg } from '../controls/icons';
import { AddComponentControls } from './components';
import Structure from '../../mol-model/structure/structure/structure';
import { ViewportHelpContent, HelpGroup, HelpText } from '../viewport/help';
export class ToggleSelectionModeButton extends PurePluginUIComponent {
export class ToggleSelectionModeButton extends PurePluginUIComponent<{ inline?: boolean }> {
componentDidMount() {
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
@@ -43,7 +42,10 @@ export class ToggleSelectionModeButton extends PurePluginUIComponent {
}
render() {
return <IconButton svg={SelectAll} onClick={this._toggleSelMode} title={'Toggle Selection Mode'} style={{ background: 'transparent' }} toggleState={this.plugin.selectionMode} />;
const style = this.props.inline
? { background: 'transparent', width: 'auto', height: 'auto', lineHeight: 'unset' }
: { background: 'transparent' };
return <IconButton svg={CursorSvg} onClick={this._toggleSelMode} title={'Toggle Selection Mode'} style={style} toggleState={this.plugin.selectionMode} />;
}
}
@@ -195,9 +197,9 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
return <>
<div className='msp-flex-row' style={{ background: 'none' }}>
<PureSelectControl title={`Picking Level for selecting and highlighting`} param={StructureSelectionParams.granularity} name='granularity' value={granularity} onChange={this.setGranuality} isDisabled={this.isDisabled} />
<ToggleButton icon={Union} title={`${ActionHeader.get('add')}. Hold shift key to keep menu open.`} toggle={this.toggleAdd} isSelected={this.state.action === 'add'} disabled={this.isDisabled} />
<ToggleButton icon={Subtract} title={`${ActionHeader.get('remove')}. Hold shift key to keep menu open.`} toggle={this.toggleRemove} isSelected={this.state.action === 'remove'} disabled={this.isDisabled} />
<ToggleButton icon={Intersect} title={`${ActionHeader.get('intersect')}. Hold shift key to keep menu open.`} toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} />
<ToggleButton icon={UnionSvg} title={`${ActionHeader.get('add')}. Hold shift key to keep menu open.`} toggle={this.toggleAdd} isSelected={this.state.action === 'add'} disabled={this.isDisabled} />
<ToggleButton icon={SubtractSvg} title={`${ActionHeader.get('remove')}. Hold shift key to keep menu open.`} toggle={this.toggleRemove} isSelected={this.state.action === 'remove'} disabled={this.isDisabled} />
<ToggleButton icon={IntersectSvg} title={`${ActionHeader.get('intersect')}. Hold shift key to keep menu open.`} toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} />
<ToggleButton icon={SetSvg} title={`${ActionHeader.get('set')}. Hold shift key to keep menu open.`} toggle={this.toggleSet} isSelected={this.state.action === 'set'} disabled={this.isDisabled} />
<ToggleButton icon={Brush} title='Color Selection' toggle={this.toggleColor} isSelected={this.state.action === 'color'} disabled={this.isDisabled} style={{ marginLeft: '10px' }} />
@@ -224,7 +226,7 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
{this.state.action === 'help' && <div className='msp-selection-viewport-controls-actions'>
<ControlGroup header='Help' title='Click to close.' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleHelp} topRightIcon={Close} maxHeight='300px'>
<HelpGroup header='Selection Operations'>
<HelpText>Use <Icon svg={Union} inline /> <Icon svg={Subtract} inline /> <Icon svg={Intersect} inline /> <Icon svg={SetSvg} inline /> to modify the selection.</HelpText>
<HelpText>Use <Icon svg={UnionSvg} inline /> <Icon svg={SubtractSvg} inline /> <Icon svg={IntersectSvg} inline /> <Icon svg={SetSvg} inline /> to modify the selection.</HelpText>
</HelpGroup>
<HelpGroup header='Representation Operations'>
<HelpText>Use <Icon svg={Brush} inline /> <Icon svg={CubeSvg} inline /> <Icon svg={Remove} inline /> <Icon svg={Restore} inline /> to color, create selection/representation, subtract it, or undo actions.</HelpText>

View File

@@ -16,7 +16,7 @@ import Tune from '@material-ui/icons/Tune';
import { CollapsableControls, PurePluginUIComponent } from '../base';
import { Icon } from '../controls/icons';
import { Button, ToggleButton, IconButton } from '../controls/common';
import { StructureElement, StructureSelection, QueryContext, Structure } from '../../mol-model/structure';
import { StructureElement, StructureSelection, QueryContext, Structure, StructureProperties } from '../../mol-model/structure';
import { Mat4 } from '../../mol-math/linear-algebra';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { StateObjectRef, StateObjectCell, StateSelection } from '../../mol-state';
@@ -182,8 +182,7 @@ export class SuperpositionControls extends PurePluginUIComponent<{}, Superpositi
}
moveHistory(e: StructureSelectionHistoryEntry, direction: 'up' | 'down') {
// TODO take structure grouping into account
this.plugin.managers.structure.selection.modifyHistory(e, direction);
this.plugin.managers.structure.selection.modifyHistory(e, direction, void 0, true);
}
focusLoci(loci: StructureElement.Loci) {
@@ -222,18 +221,19 @@ export class SuperpositionControls extends PurePluginUIComponent<{}, Superpositi
}
get chainEntries() {
const location = StructureElement.Location.create();
const entries: LociEntry[] = [];
this.plugin.managers.structure.selection.entries.forEach(({ selection }, ref) => {
const cell = StateObjectRef.resolveAndCheck(this.plugin.state.data, ref);
if (!cell || StructureElement.Loci.isEmpty(selection)) return;
// only single chain selections
// TODO wrongly assumes unit is equal to chain
if (selection.elements.length > 1) return;
// only single polymer chain selections
const l = StructureElement.Loci.getFirstLocation(selection, location)!;
if (selection.elements.length > 1 || StructureProperties.entity.type(l) !== 'polymer') return;
const stats = StructureElement.Stats.ofLoci(selection);
const counts = structureElementStatsLabel(stats, { countsOnly: true });
const chain = elementLabel(stats.firstElementLoc, { reverse: true, granularity: 'chain' }).split('|');
const chain = elementLabel(l, { reverse: true, granularity: 'chain' }).split('|');
const label = `${counts} | ${chain[0]} | ${chain[chain.length - 1]}`;
entries.push({ loci: selection, label, cell });
});
@@ -241,7 +241,6 @@ export class SuperpositionControls extends PurePluginUIComponent<{}, Superpositi
}
get atomEntries() {
// TODO have stable order of structureEntries, independent of history order
const structureEntries = new Map<Structure, StructureSelectionHistoryEntry[]>();
const history = this.plugin.managers.structure.selection.additionsHistory;
@@ -279,7 +278,7 @@ export class SuperpositionControls extends PurePluginUIComponent<{}, Superpositi
{entries.map((e, i) => this.lociEntry(e, i))}
</div>}
{entries.length < 2 && <div className='msp-control-offset msp-help-text'>
<div className='msp-help-description'><Icon svg={HelpOutline} inline />Add 2 or more selections <ToggleSelectionModeButton /> from separate structures. Selections must be limited to single chains or parts of single chains.</div>
<div className='msp-help-description'><Icon svg={HelpOutline} inline />Add 2 or more selections (toggle <ToggleSelectionModeButton inline /> mode) from separate structures. Selections must be limited to single polymer chains or residues therein.</div>
</div>}
{entries.length > 1 && <Button title='Superpose structures by selected chains.' className='msp-btn-commit msp-btn-commit-on' onClick={this.superposeChains} style={{ marginTop: '1px' }}>
Superpose
@@ -294,7 +293,7 @@ export class SuperpositionControls extends PurePluginUIComponent<{}, Superpositi
{entries.map((e, i) => this.atomsLociEntry(e, i))}
</div>}
{entries.length < 2 && <div className='msp-control-offset msp-help-text'>
<div className='msp-help-description'><Icon svg={HelpOutline} inline />Add 1 or more selections <ToggleSelectionModeButton /> from
<div className='msp-help-description'><Icon svg={HelpOutline} inline />Add 1 or more selections (toggle <ToggleSelectionModeButton inline /> mode) from
separate structures. Selections must be limited to single atoms.</div>
</div>}
{entries.length > 1 && <Button title='Superpose structures by selected atoms.' className='msp-btn-commit msp-btn-commit-on' onClick={this.superposeAtoms} style={{ marginTop: '1px' }}>

View File

@@ -137,6 +137,11 @@ export class PluginContext {
return this.state.data.build();
}
readonly helpers = {
substructureParent: new SubstructureParentHelper(this),
viewportScreenshot: void 0 as ViewportScreenshotHelper | undefined
} as const;
readonly managers = {
structure: {
hierarchy: new StructureHierarchyManager(this),
@@ -164,11 +169,6 @@ export class PluginContext {
readonly customStructureControls = new Map<string, { new(): PluginUIComponent<any, any, any> }>();
readonly genericRepresentationControls = new Map<string, (selection: StructureHierarchyManager['selection']) => [StructureHierarchyRef[], string]>();
readonly helpers = {
substructureParent: new SubstructureParentHelper(this),
viewportScreenshot: void 0 as ViewportScreenshotHelper | undefined
} as const;
/**
* Used to store application specific custom state which is then available
* to State Actions and similar constructs via the PluginContext.
@@ -243,6 +243,7 @@ export class PluginContext {
this.state.dispose();
this.tasks.dispose();
this.layout.dispose();
this.helpers.substructureParent.dispose();
objectForEach(this.managers, m => (m as any)?.dispose?.());
objectForEach(this.managers.structure, m => (m as any)?.dispose?.());

View File

@@ -8,10 +8,18 @@ import { Structure } from '../../mol-model/structure';
import { PluginStateObject } from '../../mol-plugin-state/objects';
import { State, StateObject, StateObjectCell, StateSelection } from '../../mol-state';
import { PluginContext } from '../context';
import { RxEventHelper } from '../../mol-util/rx-event-helper';
export { SubstructureParentHelper };
class SubstructureParentHelper {
private ev = RxEventHelper.create();
readonly events = {
updated: this.ev<{ ref: string, oldObj: PluginStateObject.Molecule.Structure | undefined, obj: PluginStateObject.Molecule.Structure }>(),
removed: this.ev<{ ref: string, obj: PluginStateObject.Molecule.Structure | undefined }>(),
}
// private decorators = new Map<string, string[]>();
private root = new Map<Structure, { ref: string, count: number }>();
private tracked = new Map<string, Structure>();
@@ -36,7 +44,7 @@ class SubstructureParentHelper {
}
private addMapping(state: State, ref: string, obj: StateObject) {
if (!PluginStateObject.Molecule.Structure.is(obj)) return;
if (!PluginStateObject.Molecule.Structure.is(obj)) return false;
this.tracked.set(ref, obj.data);
@@ -53,10 +61,11 @@ class SubstructureParentHelper {
this.root.set(obj.data, { ref: parent.transform.ref, count: 1 });
}
}
return true;
}
private removeMapping(ref: string) {
if (!this.tracked.has(ref)) return;
if (!this.tracked.has(ref)) return false;
const s = this.tracked.get(ref)!;
this.tracked.delete(ref);
@@ -68,13 +77,19 @@ class SubstructureParentHelper {
} else {
this.root.delete(s);
}
return true;
}
private updateMapping(state: State, ref: string, oldObj: StateObject | undefined, obj: StateObject) {
if (!PluginStateObject.Molecule.Structure.is(obj)) return;
if (!PluginStateObject.Molecule.Structure.is(obj)) return false;
this.removeMapping(ref);
this.addMapping(state, ref, obj);
return true;
}
dispose() {
this.ev.dispose();
}
constructor(private plugin: PluginContext) {
@@ -83,11 +98,15 @@ class SubstructureParentHelper {
});
plugin.state.data.events.object.removed.subscribe(e => {
this.removeMapping(e.ref);
if (this.removeMapping(e.ref)) {
this.events.removed.next({ ref: e.ref, obj: e.obj });
}
});
plugin.state.data.events.object.updated.subscribe(e => {
this.updateMapping(e.state, e.ref, e.oldObj, e.obj);
if (this.updateMapping(e.state, e.ref, e.oldObj, e.obj)) {
this.events.updated.next({ ref: e.ref, oldObj: e.oldObj, obj: e.obj });
}
});
}
}

View File

@@ -46,7 +46,7 @@ interface ComplexVisualBuilder<P extends StructureParams, G extends Geometry> {
createGeometry(ctx: VisualContext, structure: Structure, theme: Theme, props: PD.Values<P>, geometry?: G): Promise<G> | G
createLocationIterator(structure: Structure): LocationIterator
getLoci(pickingId: PickingId, structure: Structure, id: number): Loci
eachLocation(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean): boolean,
eachLocation(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean, isMarking: boolean): boolean,
setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure): void
}
@@ -173,11 +173,11 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
return false;
}
function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
function lociApply(loci: Loci, apply: (interval: Interval) => boolean, isMarking: boolean) {
if (lociIsSuperset(loci)) {
return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount));
} else {
return eachLocation(loci, currentStructure, apply);
return eachLocation(loci, currentStructure, apply, isMarking);
}
}

View File

@@ -51,7 +51,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
if (!_structure && !structure) {
throw new Error('missing structure');
} else if (structure && !_structure) {
// console.log(label, 'initial structure')
// console.log(label, 'initial structure');
// First call with a structure, create visuals for each group.
_groups = structure.unitSymmetryGroups;
for (let i = 0; i < _groups.length; i++) {
@@ -75,7 +75,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
const group = _groups[i];
const visualGroup = oldVisuals.get(group.hashCode);
if (visualGroup) {
// console.log(label, 'found visualGroup to reuse')
// console.log(label, 'found visualGroup to reuse');
// console.log('old', visualGroup.group)
// console.log('new', group)
const { visual } = visualGroup;
@@ -91,7 +91,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
applyMarkerAction(arr, Interval.ofBounds(0, arr.length), MarkerAction.RemoveHighlight);
}
} else {
// console.log(label, 'did not find visualGroup to reuse, creating new')
// console.log(label, 'did not find visualGroup to reuse, creating new');
// newGroups.push(group)
const visual = visualCtor(materialId);
const promise = visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, { group, structure });
@@ -118,7 +118,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
// })
// unusedVisuals.forEach(visual => visual.destroy())
} else if (structure && structure !== _structure && Structure.areEquivalent(structure, _structure)) {
// console.log(label, 'structures equivalent but not identical')
// console.log(label, 'structures equivalent but not identical');
// Expects that for structures with the same hashCode,
// the unitSymmetryGroups are the same as well.
// Re-uses existing visuals for the groups of the new structure.
@@ -132,14 +132,13 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
const promise = visualGroup.visual.createOrUpdate({ webgl: ctx.webgl, runtime }, _theme, _props, { group, structure });
if (promise) await promise;
visualGroup.group = group;
} else {
throw new Error(`expected to find visual for hashCode ${group.hashCode}`);
}
if (runtime.shouldUpdate) await runtime.update({ message: 'Creating or updating UnitsVisual', current: i, max: _groups.length });
}
} else {
// console.log(label, 'no new structure')
// console.log(label, 'no new structure');
// No new structure given, just update all visuals with new props.
const visualsList: [ UnitsVisual<P>, Unit.SymmetryGroup ][] = []; // TODO avoid allocation
visuals.forEach(({ visual, group }) => visualsList.push([ visual, group ]));

View File

@@ -54,7 +54,7 @@ interface UnitsVisualBuilder<P extends StructureParams, G extends Geometry> {
createGeometry(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: PD.Values<P>, geometry?: G): Promise<G> | G
createLocationIterator(structureGroup: StructureGroup): LocationIterator
getLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number): Loci
eachLocation(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean): boolean
eachLocation(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean, isMarking: boolean): boolean
setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructureGroup: StructureGroup, currentStructureGroup: StructureGroup): void
}
@@ -224,11 +224,11 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
return false;
}
function lociApply(loci: Loci, apply: (interval: Interval) => boolean) {
function lociApply(loci: Loci, apply: (interval: Interval) => boolean, isMarking: boolean) {
if (lociIsSuperset(loci)) {
return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount));
} else {
return eachLocation(loci, currentStructureGroup, apply);
return eachLocation(loci, currentStructureGroup, apply, isMarking);
}
}

View File

@@ -155,7 +155,7 @@ function getBondLoci(pickingId: PickingId, structure: Structure, id: number) {
return EmptyLoci;
}
function eachBond(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) {
function eachBond(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean, isMarking: boolean) {
let changed = false;
if (Bond.isLoci(loci)) {
if (!Structure.areEquivalent(loci.structure, structure)) return false;
@@ -181,7 +181,7 @@ function eachBond(loci: Loci, structure: Structure, apply: (interval: Interval)
OrderedSet.forEach(e.indices, v => {
if (!b.connectedIndices.includes(v)) return;
b.getEdges(v).forEach(bi => {
if (OrderedSet.has(otherLociIndices, bi.indexB)) {
if (!isMarking || OrderedSet.has(otherLociIndices, bi.indexB)) {
const idx = structure.interUnitBonds.getEdgeIndex(v, unit, bi.indexB, b.unitB);
if (apply(Interval.ofSingleton(idx))) changed = true;
}

View File

@@ -154,7 +154,7 @@ function getBondLoci(pickingId: PickingId, structureGroup: StructureGroup, id: n
return EmptyLoci;
}
function eachBond(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
function eachBond(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean, isMarking: boolean) {
let changed = false;
if (Bond.isLoci(loci)) {
const { structure, group } = structureGroup;
@@ -183,7 +183,7 @@ function eachBond(loci: Loci, structureGroup: StructureGroup, apply: (interval:
const { offset, b } = unit.bonds;
OrderedSet.forEach(e.indices, v => {
for (let t = offset[v], _t = offset[v + 1]; t < _t; t++) {
if (OrderedSet.has(e.indices, b[t])) {
if (!isMarking || OrderedSet.has(e.indices, b[t])) {
if (apply(Interval.ofSingleton(unitIdx * groupCount + t))) changed = true;
}
}

View File

@@ -45,7 +45,7 @@ interface Visual<D, P extends PD.Params> {
destroy: () => void
}
namespace Visual {
export type LociApply = (loci: Loci, apply: (interval: Interval) => boolean) => boolean
export type LociApply = (loci: Loci, apply: (interval: Interval) => boolean, isMarking: boolean) => boolean
export function setVisibility(renderObject: GraphicsRenderObject | undefined, visible: boolean) {
if (renderObject) renderObject.state.visible = visible;
@@ -70,7 +70,7 @@ namespace Visual {
if (isEveryLoci(loci)) {
changed = applyMarkerAction(array, Interval.ofLength(count), action);
} else if (!isEmptyLoci(loci)) {
changed = lociApply(loci, interval => applyMarkerAction(array, interval, action));
changed = lociApply(loci, interval => applyMarkerAction(array, interval, action), true);
}
if (changed) ValueCell.update(tMarker, tMarker.ref.value);
return changed;
@@ -98,7 +98,7 @@ namespace Visual {
? clearOverpaint(array, start, end)
: applyOverpaintColor(array, start, end, color, overpaint.alpha);
};
lociApply(loci, apply);
lociApply(loci, apply, false);
}
ValueCell.update(tOverpaint, tOverpaint.ref.value);
}
@@ -123,7 +123,7 @@ namespace Visual {
const end = Interval.end(interval);
return applyTransparencyValue(array, start, end, value);
};
lociApply(loci, apply);
lociApply(loci, apply, false);
ValueCell.update(tTransparency, tTransparency.ref.value);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -16,13 +16,12 @@ let isProductionMode = process.env.NODE_ENV === 'production';
*/
let isDebugMode = process.env.DEBUG === '*' || process.env.DEBUG === 'molstar';
if (typeof window !== 'undefined' && !(window as any).setMolStarDebugMode) {
try {
(window as any).setMolStarDebugMode = function setMolStarDebugMode(isDebug?: boolean, isProduction?: boolean) {
if (typeof isDebug !== 'undefined') isDebugMode = isDebug;
if (typeof isProduction !== 'undefined') isProductionMode = isProduction;
};
} catch { }
export { isProductionMode, isDebugMode };
export function setProductionMode(value?: boolean) {
if (typeof value !== 'undefined') isProductionMode = value;
}
export { isProductionMode, isDebugMode };
export function setDebugMode(value?: boolean) {
if (typeof value !== 'undefined') isDebugMode = value;
}