@molstudio/core 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -51,6 +51,11 @@ export declare class MolStudio {
51
51
  loadVolume({ data, label, format }: LoadDataOptions<VolumeFormat>): Promise<void>;
52
52
  loadVolumeFromUrl(url: string, label: string, format: VolumeFormat, isBinary: boolean): Promise<void>;
53
53
  private applyStructurePreset;
54
+ /**
55
+ * Mol*'s default preset colors ligand carbons by chain, the same color as the protein around them;
56
+ * element colors keep a ligand distinct from its binding pocket.
57
+ */
58
+ private colorLigandCarbonsByElement;
54
59
  private applyVolumeVisuals;
55
60
  setVisible(id: string, visible: boolean): void;
56
61
  /**
@@ -113,6 +118,10 @@ export declare class MolStudio {
113
118
  private transparencyLayers;
114
119
  private partLoci;
115
120
  private residueRef;
121
+ /**
122
+ * Fits the visible structures, or everything when none is shown. A map's box is usually much larger
123
+ * than its density, so fitting it would leave a fitted model small in the middle of the view.
124
+ */
116
125
  resetCamera(): void;
117
126
  setSpinning(spinning: boolean): void;
118
127
  setBackground(hex: string): void;
package/dist/MolStudio.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { PLDDTConfidenceColorThemeProvider } from "molstar/lib/extensions/model-archive/quality-assessment/color/plddt";
2
+ import { QualityAssessment } from "molstar/lib/extensions/model-archive/quality-assessment/prop";
3
+ import { MmcifFormat } from "molstar/lib/mol-model-formats/structure/mmcif";
2
4
  import { DownloadDensity } from "molstar/lib/mol-plugin-state/actions/volume";
3
5
  import { PluginConfig } from "molstar/lib/mol-plugin/config";
4
6
  import { Volume } from "molstar/lib/mol-model/volume";
5
7
  import { Loci } from "molstar/lib/mol-model/loci";
6
- import { Structure, StructureElement } from "molstar/lib/mol-model/structure";
8
+ import { Model, Structure, StructureElement } from "molstar/lib/mol-model/structure";
7
9
  import { createStructureColorThemeParams, createStructureRepresentationParams, } from "molstar/lib/mol-plugin-state/helpers/structure-representation-params";
8
10
  import { clearStructureTransparency, setStructureTransparency } from "molstar/lib/mol-plugin-state/helpers/structure-transparency";
9
11
  import { createVolumeColorThemeParams } from "molstar/lib/mol-plugin-state/helpers/volume-representation-params";
@@ -70,7 +72,10 @@ export class MolStudio {
70
72
  }));
71
73
  }
72
74
  static async create(target) {
73
- const plugin = new PluginContext(DefaultPluginSpec());
75
+ const spec = DefaultPluginSpec();
76
+ // Mol*'s "auto" mode renders at 1x on mobile browsers, which blurs structures on high-DPI phones.
77
+ spec.config = [...(spec.config ?? []), [PluginConfig.General.ResolutionMode, "native"]];
78
+ const plugin = new PluginContext(spec);
74
79
  await plugin.init();
75
80
  // Only the pLDDT theme is needed; Mol*'s quality-assessment behaviour also pulls in its React UI.
76
81
  plugin.representation.structure.themes.colorThemeRegistry.add(PLDDTConfidenceColorThemeProvider);
@@ -219,7 +224,27 @@ export class MolStudio {
219
224
  }
220
225
  async applyStructurePreset(raw, format) {
221
226
  const trajectory = await this.plugin.builders.structure.parseTrajectory(raw, format);
222
- await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, "default");
227
+ const result = await this.plugin.builders.structure.hierarchy.applyPreset(trajectory, "default");
228
+ // Mol* parses some malformed input (e.g. text that is not SDF) into an empty structure without failing.
229
+ if (!result?.structure.data?.elementCount) {
230
+ throw new Error(`The ${format} data contains no atoms.`);
231
+ }
232
+ await this.colorLigandCarbonsByElement(result.structure.ref);
233
+ }
234
+ /**
235
+ * Mol*'s default preset colors ligand carbons by chain, the same color as the protein around them;
236
+ * element colors keep a ligand distinct from its binding pocket.
237
+ */
238
+ async colorLigandCarbonsByElement(structureRef) {
239
+ const structure = this.requireStructure(structureRef);
240
+ const reprs = structure.components
241
+ .filter((component) => staticComponentType(component) === "ligand")
242
+ .flatMap((component) => component.representations)
243
+ .filter((repr) => repr.cell.transform.params?.colorTheme.name === "element-symbol");
244
+ await this.updateEach(reprs, (old) => ({
245
+ ...old,
246
+ colorTheme: { ...old.colorTheme, params: { ...old.colorTheme.params, carbonColor: { name: "element-symbol", params: {} } } },
247
+ }));
223
248
  }
224
249
  async applyVolumeVisuals(raw, format) {
225
250
  const provider = this.plugin.dataFormats.get(format);
@@ -373,7 +398,7 @@ export class MolStudio {
373
398
  ? structure.components.flatMap((component) => component.representations)
374
399
  : (volume?.representations ?? this.requireComponent(target).representations);
375
400
  const renderObjects = reprs.flatMap((repr) => repr.cell.obj?.data.repr.renderObjects ?? []);
376
- this.plugin.managers.camera.focusRenderObjects(renderObjects);
401
+ this.plugin.managers.camera.focusRenderObjects(renderObjects, FOCUS_OPTIONS);
377
402
  }
378
403
  async remove(id) {
379
404
  await PluginCommands.State.RemoveObject(this.plugin, {
@@ -401,7 +426,7 @@ export class MolStudio {
401
426
  const loci = this.partLoci(part);
402
427
  if (part.scope === "residue")
403
428
  this.plugin.managers.structure.focus.setFromLoci(loci);
404
- this.plugin.managers.camera.focusLoci(loci);
429
+ this.plugin.managers.camera.focusLoci(loci, FOCUS_OPTIONS);
405
430
  }
406
431
  /** Leaves Mol*'s focus view, removing its target and surroundings components, and returns to the earlier view. */
407
432
  clearFocus() {
@@ -555,8 +580,21 @@ export class MolStudio {
555
580
  return owner ? { objectId: owner.cell.transform.ref, residueIndex } : null;
556
581
  }
557
582
  // ---- viewport ----
583
+ /**
584
+ * Fits the visible structures, or everything when none is shown. A map's box is usually much larger
585
+ * than its density, so fitting it would leave a fitted model small in the middle of the view.
586
+ */
558
587
  resetCamera() {
559
- PluginCommands.Camera.Reset(this.plugin, {});
588
+ const renderObjects = this.plugin.managers.structure.hierarchy.current.structures
589
+ .flatMap((structure) => structure.components)
590
+ .flatMap((component) => component.representations)
591
+ .flatMap((repr) => repr.cell.obj?.data.repr.renderObjects ?? [])
592
+ .filter((renderObject) => renderObject.state.visible);
593
+ if (renderObjects.length === 0) {
594
+ PluginCommands.Camera.Reset(this.plugin, {});
595
+ return;
596
+ }
597
+ this.plugin.managers.camera.focusRenderObjects(renderObjects, { extraRadius: 0 });
560
598
  }
561
599
  setSpinning(spinning) {
562
600
  const canvas3d = this.requireCanvas();
@@ -720,8 +758,7 @@ export class MolStudio {
720
758
  }
721
759
  applicableSchemes(ref) {
722
760
  const structure = ref.cell.obj?.data;
723
- const plddt = this.plugin.representation.structure.themes.colorThemeRegistry.get("plddt-confidence");
724
- const hasPlddt = !!structure && !!plddt?.isApplicable({ structure });
761
+ const hasPlddt = !!structure && structure.models.some(isPredictedModel);
725
762
  return hasPlddt ? ["plddt-confidence", ...BASE_SCHEMES] : [...BASE_SCHEMES];
726
763
  }
727
764
  // ---- lookup helpers ----
@@ -834,6 +871,21 @@ const AXES = {
834
871
  };
835
872
  /** Matches Mol*'s default camera focus animation. */
836
873
  const CAMERA_TRANSITION_MS = 250;
874
+ /** Keeps the surroundings of small targets (a ligand, a residue) in view instead of filling the screen with them. */
875
+ const FOCUS_OPTIONS = { minRadius: 14 };
876
+ /**
877
+ * Whether B-factors hold pLDDT scores. Mol* assumes so for any model not marked experimental, but most
878
+ * PDB entries lack that mark, so an entry with experimental methods recorded does not count.
879
+ */
880
+ function isPredictedModel(model) {
881
+ if (QualityAssessment.isApplicable(model, "pLDDT"))
882
+ return true;
883
+ if (!MmcifFormat.is(model.sourceData) || !model.atomicConformation.B_iso_or_equiv.isDefined)
884
+ return false;
885
+ if (Model.isExperimental(model))
886
+ return false;
887
+ return model.sourceData.data.db.exptl.method.rowCount === 0;
888
+ }
837
889
  const FOCUS_TAGS = {
838
890
  [StructureFocusRepresentationTags.TargetSel]: "target",
839
891
  [StructureFocusRepresentationTags.SurrSel]: "surroundings",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@molstudio/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "UI-less Mol* controller that exposes the viewer as objects and display operations.",
5
5
  "license": "MIT",
6
6
  "keywords": [