mirror of
https://github.com/molstar/molstar.git
synced 2026-06-06 22:54:22 +08:00
Compare commits
106 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b2dfd9245 | ||
|
|
678790efa3 | ||
|
|
a121c5e2cd | ||
|
|
47b242244e | ||
|
|
2b9d3fd33a | ||
|
|
81404036a2 | ||
|
|
e2dc15cf0f | ||
|
|
365a91879f | ||
|
|
e47e0eb51a | ||
|
|
390046e38f | ||
|
|
b8eb5191a2 | ||
|
|
4cc416ca28 | ||
|
|
f4b2458390 | ||
|
|
1cad6eef74 | ||
|
|
2923be6006 | ||
|
|
70959641a1 | ||
|
|
051608f56c | ||
|
|
71c1a4e85b | ||
|
|
ba06c9e413 | ||
|
|
8e4dfd1ffd | ||
|
|
fac8aa529f | ||
|
|
d35b4b5e62 | ||
|
|
dd1789478b | ||
|
|
af27a00a01 | ||
|
|
17cea8f99c | ||
|
|
4f6d5a7dc7 | ||
|
|
857972653e | ||
|
|
98ff0f5c55 | ||
|
|
43803a91ea | ||
|
|
8a1bab8bcb | ||
|
|
17a47faaff | ||
|
|
f793167e91 | ||
|
|
d1c2c8e837 | ||
|
|
5d7ef8196e | ||
|
|
e0715cbf5c | ||
|
|
1af8522de3 | ||
|
|
c6becd5741 | ||
|
|
0ca368f29f | ||
|
|
5039a448ad | ||
|
|
9ef38f02c9 | ||
|
|
66f4ff1140 | ||
|
|
cc077656a9 | ||
|
|
3ef1a2ec0a | ||
|
|
144bf6954e | ||
|
|
50e5538148 | ||
|
|
efe95f92c7 | ||
|
|
09f858a755 | ||
|
|
e7082d4ccc | ||
|
|
732a8f4bd0 | ||
|
|
82ca06b29e | ||
|
|
a05429f13f | ||
|
|
377de7ad40 | ||
|
|
74f4d00c8d | ||
|
|
be3825372e | ||
|
|
d62c5c9050 | ||
|
|
9ba5112beb | ||
|
|
048658ee39 | ||
|
|
2918081dd9 | ||
|
|
1a67868c07 | ||
|
|
525dfaddd2 | ||
|
|
8aa12c0d31 | ||
|
|
8cb464a686 | ||
|
|
e0371d7e32 | ||
|
|
e7da6bc194 | ||
|
|
933869b5e1 | ||
|
|
5d6adc46fe | ||
|
|
b8a98efcaf | ||
|
|
53b358f70a | ||
|
|
bce9e5b0ad | ||
|
|
cf7f9d6aba | ||
|
|
6af1bc5def | ||
|
|
936808e271 | ||
|
|
8b985d0424 | ||
|
|
b290cf121a | ||
|
|
89df6cec42 | ||
|
|
fc5fc7fcdb | ||
|
|
a1de5bb304 | ||
|
|
25c8a41e91 | ||
|
|
959249b572 | ||
|
|
44610b8b1a | ||
|
|
4070453209 | ||
|
|
cf2193f4fc | ||
|
|
bfc0a3d1fe | ||
|
|
53a2155d8c | ||
|
|
f22121521b | ||
|
|
755655d067 | ||
|
|
41ab186fd2 | ||
|
|
9039c653cb | ||
|
|
dde3f4ecff | ||
|
|
64cd05cc14 | ||
|
|
593e8f4993 | ||
|
|
d2d9eb622f | ||
|
|
ce9883517f | ||
|
|
bc2afe1d68 | ||
|
|
5a2ee03b48 | ||
|
|
2d86c76788 | ||
|
|
7f8995a4d8 | ||
|
|
64ab8bf78d | ||
|
|
bf9663e177 | ||
|
|
fb729446e2 | ||
|
|
3e4082bf6e | ||
|
|
7148c7197b | ||
|
|
0d9d173ef4 | ||
|
|
6eaf8e1911 | ||
|
|
5c882f1aa5 | ||
|
|
2320518b87 |
35
CHANGELOG.md
35
CHANGELOG.md
@@ -5,6 +5,41 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v4.4.0] - 2023-06-30
|
||||
|
||||
- Clean `solidInterior` transparent cylinders
|
||||
- Create a transformer to deflate compressed data
|
||||
- Adjust Quick Styles panel button labels
|
||||
- Improve camera interpolation code (interpolate camera rotation instead of just position)
|
||||
- Mesoscale Explorer
|
||||
- Add `illustrative` coloring option
|
||||
- Press 'C' to toggle between center and zoom & center on click
|
||||
- Add entities selection description
|
||||
- Clicking a leaf node in the right panel tree will center each instance in turn
|
||||
- Add measurement controls to right panel
|
||||
- Mouse left click on label with snapshot key will load snapshot
|
||||
- Mouse hover over label with protein name highlight entities with the same name
|
||||
- Custom ViewportSnapshotDescription with custom MarkdowAnchor
|
||||
- \# other snapshots with a given key \[...](#key)
|
||||
- i highlight a protein with a given NAME \[...](iNAME)
|
||||
- g highlight a group with a given group type and group name \[...](ggrouptype.groupname)
|
||||
- h URLs with a given link \[...](http...)
|
||||
- Snapshot description panel window size and text can be resized and hidden with new icons
|
||||
- Add styles controls to right panel
|
||||
- Add viewport settings to left panel
|
||||
- Add app info component to left panel with interactive tour and doc link
|
||||
- Fixes SSAO edge artifacts (#1122)
|
||||
- Add `reuseOcclusion` parameter to multi-sample pass
|
||||
- Add `blurDepthBias` parameter to occlusion pass
|
||||
- Handle near clip in SSAO blur
|
||||
- Support reading score from B-factor in pLDDT color theme
|
||||
- Add Cel-shading support
|
||||
- `celShaded` geometry parameter
|
||||
- `celSteps` renderer parameter
|
||||
- Add the ability to customize the Snapshot Description component via `PluginUISpec.components.viewport.snapshotDescription`
|
||||
- Add `doNotDisposeCanvas3DContext` option to `PluginContext.dispose`
|
||||
- Remove support for density data from edmaps.rcsb.org
|
||||
|
||||
## [v4.3.0] - 2023-05-26
|
||||
|
||||
- Fix State Snapshots export animation (#1140)
|
||||
|
||||
@@ -107,6 +107,7 @@ entity.id
|
||||
entity.type
|
||||
entity.src_method
|
||||
entity.pdbx_description
|
||||
entity.pdbx_parent_entity_id
|
||||
entity.formula_weight
|
||||
entity.pdbx_number_of_molecules
|
||||
entity.details
|
||||
|
||||
|
607
package-lock.json
generated
607
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
33
package.json
33
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "4.3.0",
|
||||
"version": "4.4.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -107,7 +107,8 @@
|
||||
"Christian Dominguez <christian.99dominguez@gmail.com>",
|
||||
"Cai Huiyu <szmun.caihy@gmail.com>",
|
||||
"Ryan DiRisio <rjdiris@gmail.com>",
|
||||
"Dušan Veľký <dvelky@mail.muni.cz>"
|
||||
"Dušan Veľký <dvelky@mail.muni.cz>",
|
||||
"Neli Fonseca <neli@ebi.ac.uk>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
@@ -115,15 +116,15 @@
|
||||
"@types/gl": "^6.0.5",
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/react": "^18.3.1",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||
"@typescript-eslint/parser": "^7.8.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
||||
"@typescript-eslint/parser": "^7.14.1",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^8.2.2",
|
||||
"cpx2": "^7.0.1",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"css-loader": "^7.1.1",
|
||||
"css-loader": "^7.1.2",
|
||||
"eslint": "^8.57.0",
|
||||
"extra-watch-webpack-plugin": "^1.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
@@ -136,14 +137,14 @@
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"sass": "^1.76.0",
|
||||
"sass": "^1.77.6",
|
||||
"sass-loader": "^14.2.1",
|
||||
"simple-git": "^3.24.0",
|
||||
"simple-git": "^3.25.0",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"style-loader": "^4.0.0",
|
||||
"ts-jest": "^29.1.2",
|
||||
"typescript": "^5.4.5",
|
||||
"webpack": "^5.91.0",
|
||||
"ts-jest": "^29.1.5",
|
||||
"typescript": "^5.5.2",
|
||||
"webpack": "^5.92.1",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -151,9 +152,9 @@
|
||||
"@types/benchmark": "^2.1.5",
|
||||
"@types/compression": "1.7.5",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^18.19.31",
|
||||
"@types/node": "^18.19.39",
|
||||
"@types/node-fetch": "^2.6.11",
|
||||
"@types/swagger-ui-dist": "3.30.4",
|
||||
"@types/swagger-ui-dist": "3.30.5",
|
||||
"argparse": "^2.0.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"compression": "^1.7.4",
|
||||
@@ -161,13 +162,13 @@
|
||||
"express": "^4.19.2",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immer": "^10.1.1",
|
||||
"immutable": "^4.3.5",
|
||||
"immutable": "^4.3.6",
|
||||
"io-ts": "^2.2.21",
|
||||
"node-fetch": "^2.7.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"rxjs": "^7.8.1",
|
||||
"swagger-ui-dist": "^5.17.2",
|
||||
"tslib": "^2.6.2",
|
||||
"swagger-ui-dist": "^5.17.14",
|
||||
"tslib": "^2.6.3",
|
||||
"util.promisify": "^1.1.2",
|
||||
"xhr2": "^0.2.1"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -46,6 +46,7 @@ function occlusionStyle(plugin: PluginContext) {
|
||||
...plugin.canvas3d!.props.postprocessing,
|
||||
occlusion: { name: 'on', params: {
|
||||
blurKernelSize: 15,
|
||||
blurDepthBias: 0.5,
|
||||
multiScale: { name: 'off', params: {} },
|
||||
radius: 5,
|
||||
bias: 0.8,
|
||||
|
||||
@@ -30,6 +30,7 @@ import { Asset } from '../../mol-util/assets';
|
||||
import { AnimateCameraSpin } from '../../mol-plugin-state/animation/built-in/camera-spin';
|
||||
import { AnimateCameraRock } from '../../mol-plugin-state/animation/built-in/camera-rock';
|
||||
import { AnimateStateSnapshots } from '../../mol-plugin-state/animation/built-in/state-snapshots';
|
||||
import { MesoViewportSnapshotDescription } from './ui/entities';
|
||||
|
||||
export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
|
||||
export { setDebugMode, setProductionMode, setTimingMode, consoleStats } from '../../mol-util/debug';
|
||||
@@ -47,6 +48,7 @@ export type MesoscaleExplorerState = {
|
||||
examples?: ExampleEntry[],
|
||||
graphicsMode: GraphicsMode,
|
||||
stateRef?: string,
|
||||
driver?: any,
|
||||
stateCache: { [k: string]: any },
|
||||
}
|
||||
|
||||
@@ -90,7 +92,8 @@ const DefaultMesoscaleExplorerOptions = {
|
||||
emdbProvider: PluginConfig.Download.DefaultEmdbProvider.defaultValue,
|
||||
saccharideCompIdMapType: 'default' as SaccharideCompIdMapType,
|
||||
|
||||
graphicsMode: 'quality' as GraphicsMode
|
||||
graphicsMode: 'quality' as GraphicsMode,
|
||||
driver: undefined
|
||||
};
|
||||
type MesoscaleExplorerOptions = typeof DefaultMesoscaleExplorerOptions;
|
||||
|
||||
@@ -170,6 +173,9 @@ export class MesoscaleExplorer {
|
||||
right: RightPanel,
|
||||
},
|
||||
remoteState: 'none',
|
||||
viewport: {
|
||||
snapshotDescription: MesoViewportSnapshotDescription,
|
||||
}
|
||||
},
|
||||
config: [
|
||||
[PluginConfig.General.DisableAntialiasing, o.disableAntialiasing],
|
||||
@@ -208,6 +214,11 @@ export class MesoscaleExplorer {
|
||||
let examples: MesoscaleExplorerState['examples'] = undefined;
|
||||
try {
|
||||
examples = await plugin.fetch({ url: './examples/list.json', type: 'json' }).run();
|
||||
// extend the array with file tour.json if it exists
|
||||
const tour = await plugin.fetch({ url: './examples/tour.json', type: 'json' }).run();
|
||||
if (tour) {
|
||||
examples = examples?.concat(tour);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
@@ -215,6 +226,7 @@ export class MesoscaleExplorer {
|
||||
(plugin.customState as MesoscaleExplorerState) = {
|
||||
examples,
|
||||
graphicsMode: o.graphicsMode,
|
||||
driver: o.driver,
|
||||
stateCache: {},
|
||||
};
|
||||
|
||||
|
||||
@@ -11,10 +11,12 @@ import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observ
|
||||
import { Binding } from '../../../mol-util/binding';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { StructureElement } from '../../../mol-model/structure';
|
||||
|
||||
const B = ButtonsType;
|
||||
const M = ModifiersKeys;
|
||||
const Trigger = Binding.Trigger;
|
||||
const Key = Binding.TriggerKey;
|
||||
|
||||
const DefaultMesoFocusLociBindings = {
|
||||
clickCenter: Binding([
|
||||
@@ -23,13 +25,14 @@ const DefaultMesoFocusLociBindings = {
|
||||
clickCenterFocus: Binding([
|
||||
Trigger(B.Flag.Secondary, M.create()),
|
||||
], 'Camera center and focus', 'Click element using ${triggers}'),
|
||||
keyCenterOnly: Binding([Key('C')], 'Center Only Toggle', 'Press ${triggers}'),
|
||||
};
|
||||
const MesoFocusLociParams = {
|
||||
|
||||
export const MesoFocusLociParams = {
|
||||
minRadius: PD.Numeric(8, { min: 1, max: 50, step: 1 }),
|
||||
extraRadius: PD.Numeric(4, { min: 1, max: 50, step: 1 }, { description: 'Value added to the bounding-sphere radius of the Loci' }),
|
||||
durationMs: PD.Numeric(250, { min: 0, max: 1000, step: 1 }, { description: 'Camera transition duration' }),
|
||||
centerOnly: PD.Boolean(true, { description: 'Keep current camera distance' }),
|
||||
|
||||
bindings: PD.Value(DefaultMesoFocusLociBindings, { isHidden: true }),
|
||||
};
|
||||
type MesoFocusLociProps = PD.Values<typeof MesoFocusLociParams>
|
||||
@@ -47,25 +50,45 @@ export const MesoFocusLoci = PluginBehavior.create<MesoFocusLociProps>({
|
||||
const sphere = Loci.getBoundingSphere(loci) || Sphere3D();
|
||||
|
||||
const { clickCenter, clickCenterFocus } = this.params.bindings;
|
||||
const { durationMs, extraRadius, minRadius } = this.params;
|
||||
const { durationMs, extraRadius, minRadius, centerOnly } = this.params;
|
||||
const radius = Math.max(sphere.radius + extraRadius, minRadius);
|
||||
|
||||
if (Binding.match(clickCenter, button, modifiers)) {
|
||||
// left mouse button
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
PluginCommands.Camera.Reset(this.ctx, { });
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshot = canvas3d.camera.getCenter(sphere.center);
|
||||
canvas3d.requestCameraReset({ durationMs, snapshot });
|
||||
if (StructureElement.Loci.is(current.loci)) {
|
||||
if (centerOnly) {
|
||||
const snapshot = canvas3d.camera.getCenter(sphere.center);
|
||||
canvas3d.requestCameraReset({ durationMs, snapshot });
|
||||
} else {
|
||||
this.ctx.managers.camera.focusSphere(sphere, this.params);
|
||||
}
|
||||
}
|
||||
} else if (Binding.match(clickCenterFocus, button, modifiers)) {
|
||||
// right mouse button
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
PluginCommands.Camera.Reset(this.ctx, { });
|
||||
return;
|
||||
}
|
||||
if (centerOnly) {
|
||||
const snapshot = canvas3d.camera.getCenter(sphere.center, radius);
|
||||
canvas3d.requestCameraReset({ durationMs, snapshot });
|
||||
} else {
|
||||
this.ctx.managers.camera.focusSphere(sphere, this.params);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const snapshot = canvas3d.camera.getCenter(sphere.center, radius);
|
||||
canvas3d.requestCameraReset({ durationMs, snapshot });
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.key, ({ code, key, modifiers }) => {
|
||||
if (!this.ctx.canvas3d) return;
|
||||
const b = { ...DefaultMesoFocusLociBindings, ...this.params.bindings };
|
||||
const { centerOnly } = this.params;
|
||||
|
||||
if (Binding.matchKey(b.keyCenterOnly, code, modifiers, key)) {
|
||||
this.params.centerOnly = !centerOnly;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,12 +16,16 @@ import { StateTreeSpine } from '../../../mol-state/tree/spine';
|
||||
import { Representation } from '../../../mol-repr/representation';
|
||||
import { MarkerAction } from '../../../mol-util/marker-action';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { MesoscaleState, expandAllGroups, getCellDescription, getEveryEntity } from '../data/state';
|
||||
|
||||
const B = ButtonsType;
|
||||
const M = ModifiersKeys;
|
||||
const Trigger = Binding.Trigger;
|
||||
|
||||
const DefaultMesoSelectLociBindings = {
|
||||
click: Binding([
|
||||
Trigger(B.Flag.Primary, M.create())
|
||||
], 'Click', 'Click element using ${triggers}'),
|
||||
clickToggleSelect: Binding([
|
||||
Trigger(B.Flag.Primary, M.create({ shift: true })),
|
||||
Trigger(B.Flag.Primary, M.create({ control: true })),
|
||||
@@ -63,15 +67,36 @@ export const MesoSelectLoci = PluginBehavior.create<MesoSelectLociProps>({
|
||||
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, button, modifiers }) => {
|
||||
if (!this.ctx.canvas3d || this.ctx.isBusy) return;
|
||||
|
||||
const { clickToggleSelect } = this.params.bindings;
|
||||
const { click, clickToggleSelect } = this.params.bindings;
|
||||
if (Binding.match(clickToggleSelect, button, modifiers)) {
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
this.ctx.managers.interactivity.lociSelects.deselectAll();
|
||||
return;
|
||||
}
|
||||
|
||||
const loci = Loci.normalize(current.loci, modifiers.control ? 'entity' : 'chain');
|
||||
this.ctx.managers.interactivity.lociSelects.toggle({ loci }, false);
|
||||
if (StructureElement.Loci.is(current.loci)) {
|
||||
const cell = this.ctx.helpers.substructureParent.get(current.loci.structure);
|
||||
const d = getCellDescription(cell!);
|
||||
MesoscaleState.set(this.ctx, { focusInfo: `${d}` });
|
||||
}
|
||||
}
|
||||
if (Binding.match(click, button, modifiers)) {
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
MesoscaleState.set(this.ctx, { focusInfo: '', filter: '' });
|
||||
return;
|
||||
}
|
||||
const snapshotKey = current.repr?.props?.snapshotKey?.trim() ?? '';
|
||||
if (snapshotKey) {
|
||||
this.ctx.managers.snapshot.applyKey(snapshotKey);
|
||||
} else {
|
||||
if (StructureElement.Loci.is(current.loci)) {
|
||||
const cell = this.ctx.helpers.substructureParent.get(current.loci.structure);
|
||||
const d = getCellDescription(cell!);
|
||||
MesoscaleState.set(this.ctx, { focusInfo: `${d}`, filter: `${cell?.obj?.label}` });
|
||||
expandAllGroups(this.ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
this.ctx.managers.interactivity.lociSelects.addProvider(this.lociMarkProvider);
|
||||
@@ -87,22 +112,41 @@ export const MesoSelectLoci = PluginBehavior.create<MesoSelectLociProps>({
|
||||
this.ctx.managers.interactivity.lociHighlights.clearHighlights();
|
||||
return;
|
||||
}
|
||||
|
||||
if (modifiers.control) {
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci: EveryLoci }, false);
|
||||
} else {
|
||||
const loci = Loci.normalize(current.loci, 'chain');
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci }, false);
|
||||
if (StructureElement.Loci.is(current.loci)) {
|
||||
if (modifiers.control) {
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci: EveryLoci }, false);
|
||||
} else {
|
||||
const loci = Loci.normalize(current.loci, 'chain');
|
||||
this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci }, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Loci.isEmpty(current.loci)) {
|
||||
this.ctx.behaviors.labels.highlight.next({ labels: [] });
|
||||
this.ctx.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
|
||||
} else {
|
||||
const labels: string[] = [];
|
||||
if (StructureElement.Loci.is(current.loci)) {
|
||||
const cell = this.ctx.helpers.substructureParent.get(current.loci.structure);
|
||||
labels.push(cell?.obj?.label || 'Unknown');
|
||||
const d = getCellDescription(cell!);
|
||||
labels.push(d);
|
||||
} else {
|
||||
const loci = Loci.normalize(current.loci, this.ctx.managers.interactivity.props.granularity);
|
||||
if (loci.kind === 'group-loci') {
|
||||
if ('shape' in current.loci && current.loci.shape.geometry.kind === 'text') {
|
||||
const qname = current.repr?.props.customText;
|
||||
// highlight protein with same name
|
||||
const entities = getEveryEntity(this.ctx, qname);
|
||||
for (const r of entities) {
|
||||
const repr = r.obj?.data.repr;
|
||||
if (repr) {
|
||||
this.ctx.canvas3d?.mark({ repr, loci: EveryLoci }, MarkerAction.Highlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
labels.push(loci.shape.getLabel(0, 0));
|
||||
}
|
||||
}
|
||||
this.ctx.behaviors.labels.highlight.next({ labels });
|
||||
}
|
||||
|
||||
@@ -161,15 +161,12 @@ const CellpackStructure = PluginStateTransform.BuiltIn({
|
||||
|
||||
const unitsByEntity = getUnitsByEntity(parent);
|
||||
const units = unitsByEntity.get(idx) || [];
|
||||
// if (!unitsByEntity.get(idx)) {
|
||||
// console.log(entities.data.pdbx_description.value(idx));
|
||||
// }
|
||||
|
||||
const structure = Structure.create(units);
|
||||
|
||||
const description = entities.data.pdbx_description.value(idx)[0] || 'model';
|
||||
const label = description.split('.').at(-1) || a.label;
|
||||
|
||||
return new PSO.Molecule.Structure(structure, { label, description: `${a.description}` });
|
||||
const description_label = entities.data.pdbx_description.value(idx)[0] || 'model';
|
||||
const label = description_label.split('.').at(-1) || a.label;
|
||||
const description = entities.data.pdbx_parent_entity_id.value(idx) || label;
|
||||
return new PSO.Molecule.Structure(structure, { label, description: description }); // `${a.description}`
|
||||
});
|
||||
},
|
||||
dispose({ b }) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/
|
||||
import { StateObjectRef, StateObjectSelector, StateBuilder } from '../../../../mol-state';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { ColorNames } from '../../../../mol-util/color/names';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getDistinctGroupColors, getGraphicsModeProps, getMesoscaleGroupParams } from '../state';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getDistinctGroupColors, getGraphicsModeProps, getMesoscaleGroupParams, updateColors } from '../state';
|
||||
import { CellpackAssembly, CellpackStructure } from './model';
|
||||
|
||||
function getSpacefillParams(color: Color, sizeFactor: number, graphics: GraphicsMode, merge?: boolean) {
|
||||
@@ -96,12 +96,12 @@ export async function createCellpackHierarchy(plugin: PluginContext, trajectory:
|
||||
|
||||
const compRoot = await state.build()
|
||||
.toRoot()
|
||||
.applyOrUpdateTagged('group:comp:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `comp:`, label: 'compartment', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:comp:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.applyOrUpdateTagged('group:comp:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `comp:`, label: 'compartment', color: { type: 'custom', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:comp:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
const funcRoot = await state.build()
|
||||
.toRoot()
|
||||
.applyOrUpdateTagged('group:func:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `func:`, label: 'function', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:func:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.applyOrUpdateTagged('group:func:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `func:`, label: 'function', color: { type: 'custom', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:func:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
if (entities._rowCount > 1) {
|
||||
@@ -159,7 +159,7 @@ export async function createCellpackHierarchy(plugin: PluginContext, trajectory:
|
||||
parent.cell!.state.isCollapsed = false;
|
||||
const group = await state.build()
|
||||
.to(parent)
|
||||
.applyOrUpdateTagged(`group:comp:${n}`, MesoscaleGroup, { ...groupParams, root: parent === compRoot, index: colorIdx, tag: `comp:${n}`, label, color: { type: 'generate', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `comp:${p}`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.applyOrUpdateTagged(`group:comp:${n}`, MesoscaleGroup, { ...groupParams, root: parent === compRoot, index: colorIdx, tag: `comp:${n}`, label, color: { type: 'generate', illustrative: false, value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `comp:${p}`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit({ revertOnError: true });
|
||||
compGroups.set(n, group);
|
||||
}
|
||||
@@ -171,7 +171,7 @@ export async function createCellpackHierarchy(plugin: PluginContext, trajectory:
|
||||
const color = colorIdx !== undefined ? baseFuncColors[colorIdx] : ColorNames.white;
|
||||
const group = await state.build()
|
||||
.to(funcRoot)
|
||||
.applyOrUpdateTagged(`group:func:${f}`, MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `func:${f}`, label: f, color: { type: 'custom', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'func:', state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.applyOrUpdateTagged(`group:func:${f}`, MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `func:${f}`, label: f, color: { type: 'custom', illustrative: false, value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'func:', state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit({ revertOnError: true });
|
||||
funcGroups.set(f, group);
|
||||
}
|
||||
@@ -201,6 +201,9 @@ export async function createCellpackHierarchy(plugin: PluginContext, trajectory:
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(color, sizeFactor, graphicsMode), { tags: [`comp:${n}`, `func:${f}`] });
|
||||
}
|
||||
await build.commit();
|
||||
const values = { type: 'group-generate', value: ColorNames.white, lightness: 0, alpha: 1 };
|
||||
const options = { ignoreLight: true, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 }, celShaded: true, };
|
||||
await updateColors(plugin, values, options);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(e);
|
||||
|
||||
@@ -76,6 +76,7 @@ const StructureFromGeneric = PluginStateTransform.BuiltIn({
|
||||
params: {
|
||||
instances: PD.Value<GenericInstances<Asset>>(EmptyInstances),
|
||||
label: PD.Optional(PD.Text('')),
|
||||
description: PD.Optional(PD.Text('')),
|
||||
cellSize: PD.Numeric(500, { min: 0, max: 10000, step: 100 }),
|
||||
}
|
||||
})({
|
||||
@@ -111,7 +112,7 @@ const StructureFromGeneric = PluginStateTransform.BuiltIn({
|
||||
structure = assembler.getStructure();
|
||||
}
|
||||
|
||||
const props = { label, description: Structure.elementDescription(structure) };
|
||||
const props = { label, description: params.description || Structure.elementDescription(structure) };
|
||||
return new SO.Molecule.Structure(structure, props);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -124,7 +124,7 @@ export async function createGenericHierarchy(plugin: PluginContext, file: Asset.
|
||||
async function addGroup(g: GenericGroup, cell: StateObjectSelector, parent: string) {
|
||||
const group = await state.build()
|
||||
.to(cell)
|
||||
.apply(MesoscaleGroup, { ...groupParams, index: undefined, tag: `${g.root}:${g.id}`, label: g.label || g.id, color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: [`group:${g.root}:${g.id}`, g.root === parent ? `${g.root}:` : `${g.root}:${parent}`], state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.apply(MesoscaleGroup, { ...groupParams, index: undefined, tag: `${g.root}:${g.id}`, label: g.label || g.id, description: g.description, color: { type: 'custom', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: [`group:${g.root}:${g.id}`, g.root === parent ? `${g.root}:` : `${g.root}:${parent}`], state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
if (g.children) {
|
||||
@@ -137,7 +137,7 @@ export async function createGenericHierarchy(plugin: PluginContext, file: Asset.
|
||||
for (const r of manifest.roots) {
|
||||
const root = await state.build()
|
||||
.toRoot()
|
||||
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `${r.id}:`, label: r.label || r.id, color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `group:${r.id}:`, state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.apply(MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `${r.id}:`, label: r.label || r.id, description: r.description, color: { type: 'custom', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `group:${r.id}:`, state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
if (r.children) {
|
||||
@@ -193,11 +193,12 @@ export async function createGenericHierarchy(plugin: PluginContext, file: Asset.
|
||||
const file = Asset.File(new File([t], ent.file));
|
||||
|
||||
const color = ColorNames.skyblue;
|
||||
const label = ent.label || ent.file.split('.')[0];
|
||||
|
||||
const sizeFactor = ent.sizeFactor || 1;
|
||||
const tags = ent.groups.map(({ id, root }) => `${root}:${id}`);
|
||||
const instances = ent.instances && getAssetInstances(ent.instances);
|
||||
|
||||
const description = ent.description;
|
||||
const label = ent.label || ent.file.split('.')[0];
|
||||
build = build
|
||||
.toRoot()
|
||||
.apply(ReadFile, { file, label, isBinary });
|
||||
@@ -233,7 +234,7 @@ export async function createGenericHierarchy(plugin: PluginContext, file: Asset.
|
||||
|
||||
build = build
|
||||
.apply(ModelFromTrajectory, { modelIndex: 0 })
|
||||
.apply(StructureFromGeneric, { instances, label })
|
||||
.apply(StructureFromGeneric, { instances, label, description })
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(color, sizeFactor, graphicsMode, clipVariant), { tags });
|
||||
} else if (['ply'].includes(info.ext)) {
|
||||
if (['ply'].includes(info.ext)) {
|
||||
@@ -250,9 +251,9 @@ export async function createGenericHierarchy(plugin: PluginContext, file: Asset.
|
||||
}
|
||||
await build.commit();
|
||||
|
||||
const rootId = `${manifest.roots[0].id}:`;
|
||||
const values = { type: 'group-generate', value: ColorNames.white, lightness: 0, alpha: 1 };
|
||||
await updateColors(plugin, values, rootId, '');
|
||||
const options = { ignoreLight: true, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 }, celShaded: true, };
|
||||
await updateColors(plugin, values, options);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(e);
|
||||
|
||||
@@ -186,9 +186,10 @@ const MmcifStructure = PluginStateTransform.BuiltIn({
|
||||
} else {
|
||||
structure = Structure.create(units);
|
||||
}
|
||||
|
||||
// could also use _struct_ref.pdbx_db_accession to point to uniprot with _struct_ref.db_name == UNP
|
||||
const label = entities.data.pdbx_description.value(idx).join(', ') || 'model';
|
||||
return new PSO.Molecule.Structure(structure, { label, description: `${a.description}` });
|
||||
const description = `*Entity id* ${entities.data.id.value(idx)} *src_method* ${entities.data.src_method.value(idx)} *type* ${entities.data.type.value(idx)}`;
|
||||
return new PSO.Molecule.Structure(structure, { label, description: description });
|
||||
});
|
||||
},
|
||||
update({ newParams, oldParams }) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { StateObjectRef, StateObjectSelector, StateBuilder } from '../../../../m
|
||||
import { Clip } from '../../../../mol-util/clip';
|
||||
import { Color } from '../../../../mol-util/color';
|
||||
import { ColorNames } from '../../../../mol-util/color/names';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getDistinctGroupColors, getGraphicsModeProps, getMesoscaleGroupParams } from '../state';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getDistinctGroupColors, getGraphicsModeProps, getMesoscaleGroupParams, updateColors } from '../state';
|
||||
import { MmcifAssembly, MmcifStructure } from './model';
|
||||
|
||||
function getSpacefillParams(color: Color, scaleFactor: number, graphics: GraphicsMode, clipVariant: Clip.Variant) {
|
||||
@@ -114,7 +114,7 @@ export async function createMmcifHierarchy(plugin: PluginContext, trajectory: St
|
||||
|
||||
const entRoot = await state.build()
|
||||
.toRoot()
|
||||
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'custom', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'custom', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
const getEntityType = (i: number) => {
|
||||
@@ -148,7 +148,7 @@ export async function createMmcifHierarchy(plugin: PluginContext, trajectory: St
|
||||
const color = colorIdx !== undefined ? baseEntColors[colorIdx] : ColorNames.white;
|
||||
const group = await state.build()
|
||||
.to(entRoot)
|
||||
.applyOrUpdateTagged(`group:ent:${t}`, MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `ent:${t}`, label: t, color: { type: 'generate', value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.applyOrUpdateTagged(`group:ent:${t}`, MesoscaleGroup, { ...groupParams, index: colorIdx, tag: `ent:${t}`, label: t, color: { type: 'generate', illustrative: false, value: color, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit({ revertOnError: true });
|
||||
entGroups.set(t, group);
|
||||
}
|
||||
@@ -172,6 +172,9 @@ export async function createMmcifHierarchy(plugin: PluginContext, trajectory: St
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(color, scaleFactor, graphicsMode, clipVariant), { tags: [`ent:${t}`] });
|
||||
}
|
||||
await build.commit();
|
||||
const values = { type: 'group-generate', value: ColorNames.white, lightness: 0, alpha: 1 };
|
||||
const options = { ignoreLight: true, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 }, celShaded: true, };
|
||||
await updateColors(plugin, values, options);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(e);
|
||||
|
||||
@@ -39,8 +39,10 @@ const StructureFromPetworld = PluginStateTransform.BuiltIn({
|
||||
|
||||
const { frame } = s.model.sourceData.data;
|
||||
const pdbx_model = frame.categories.pdbx_model.getField('name')!;
|
||||
const pdbx_description = frame.categories.pdbx_model.getField('description')!;
|
||||
const description = pdbx_description ? pdbx_description.str(params.modelIndex) : Structure.elementDescription(s);
|
||||
const label = pdbx_model.str(params.modelIndex);
|
||||
const props = { label, description: Structure.elementDescription(s) };
|
||||
const props = { label, description: description };
|
||||
return new SO.Molecule.Structure(s, props);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -11,7 +11,7 @@ import { SpacefillRepresentationProvider } from '../../../../mol-repr/structure/
|
||||
import { StructureRepresentation3D } from '../../../../mol-plugin-state/transforms/representation';
|
||||
import { PluginContext } from '../../../../mol-plugin/context';
|
||||
import { PluginStateObject } from '../../../../mol-plugin-state/objects';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getGraphicsModeProps, getMesoscaleGroupParams } from '../state';
|
||||
import { GraphicsMode, MesoscaleGroup, MesoscaleState, getDistinctBaseColors, getGraphicsModeProps, getMesoscaleGroupParams, updateColors } from '../state';
|
||||
import { ColorNames } from '../../../../mol-util/color/names';
|
||||
import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
|
||||
import { Task } from '../../../../mol-task';
|
||||
@@ -97,12 +97,12 @@ export async function createPetworldHierarchy(plugin: PluginContext, trajectory:
|
||||
|
||||
const group = await state.build()
|
||||
.toRoot()
|
||||
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'generate', value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.applyOrUpdateTagged('group:ent:', MesoscaleGroup, { ...groupParams, root: true, index: -1, tag: `ent:`, label: 'entity', color: { type: 'generate', illustrative: false, value: ColorNames.white, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: 'group:ent:', state: { isCollapsed: false, isHidden: groupParams.hidden } })
|
||||
.commit({ revertOnError: true });
|
||||
|
||||
await state.build()
|
||||
.to(group)
|
||||
.applyOrUpdateTagged(`group:ent:mem`, MesoscaleGroup, { ...groupParams, index: undefined, tag: `ent:mem`, label: 'Membrane', color: { type: 'uniform', value: ColorNames.lightgrey, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.applyOrUpdateTagged(`group:ent:mem`, MesoscaleGroup, { ...groupParams, index: undefined, tag: `ent:mem`, label: 'Membrane', color: { type: 'uniform', illustrative: false, value: ColorNames.lightgrey, variability: 20, shift: 0, lightness: 0, alpha: 1, emissive: 0 } }, { tags: `ent:`, state: { isCollapsed: true, isHidden: groupParams.hidden } })
|
||||
.commit();
|
||||
|
||||
const colors = getDistinctBaseColors(other.length, 0);
|
||||
@@ -124,6 +124,9 @@ export async function createPetworldHierarchy(plugin: PluginContext, trajectory:
|
||||
.apply(StructureRepresentation3D, getSpacefillParams(colors[i], graphicsMode), { tags: [`ent:`] });
|
||||
}
|
||||
await build.commit();
|
||||
const values = { type: 'group-generate', value: ColorNames.white, lightness: 0, alpha: 1 };
|
||||
const options = { ignoreLight: true, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 }, celShaded: true, };
|
||||
await updateColors(plugin, values, options);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
plugin.log.error(e);
|
||||
|
||||
@@ -24,6 +24,7 @@ import { SpacefillRepresentationProvider } from '../../../mol-repr/structure/rep
|
||||
import { assertUnreachable } from '../../../mol-util/type-helpers';
|
||||
import { MesoscaleExplorerState } from '../app';
|
||||
import { saturate } from '../../../mol-math/interpolate';
|
||||
import { Material } from '../../../mol-util/material';
|
||||
|
||||
function getHueRange(hue: number, variability: number) {
|
||||
let min = hue - variability;
|
||||
@@ -106,6 +107,7 @@ export function getDistinctBaseColors(count: number, shift: number, props?: Part
|
||||
|
||||
export const ColorParams = {
|
||||
type: PD.Select('generate', PD.arrayToOptions(['generate', 'uniform', 'custom'])),
|
||||
illustrative: PD.Boolean(false, { description: 'Illustrative style', hideIf: p => p.type === 'custom' }),
|
||||
value: PD.Color(Color(0xFFFFFF), { hideIf: p => p.type === 'custom' }),
|
||||
variability: PD.Numeric(20, { min: 1, max: 180, step: 1 }, { hideIf: p => p.type !== 'generate' }),
|
||||
shift: PD.Numeric(0, { min: 0, max: 100, step: 1 }, { hideIf: p => !p.type.includes('generate') }),
|
||||
@@ -119,6 +121,7 @@ export const ColorValueParam = PD.Color(Color(0xFFFFFF));
|
||||
|
||||
export const RootParams = {
|
||||
type: PD.Select('custom', PD.arrayToOptions(['group-generate', 'group-uniform', 'generate', 'uniform', 'custom'])),
|
||||
illustrative: PD.Boolean(false, { description: 'Illustrative style', hideIf: p => p.type === 'custom' }),
|
||||
value: PD.Color(Color(0xFFFFFF), { hideIf: p => p.type !== 'uniform' }),
|
||||
variability: PD.Numeric(20, { min: 1, max: 180, step: 1 }, { hideIf: p => p.type !== 'group-generate' }),
|
||||
shift: PD.Numeric(0, { min: 0, max: 100, step: 1 }, { hideIf: p => !p.type.includes('generate') }),
|
||||
@@ -132,6 +135,10 @@ export const LightnessParams = {
|
||||
};
|
||||
export const DimLightness = 6;
|
||||
|
||||
export const IllustrativeParams = {
|
||||
illustrative: PD.Boolean(false, { description: 'Illustrative style' }),
|
||||
};
|
||||
|
||||
export const OpacityParams = {
|
||||
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
};
|
||||
@@ -140,11 +147,24 @@ export const EmissiveParams = {
|
||||
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
|
||||
};
|
||||
|
||||
export const celShaded = {
|
||||
celShaded: PD.Boolean(false, { description: 'Cel Shading light for stylized rendering of representations' })
|
||||
};
|
||||
|
||||
export type celShadedProps = PD.Values<typeof celShaded>;
|
||||
|
||||
|
||||
export const PatternParams = {
|
||||
frequency: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
amplitude: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
|
||||
};
|
||||
|
||||
export const StyleParams = {
|
||||
ignoreLight: PD.Boolean(false, { description: 'Ignore light for stylized rendering of representations' }),
|
||||
materialStyle: Material.getParam(),
|
||||
celShaded: PD.Boolean(false, { description: 'Cel Shading light for stylized rendering of representations' }),
|
||||
};
|
||||
|
||||
export const LodParams = {
|
||||
lodLevels: Spheres.Params.lodLevels,
|
||||
cellSize: Spheres.Params.cellSize,
|
||||
@@ -250,6 +270,7 @@ export const MesoscaleGroupParams = {
|
||||
index: PD.Value<number>(-1, { isHidden: true }),
|
||||
tag: PD.Value<string>('', { isHidden: true }),
|
||||
label: PD.Value<string>('', { isHidden: true }),
|
||||
description: PD.Value<string>('', { isHidden: true }),
|
||||
hidden: PD.Boolean(false),
|
||||
color: PD.Group(RootParams),
|
||||
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
|
||||
@@ -271,7 +292,7 @@ export const MesoscaleGroup = PluginStateTransform.BuiltIn({
|
||||
})({
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Apply Mesoscale Group', async () => {
|
||||
return new MesoscaleGroupObject({}, { label: params.label });
|
||||
return new MesoscaleGroupObject({}, { label: params.label, description: params.description });
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -360,7 +381,10 @@ export const MesoscaleStateParams = {
|
||||
filter: PD.Value<string>('', { isHidden: true }),
|
||||
graphics: PD.Select('quality', PD.arrayToOptions(['ultra', 'quality', 'balanced', 'performance', 'custom'] as GraphicsMode[])),
|
||||
description: PD.Value<string>('', { isHidden: true }),
|
||||
focusInfo: PD.Value<string>('', { isHidden: true }),
|
||||
link: PD.Value<string>('', { isHidden: true }),
|
||||
textSizeDescription: PD.Numeric(14, { min: 1, max: 100, step: 1 }, { isHidden: true }),
|
||||
index: PD.Value<number>(-1, { isHidden: true })
|
||||
};
|
||||
|
||||
export class MesoscaleStateObject extends PSO.Create<MesoscaleState>({ name: 'Mesoscale State', typeClass: 'Object' }) { }
|
||||
@@ -447,7 +471,7 @@ export function getAllGroups(plugin: PluginContext, tag?: string) {
|
||||
return _getAllGroups(plugin, tag, []);
|
||||
}
|
||||
|
||||
export function getAllLeafGroups(plugin: PluginContext, tag: string) {
|
||||
export function getAllLeafGroups(plugin: PluginContext, tag: string | undefined) {
|
||||
const allGroups = getAllGroups(plugin, tag);
|
||||
allGroups.sort((a, b) => a.params?.values.index - b.params?.values.index);
|
||||
return allGroups.filter(g => {
|
||||
@@ -481,7 +505,8 @@ function getFilterMatcher(filter: string) {
|
||||
: new RegExp(escapeRegExp(filter), 'gi');
|
||||
}
|
||||
|
||||
export function getFilteredEntities(plugin: PluginContext, tag: string, filter: string) {
|
||||
export function getFilteredEntities(plugin: PluginContext, tag: string | undefined, filter: string | undefined) {
|
||||
if (!filter) return getEntities(plugin, tag);
|
||||
const matcher = getFilterMatcher(filter);
|
||||
return getEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
|
||||
}
|
||||
@@ -498,21 +523,42 @@ export function getAllEntities(plugin: PluginContext, tag?: string) {
|
||||
return _getAllEntities(plugin, tag, []);
|
||||
}
|
||||
|
||||
export function getAllFilteredEntities(plugin: PluginContext, tag: string, filter: string) {
|
||||
export function getAllFilteredEntities(plugin: PluginContext, tag: string | undefined, filter: string | undefined) {
|
||||
if (!filter) return getAllEntities(plugin, tag);
|
||||
const matcher = getFilterMatcher(filter);
|
||||
return getAllEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
|
||||
}
|
||||
|
||||
export function getEveryEntity(plugin: PluginContext, filter?: string, tag?: string) {
|
||||
if (filter) {
|
||||
const matcher = getFilterMatcher(filter);
|
||||
return getAllEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
|
||||
} else {
|
||||
return getAllEntities(plugin, tag);
|
||||
}
|
||||
}
|
||||
|
||||
export function getEntityLabel(plugin: PluginContext, cell: StateObjectCell) {
|
||||
return StateObjectRef.resolve(plugin.state.data, cell.transform.parent)?.obj?.label || 'Entity';
|
||||
}
|
||||
|
||||
//
|
||||
export function getCellDescription(cell: StateObjectCell) {
|
||||
// markdown style for description
|
||||
return '**' + cell?.obj?.label + '**\n\n' + cell?.obj?.description;
|
||||
}
|
||||
|
||||
export async function updateColors(plugin: PluginContext, values: PD.Values, tag: string, filter: string) {
|
||||
export function getEntityDescription(plugin: PluginContext, cell: StateObjectCell) {
|
||||
const s = StateObjectRef.resolve(plugin.state.data, cell.transform.parent);
|
||||
const d = getCellDescription(s!);
|
||||
return d;
|
||||
}
|
||||
|
||||
|
||||
export async function updateColors(plugin: PluginContext, values: PD.Values, options?: PD.Values, tag?: string, filter?: string) {
|
||||
const update = plugin.state.data.build();
|
||||
const { type, value, shift, lightness, alpha, emissive } = values;
|
||||
|
||||
const { type, illustrative, value, shift, lightness, alpha, emissive } = values;
|
||||
const doLighting = (options !== undefined);
|
||||
const { ignoreLight, materialStyle: material, celShaded } = options ? options : { ignoreLight: true, materialStyle: { metalness: 0, roughness: 0.2, bumpiness: 0 }, celShaded: false };
|
||||
if (type === 'group-generate' || type === 'group-uniform') {
|
||||
const groups = getAllLeafGroups(plugin, tag);
|
||||
const baseColors = getDistinctBaseColors(groups.length, shift);
|
||||
@@ -531,11 +577,19 @@ export async function updateColors(plugin: PluginContext, values: PD.Values, tag
|
||||
const c = type === 'group-generate' ? groupColors[j] : baseColors[i];
|
||||
update.to(entities[j]).update(old => {
|
||||
if (old.type) {
|
||||
old.colorTheme.params.value = c;
|
||||
old.colorTheme.params.lightness = lightness;
|
||||
if (illustrative) {
|
||||
old.colorTheme = { name: 'illustrative', params: { style: { name: 'uniform', params: { value: c, lightness: lightness } } } };
|
||||
} else {
|
||||
old.colorTheme = { name: 'uniform', params: { value: c, lightness: lightness } };
|
||||
}
|
||||
old.type.params.alpha = alpha;
|
||||
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
|
||||
old.type.params.emissive = emissive;
|
||||
if (doLighting) {
|
||||
old.type.params.ignoreLight = ignoreLight;
|
||||
old.type.params.material = material;
|
||||
old.type.params.celShaded = celShaded;
|
||||
}
|
||||
} else if (old.coloring) {
|
||||
old.coloring.params.color = c;
|
||||
old.coloring.params.lightness = lightness;
|
||||
@@ -548,6 +602,7 @@ export async function updateColors(plugin: PluginContext, values: PD.Values, tag
|
||||
|
||||
update.to(g.transform.ref).update(old => {
|
||||
old.color.type = type === 'group-generate' ? 'generate' : 'uniform';
|
||||
old.color.illustrative = illustrative;
|
||||
old.color.value = baseColors[i];
|
||||
old.color.lightness = lightness;
|
||||
old.color.alpha = alpha;
|
||||
@@ -566,11 +621,19 @@ export async function updateColors(plugin: PluginContext, values: PD.Values, tag
|
||||
const c = type === 'generate' ? groupColors[j] : value;
|
||||
update.to(entities[j]).update(old => {
|
||||
if (old.type) {
|
||||
old.colorTheme.params.value = c;
|
||||
old.colorTheme.params.lightness = lightness;
|
||||
if (illustrative) {
|
||||
old.colorTheme = { name: 'illustrative', params: { style: { name: 'uniform', params: { value: c, lightness: lightness } } } };
|
||||
} else {
|
||||
old.colorTheme = { name: 'uniform', params: { value: c, lightness: lightness } };
|
||||
}
|
||||
old.type.params.alpha = alpha;
|
||||
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
|
||||
old.type.params.emissive = emissive;
|
||||
if (doLighting) {
|
||||
old.type.params.ignoreLight = ignoreLight;
|
||||
old.type.params.material = material;
|
||||
old.type.params.celShaded = celShaded;
|
||||
}
|
||||
} else if (old.coloring) {
|
||||
old.coloring.params.color = c;
|
||||
old.coloring.params.lightness = lightness;
|
||||
@@ -585,6 +648,7 @@ export async function updateColors(plugin: PluginContext, values: PD.Values, tag
|
||||
for (const o of others) {
|
||||
update.to(o).update(old => {
|
||||
old.color.type = type === 'generate' ? 'custom' : 'uniform';
|
||||
old.color.illustrative = illustrative;
|
||||
old.color.value = value;
|
||||
old.color.lightness = lightness;
|
||||
old.color.alpha = alpha;
|
||||
@@ -603,3 +667,17 @@ export function expandAllGroups(plugin: PluginContext) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export async function updateReprParams(plugin: PluginContext, options: PD.Values) {
|
||||
const update = plugin.state.data.build();
|
||||
const { ignoreLight, materialStyle: material, celShaded } = options;
|
||||
const entities = getAllEntities(plugin);
|
||||
for (let j = 0; j < entities.length; ++j) {
|
||||
update.to(entities[j]).update(old => {
|
||||
old.type.params.ignoreLight = ignoreLight;
|
||||
old.type.params.material = material;
|
||||
old.type.params.celShaded = celShaded;
|
||||
});
|
||||
}
|
||||
await update.commit();
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||
<link rel="stylesheet" href="./extras/driver.css"/>
|
||||
<title>Mol* Mesoscale Explorer</title>
|
||||
<style>
|
||||
* {
|
||||
@@ -38,8 +39,11 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="./extras/driver.js.iife.js"></script>
|
||||
<script type="text/javascript" src="./molstar.js"></script>
|
||||
<script type="text/javascript">
|
||||
const driver = window.driver ? window.driver.js.driver() : undefined;
|
||||
|
||||
function getParam(name, regex) {
|
||||
var r = new RegExp(name + '=' + '(' + regex + ')[&]?', 'i');
|
||||
return decodeURIComponent(((window.location.search || '').match(r) || [])[1] || '');
|
||||
@@ -64,6 +68,7 @@
|
||||
allowMajorPerformanceCaveat: allowMajorPerformanceCaveat,
|
||||
powerPreference: powerPreference || 'high-performance',
|
||||
graphicsMode: graphicsMode || 'quality',
|
||||
driver: driver
|
||||
}).then(me => {
|
||||
var example = getParam('example', '[^&]+').trim();
|
||||
if (example) {
|
||||
@@ -98,4 +103,4 @@
|
||||
</script>
|
||||
<!-- __MOLSTAR_ANALYTICS__ -->
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -24,6 +24,7 @@ $logo-background: rgba(0,0,0,0.75);
|
||||
}
|
||||
|
||||
@import 'mol-plugin-ui/skin/base/base';
|
||||
@import 'mol-plugin-ui/skin/base/variables';
|
||||
|
||||
a {
|
||||
color: $font-color;
|
||||
@@ -31,3 +32,35 @@ a {
|
||||
color: $hover-font-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.msp-snapshot-description-me {
|
||||
background: rgba(red($default-background), green($default-background), blue($default-background), 0.5);
|
||||
|
||||
position: absolute;
|
||||
height: 50vh; // 50% of the viewport height
|
||||
left: 0;
|
||||
top: $control-spacing + $row-height;
|
||||
padding: (0.66 * $control-spacing) $control-spacing;
|
||||
|
||||
resize: both; /* Allows resizing in both directions */
|
||||
overflow: auto; /* Adjust as needed */
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
color: $font-color;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
padding-left: $control-spacing + 4px;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.shown {
|
||||
display: block; // or 'flex', 'grid', etc. depending on your layout
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import { PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { PluginReactContext, PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { Button, ControlGroup, IconButton } from '../../../mol-plugin-ui/controls/common';
|
||||
import { ArrowDropDownSvg, ArrowRightSvg, CloseSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg, ContentCutSvg, BrushSvg, SearchSvg } from '../../../mol-plugin-ui/controls/icons';
|
||||
import { ArrowDropDownSvg, ArrowRightSvg, CloseSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg, ContentCutSvg, BrushSvg, SearchSvg, TooltipTextSvg, TooltipTextOutlineSvg, PlusBoxSvg, MinusBoxSvg } from '../../../mol-plugin-ui/controls/icons';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { State, StateObjectCell, StateSelection, StateTransformer } from '../../../mol-state';
|
||||
import { ParameterControls, ParameterMappingControl, ParamOnChange, SelectControl } from '../../../mol-plugin-ui/controls/parameters';
|
||||
@@ -18,14 +18,17 @@ import { CombinedColorControl } from '../../../mol-plugin-ui/controls/color';
|
||||
import { MarkerAction } from '../../../mol-util/marker-action';
|
||||
import { EveryLoci, Loci } from '../../../mol-model/loci';
|
||||
import { deepEqual } from '../../../mol-util';
|
||||
import { ColorValueParam, ColorParams, ColorProps, DimLightness, LightnessParams, LodParams, MesoscaleGroup, MesoscaleGroupProps, OpacityParams, SimpleClipParams, SimpleClipProps, createClipMapping, getClipObjects, getDistinctGroupColors, RootParams, MesoscaleState, getRoots, getAllGroups, getAllLeafGroups, getFilteredEntities, getAllFilteredEntities, getGroups, getEntities, getAllEntities, getEntityLabel, updateColors, getGraphicsModeProps, GraphicsMode, MesoscaleStateParams, setGraphicsCanvas3DProps, PatternParams, expandAllGroups, EmissiveParams } from '../data/state';
|
||||
import React from 'react';
|
||||
import { ColorValueParam, ColorParams, ColorProps, DimLightness, LightnessParams, LodParams, MesoscaleGroup, MesoscaleGroupProps, OpacityParams, SimpleClipParams, SimpleClipProps, createClipMapping, getClipObjects, getDistinctGroupColors, RootParams, MesoscaleState, getRoots, getAllGroups, getAllLeafGroups, getFilteredEntities, getAllFilteredEntities, getGroups, getEntities, getAllEntities, getEntityLabel, updateColors, getGraphicsModeProps, GraphicsMode, MesoscaleStateParams, setGraphicsCanvas3DProps, PatternParams, expandAllGroups, EmissiveParams, IllustrativeParams, getCellDescription, getEntityDescription, getEveryEntity } from '../data/state';
|
||||
import React, { useState } from 'react';
|
||||
import { MesoscaleExplorerState } from '../app';
|
||||
import { StructureElement } from '../../../mol-model/structure/structure/element';
|
||||
import { PluginStateObject as PSO } from '../../../mol-plugin-state/objects';
|
||||
import { Structure } from '../../../mol-model/structure';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { Sphere3D } from '../../../mol-math/geometry';
|
||||
import { MesoFocusLoci } from '../behavior/camera';
|
||||
import Markdown from 'react-markdown';
|
||||
import { combineLatest } from 'rxjs';
|
||||
|
||||
function centerLoci(plugin: PluginContext, loci: Loci, durationMs = 250) {
|
||||
const { canvas3d } = plugin;
|
||||
@@ -60,6 +63,7 @@ export class ModelInfo extends PluginUIComponent<{}, { isDisabled: boolean }> {
|
||||
if (!state.description && !state.link) return;
|
||||
|
||||
return {
|
||||
selectionDescription: state.focusInfo,
|
||||
description: state.description,
|
||||
link: state.link,
|
||||
};
|
||||
@@ -68,7 +72,7 @@ export class ModelInfo extends PluginUIComponent<{}, { isDisabled: boolean }> {
|
||||
render() {
|
||||
const info = this.info;
|
||||
return info && <>
|
||||
<div className='msp-help-text'>
|
||||
<div id='modelinfo' className='msp-help-text'>
|
||||
<div>{info.description}</div>
|
||||
<div><a href={info.link} target='_blank'>Source</a></div>
|
||||
</div>
|
||||
@@ -101,17 +105,20 @@ export class SelectionInfo extends PluginUIComponent<{}, { isDisabled: boolean }
|
||||
}
|
||||
|
||||
get info() {
|
||||
const info: { label: string, key: string }[] = [];
|
||||
const infos: { label: string, key: string, description?: string }[] = [];
|
||||
this.plugin.managers.structure.selection.entries.forEach((e, k) => {
|
||||
if (StructureElement.Loci.is(e.selection) && !StructureElement.Loci.isEmpty(e.selection)) {
|
||||
const cell = this.plugin.helpers.substructureParent.get(e.selection.structure);
|
||||
info.push({
|
||||
const { entities } = e.selection.structure.model;
|
||||
const description = entities.data.pdbx_description.value(0)[0] || 'model';
|
||||
infos.push({
|
||||
description: description,
|
||||
label: cell?.obj?.label || 'Unknown',
|
||||
key: k,
|
||||
});
|
||||
}
|
||||
});
|
||||
return info;
|
||||
return infos;
|
||||
}
|
||||
|
||||
find(label: string) {
|
||||
@@ -133,31 +140,40 @@ export class SelectionInfo extends PluginUIComponent<{}, { isDisabled: boolean }
|
||||
|
||||
const loci = Structure.toStructureElementLoci(e.selection.structure);
|
||||
centerLoci(this.plugin, loci);
|
||||
const cell = this.plugin.helpers.substructureParent.get(loci.structure);
|
||||
const d = getCellDescription(cell!); // '### ' + cell?.obj?.label + '\n\n' + cell?.obj?.description;
|
||||
MesoscaleState.set(this.plugin, { focusInfo: `${d}` });
|
||||
}
|
||||
|
||||
get selection() {
|
||||
const info = this.info;
|
||||
const help_selection = <><div>Use <i>ctrl+left</i> to select entities, either on the 3D canvas or in the tree below</div><div>Use <i>shift+left</i> to select individual chain on the 3D canvas</div></>;
|
||||
if (!info.length) return <>
|
||||
<div className='msp-help-text'>
|
||||
<div>Use <i>ctrl+left click</i> to select entities, either on the 3D canvas or in the tree below</div>
|
||||
<div id='seleinfo' className='msp-help-text'>
|
||||
{help_selection}
|
||||
</div>
|
||||
</>;
|
||||
|
||||
return <>
|
||||
{info.map((entry, index) => {
|
||||
const label = <Button className={`msp-btn-tree-label`} noOverflow disabled={this.state.isDisabled}
|
||||
onClick={() => this.center(entry.key)}
|
||||
>
|
||||
<span title={entry.label}>{entry.label}</span>
|
||||
</Button>;
|
||||
const find = <IconButton svg={SearchSvg} toggleState={false} disabled={this.state.isDisabled} small onClick={() => this.find(entry.label)} />;
|
||||
const remove = <IconButton svg={CloseSvg} toggleState={false} disabled={this.state.isDisabled} onClick={() => this.remove(entry.key)} />;
|
||||
return <div key={index} className={`msp-flex-row`} style={{ margin: `1px 5px 1px ${1 * 10 + 5}px` }}>
|
||||
{label}
|
||||
{find}
|
||||
{remove}
|
||||
</div>;
|
||||
})}
|
||||
<div id='seleinfo'>
|
||||
{info.map((entry, index) => {
|
||||
const label = <Button className={`msp-btn-tree-label`} noOverflow disabled={this.state.isDisabled}
|
||||
onClick={() => this.center(entry.key)}
|
||||
>
|
||||
<span title={entry.label}>
|
||||
{entry.label}
|
||||
</span>
|
||||
</Button>;
|
||||
const find = <IconButton svg={SearchSvg} toggleState={false} disabled={this.state.isDisabled} small onClick={() => this.find(entry.label)} />;
|
||||
const remove = <IconButton svg={CloseSvg} toggleState={false} disabled={this.state.isDisabled} onClick={() => this.remove(entry.key)} />;
|
||||
return <>
|
||||
<div key={index} className={`msp-flex-row`} style={{ margin: `1px 5px 1px ${1 * 10 + 5}px` }}>
|
||||
{label}
|
||||
{find}
|
||||
{remove}
|
||||
</div>
|
||||
</>;
|
||||
})};
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
|
||||
@@ -216,7 +232,7 @@ export class SelectionInfo extends PluginUIComponent<{}, { isDisabled: boolean }
|
||||
|
||||
renderStyle() {
|
||||
const style = this.style || '';
|
||||
return <div style={{ margin: '5px', marginBottom: '10px' }}>
|
||||
return <div id='selestyle' style={{ margin: '5px', marginBottom: '10px' }}>
|
||||
<SelectControl name={'Style'} param={SelectionStyleParam} value={style} onChange={(e) => { this.setStyle(e.value); }} />
|
||||
</div>;
|
||||
}
|
||||
@@ -229,6 +245,192 @@ export class SelectionInfo extends PluginUIComponent<{}, { isDisabled: boolean }
|
||||
}
|
||||
}
|
||||
|
||||
export function MesoMarkdownAnchor({ href, children, element }: { href?: string, children?: any, element?: any }) {
|
||||
const plugin = React.useContext(PluginReactContext);
|
||||
if (!href) return element;
|
||||
// Decode the href to handle encoded spaces and other characters
|
||||
const decodedHref = href ? decodeURIComponent(href) : '';
|
||||
const handleHover = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
if (decodedHref.startsWith('i')) {
|
||||
plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
|
||||
const query_names = decodedHref.substring(1).split(',');
|
||||
for (const query_name of query_names) {
|
||||
const entities = getEveryEntity(plugin, query_name);
|
||||
for (const r of entities) {
|
||||
const repr = r.obj?.data.repr;
|
||||
if (repr) {
|
||||
plugin.canvas3d?.mark({ repr, loci: EveryLoci }, MarkerAction.Highlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (decodedHref.startsWith('g')) {
|
||||
plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
|
||||
const qindex = decodedHref.indexOf('.');
|
||||
const query = decodedHref.substring(1, qindex) + ':';
|
||||
const query_names = decodedHref.substring(qindex + 1).split(',');
|
||||
for (const query_name of query_names) {
|
||||
const e = getAllEntities(plugin, query + query_name);
|
||||
for (const r of e) {
|
||||
const repr = r.obj?.data.repr;
|
||||
if (repr) {
|
||||
plugin.canvas3d?.mark({ repr, loci: EveryLoci }, MarkerAction.Highlight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleLeave = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
// Implement your hover off logic here
|
||||
// Example: Perform an action if the href starts with 'h'
|
||||
if (decodedHref.startsWith('i') || decodedHref.startsWith('g')) {
|
||||
// Example hover off action
|
||||
e.preventDefault();
|
||||
plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
|
||||
}
|
||||
};
|
||||
const handleClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
if (href.startsWith('#')) {
|
||||
plugin.managers.snapshot.applyKey(decodedHref.substring(1));
|
||||
} else if (decodedHref.startsWith('i')) {
|
||||
plugin.managers.interactivity.lociSelects.deselectAll();
|
||||
plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
|
||||
const query_names = decodedHref.substring(1).split(',');
|
||||
for (const query_name of query_names) {
|
||||
const entities = getFilteredEntities(plugin, '', query_name);
|
||||
for (const r of entities) {
|
||||
const repr = r.obj?.data.repr;
|
||||
if (repr) {
|
||||
plugin.canvas3d?.mark({ repr, loci: EveryLoci }, MarkerAction.Highlight);
|
||||
}
|
||||
const cell = r as StateObjectCell<PSO.Molecule.Structure.Representation3D | PSO.Shape.Representation3D> | undefined;
|
||||
if (!(cell?.obj?.data.sourceData instanceof Structure)) {
|
||||
return;
|
||||
}
|
||||
const loci = Structure.toStructureElementLoci(cell.obj.data.sourceData);
|
||||
plugin.managers.interactivity.lociSelects.toggle({ loci }, false);
|
||||
}
|
||||
}
|
||||
} else if (decodedHref.startsWith('g')) {
|
||||
plugin.managers.interactivity.lociSelects.deselectAll();
|
||||
plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
|
||||
const qindex = decodedHref.indexOf('.');
|
||||
const query = decodedHref.substring(1, qindex) + ':';
|
||||
const query_names = decodedHref.substring(qindex + 1).split(',');
|
||||
for (const query_name of query_names) {
|
||||
const entities = getAllEntities(plugin, query + query_name);
|
||||
for (const r of entities) {
|
||||
const repr = r.obj?.data.repr;
|
||||
if (repr) {
|
||||
plugin.canvas3d?.mark({ repr, loci: EveryLoci }, MarkerAction.Highlight);
|
||||
}
|
||||
const cell = r as StateObjectCell<PSO.Molecule.Structure.Representation3D | PSO.Shape.Representation3D> | undefined;
|
||||
if (!(cell?.obj?.data.sourceData instanceof Structure)) return;
|
||||
const loci = Structure.toStructureElementLoci(cell.obj.data.sourceData);
|
||||
plugin.managers.interactivity.lociSelects.toggle({ loci }, false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// open the link in a new tab
|
||||
window.open(decodedHref, '_blank');
|
||||
}
|
||||
};
|
||||
|
||||
if (decodedHref[0] === '#') {
|
||||
return <a href={decodedHref[0]} onMouseOver={handleHover} onClick={handleClick}>{children}</a>;
|
||||
}
|
||||
if (decodedHref[0] === 'i' || decodedHref[0] === 'g') {
|
||||
return <a href={decodedHref[0]} onMouseLeave={handleLeave} onMouseOver={handleHover} onClick={handleClick}>{children}</a>;
|
||||
}
|
||||
if (decodedHref[0] === 'h') {
|
||||
return <a href={decodedHref[0]} onClick={handleClick} rel='noopener noreferrer'>{children}</a>;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
export function MesoViewportSnapshotDescription() {
|
||||
let tSize = 14;
|
||||
const plugin = React.useContext(PluginReactContext);
|
||||
if (MesoscaleState.has(plugin)) {
|
||||
const state = MesoscaleState.get(plugin);
|
||||
tSize = state.textSizeDescription;
|
||||
}
|
||||
const [_, setV] = React.useState(0);
|
||||
const [isShown, setIsShown] = useState(true);
|
||||
const [textSize, setTextSize] = useState(tSize);
|
||||
const toggleVisibility = () => {
|
||||
setIsShown(!isShown);
|
||||
};
|
||||
|
||||
const increaseTextSize = () => {
|
||||
setTextSize(prevSize => Math.min(prevSize + 2, 50)); // Increase the text size by 2px, but not above 50px
|
||||
};
|
||||
|
||||
const decreaseTextSize = () => {
|
||||
setTextSize(prevSize => Math.max(prevSize - 2, 2)); // Decrease the text size by 2px, but not below 2px
|
||||
};
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
const sub = plugin.managers.snapshot.events.changed.subscribe(() => setV(v => v + 1));
|
||||
return () => sub.unsubscribe();
|
||||
}, [plugin]);
|
||||
|
||||
const current = plugin.managers.snapshot.state.current;
|
||||
if (!current) return null;
|
||||
|
||||
const e = plugin.managers.snapshot.getEntry(current)!;
|
||||
if (!e?.description?.trim()) return null;
|
||||
if (MesoscaleState.has(plugin)) {
|
||||
MesoscaleState.set(plugin, { textSizeDescription: textSize });
|
||||
}
|
||||
const showInfo = <IconButton svg={isShown ? TooltipTextSvg : TooltipTextOutlineSvg} flex='20px' onClick={toggleVisibility} title={isShown ? 'Hide Description' : 'Show Description'}/>;
|
||||
const increasePoliceSize = <IconButton svg={PlusBoxSvg} flex='20px' onClick={increaseTextSize} title='Bigger Text' />;
|
||||
const decreasePoliceSize = <IconButton svg={MinusBoxSvg} flex='20px' onClick={decreaseTextSize} title='Smaller Text' />;
|
||||
return (
|
||||
<>
|
||||
<div id='snapinfoctrl' className="msp-state-snapshot-viewport-controls" style={{ marginRight: '30px' }}>
|
||||
{showInfo}{increasePoliceSize}{decreasePoliceSize}
|
||||
</div>
|
||||
<div id='snapinfo' className={`msp-snapshot-description-me ${isShown ? 'shown' : 'hidden'}`} style={{ fontSize: `${textSize}px` }}>
|
||||
{<Markdown skipHtml={false} components={{ a: MesoMarkdownAnchor }}>{e.description}</Markdown>}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export class FocusInfo extends PluginUIComponent<{}, { isDisabled: boolean }> {
|
||||
componentDidMount() {
|
||||
this.subscribe(combineLatest([
|
||||
this.plugin.state.data.behaviors.isUpdating,
|
||||
this.plugin.managers.structure.selection.events.changed
|
||||
]), ([isUpdating]) => {
|
||||
if (!isUpdating) this.forceUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
get info() {
|
||||
let focusInfo = '';
|
||||
if (MesoscaleState.has(this.plugin)) {
|
||||
const state = MesoscaleState.get(this.plugin);
|
||||
if (state.focusInfo) focusInfo = state.focusInfo;
|
||||
}
|
||||
return focusInfo;
|
||||
}
|
||||
|
||||
render() {
|
||||
const focusInfo = this.info;
|
||||
const description = (focusInfo !== '') ? <Markdown skipHtml components={{ a: MesoMarkdownAnchor }}>{focusInfo}</Markdown> : '';
|
||||
return <>
|
||||
<div id='focusinfo' className='msp-help-text'>
|
||||
{description}
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class EntityControls extends PluginUIComponent<{}, { isDisabled: boolean }> {
|
||||
filterRef = React.createRef<HTMLInputElement>();
|
||||
prevFilter = '';
|
||||
@@ -336,7 +538,7 @@ export class EntityControls extends PluginUIComponent<{}, { isDisabled: boolean
|
||||
|
||||
renderGraphics() {
|
||||
const graphics = this.graphics;
|
||||
return <div style={{ margin: '5px', marginBottom: '10px' }}>
|
||||
return <div id='graphicsquality' style={{ margin: '5px', marginBottom: '10px' }}>
|
||||
<SelectControl name={'Graphics'} param={MesoscaleStateParams.graphics} value={`${graphics}`} onChange={(e) => { this.setGraphics(e.value); }} />
|
||||
</div>;
|
||||
}
|
||||
@@ -363,7 +565,7 @@ export class EntityControls extends PluginUIComponent<{}, { isDisabled: boolean
|
||||
|
||||
return <>
|
||||
{this.renderGraphics()}
|
||||
<div className={`msp-flex-row msp-control-row`} style={{ margin: '5px', marginBottom: '10px' }}>
|
||||
<div id='searchtree' className={`msp-flex-row msp-control-row`} style={{ margin: '5px', marginBottom: '10px' }}>
|
||||
<input type='text' ref={this.filterRef}
|
||||
value={filter}
|
||||
placeholder='Search'
|
||||
@@ -373,10 +575,12 @@ export class EntityControls extends PluginUIComponent<{}, { isDisabled: boolean
|
||||
/>
|
||||
<IconButton svg={CloseSvg} toggleState={false} disabled={disabled} onClick={() => this.setFilter('')} />
|
||||
</div>
|
||||
{options.length > 1 && <div style={{ margin: '5px', marginBottom: '10px' }}>
|
||||
{options.length > 1 && <div id='grouptree' style={{ margin: '5px', marginBottom: '10px' }}>
|
||||
<SelectControl name={'Group By'} param={groupParam} value={`${groupBy}`} onChange={(e) => { this.setGroupBy(parseInt(e.value)); }} />
|
||||
</div>}
|
||||
<GroupNode filter={filter} cell={root} depth={0} />
|
||||
<div id='tree' style={{ position: 'relative', overflowY: 'auto', borderBottom: '1px solid #000', maxHeight: '600px' }}>
|
||||
<GroupNode filter={filter} cell={root} depth={0} />
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
}
|
||||
@@ -435,6 +639,12 @@ export class GroupNode extends Node<{ filter: string }, { isCollapsed: boolean,
|
||||
this.setState({ action: this.state.action === 'root' ? undefined : 'root' });
|
||||
};
|
||||
|
||||
showInfo = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
const d = getCellDescription(this.cell); // '### ' + this.cell?.obj?.label + '\n\n' + this.cell?.obj?.description;
|
||||
MesoscaleState.set(this.plugin, { focusInfo: `${d}` });
|
||||
};
|
||||
|
||||
highlight = (e: React.MouseEvent<HTMLElement>) => {
|
||||
e.preventDefault();
|
||||
this.plugin.canvas3d?.mark({ loci: EveryLoci }, MarkerAction.RemoveHighlight);
|
||||
@@ -493,7 +703,7 @@ export class GroupNode extends Node<{ filter: string }, { isCollapsed: boolean,
|
||||
|
||||
updateColor = (values: ColorProps) => {
|
||||
const update = this.plugin.state.data.build();
|
||||
const { value, type, lightness, alpha, emissive } = values;
|
||||
const { value, illustrative, type, lightness, alpha, emissive } = values;
|
||||
|
||||
const entities = this.filteredEntities;
|
||||
|
||||
@@ -507,8 +717,11 @@ export class GroupNode extends Node<{ filter: string }, { isCollapsed: boolean,
|
||||
const c = type === 'generate' ? groupColors[i] : value;
|
||||
update.to(entities[i]).update(old => {
|
||||
if (old.type) {
|
||||
old.colorTheme.params.value = c;
|
||||
old.colorTheme.params.lightness = lightness;
|
||||
if (illustrative) {
|
||||
old.colorTheme = { name: 'illustrative', params: { style: { name: 'uniform', params: { value: c, lightness } } } };
|
||||
} else {
|
||||
old.colorTheme = { name: 'uniform', params: { value: c, lightness } };
|
||||
}
|
||||
old.type.params.alpha = alpha;
|
||||
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
|
||||
old.type.params.emissive = emissive;
|
||||
@@ -536,7 +749,7 @@ export class GroupNode extends Node<{ filter: string }, { isCollapsed: boolean,
|
||||
};
|
||||
|
||||
updateRoot = async (values: PD.Values) => {
|
||||
await updateColors(this.plugin, values, this.cell.params?.values.tag, this.props.filter);
|
||||
await updateColors(this.plugin, values, undefined, this.cell.params?.values.tag, this.props.filter);
|
||||
|
||||
const update = this.plugin.state.data.build();
|
||||
|
||||
@@ -655,6 +868,7 @@ export class GroupNode extends Node<{ filter: string }, { isCollapsed: boolean,
|
||||
const label = <Button className={`msp-btn-tree-label`} noOverflow disabled={disabled}
|
||||
onMouseEnter={this.highlight}
|
||||
onMouseLeave={this.clearHighlight}
|
||||
onClick={this.showInfo}
|
||||
>
|
||||
<span title={groupLabel}>{groupLabel}</span>
|
||||
</Button>;
|
||||
@@ -774,18 +988,63 @@ export class EntityNode extends Node<{}, { action?: 'color' | 'clip', isDisabled
|
||||
if (e.ctrlKey) {
|
||||
this.toggleSelect(e);
|
||||
} else {
|
||||
this.center(e);
|
||||
const d = getEntityDescription(this.plugin, this.cell);
|
||||
if (this.cell?.obj?.data.sourceData.state.models.length !== 0) {
|
||||
const repr = this.cell?.obj?.data.repr;
|
||||
if (repr) {
|
||||
// for fiber need to think how to handle.
|
||||
const aloci = repr.getAllLoci()[0];
|
||||
const locis = Loci.normalize(aloci, 'chainInstances') as StructureElement.Loci;
|
||||
const nChain = aloci.structure.state.unitSymmetryGroups.length;
|
||||
let index = MesoscaleState.get(this.plugin).index + 1;
|
||||
if (index * nChain >= locis.elements.length) index = 0;
|
||||
const elems = locis.elements.slice(index * nChain, ((index + 1) * nChain)); // end index is not included
|
||||
const loci = StructureElement.Loci(aloci.structure, elems);
|
||||
const sphere = Loci.getBoundingSphere(loci) || Sphere3D();
|
||||
const state = this.plugin.state.behaviors;
|
||||
const selections = state.select(StateSelection.Generators.ofTransformer(MesoFocusLoci));
|
||||
const params = selections.length === 1 ? selections[0].obj?.data.params : undefined;
|
||||
if (!params.centerOnly) {
|
||||
this.plugin.managers.camera.focusSphere(sphere, params);
|
||||
} else {
|
||||
const snapshot = this.plugin.canvas3d?.camera.getCenter(sphere.center);
|
||||
this.plugin.canvas3d?.requestCameraReset({ durationMs: params.durationMs, snapshot });
|
||||
}
|
||||
MesoscaleState.set(this.plugin, { index: index, focusInfo: `${d}` });
|
||||
}
|
||||
} else {
|
||||
this.center(e);
|
||||
MesoscaleState.set(this.plugin, { focusInfo: `${d}` });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
get colorValue(): Color | undefined {
|
||||
return this.cell.transform.params?.colorTheme?.params.value ?? this.cell.transform.params?.coloring?.params.color;
|
||||
if (this.cell.transform.params?.colorTheme?.params.value) {
|
||||
return this.cell.transform.params?.colorTheme?.params.value;
|
||||
} else if (this.cell.transform.params?.colorTheme?.name === 'illustrative') {
|
||||
return this.cell.transform.params?.colorTheme?.params.style.params.value;
|
||||
} else {
|
||||
return this.cell.transform.params?.colorTheme?.params.value ?? this.cell.transform.params?.coloring?.params.color;
|
||||
}
|
||||
}
|
||||
|
||||
get illustrativeValue(): { illustrative: boolean } | undefined {
|
||||
return {
|
||||
illustrative: (this.cell.transform.params?.colorTheme?.name === 'illustrative')
|
||||
};
|
||||
}
|
||||
|
||||
get lightnessValue(): { lightness: number } | undefined {
|
||||
return {
|
||||
lightness: this.cell.transform.params?.colorTheme?.params.lightness ?? this.cell.transform.params?.coloring?.params.lightness ?? 0
|
||||
};
|
||||
if (this.cell.transform.params?.colorTheme?.name === 'illustrative') {
|
||||
return {
|
||||
lightness: this.cell.transform.params?.colorTheme?.params.style.params.lightness ?? 0
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
lightness: this.cell.transform.params?.colorTheme?.params.lightness ?? this.cell.transform.params?.coloring?.params.lightness ?? 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
get opacityValue(): { alpha: number } | undefined {
|
||||
@@ -838,7 +1097,11 @@ export class EntityNode extends Node<{}, { action?: 'color' | 'clip', isDisabled
|
||||
}
|
||||
update.to(this.ref).update(old => {
|
||||
if (old.colorTheme) {
|
||||
old.colorTheme.params.value = value;
|
||||
if (old.colorTheme.name === 'illustrative') {
|
||||
old.colorTheme.params.style.params.value = value;
|
||||
} else {
|
||||
old.colorTheme.params.value = value;
|
||||
}
|
||||
} else if (old.coloring) {
|
||||
old.coloring.params.color = value;
|
||||
}
|
||||
@@ -846,10 +1109,26 @@ export class EntityNode extends Node<{}, { action?: 'color' | 'clip', isDisabled
|
||||
update.commit();
|
||||
};
|
||||
|
||||
updateIllustrative = (values: PD.Values) => {
|
||||
return this.plugin.build().to(this.ref).update(old => {
|
||||
if (old.colorTheme) {
|
||||
if (old.colorTheme.name !== 'illustrative' && values.illustrative) {
|
||||
old.colorTheme = { name: 'illustrative', params: { style: { name: 'uniform', params: { value: old.colorTheme.params.value, lightness: old.colorTheme.params.lightness } } } };
|
||||
} else if (old.colorTheme.name === 'illustrative' && !values.illustrative) {
|
||||
old.colorTheme = { name: 'uniform', params: { value: old.colorTheme.params.style.params.value, lightness: old.colorTheme.params.style.params.lightness } };
|
||||
}
|
||||
}
|
||||
}).commit();
|
||||
};
|
||||
|
||||
updateLightness = (values: PD.Values) => {
|
||||
return this.plugin.build().to(this.ref).update(old => {
|
||||
if (old.colorTheme) {
|
||||
old.colorTheme.params.lightness = values.lightness;
|
||||
if (old.colorTheme.name === 'illustrative') {
|
||||
old.colorTheme.params.style.params.lightness = values.lightness;
|
||||
} else {
|
||||
old.colorTheme.params.lightness = values.lightness;
|
||||
}
|
||||
} else if (old.coloring) {
|
||||
old.coloring.params.lightness = values.lightness;
|
||||
}
|
||||
@@ -924,6 +1203,7 @@ export class EntityNode extends Node<{}, { action?: 'color' | 'clip', isDisabled
|
||||
const depth = this.props.depth;
|
||||
const colorValue = this.colorValue;
|
||||
const lightnessValue = this.lightnessValue;
|
||||
const illustrativeValue = this.illustrativeValue;
|
||||
const opacityValue = this.opacityValue;
|
||||
const emissiveValue = this.emissiveValue;
|
||||
const lodValue = this.lodValue;
|
||||
@@ -953,6 +1233,7 @@ export class EntityNode extends Node<{}, { action?: 'color' | 'clip', isDisabled
|
||||
<ControlGroup header='Color' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleColor}
|
||||
topRightIcon={CloseSvg} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'>
|
||||
<CombinedColorControl param={ColorValueParam} value={colorValue ?? Color(0xFFFFFF)} onChange={this.updateColor} name='color' hideNameRow />
|
||||
<ParameterControls params={IllustrativeParams} values={illustrativeValue} onChangeValues={this.updateIllustrative} />
|
||||
<ParameterControls params={LightnessParams} values={lightnessValue} onChangeValues={this.updateLightness} />
|
||||
<ParameterControls params={OpacityParams} values={opacityValue} onChangeValues={this.updateOpacity} />
|
||||
<ParameterControls params={EmissiveParams} values={emissiveValue} onChangeValues={this.updateEmissive} />
|
||||
|
||||
@@ -5,20 +5,63 @@
|
||||
*/
|
||||
|
||||
import { Mp4EncoderUI } from '../../../extensions/mp4-export/ui';
|
||||
import { PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { CollapsableControls, CollapsableState, PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { SectionHeader } from '../../../mol-plugin-ui/controls/common';
|
||||
import { ParameterControls } from '../../../mol-plugin-ui/controls/parameters';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
import { StructureMeasurementsControls } from '../../../mol-plugin-ui/structure/measurements';
|
||||
import { MesoscaleExplorerState } from '../app';
|
||||
import { MesoscaleState } from '../data/state';
|
||||
import { EntityControls, ModelInfo, SelectionInfo } from './entities';
|
||||
import { LoaderControls, ExampleControls, SessionControls, SnapshotControls, DatabaseControls } from './states';
|
||||
import { EntityControls, FocusInfo, ModelInfo, SelectionInfo } from './entities';
|
||||
import { LoaderControls, ExampleControls, SessionControls, SnapshotControls, DatabaseControls, MesoQuickStylesControls, ExplorerInfo } from './states';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { TuneSvg } from '../../../mol-plugin-ui/controls/icons';
|
||||
import { RendererParams } from '../../../mol-gl/renderer';
|
||||
import { TrackballControlsParams } from '../../../mol-canvas3d/controls/trackball';
|
||||
|
||||
const Spacer = () => <div style={{ height: '2em' }} />;
|
||||
|
||||
const ViewportParams = {
|
||||
renderer: PD.Group(RendererParams),
|
||||
trackball: PD.Group(TrackballControlsParams),
|
||||
};
|
||||
|
||||
class ViewportSettingsUI extends CollapsableControls<{}, {}> {
|
||||
protected defaultState(): CollapsableState {
|
||||
return {
|
||||
header: 'Viewport Settings',
|
||||
isCollapsed: true,
|
||||
brand: { accent: 'cyan', svg: TuneSvg }
|
||||
};
|
||||
}
|
||||
|
||||
protected renderControls(): JSX.Element | null {
|
||||
return <>
|
||||
{this.plugin.canvas3d && this.plugin.canvas3dContext && <>
|
||||
<ParameterControls params={ViewportParams} values={this.plugin.canvas3d.props} onChange={this.setSettings} />
|
||||
</>}
|
||||
</>;
|
||||
}
|
||||
|
||||
private setSettings = (p: { param: PD.Base<any>, name: string, value: any }) => {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { [p.name]: p.value } });
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
|
||||
this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
export class LeftPanel extends PluginUIComponent {
|
||||
render() {
|
||||
const customState = this.plugin.customState as MesoscaleExplorerState;
|
||||
|
||||
return <div className='msp-scrollable-container'>
|
||||
{customState.driver && <>
|
||||
<ExplorerInfo />
|
||||
<Spacer />
|
||||
</>}
|
||||
<SectionHeader title='Database' />
|
||||
<DatabaseControls />
|
||||
<Spacer />
|
||||
@@ -42,6 +85,7 @@ export class LeftPanel extends PluginUIComponent {
|
||||
<Spacer />
|
||||
|
||||
<Mp4EncoderUI />
|
||||
<ViewportSettingsUI />
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -59,6 +103,13 @@ export class RightPanel extends PluginUIComponent<{}, { isDisabled: boolean }> {
|
||||
);
|
||||
}
|
||||
|
||||
get hasFocusInfo() {
|
||||
return (
|
||||
MesoscaleState.has(this.plugin) &&
|
||||
!!(MesoscaleState.get(this.plugin).focusInfo !== '')
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
|
||||
this.setState({ isDisabled: v });
|
||||
@@ -89,10 +140,18 @@ export class RightPanel extends PluginUIComponent<{}, { isDisabled: boolean }> {
|
||||
<SectionHeader title='Selection' />
|
||||
<SelectionInfo />
|
||||
<Spacer />
|
||||
<StructureMeasurementsControls initiallyCollapsed={true}/>
|
||||
</>
|
||||
|
||||
<MesoQuickStylesControls />
|
||||
<Spacer />
|
||||
<SectionHeader title='Entities' />
|
||||
<EntityControls />
|
||||
<Spacer />
|
||||
{this.hasFocusInfo && <>
|
||||
<SectionHeader title='Focus Info' />
|
||||
<FocusInfo />
|
||||
<Spacer />
|
||||
</>}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
|
||||
import Markdown from 'react-markdown';
|
||||
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
|
||||
import { MmcifProvider } from '../../../mol-plugin-state/formats/trajectory';
|
||||
import { StructureComponentManager } from '../../../mol-plugin-state/manager/structure/component';
|
||||
import { PluginStateObject } from '../../../mol-plugin-state/objects';
|
||||
import { PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { Button, ExpandGroup } from '../../../mol-plugin-ui/controls/common';
|
||||
import { GetAppSvg, Icon, OpenInBrowserSvg } from '../../../mol-plugin-ui/controls/icons';
|
||||
import { Button, ExpandGroup, IconButton } from '../../../mol-plugin-ui/controls/common';
|
||||
import { GetAppSvg, HelpOutlineSvg, MagicWandSvg, TourSvg, Icon, OpenInBrowserSvg } from '../../../mol-plugin-ui/controls/icons';
|
||||
import { CollapsableControls, PluginUIComponent } from '../../../mol-plugin-ui/base';
|
||||
import { ApplyActionControl } from '../../../mol-plugin-ui/state/apply-action';
|
||||
import { LocalStateSnapshotList, LocalStateSnapshotParams, LocalStateSnapshots } from '../../../mol-plugin-ui/state/snapshots';
|
||||
import { PluginCommands } from '../../../mol-plugin/commands';
|
||||
@@ -24,7 +26,7 @@ import { createCellpackHierarchy } from '../data/cellpack/preset';
|
||||
import { createGenericHierarchy } from '../data/generic/preset';
|
||||
import { createMmcifHierarchy } from '../data/mmcif/preset';
|
||||
import { createPetworldHierarchy } from '../data/petworld/preset';
|
||||
import { MesoscaleState, MesoscaleStateObject, setGraphicsCanvas3DProps } from '../data/state';
|
||||
import { MesoscaleState, MesoscaleStateObject, setGraphicsCanvas3DProps, updateColors } from '../data/state';
|
||||
|
||||
function adjustPluginProps(ctx: PluginContext) {
|
||||
ctx.managers.interactivity.setProps({ granularity: 'chain' });
|
||||
@@ -77,6 +79,7 @@ function adjustPluginProps(ctx: PluginContext) {
|
||||
radius: 5,
|
||||
bias: 1,
|
||||
blurKernelSize: 11,
|
||||
blurDepthBias: 0.5,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
}
|
||||
@@ -269,7 +272,7 @@ export class DatabaseControls extends PluginUIComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div style={{ margin: '5px' }}>
|
||||
return <div id='database' style={{ margin: '5px' }}>
|
||||
<ApplyActionControl state={this.plugin.state.data} action={LoadDatabase} nodeRef={this.plugin.state.data.tree.root.ref} applyLabel={'Load'} hideHeader />
|
||||
</div>;
|
||||
}
|
||||
@@ -281,7 +284,7 @@ export class LoaderControls extends PluginUIComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div style={{ margin: '5px' }}>
|
||||
return <div id='loader' style={{ margin: '5px' }}>
|
||||
<ApplyActionControl state={this.plugin.state.data} action={LoadModel} nodeRef={this.plugin.state.data.tree.root.ref} applyLabel={'Load'} hideHeader />
|
||||
</div>;
|
||||
}
|
||||
@@ -293,7 +296,7 @@ export class ExampleControls extends PluginUIComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div style={{ margin: '5px' }}>
|
||||
return <div id='example' style={{ margin: '5px' }}>
|
||||
<ApplyActionControl state={this.plugin.state.data} action={LoadExample} nodeRef={this.plugin.state.data.tree.root.ref} applyLabel={'Load'} hideHeader />
|
||||
</div>;
|
||||
}
|
||||
@@ -330,7 +333,7 @@ export class SessionControls extends PluginUIComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
return <div style={{ margin: '5px' }}>
|
||||
return <div id='session' style={{ margin: '5px' }}>
|
||||
<div className='msp-flex-row'>
|
||||
<Button icon={GetAppSvg} onClick={this.downloadToFileZip} title='Download the state.'>
|
||||
Download
|
||||
@@ -346,14 +349,14 @@ export class SessionControls extends PluginUIComponent {
|
||||
export class SnapshotControls extends PluginUIComponent<{}> {
|
||||
render() {
|
||||
return <div style={{ margin: '5px' }}>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<div id='snaplist' style={{ marginBottom: '10px' }}>
|
||||
<LocalStateSnapshotList />
|
||||
</div>
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<div id='snap' style={{ marginBottom: '10px' }}>
|
||||
<LocalStateSnapshots />
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '10px' }}>
|
||||
<div id='snapoption' style={{ marginBottom: '10px' }}>
|
||||
<ExpandGroup header='Snapshot Options' initiallyExpanded={false}>
|
||||
<LocalStateSnapshotParams />
|
||||
</ExpandGroup>
|
||||
@@ -361,3 +364,497 @@ export class SnapshotControls extends PluginUIComponent<{}> {
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExplorerInfo extends PluginUIComponent<{}, { isDisabled: boolean, showHelp: boolean }> {
|
||||
state = {
|
||||
isDisabled: false,
|
||||
showHelp: false
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.state.data.behaviors.isUpdating, v => {
|
||||
this.setState({ isDisabled: v });
|
||||
});
|
||||
|
||||
this.subscribe(this.plugin.state.events.cell.stateUpdated, e => {
|
||||
if (!this.state.isDisabled && MesoscaleState.has(this.plugin) && MesoscaleState.ref(this.plugin) === e.ref) {
|
||||
this.forceUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupDriver = () => {
|
||||
// setup the tour of the interface
|
||||
const driver = (this.plugin.customState as MesoscaleExplorerState).driver;
|
||||
if (!driver) return;
|
||||
|
||||
driver.setSteps([
|
||||
// Left panel
|
||||
{ element: '#explorerinfo', popover: { title: 'Explorer Header Info', description: 'This section displays the explorer header with version information, documentation access, and tour navigation. Use the right and left arrow keys to navigate the tour.', side: 'left', align: 'start' } },
|
||||
{ element: '#database', popover: { title: 'Import from PDB', description: 'Load structures directly from PDB and PDB-DEV databases.', side: 'bottom', align: 'start' } },
|
||||
{ element: '#loader', popover: { title: 'Import from File', description: 'Load local files (.molx, .molj, .zip, .cif, .bcif) using this option.', side: 'bottom', align: 'start' } },
|
||||
{ element: '#example', popover: { title: 'Example Models and Tours', description: 'Select from a range of example models and tours provided.', side: 'left', align: 'start' } },
|
||||
{ element: '#session', popover: { title: 'Session Management', description: 'Download the current session in .molx format.', side: 'top', align: 'start' } },
|
||||
{ element: '#snaplist', popover: { title: 'Snapshot List', description: 'View and manage the list of snapshots. You can reorder them and edit their titles, keys, and descriptions. Snapshot states cannot be edited.', side: 'right', align: 'start' } },
|
||||
{ element: '#snap', popover: { title: 'Add Snapshot', description: 'Save the current state (e.g., camera position, color, visibility, etc.) in a snapshot with an optional title, key, and description.', side: 'right', align: 'start' } },
|
||||
{ element: '#snapoption', popover: { title: 'Snapshot Options', description: 'These options are saved in the snapshot. Set them before adding a snapshot to see their effect during animation playback.', side: 'right', align: 'start' } },
|
||||
{ element: '#exportanimation', popover: { title: 'Export Animation', description: 'Create movies or scenes with rocking, rotating, or snapshots animations.', side: 'right', align: 'start' } },
|
||||
{ element: '#viewportsettings', popover: { title: 'Viewport Settings', description: 'Advanced settings for the renderer and trackball.', side: 'right', align: 'start' } },
|
||||
// Viewport
|
||||
{ element: '#snapinfo', popover: { title: 'Snapshot Description', description: 'Save the current state (e.g., camera position, color, visibility, etc.) in a snapshot with an optional title, key, and description.', side: 'right', align: 'start' } },
|
||||
{ element: '#snapinfoctrl', popover: { title: 'Snapshot Description Control', description: 'Control the visibility and text size of the snapshot description widget.', side: 'right', align: 'start' } },
|
||||
// Right panel
|
||||
{ element: '#modelinfo', popover: { title: 'Model Information', description: 'Summary information about the model, if available.', side: 'right', align: 'start' } },
|
||||
{ element: '#selestyle', popover: { title: 'Selection Style', description: 'Choose the rendering style for entity selection accessed via Shift/Ctrl mouse. Options include: Color & Outline, Color, Outline.', side: 'right', align: 'start' } },
|
||||
{ element: '#seleinfo', popover: { title: 'Selection List', description: 'View the current list of selected entities.', side: 'right', align: 'start' } },
|
||||
{ element: '#measurements', popover: { title: 'Measurements', description: 'Use this widget to create labels, measure distances, angles, dihedral orientations, and planes for the selected entities.', side: 'right', align: 'start' } },
|
||||
{ element: '#quickstyles', popover: { title: 'Quick Styles', description: 'Change between a selection of style presets.', side: 'right', align: 'start' } },
|
||||
{ element: '#graphicsquality', popover: { title: 'Graphics Quality', description: 'Adjust the overall graphics quality. Lower quality improves performance. Options are: Ultra, Quality (Default), Balanced, Performance, Custom. Custom settings use the Culling & LOD values set in the Tree.', side: 'right', align: 'start' } },
|
||||
{ element: '#searchtree', popover: { title: 'Search', description: 'Filter the entity tree based on your queries.', side: 'right', align: 'start' } },
|
||||
{ element: '#grouptree', popover: { title: 'Group By', description: 'Change the grouping of the hierarchy tree, e.g., group by instance or by compartment.', side: 'right', align: 'start' } },
|
||||
{ element: '#tree', popover: { title: 'Tree Hierarchy', description: 'View the hierarchical tree of entity types in the model.', side: 'right', align: 'start' } },
|
||||
{ element: '#focusinfo', popover: { title: 'Selection Description', description: 'Detailed information about the current selection, if present in the loaded file.', side: 'right', align: 'start' } },
|
||||
{ popover: { title: 'Happy Exploring!', description: 'That’s all! Go ahead and start exploring or creating mesoscale tours.' } }
|
||||
]);
|
||||
driver.refresh();
|
||||
};
|
||||
|
||||
openHelp = () => {
|
||||
// open a new page with the documentation
|
||||
window.open('https://molstar.org/me/docs', '_blank');
|
||||
};
|
||||
|
||||
toggleHelp = () => {
|
||||
const driver = (this.plugin.customState as MesoscaleExplorerState).driver;
|
||||
if (!driver || !driver.hasNextStep()) {
|
||||
this.setupDriver();
|
||||
}
|
||||
this.setState({ showHelp: !this.state.showHelp }, () => {
|
||||
if (this.state.showHelp && driver) {
|
||||
driver.drive(); // start at 0
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const driver = (this.plugin.customState as MesoscaleExplorerState).driver;
|
||||
if (!driver) return;
|
||||
|
||||
const legend = `## Welcome to Mol* Mesoscale Explorer`;
|
||||
const help = <IconButton svg={HelpOutlineSvg} toggleState={false} small onClick={this.openHelp} title='Open the Documentation' />;
|
||||
const tour = <IconButton svg={TourSvg} toggleState={false} small onClick={this.toggleHelp} title='Start the interactive tour' />;
|
||||
return <>
|
||||
<div id='explorerinfo' style={{ paddingLeft: 4 }} className='msp-help-text'>
|
||||
<Markdown>{legend}</Markdown>
|
||||
{tour}{help}
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class MesoQuickStylesControls extends CollapsableControls {
|
||||
defaultState() {
|
||||
return {
|
||||
isCollapsed: true,
|
||||
header: 'Quick Styles',
|
||||
brand: { accent: 'gray' as const, svg: MagicWandSvg }
|
||||
};
|
||||
}
|
||||
|
||||
renderControls() {
|
||||
return <>
|
||||
<MesoQuickStyles />
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
export class MesoQuickStyles extends PluginUIComponent {
|
||||
state = {
|
||||
celShaded: false,
|
||||
};
|
||||
default_color_values = {
|
||||
type: 'group-generate',
|
||||
illustrative: false,
|
||||
value: [1, 1, 1, 1],
|
||||
variability: 20,
|
||||
shift: 0,
|
||||
lightness: 0,
|
||||
alpha: 1,
|
||||
emissive: 0
|
||||
};
|
||||
illustrative_color_values = {
|
||||
type: 'group-generate',
|
||||
illustrative: true,
|
||||
value: [1, 1, 1, 1],
|
||||
variability: 20,
|
||||
shift: 0,
|
||||
lightness: 0,
|
||||
alpha: 1,
|
||||
emissive: 0
|
||||
};
|
||||
async default() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
this.plugin.canvas3d.setProps({
|
||||
renderer: {
|
||||
exposure: 1.1,
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
multiScale: {
|
||||
name: 'on',
|
||||
params: {
|
||||
levels: [
|
||||
{ radius: 2, bias: 1.0 },
|
||||
{ radius: 5, bias: 1.0 },
|
||||
{ radius: 8, bias: 1.0 },
|
||||
{ radius: 11, bias: 1.0 },
|
||||
],
|
||||
nearThreshold: 10,
|
||||
farThreshold: 1500,
|
||||
}
|
||||
},
|
||||
radius: 5,
|
||||
bias: 1,
|
||||
blurKernelSize: 11,
|
||||
blurDepthBias: 0.5,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
}
|
||||
},
|
||||
shadow: {
|
||||
name: 'on',
|
||||
params: {
|
||||
bias: 0.6,
|
||||
maxDistance: 80,
|
||||
steps: 3,
|
||||
tolerance: 1.0,
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
name: 'on',
|
||||
params: {
|
||||
scale: 1,
|
||||
threshold: 0.15,
|
||||
color: Color(0x000000),
|
||||
includeTransparent: false,
|
||||
}
|
||||
},
|
||||
dof: { name: 'off', params: {} },
|
||||
}
|
||||
});
|
||||
|
||||
const loptions = { ignoreLight: true, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 } };
|
||||
const options = { ...loptions, celShaded: false, };
|
||||
await this.plugin.managers.structure.component.setOptions(loptions as StructureComponentManager.Options);
|
||||
await updateColors(this.plugin, this.default_color_values, options);
|
||||
}
|
||||
|
||||
async celshading() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
this.plugin.canvas3d.setProps({
|
||||
renderer: {
|
||||
exposure: 1.5,
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
multiScale: {
|
||||
name: 'on',
|
||||
params: {
|
||||
levels: [
|
||||
{ radius: 2, bias: 1.0 },
|
||||
{ radius: 5, bias: 1.0 },
|
||||
{ radius: 8, bias: 1.0 },
|
||||
{ radius: 11, bias: 1.0 },
|
||||
],
|
||||
nearThreshold: 10,
|
||||
farThreshold: 1500,
|
||||
}
|
||||
},
|
||||
radius: 5,
|
||||
bias: 1.5,
|
||||
blurKernelSize: 11,
|
||||
blurDepthBias: 0.5,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
}
|
||||
},
|
||||
shadow: {
|
||||
name: 'on',
|
||||
params: {
|
||||
bias: 0.4,
|
||||
maxDistance: 256,
|
||||
steps: 64,
|
||||
tolerance: 1.0,
|
||||
}
|
||||
},
|
||||
outline: { name: 'off', params: {} },
|
||||
dof: { name: 'off', params: {} },
|
||||
}
|
||||
});
|
||||
// ignore Light
|
||||
const loptions = { ignoreLight: false, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 } };
|
||||
const options = { ...loptions, celShaded: true, };
|
||||
await this.plugin.managers.structure.component.setOptions(loptions as StructureComponentManager.Options);
|
||||
await updateColors(this.plugin, this.default_color_values, options);
|
||||
}
|
||||
|
||||
async stylizedDof() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
this.plugin.canvas3d.setProps({
|
||||
renderer: {
|
||||
exposure: 1.1,
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
multiScale: {
|
||||
name: 'on',
|
||||
params: {
|
||||
levels: [
|
||||
{ radius: 2, bias: 1.0 },
|
||||
{ radius: 5, bias: 1.0 },
|
||||
{ radius: 8, bias: 1.0 },
|
||||
{ radius: 11, bias: 1.0 },
|
||||
],
|
||||
nearThreshold: 10,
|
||||
farThreshold: 1500,
|
||||
}
|
||||
},
|
||||
radius: 5,
|
||||
bias: 1.3,
|
||||
blurKernelSize: 11,
|
||||
blurDepthBias: 0.5,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
}
|
||||
},
|
||||
shadow: {
|
||||
name: 'on',
|
||||
params: {
|
||||
bias: 0.4,
|
||||
maxDistance: 256,
|
||||
steps: 64,
|
||||
tolerance: 1.0,
|
||||
}
|
||||
},
|
||||
outline: { name: 'off', params: {} },
|
||||
dof: {
|
||||
name: 'on',
|
||||
params: {
|
||||
blurSize: 9,
|
||||
blurSpread: 1.0,
|
||||
inFocus: 0.0,
|
||||
PPM: 200.0,
|
||||
center: 'camera-target',
|
||||
mode: 'sphere',
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// ignore Light
|
||||
const loptions = { ignoreLight: false, materialStyle: { metalness: 0, roughness: 0.2, bumpiness: 0 } };
|
||||
const options = { ...loptions, celShaded: false };
|
||||
await this.plugin.managers.structure.component.setOptions(loptions as StructureComponentManager.Options);
|
||||
await updateColors(this.plugin, this.default_color_values, options);
|
||||
}
|
||||
|
||||
async illustrative() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
this.plugin.canvas3d.setProps({
|
||||
renderer: {
|
||||
exposure: 1.5,
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
multiScale: {
|
||||
name: 'on',
|
||||
params: {
|
||||
levels: [
|
||||
{ radius: 2, bias: 1.0 },
|
||||
{ radius: 5, bias: 1.0 },
|
||||
{ radius: 8, bias: 1.0 },
|
||||
{ radius: 11, bias: 1.0 },
|
||||
],
|
||||
nearThreshold: 10,
|
||||
farThreshold: 1500,
|
||||
}
|
||||
},
|
||||
radius: 5,
|
||||
bias: 1.5,
|
||||
blurKernelSize: 11,
|
||||
blurDepthBias: 0.5,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
}
|
||||
},
|
||||
shadow: {
|
||||
name: 'on',
|
||||
params: {
|
||||
bias: 0.4,
|
||||
maxDistance: 256,
|
||||
steps: 64,
|
||||
tolerance: 1.0,
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
name: 'on',
|
||||
params: {
|
||||
scale: 1,
|
||||
threshold: 0.15,
|
||||
color: Color(0x000000),
|
||||
includeTransparent: false,
|
||||
}
|
||||
},
|
||||
dof: { name: 'off', params: {} },
|
||||
}
|
||||
});
|
||||
// ignore Light
|
||||
const loptions = { ignoreLight: true, materialStyle: { metalness: 0, roughness: 1.0, bumpiness: 0 } };
|
||||
const options = { ...loptions, celShaded: false, };
|
||||
await this.plugin.managers.structure.component.setOptions(loptions as StructureComponentManager.Options);
|
||||
await updateColors(this.plugin, this.illustrative_color_values, options);
|
||||
}
|
||||
|
||||
async shiny() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
this.plugin.canvas3d.setProps({
|
||||
renderer: {
|
||||
exposure: 1.5,
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
multiScale: {
|
||||
name: 'on',
|
||||
params: {
|
||||
levels: [
|
||||
{ radius: 2, bias: 1.0 },
|
||||
{ radius: 5, bias: 1.0 },
|
||||
{ radius: 8, bias: 1.0 },
|
||||
{ radius: 11, bias: 1.0 },
|
||||
],
|
||||
nearThreshold: 10,
|
||||
farThreshold: 1500,
|
||||
}
|
||||
},
|
||||
radius: 5,
|
||||
bias: 1.3,
|
||||
blurKernelSize: 11,
|
||||
blurDepthBias: 0.5,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
}
|
||||
},
|
||||
shadow: { name: 'off', params: {} },
|
||||
outline: { name: 'off', params: {} },
|
||||
dof: { name: 'off', params: {} },
|
||||
}
|
||||
});
|
||||
// ignore Light
|
||||
const loptions = { ignoreLight: false, materialStyle: { metalness: 0, roughness: 0.2, bumpiness: 0 } };
|
||||
const options = { ...loptions, celShaded: false };
|
||||
await this.plugin.managers.structure.component.setOptions(loptions as StructureComponentManager.Options);
|
||||
await updateColors(this.plugin, this.default_color_values, options);
|
||||
}
|
||||
|
||||
async stylized() {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
this.plugin.canvas3d.setProps({
|
||||
renderer: {
|
||||
exposure: 1.1,
|
||||
},
|
||||
postprocessing: {
|
||||
occlusion: {
|
||||
name: 'on',
|
||||
params: {
|
||||
samples: 32,
|
||||
multiScale: {
|
||||
name: 'on',
|
||||
params: {
|
||||
levels: [
|
||||
{ radius: 2, bias: 1.0 },
|
||||
{ radius: 5, bias: 1.0 },
|
||||
{ radius: 8, bias: 1.0 },
|
||||
{ radius: 11, bias: 1.0 },
|
||||
],
|
||||
nearThreshold: 10,
|
||||
farThreshold: 1500,
|
||||
}
|
||||
},
|
||||
radius: 5,
|
||||
bias: 1.3,
|
||||
blurKernelSize: 11,
|
||||
blurDepthBias: 0.5,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
}
|
||||
},
|
||||
shadow: {
|
||||
name: 'on',
|
||||
params: {
|
||||
bias: 0.4,
|
||||
maxDistance: 256,
|
||||
steps: 64,
|
||||
tolerance: 1.0,
|
||||
}
|
||||
},
|
||||
outline: {
|
||||
name: 'on',
|
||||
params: {
|
||||
scale: 1,
|
||||
threshold: 0.15,
|
||||
color: Color(0x000000),
|
||||
includeTransparent: false,
|
||||
}
|
||||
},
|
||||
dof: { name: 'off', params: {} },
|
||||
}
|
||||
});
|
||||
// ignore Light
|
||||
const loptions = { ignoreLight: false, materialStyle: { metalness: 0, roughness: 0.2, bumpiness: 0 } };
|
||||
const options = { ...loptions, celShaded: false };
|
||||
await this.plugin.managers.structure.component.setOptions(loptions as StructureComponentManager.Options);
|
||||
await updateColors(this.plugin, this.illustrative_color_values, options);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <>
|
||||
<div className='msp-flex-row'>
|
||||
<Button noOverflow title='Applies default representation preset and sets outline and occlusion effects to default' onClick={() => this.default()} style={{ width: 'auto' }}>
|
||||
Default
|
||||
</Button>
|
||||
<Button noOverflow title='Applies celShading' onClick={() => this.celshading()} style={{ width: 'auto' }}>
|
||||
Cel-shaded
|
||||
</Button>
|
||||
<Button noOverflow title='Applies illustrative colors preset' onClick={() => this.illustrative()} style={{ width: 'auto' }}>
|
||||
Illustrative
|
||||
</Button>
|
||||
</div>
|
||||
<div className='msp-flex-row'>
|
||||
<Button noOverflow title='Apply shiny material to default' onClick={() => this.shiny()} style={{ width: 'auto' }}>
|
||||
Shiny
|
||||
</Button>
|
||||
<Button noOverflow title='Enable shiny material, outline, and illustrative colors' onClick={() => this.stylized()} style={{ width: 'auto' }}>
|
||||
Shiny-Illustrative
|
||||
</Button>
|
||||
<Button noOverflow title='Enable DOF and shiny material' onClick={() => this.stylizedDof()} style={{ width: 'auto' }}>
|
||||
Shiny-DOF
|
||||
</Button>
|
||||
</div>
|
||||
</>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Neli Fonseca <neli@ebi.ac.uk>
|
||||
*/
|
||||
|
||||
import { ANVILMembraneOrientation } from '../../extensions/anvil/behavior';
|
||||
@@ -423,6 +424,34 @@ export class Viewer {
|
||||
});
|
||||
}
|
||||
|
||||
loadFullResolutionEMDBMap(emdbId: string, options: { isoValue: Volume.IsoValue, color?: Color }) {
|
||||
const plugin = this.plugin;
|
||||
const numericId = parseInt(emdbId.toUpperCase().replace('EMD-', ''));
|
||||
const url = `https://ftp.ebi.ac.uk/pub/databases/emdb/structures/EMD-${numericId}/map/emd_${numericId}.map.gz`;
|
||||
|
||||
return plugin.dataTransaction(async () => {
|
||||
const data = await plugin.build().toRoot()
|
||||
.apply(StateTransforms.Data.Download, { url, isBinary: true, label: emdbId }, { state: { isGhost: true } })
|
||||
.apply(StateTransforms.Data.DeflateData)
|
||||
.commit();
|
||||
|
||||
const parsed = await plugin.dataFormats.get('ccp4')!.parse(plugin, data, { entryId: emdbId });
|
||||
const firstVolume = (parsed.volume || parsed.volumes[0]) as StateObjectSelector<PluginStateObject.Volume.Data>;
|
||||
if (!firstVolume?.isOk) throw new Error('Failed to parse any volume.');
|
||||
|
||||
const volume: StateObjectSelector<PluginStateObject.Volume.Data> = parsed.volumes?.[0] ?? parsed.volume;
|
||||
await plugin.build()
|
||||
.to(volume)
|
||||
.apply(StateTransforms.Representation.VolumeRepresentation3D, createVolumeRepresentationParams(this.plugin, firstVolume.data!, {
|
||||
type: 'isosurface',
|
||||
typeParams: { alpha: 1, isoValue: options.isoValue },
|
||||
color: 'uniform',
|
||||
colorParams: { value: options.color ?? Color(0x33BB33) }
|
||||
}))
|
||||
.commit();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* viewer.loadTrajectory({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -33,6 +33,7 @@ const Canvas3DPresets = {
|
||||
radius: 5,
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
blurDepthBias: 0.5,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2021-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Mandar Deshpande <mandar@ebi.ac.uk>
|
||||
* @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
|
||||
@@ -41,8 +41,13 @@ export function PLDDTConfidenceColorTheme(ctx: ThemeDataContext, props: PD.Value
|
||||
const getColor = (location: StructureElement.Location): Color => {
|
||||
const { unit, element } = location;
|
||||
if (!Unit.isAtomic(unit)) return DefaultColor;
|
||||
|
||||
const qualityAssessment = QualityAssessmentProvider.get(unit.model).value;
|
||||
const score = qualityAssessment?.pLDDT?.get(unit.model.atomicHierarchy.residueAtomSegments.index[element]) ?? -1;
|
||||
let score = qualityAssessment?.pLDDT?.get(unit.model.atomicHierarchy.residueAtomSegments.index[element]);
|
||||
if (typeof score !== 'number') {
|
||||
score = unit.model.atomicConformation.B_iso_or_equiv.value(element);
|
||||
}
|
||||
|
||||
if (score < 0) {
|
||||
return DefaultColor;
|
||||
} else if (score <= 50) {
|
||||
@@ -74,7 +79,7 @@ export function PLDDTConfidenceColorTheme(ctx: ThemeDataContext, props: PD.Value
|
||||
preferSmoothing: true,
|
||||
color,
|
||||
props,
|
||||
description: 'Assigns residue colors according to the pLDDT Confidence score.',
|
||||
description: 'Assigns residue colors according to the pLDDT Confidence score. If no Model Archive quality assessment score is available, the B-factor value is used instead.',
|
||||
legend: ConfidenceColorLegend
|
||||
};
|
||||
}
|
||||
@@ -86,7 +91,7 @@ export const PLDDTConfidenceColorThemeProvider: ColorTheme.Provider<PLDDTConfide
|
||||
factory: PLDDTConfidenceColorTheme,
|
||||
getParams: getPLDDTConfidenceColorThemeParams,
|
||||
defaultValues: PD.getDefaultValues(getPLDDTConfidenceColorThemeParams({})),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure?.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT')),
|
||||
isApplicable: (ctx: ThemeDataContext) => !!ctx.structure?.models.some(m => QualityAssessment.isApplicable(m, 'pLDDT') || m.atomicConformation.B_iso_or_equiv.isDefined),
|
||||
ensureCustomProperties: {
|
||||
attach: async (ctx: CustomProperty.Context, data: ThemeDataContext) => {
|
||||
if (data.structure) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2019 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2024 Mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
@@ -87,22 +87,48 @@ class CameraTransitionManager {
|
||||
namespace CameraTransitionManager {
|
||||
export type TransitionFunc = (out: Camera.Snapshot, t: number, source: Camera.Snapshot, target: Camera.Snapshot) => void
|
||||
|
||||
const _rot = Quat.identity();
|
||||
const _rotUp = Quat.identity();
|
||||
const _rotDist = Quat.identity();
|
||||
|
||||
const _sourcePosition = Vec3();
|
||||
const _targetPosition = Vec3();
|
||||
|
||||
export function defaultTransition(out: Camera.Snapshot, t: number, source: Camera.Snapshot, target: Camera.Snapshot): void {
|
||||
Camera.copySnapshot(out, target);
|
||||
|
||||
// Rotate up
|
||||
Quat.slerp(_rot, Quat.Identity, Quat.rotationTo(_rot, source.up, target.up), t);
|
||||
Vec3.transformQuat(out.up, source.up, _rot);
|
||||
Quat.slerp(_rotUp, Quat.Identity, Quat.rotationTo(_rotUp, source.up, target.up), t);
|
||||
Vec3.transformQuat(out.up, source.up, _rotUp);
|
||||
|
||||
// Lerp target, position & radius
|
||||
Vec3.lerp(out.target, source.target, target.target, t);
|
||||
Vec3.lerp(out.position, source.position, target.position, t);
|
||||
|
||||
// Interpolate distance
|
||||
const distSource = Vec3.distance(source.target, source.position);
|
||||
const distTarget = Vec3.distance(target.target, target.position);
|
||||
const dist = lerp(distSource, distTarget, t);
|
||||
|
||||
// Rotate between source and targer direction
|
||||
Vec3.sub(_sourcePosition, source.position, source.target);
|
||||
Vec3.normalize(_sourcePosition, _sourcePosition);
|
||||
|
||||
Vec3.sub(_targetPosition, target.position, target.target);
|
||||
Vec3.normalize(_targetPosition, _targetPosition);
|
||||
|
||||
Quat.rotationTo(_rotDist, _sourcePosition, _targetPosition);
|
||||
Quat.slerp(_rotDist, Quat.Identity, _rotDist, t);
|
||||
|
||||
Vec3.transformQuat(_sourcePosition, _sourcePosition, _rotDist);
|
||||
Vec3.scale(_sourcePosition, _sourcePosition, dist);
|
||||
|
||||
Vec3.add(out.position, out.target, _sourcePosition);
|
||||
|
||||
// Interpolate radius
|
||||
out.radius = lerp(source.radius, target.radius, t);
|
||||
// TODO take change of `clipFar` into account
|
||||
out.radiusMax = lerp(source.radiusMax, target.radiusMax, t);
|
||||
|
||||
// Lerp fov & fog
|
||||
// Interpolate fov & fog
|
||||
out.fov = lerp(source.fov, target.fov, t);
|
||||
out.fog = lerp(source.fog, target.fog, t);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -54,6 +54,7 @@ export const MultiSampleParams = {
|
||||
mode: PD.Select('temporal', [['off', 'Off'], ['on', 'On'], ['temporal', 'Temporal']]),
|
||||
sampleLevel: PD.Numeric(2, { min: 0, max: 5, step: 1 }, { description: 'Take level^2 samples.' }),
|
||||
reduceFlicker: PD.Boolean(true, { description: 'Reduce flicker in "temporal" mode.' }),
|
||||
reuseOcclusion: PD.Boolean(true, { description: 'Reuse occlusion data. It is faster but has some artefacts.' }),
|
||||
};
|
||||
export type MultiSampleProps = PD.Values<typeof MultiSampleParams>
|
||||
|
||||
@@ -161,7 +162,7 @@ export class MultiSamplePass {
|
||||
ValueCell.update(compose.values.uWeight, sampleWeight);
|
||||
|
||||
// render scene
|
||||
if (i === 0) {
|
||||
if (i === 0 || !props.multiSample.reuseOcclusion) {
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
} else {
|
||||
drawPass.postprocessing.setOcclusionOffset(
|
||||
@@ -252,7 +253,7 @@ export class MultiSamplePass {
|
||||
camera.update();
|
||||
|
||||
// render scene
|
||||
if (sampleIndex === 0) {
|
||||
if (sampleIndex === 0 || !props.multiSample.reuseOcclusion) {
|
||||
drawPass.postprocessing.setOcclusionOffset(0, 0);
|
||||
} else {
|
||||
drawPass.postprocessing.setOcclusionOffset(
|
||||
|
||||
@@ -206,6 +206,7 @@ const SsaoBlurSchema = {
|
||||
|
||||
uKernel: UniformSpec('f[]'),
|
||||
dOcclusionKernelSize: DefineSpec('number'),
|
||||
uBlurDepthBias: UniformSpec('f'),
|
||||
|
||||
uBlurDirectionX: UniformSpec('f'),
|
||||
uBlurDirectionY: UniformSpec('f'),
|
||||
@@ -227,6 +228,7 @@ function getSsaoBlurRenderable(ctx: WebGLContext, ssaoDepthTexture: Texture, dir
|
||||
|
||||
uKernel: ValueCell.create(getBlurKernel(15)),
|
||||
dOcclusionKernelSize: ValueCell.create(15),
|
||||
uBlurDepthBias: ValueCell.create(0.5),
|
||||
|
||||
uBlurDirectionX: ValueCell.create(direction === 'horizontal' ? 1 : 0),
|
||||
uBlurDirectionY: ValueCell.create(direction === 'vertical' ? 1 : 0),
|
||||
@@ -374,6 +376,7 @@ export const PostprocessingParams = {
|
||||
radius: PD.Numeric(5, { min: 0, max: 20, step: 0.1 }, { description: 'Final occlusion radius is 2^x', hideIf: p => p?.multiScale.name === 'on' }),
|
||||
bias: PD.Numeric(0.8, { min: 0, max: 3, step: 0.1 }),
|
||||
blurKernelSize: PD.Numeric(15, { min: 1, max: 25, step: 2 }),
|
||||
blurDepthBias: PD.Numeric(0.5, { min: 0, max: 1, step: 0.01 }),
|
||||
resolutionScale: PD.Numeric(1, { min: 0.1, max: 1, step: 0.05 }, { description: 'Adjust resolution of occlusion calculation' }),
|
||||
color: PD.Color(Color(0x000000)),
|
||||
}),
|
||||
@@ -650,6 +653,9 @@ export class PostprocessingPass {
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uInvProjection, invProjection);
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uInvProjection, invProjection);
|
||||
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.uBlurDepthBias, props.occlusion.params.blurDepthBias);
|
||||
ValueCell.update(this.ssaoBlurSecondPassRenderable.values.uBlurDepthBias, props.occlusion.params.blurDepthBias);
|
||||
|
||||
if (this.ssaoBlurFirstPassRenderable.values.dOrthographic.ref.value !== orthographic) {
|
||||
needsUpdateSsaoBlur = true;
|
||||
ValueCell.update(this.ssaoBlurFirstPassRenderable.values.dOrthographic, orthographic);
|
||||
|
||||
@@ -168,6 +168,7 @@ export namespace Cylinders {
|
||||
sizeAspectRatio: PD.Numeric(1, { min: 0, max: 3, step: 0.01 }),
|
||||
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
celShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
|
||||
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque'] as const), BaseGeometry.ShadingCategory),
|
||||
solidInterior: PD.Boolean(true, BaseGeometry.ShadingCategory),
|
||||
@@ -259,6 +260,7 @@ export namespace Cylinders {
|
||||
uSizeFactor: ValueCell.create(props.sizeFactor * props.sizeAspectRatio),
|
||||
uDoubleSided: ValueCell.create(props.doubleSided),
|
||||
dIgnoreLight: ValueCell.create(props.ignoreLight),
|
||||
dCelShaded: ValueCell.create(props.celShaded),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
|
||||
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
|
||||
dSolidInterior: ValueCell.create(props.solidInterior),
|
||||
@@ -279,6 +281,7 @@ export namespace Cylinders {
|
||||
ValueCell.updateIfChanged(values.uSizeFactor, props.sizeFactor * props.sizeAspectRatio);
|
||||
ValueCell.updateIfChanged(values.uDoubleSided, props.doubleSided);
|
||||
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
|
||||
ValueCell.updateIfChanged(values.dCelShaded, props.celShaded);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
|
||||
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
|
||||
ValueCell.updateIfChanged(values.dSolidInterior, props.solidInterior);
|
||||
|
||||
@@ -148,6 +148,7 @@ export namespace DirectVolume {
|
||||
export const Params = {
|
||||
...BaseGeometry.Params,
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
celShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
|
||||
controlPoints: PD.LineGraph([
|
||||
Vec2.create(0.19, 0.0), Vec2.create(0.2, 0.05), Vec2.create(0.25, 0.05), Vec2.create(0.26, 0.0),
|
||||
@@ -272,6 +273,7 @@ export namespace DirectVolume {
|
||||
dAxisOrder: ValueCell.create(directVolume.axisOrder.ref.value.join('')),
|
||||
|
||||
dIgnoreLight: ValueCell.create(props.ignoreLight),
|
||||
dCelShaded: ValueCell.create(props.celShaded),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
|
||||
};
|
||||
}
|
||||
@@ -285,6 +287,7 @@ export namespace DirectVolume {
|
||||
function updateValues(values: DirectVolumeValues, props: PD.Values<Params>) {
|
||||
BaseGeometry.updateValues(values, props);
|
||||
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
|
||||
ValueCell.updateIfChanged(values.dCelShaded, props.celShaded);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
|
||||
|
||||
const controlPoints = getControlPointsFromVec2Array(props.controlPoints);
|
||||
|
||||
@@ -628,6 +628,7 @@ export namespace Mesh {
|
||||
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
celShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
|
||||
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque'] as const), BaseGeometry.ShadingCategory),
|
||||
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
@@ -713,6 +714,7 @@ export namespace Mesh {
|
||||
dFlatShaded: ValueCell.create(props.flatShaded),
|
||||
dFlipSided: ValueCell.create(props.flipSided),
|
||||
dIgnoreLight: ValueCell.create(props.ignoreLight),
|
||||
dCelShaded: ValueCell.create(props.celShaded),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
|
||||
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
|
||||
uBumpFrequency: ValueCell.create(props.bumpFrequency),
|
||||
@@ -734,6 +736,7 @@ export namespace Mesh {
|
||||
ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
|
||||
ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
|
||||
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
|
||||
ValueCell.updateIfChanged(values.dCelShaded, props.celShaded);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
|
||||
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
|
||||
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
|
||||
|
||||
@@ -247,6 +247,7 @@ export namespace Spheres {
|
||||
sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
|
||||
doubleSided: PD.Boolean(false, BaseGeometry.CustomQualityParamInfo),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
celShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
|
||||
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque'] as const), BaseGeometry.ShadingCategory),
|
||||
solidInterior: PD.Boolean(true, BaseGeometry.ShadingCategory),
|
||||
@@ -346,6 +347,7 @@ export namespace Spheres {
|
||||
uSizeFactor: spheres.shaderData.sizeFactor,
|
||||
uDoubleSided: ValueCell.create(props.doubleSided),
|
||||
dIgnoreLight: ValueCell.create(props.ignoreLight),
|
||||
dCelShaded: ValueCell.create(props.celShaded),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
|
||||
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
|
||||
dSolidInterior: ValueCell.create(props.solidInterior),
|
||||
@@ -372,6 +374,7 @@ export namespace Spheres {
|
||||
ValueCell.updateIfChanged(values.uSizeFactor, props.sizeFactor);
|
||||
ValueCell.updateIfChanged(values.uDoubleSided, props.doubleSided);
|
||||
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
|
||||
ValueCell.updateIfChanged(values.dCelShaded, props.celShaded);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
|
||||
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
|
||||
ValueCell.updateIfChanged(values.dSolidInterior, props.solidInterior);
|
||||
|
||||
@@ -121,6 +121,7 @@ export namespace TextureMesh {
|
||||
flipSided: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
flatShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
ignoreLight: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
celShaded: PD.Boolean(false, BaseGeometry.ShadingCategory),
|
||||
xrayShaded: PD.Select<boolean | 'inverted'>(false, [[false, 'Off'], [true, 'On'], ['inverted', 'Inverted']], BaseGeometry.ShadingCategory),
|
||||
transparentBackfaces: PD.Select('off', PD.arrayToOptions(['off', 'on', 'opaque'] as const), BaseGeometry.ShadingCategory),
|
||||
bumpFrequency: PD.Numeric(0, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
|
||||
@@ -226,6 +227,7 @@ export namespace TextureMesh {
|
||||
dFlatShaded: ValueCell.create(props.flatShaded),
|
||||
dFlipSided: ValueCell.create(props.flipSided),
|
||||
dIgnoreLight: ValueCell.create(props.ignoreLight),
|
||||
dCelShaded: ValueCell.create(props.celShaded),
|
||||
dXrayShaded: ValueCell.create(props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off'),
|
||||
dTransparentBackfaces: ValueCell.create(props.transparentBackfaces),
|
||||
uBumpFrequency: ValueCell.create(props.bumpFrequency),
|
||||
@@ -247,6 +249,7 @@ export namespace TextureMesh {
|
||||
ValueCell.updateIfChanged(values.dFlatShaded, props.flatShaded);
|
||||
ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
|
||||
ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
|
||||
ValueCell.updateIfChanged(values.dCelShaded, props.celShaded);
|
||||
ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded === 'inverted' ? 'inverted' : props.xrayShaded === true ? 'on' : 'off');
|
||||
ValueCell.updateIfChanged(values.dTransparentBackfaces, props.transparentBackfaces);
|
||||
ValueCell.updateIfChanged(values.uBumpFrequency, props.bumpFrequency);
|
||||
|
||||
@@ -26,6 +26,7 @@ export const CylindersSchema = {
|
||||
padding: ValueSpec('number'),
|
||||
uDoubleSided: UniformSpec('b', 'material'),
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dCelShaded: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
|
||||
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
|
||||
dSolidInterior: DefineSpec('boolean'),
|
||||
|
||||
@@ -40,6 +40,7 @@ export const DirectVolumeSchema = {
|
||||
dAxisOrder: DefineSpec('string', ['012', '021', '102', '120', '201', '210']),
|
||||
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dCelShaded: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
|
||||
};
|
||||
export type DirectVolumeSchema = typeof DirectVolumeSchema
|
||||
|
||||
@@ -22,6 +22,7 @@ export const MeshSchema = {
|
||||
uDoubleSided: UniformSpec('b', 'material'),
|
||||
dFlipSided: DefineSpec('boolean'),
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dCelShaded: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
|
||||
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
|
||||
uBumpFrequency: UniformSpec('f', 'material'),
|
||||
|
||||
@@ -169,6 +169,7 @@ export const GlobalUniformSchema = {
|
||||
uMarkerAverage: UniformSpec('f'),
|
||||
|
||||
uXrayEdgeFalloff: UniformSpec('f'),
|
||||
uCelSteps: UniformSpec('f'),
|
||||
uExposure: UniformSpec('f'),
|
||||
|
||||
uRenderMask: UniformSpec('i'),
|
||||
|
||||
@@ -21,6 +21,7 @@ export const SpheresSchema = {
|
||||
padding: ValueSpec('number'),
|
||||
uDoubleSided: UniformSpec('b', 'material'),
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dCelShaded: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
|
||||
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
|
||||
dSolidInterior: DefineSpec('boolean'),
|
||||
|
||||
@@ -22,6 +22,7 @@ export const TextureMeshSchema = {
|
||||
uDoubleSided: UniformSpec('b', 'material'),
|
||||
dFlipSided: DefineSpec('boolean'),
|
||||
dIgnoreLight: DefineSpec('boolean'),
|
||||
dCelShaded: DefineSpec('boolean'),
|
||||
dXrayShaded: DefineSpec('string', ['off', 'on', 'inverted']),
|
||||
dTransparentBackfaces: DefineSpec('string', ['off', 'on', 'opaque']),
|
||||
uBumpFrequency: UniformSpec('f', 'material'),
|
||||
|
||||
@@ -109,6 +109,7 @@ export const RendererParams = {
|
||||
markerPriority: PD.Select(1, [[1, 'Highlight'], [2, 'Select']]),
|
||||
|
||||
xrayEdgeFalloff: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.1 }),
|
||||
celSteps: PD.Numeric(5, { min: 2, max: 16, step: 1 }),
|
||||
exposure: PD.Numeric(1, { min: 0.0, max: 3.0, step: 0.01 }),
|
||||
|
||||
light: PD.ObjectList({
|
||||
@@ -258,6 +259,7 @@ namespace Renderer {
|
||||
uMarkerAverage: ValueCell.create(0),
|
||||
|
||||
uXrayEdgeFalloff: ValueCell.create(p.xrayEdgeFalloff),
|
||||
uCelSteps: ValueCell.create(p.celSteps),
|
||||
uExposure: ValueCell.create(p.exposure),
|
||||
};
|
||||
const globalUniformList = Object.entries(globalUniforms);
|
||||
@@ -853,6 +855,12 @@ namespace Renderer {
|
||||
p.xrayEdgeFalloff = props.xrayEdgeFalloff;
|
||||
ValueCell.update(globalUniforms.uXrayEdgeFalloff, p.xrayEdgeFalloff);
|
||||
}
|
||||
|
||||
if (props.celSteps !== undefined && props.celSteps !== p.celSteps) {
|
||||
p.celSteps = props.celSteps;
|
||||
ValueCell.update(globalUniforms.uCelSteps, p.celSteps);
|
||||
}
|
||||
|
||||
if (props.exposure !== undefined && props.exposure !== p.exposure) {
|
||||
p.exposure = props.exposure;
|
||||
ValueCell.update(globalUniforms.uExposure, p.exposure);
|
||||
|
||||
@@ -173,7 +173,7 @@ function ignoreDefine(name: string, variant: string, defines: ShaderDefines): bo
|
||||
'dLightCount', 'dXrayShaded',
|
||||
'dOverpaintType', 'dOverpaint',
|
||||
'dSubstanceType', 'dSubstance',
|
||||
'dColorMarker',
|
||||
'dColorMarker', 'dCelShaded'
|
||||
];
|
||||
if (variant !== 'emissive') {
|
||||
ignore.push('dEmissiveType', 'dEmissive');
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2017-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*
|
||||
* adapted from three.js (https://github.com/mrdoob/three.js/)
|
||||
* which under the MIT License, Copyright © 2010-2021 three.js authors
|
||||
@@ -30,7 +31,16 @@ export const apply_light_color = `
|
||||
|
||||
vec4 color = material;
|
||||
|
||||
ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
|
||||
#if defined(dCelShaded)
|
||||
// clamp to avoid artifacts
|
||||
metalness = clamp(metalness, 0.0, 0.99);
|
||||
roughness = clamp(roughness, 0.05, 1.0);
|
||||
#endif
|
||||
|
||||
GeometricContext geometry;
|
||||
geometry.position = -vViewPosition;
|
||||
geometry.normal = normal;
|
||||
geometry.viewDir = normalize(vViewPosition);
|
||||
|
||||
PhysicalMaterial physicalMaterial;
|
||||
physicalMaterial.diffuseColor = color.rgb * (1.0 - metalness);
|
||||
@@ -44,30 +54,53 @@ export const apply_light_color = `
|
||||
physicalMaterial.specularColor = mix(vec3(0.04), color.rgb, metalness);
|
||||
physicalMaterial.specularF90 = 1.0;
|
||||
|
||||
GeometricContext geometry;
|
||||
geometry.position = -vViewPosition;
|
||||
geometry.normal = normal;
|
||||
geometry.viewDir = normalize(vViewPosition);
|
||||
|
||||
IncidentLight directLight;
|
||||
#pragma unroll_loop_start
|
||||
for (int i = 0; i < dLightCount; ++i) {
|
||||
directLight.direction = uLightDirection[i];
|
||||
directLight.color = uLightColor[i] * PI; // * PI for punctual light
|
||||
RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);
|
||||
}
|
||||
#pragma unroll_loop_end
|
||||
|
||||
vec3 irradiance = uAmbientColor * PI; // * PI for punctual light
|
||||
RE_IndirectDiffuse_Physical(irradiance, geometry, physicalMaterial, reflectedLight);
|
||||
vec3 outgoingLight = vec3(0.0);
|
||||
|
||||
// indirect specular only metals
|
||||
vec3 radiance = uAmbientColor * metalness;
|
||||
vec3 iblIrradiance = uAmbientColor * metalness;
|
||||
vec3 clearcoatRadiance = vec3(0.0);
|
||||
RE_IndirectSpecular_Physical(radiance, iblIrradiance, clearcoatRadiance, geometry, physicalMaterial, reflectedLight);
|
||||
#if defined(dCelShaded)
|
||||
float celDiffuse;
|
||||
float celSpecular;
|
||||
float celIntensity;
|
||||
|
||||
vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular;
|
||||
#pragma unroll_loop_start
|
||||
for (int i = 0; i < dLightCount; ++i) {
|
||||
directLight.direction = uLightDirection[i];
|
||||
directLight.color = uLightColor[i] * PI; // * PI for punctual light
|
||||
|
||||
celDiffuse = RECIPROCAL_PI * max(dot(geometry.normal, directLight.direction), 0.0) * (1.0 - metalness);
|
||||
celSpecular = luminance(saturate(dot(geometry.normal, directLight.direction)) * BRDF_GGX(directLight.direction, geometry.viewDir, geometry.normal, physicalMaterial.specularColor, physicalMaterial.specularF90, roughness));
|
||||
|
||||
celIntensity = celDiffuse + celSpecular;
|
||||
celIntensity = ceil(celIntensity * uCelSteps) / uCelSteps;
|
||||
|
||||
outgoingLight += color.rgb * directLight.color * celIntensity;
|
||||
}
|
||||
#pragma unroll_loop_end
|
||||
|
||||
outgoingLight += physicalMaterial.diffuseColor * luminance(uAmbientColor);
|
||||
#else
|
||||
ReflectedLight reflectedLight = ReflectedLight(vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0));
|
||||
|
||||
#pragma unroll_loop_start
|
||||
for (int i = 0; i < dLightCount; ++i) {
|
||||
directLight.direction = uLightDirection[i];
|
||||
directLight.color = uLightColor[i] * PI; // * PI for punctual light
|
||||
RE_Direct_Physical(directLight, geometry, physicalMaterial, reflectedLight);
|
||||
}
|
||||
#pragma unroll_loop_end
|
||||
|
||||
vec3 irradiance = uAmbientColor * PI; // * PI for punctual light
|
||||
RE_IndirectDiffuse_Physical(irradiance, geometry, physicalMaterial, reflectedLight);
|
||||
|
||||
// indirect specular only metals
|
||||
vec3 radiance = uAmbientColor * metalness;
|
||||
vec3 iblIrradiance = uAmbientColor * metalness;
|
||||
vec3 clearcoatRadiance = vec3(0.0);
|
||||
RE_IndirectSpecular_Physical(radiance, iblIrradiance, clearcoatRadiance, geometry, physicalMaterial, reflectedLight);
|
||||
|
||||
outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular;
|
||||
#endif
|
||||
outgoingLight = clamp(outgoingLight, 0.01, 0.99); // prevents black artifacts on specular highlight with transparent background
|
||||
|
||||
#if defined(dRenderVariant_color)
|
||||
|
||||
@@ -76,6 +76,7 @@ uniform vec3 uInteriorColor;
|
||||
bool interior;
|
||||
|
||||
uniform float uXrayEdgeFalloff;
|
||||
uniform float uCelSteps;
|
||||
uniform float uExposure;
|
||||
|
||||
uniform mat4 uProjection;
|
||||
|
||||
@@ -103,6 +103,12 @@ vec4 linearTosRGB(const in vec4 c) {
|
||||
return vec4(mix(pow(c.rgb, vec3(0.41666)) * 1.055 - vec3(0.055), c.rgb * 12.92, vec3(lessThanEqual(c.rgb, vec3(0.0031308)))), c.a);
|
||||
}
|
||||
|
||||
float luminance(vec3 c) {
|
||||
// https://www.w3.org/TR/WCAG21/#dfn-relative-luminance
|
||||
const vec3 W = vec3(0.2125, 0.7154, 0.0721);
|
||||
return dot(c, W);
|
||||
}
|
||||
|
||||
float linearizeDepth(const in float depth, const in float near, const in float far) {
|
||||
return (2.0 * near) / (far + near - depth * (far - near));
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
*/
|
||||
|
||||
export const cylinders_frag = `
|
||||
@@ -120,7 +121,11 @@ bool CylinderImpostor(
|
||||
#ifdef dSolidInterior
|
||||
if (interior) cameraNormal = -rayDir;
|
||||
#endif
|
||||
return true;
|
||||
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
|
||||
return true;
|
||||
#else
|
||||
return !interior;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} else if (bottomCap && y >= 0.0) {
|
||||
@@ -146,7 +151,11 @@ bool CylinderImpostor(
|
||||
#ifdef dSolidInterior
|
||||
if (interior) cameraNormal = -rayDir;
|
||||
#endif
|
||||
return true;
|
||||
#if defined(dClipVariant_pixel) && dClipObjectCount != 0
|
||||
return true;
|
||||
#else
|
||||
return !interior;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ uniform vec3 uFogColor;
|
||||
uniform float uAlpha;
|
||||
uniform bool uTransparentBackground;
|
||||
uniform float uXrayEdgeFalloff;
|
||||
uniform float uCelSteps;
|
||||
uniform float uExposure;
|
||||
|
||||
uniform int uRenderMask;
|
||||
|
||||
@@ -16,6 +16,7 @@ uniform vec2 uTexSize;
|
||||
uniform vec4 uBounds;
|
||||
|
||||
uniform float uKernel[dOcclusionKernelSize];
|
||||
uniform float uBlurDepthBias;
|
||||
|
||||
uniform float uBlurDirectionX;
|
||||
uniform float uBlurDirectionY;
|
||||
@@ -38,6 +39,10 @@ bool isBackground(const in float depth) {
|
||||
return depth == 1.0;
|
||||
}
|
||||
|
||||
bool isNearClip(const in float depth) {
|
||||
return depth == 0.0;
|
||||
}
|
||||
|
||||
bool outsideBounds(const in vec2 p) {
|
||||
return p.x < uBounds.x || p.y < uBounds.y || p.x > uBounds.z || p.y > uBounds.w;
|
||||
}
|
||||
@@ -59,16 +64,14 @@ void main(void) {
|
||||
}
|
||||
|
||||
float selfDepth = unpackRGToUnitInterval(packedDepth);
|
||||
// if background and if second pass
|
||||
if (isBackground(selfDepth) && uBlurDirectionY != 0.0) {
|
||||
// (if background and if second pass) or if near clip
|
||||
if ((isBackground(selfDepth) && uBlurDirectionY != 0.0) || isNearClip(selfDepth)) {
|
||||
gl_FragColor = vec4(packUnitIntervalToRG(1.0), packedDepth);
|
||||
return;
|
||||
}
|
||||
|
||||
float selfViewZ = getViewZ(selfDepth);
|
||||
float pixelSize = getPixelSize(coords, selfDepth);
|
||||
// max diff depth between two pixels
|
||||
float maxDiffViewZ = 1.0;
|
||||
|
||||
vec2 offset = vec2(uBlurDirectionX, uBlurDirectionY) / uTexSize;
|
||||
|
||||
@@ -86,12 +89,12 @@ void main(void) {
|
||||
vec4 sampleSsaoDepth = texture2D(tSsaoDepth, sampleCoords);
|
||||
|
||||
float sampleDepth = unpackRGToUnitInterval(sampleSsaoDepth.zw);
|
||||
if (isBackground(sampleDepth)) {
|
||||
if (isBackground(sampleDepth) || isNearClip(sampleDepth)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
float sampleViewZ = getViewZ(sampleDepth);
|
||||
if (abs(selfViewZ - sampleViewZ) > maxDiffViewZ) {
|
||||
if (abs(selfViewZ - sampleViewZ) >= uBlurDepthBias) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.391, IHM 1.25, MA 1.4.6.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.395, IHM 1.25, MA 1.4.6.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.391, IHM 1.25, MA 1.4.6.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.395, IHM 1.25, MA 1.4.6.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.391, IHM 1.25, MA 1.4.6.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.395, IHM 1.25, MA 1.4.6.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -793,6 +793,14 @@ export const mmCIF_Schema = {
|
||||
* the entry.
|
||||
*/
|
||||
pdbx_number_of_molecules: int,
|
||||
/**
|
||||
* An identifier for the parent entity if this entity
|
||||
* is part of a complex entity. For instance a chimeric
|
||||
* entity may be decomposed into several independent
|
||||
* chemical entities where each component entity was
|
||||
* obtained from a different source.
|
||||
*/
|
||||
pdbx_parent_entity_id: str,
|
||||
/**
|
||||
* Details about any entity mutation(s).
|
||||
*/
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -139,7 +139,7 @@ function atomGroupsSegmented({ unitTest, entityTest, chainTest, residueTest, ato
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const { chainElementSegments } = Unit.Kind.Spheres ? model.coarseHierarchy.spheres : model.coarseHierarchy.gaussians;
|
||||
const { chainElementSegments } = unit.kind === Unit.Kind.Spheres ? model.coarseHierarchy.spheres : model.coarseHierarchy.gaussians;
|
||||
const chainsIt = Segmentation.transientSegments(chainElementSegments, elements);
|
||||
|
||||
while (chainsIt.hasNext) {
|
||||
@@ -213,7 +213,7 @@ function atomGroupsGrouped({ unitTest, entityTest, chainTest, residueTest, atomT
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const { chainElementSegments } = Unit.Kind.Spheres ? model.coarseHierarchy.spheres : model.coarseHierarchy.gaussians;
|
||||
const { chainElementSegments } = unit.kind === Unit.Kind.Spheres ? model.coarseHierarchy.spheres : model.coarseHierarchy.gaussians;
|
||||
const chainsIt = Segmentation.transientSegments(chainElementSegments, elements);
|
||||
while (chainsIt.hasNext) {
|
||||
const chainSegment = chainsIt.move();
|
||||
|
||||
@@ -161,7 +161,7 @@ const entity = {
|
||||
pdbx_mutation: p(l => l.unit.model.entities.data.pdbx_mutation.value(eK(l))),
|
||||
pdbx_fragment: p(l => l.unit.model.entities.data.pdbx_fragment.value(eK(l))),
|
||||
pdbx_ec: p(l => l.unit.model.entities.data.pdbx_ec.value(eK(l))),
|
||||
|
||||
pdbx_parent_entity_id: p(l => l.unit.model.entities.data.pdbx_parent_entity_id.value(eK(l))),
|
||||
subtype: p(l => l.unit.model.entities.subtype.value(eK(l))),
|
||||
prd_id: p(l => l.unit.model.entities.prd_id?.value(eK(l)) ?? ''),
|
||||
};
|
||||
@@ -197,4 +197,4 @@ const StructureProperties = {
|
||||
};
|
||||
|
||||
type StructureProperties = typeof StructureProperties
|
||||
export { StructureProperties };
|
||||
export { StructureProperties };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2024 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>
|
||||
@@ -31,7 +31,7 @@ const DownloadDensity = StateAction.build({
|
||||
'pdb-xray': PD.Group({
|
||||
provider: PD.Group({
|
||||
id: PD.Text('1tqn', { label: 'Id' }),
|
||||
server: PD.Select('rcsb', [['pdbe', 'PDBe'], ['rcsb', 'RCSB PDB']]),
|
||||
server: PD.Select('pdbe', [['pdbe', 'PDBe']]),
|
||||
}, { pivot: 'id' }),
|
||||
type: PD.Select('2fofc', [['2fofc', '2Fo-Fc'], ['fofc', 'Fo-Fc']]),
|
||||
}, { isFlat: true }),
|
||||
@@ -74,18 +74,12 @@ const DownloadDensity = StateAction.build({
|
||||
downloadParams = src.params;
|
||||
break;
|
||||
case 'pdb-xray':
|
||||
downloadParams = src.params.provider.server === 'pdbe' ? {
|
||||
downloadParams = {
|
||||
url: Asset.Url(src.params.type === '2fofc'
|
||||
? `https://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.provider.id.toLowerCase()}.ccp4`
|
||||
: `https://www.ebi.ac.uk/pdbe/coordinates/files/${src.params.provider.id.toLowerCase()}_diff.ccp4`),
|
||||
isBinary: true,
|
||||
label: `PDBe X-ray map: ${src.params.provider.id}`
|
||||
} : {
|
||||
url: Asset.Url(src.params.type === '2fofc'
|
||||
? `https://edmaps.rcsb.org/maps/${src.params.provider.id.toLowerCase()}_2fofc.dsn6`
|
||||
: `https://edmaps.rcsb.org/maps/${src.params.provider.id.toLowerCase()}_fofc.dsn6`),
|
||||
isBinary: true,
|
||||
label: `RCSB X-ray map: ${src.params.provider.id}`
|
||||
};
|
||||
break;
|
||||
case 'pdb-emd-ds':
|
||||
@@ -123,9 +117,7 @@ const DownloadDensity = StateAction.build({
|
||||
break;
|
||||
case 'pdb-xray':
|
||||
entryId = src.params.provider.id;
|
||||
provider = src.params.provider.server === 'pdbe'
|
||||
? plugin.dataFormats.get('ccp4')
|
||||
: plugin.dataFormats.get('dsn6');
|
||||
provider = plugin.dataFormats.get('ccp4');
|
||||
break;
|
||||
case 'pdb-emd-ds':
|
||||
case 'pdb-xray-ds':
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2024 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>
|
||||
* @author Neli Fonseca <neli@ebi.ac.uk>
|
||||
*/
|
||||
|
||||
import * as CCP4 from '../../mol-io/reader/ccp4/parser';
|
||||
@@ -23,9 +24,12 @@ import { ColorNames } from '../../mol-util/color/names';
|
||||
import { assertUnreachable } from '../../mol-util/type-helpers';
|
||||
import { parsePrmtop } from '../../mol-io/reader/prmtop/parser';
|
||||
import { parseTop } from '../../mol-io/reader/top/parser';
|
||||
import { ungzip } from '../../mol-util/zip/zip';
|
||||
import { utf8Read } from '../../mol-io/common/utf8';
|
||||
|
||||
export { Download };
|
||||
export { DownloadBlob };
|
||||
export { DeflateData };
|
||||
export { RawData };
|
||||
export { ReadFile };
|
||||
export { ParseBlob };
|
||||
@@ -134,6 +138,33 @@ const DownloadBlob = PluginStateTransform.BuiltIn({
|
||||
// }
|
||||
});
|
||||
|
||||
type DeflateData = typeof DeflateData
|
||||
const DeflateData = PluginStateTransform.BuiltIn({
|
||||
name: 'defalate-data',
|
||||
display: { name: 'Deflate', description: 'Deflate compressed data' },
|
||||
params: {
|
||||
method: PD.Select('gzip', [['gzip', 'gzip']]), // later on we might have to add say brotli
|
||||
isString: PD.Boolean(false),
|
||||
stringEncoding: PD.Optional(PD.Select('utf-8', [['utf-8', 'UTF8']])),
|
||||
label: PD.Optional(PD.Text(''))
|
||||
},
|
||||
from: [SO.Data.Binary],
|
||||
to: [SO.Data.Binary, SO.Data.String]
|
||||
})({
|
||||
apply({ a, params }, plugin: PluginContext) {
|
||||
return Task.create('Gzip', async ctx => {
|
||||
const decompressedData = await ungzip(ctx, a.data);
|
||||
const label = params.label ? params.label : a.label;
|
||||
// handle decoding based on stringEncoding param
|
||||
if (params.isString) {
|
||||
const textData = utf8Read(decompressedData, 0, decompressedData.length);
|
||||
return new SO.Data.String(textData, { label });
|
||||
}
|
||||
return new SO.Data.Binary(decompressedData as Uint8Array, { label });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
type RawData = typeof RawData
|
||||
const RawData = PluginStateTransform.BuiltIn({
|
||||
name: 'raw-data',
|
||||
|
||||
@@ -95,13 +95,13 @@ export abstract class CollapsableControls<P = {}, S = {}, SS = {}> extends Plugi
|
||||
|
||||
render() {
|
||||
if (this.state.isHidden) return null;
|
||||
|
||||
const divid = this.state.header.toLowerCase().replace(/\s/g, '');
|
||||
const wrapClass = this.state.isCollapsed
|
||||
? 'msp-transform-wrapper msp-transform-wrapper-collapsed'
|
||||
: 'msp-transform-wrapper';
|
||||
|
||||
return <div className={wrapClass}>
|
||||
<div className='msp-transform-header'>
|
||||
<div id={divid} className='msp-transform-header'>
|
||||
<Button icon={this.state.brand ? void 0 : this.state.isCollapsed ? ArrowRightSvg : ArrowDropDownSvg} noOverflow onClick={this.toggleCollapsed}
|
||||
className={this.state.brand ? `msp-transform-header-brand msp-transform-header-brand-${this.state.brand.accent}` : void 0} title={`Click to ${this.state.isCollapsed ? 'expand' : 'collapse'}`}>
|
||||
{/* {this.state.brand && <div className={`msp-accent-bg-${this.state.brand.accent}`}>{this.state.brand.svg ? <Icon svg={this.state.brand.svg} /> : this.state.brand.name}</div>} */}
|
||||
@@ -122,4 +122,4 @@ export abstract class CollapsableControls<P = {}, S = {}, SS = {}> extends Plugi
|
||||
if (props.header !== undefined) state.header = props.header;
|
||||
this.state = state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ export function ViewportSnapshotDescription() {
|
||||
const e = plugin.managers.snapshot.getEntry(current)!;
|
||||
if (!e?.description?.trim()) return null;
|
||||
|
||||
return <div className='msp-snapshot-description-wrapper'>
|
||||
return <div id='snapinfo' className='msp-snapshot-description-wrapper'>
|
||||
<Markdown skipHtml components={{ a: MarkdownAnchor }}>{e.description}</Markdown>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -169,9 +169,19 @@ const _SubscriptionsOutlined = <svg width='24px' height='24px' viewBox='0 0 24 2
|
||||
export function SubscriptionsOutlinedSvg() { return _SubscriptionsOutlined; }
|
||||
const _SwapHoriz = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M6.99 11L3 15l3.99 4v-3H14v-2H6.99v-3zM21 9l-3.99-4v3H10v2h7.01v3L21 9z' /></svg>;
|
||||
export function SwapHorizSvg() { return _SwapHoriz; }
|
||||
const _Tour = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M21 4H7V2H5v20h2v-8h14l-2-5zm-6 5c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2' /></svg>;
|
||||
export function TourSvg() { return _Tour; }
|
||||
const _Tune = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M3 17v2h6v-2H3zM3 5v2h10V5H3zm10 16v-2h8v-2h-8v-2h-2v6h2zM7 9v2H3v2h4v2h2V9H7zm14 4v-2H11v2h10zm-6-4h2V7h4V5h-4V3h-2v6z' /></svg>;
|
||||
export function TuneSvg() { return _Tune; }
|
||||
const _VisibilityOffOutlined = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M12 6c3.79 0 7.17 2.13 8.82 5.5-.59 1.22-1.42 2.27-2.41 3.12l1.41 1.41c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l1.65 1.65C10.66 6.09 11.32 6 12 6zm-1.07 1.14L13 9.21c.57.25 1.03.71 1.28 1.28l2.07 2.07c.08-.34.14-.7.14-1.07C16.5 9.01 14.48 7 12 7c-.37 0-.72.05-1.07.14zM2.01 3.87l2.68 2.68C3.06 7.83 1.77 9.53 1 11.5 2.73 15.89 7 19 12 19c1.52 0 2.98-.29 4.32-.82l3.42 3.42 1.41-1.41L3.42 2.45 2.01 3.87zm7.5 7.5l2.61 2.61c-.04.01-.08.02-.12.02-1.38 0-2.5-1.12-2.5-2.5 0-.05.01-.08.01-.13zm-3.4-3.4l1.75 1.75c-.23.55-.36 1.15-.36 1.78 0 2.48 2.02 4.5 4.5 4.5.63 0 1.23-.13 1.77-.36l.98.98c-.88.24-1.8.38-2.75.38-3.79 0-7.17-2.13-8.82-5.5.7-1.43 1.72-2.61 2.93-3.53z' /></svg>;
|
||||
const _TooltipText = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M20 2H4c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2M6 9h12v2H6zm8 5H6v-2h8zm4-6H6V6h12z' /></svg>;
|
||||
export function TooltipTextSvg() { return _TooltipText; }
|
||||
const _TooltipTextOutline = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M4 4h16v12H5.17L4 17.17zm0-2c-1.1 0-1.99.9-1.99 2L2 22l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm2 10h8v2H6zm0-3h12v2H6zm0-3h12v2H6z' /></svg>;
|
||||
export function TooltipTextOutlineSvg() { return _TooltipTextOutline; }
|
||||
const _PlusBox = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2m-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4z' /></svg>;
|
||||
export function PlusBoxSvg() { return _PlusBox; }
|
||||
const _MinusBox = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2m-2 10H7v-2h10z' /></svg>;
|
||||
export function MinusBoxSvg() { return _MinusBox; }
|
||||
export function VisibilityOffOutlinedSvg() { return _VisibilityOffOutlined; }
|
||||
const _VisibilityOutlined = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M12 6c3.79 0 7.17 2.13 8.82 5.5C19.17 14.87 15.79 17 12 17s-7.17-2.13-8.82-5.5C4.83 8.13 8.21 6 12 6m0-2C7 4 2.73 7.11 1 11.5 2.73 15.89 7 19 12 19s9.27-3.11 11-7.5C21.27 7.11 17 4 12 4zm0 5c1.38 0 2.5 1.12 2.5 2.5S13.38 14 12 14s-2.5-1.12-2.5-2.5S10.62 9 12 9m0-2c-2.48 0-4.5 2.02-4.5 4.5S9.52 16 12 16s4.5-2.02 4.5-4.5S14.48 7 12 7z' /></svg>;
|
||||
export function VisibilityOutlinedSvg() { return _VisibilityOutlined; }
|
||||
|
||||
@@ -83,7 +83,7 @@ class Layout extends PluginUIComponent {
|
||||
this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
|
||||
}
|
||||
|
||||
region(kind: RegionKind, Element?: React.ComponentClass) {
|
||||
region(kind: RegionKind, Element?: React.ComponentClass | React.FC) {
|
||||
return <div className={`msp-layout-region msp-layout-${kind}`}>
|
||||
<div className='msp-layout-static'>
|
||||
{Element ? <Element /> : null}
|
||||
@@ -268,6 +268,7 @@ export class ControlsWrapper extends PluginUIComponent {
|
||||
export class DefaultViewport extends PluginUIComponent {
|
||||
render() {
|
||||
const VPControls = this.plugin.spec.components?.viewport?.controls || ViewportControls;
|
||||
const SnapshotDescription = this.plugin.spec.components?.viewport?.snapshotDescription || ViewportSnapshotDescription;
|
||||
|
||||
return <>
|
||||
<Viewport />
|
||||
@@ -275,7 +276,7 @@ export class DefaultViewport extends PluginUIComponent {
|
||||
<AnimationViewportControls />
|
||||
<TrajectoryViewportControls />
|
||||
<StateSnapshotViewportControls />
|
||||
<ViewportSnapshotDescription />
|
||||
<SnapshotDescription />
|
||||
</div>
|
||||
<SelectionViewportControls />
|
||||
<VPControls />
|
||||
|
||||
@@ -345,6 +345,12 @@
|
||||
text-align: left;
|
||||
color: color-lower-contrast($font-color, 15%);
|
||||
}
|
||||
|
||||
> p {
|
||||
padding: (math.div($control-spacing, 2)) $control-spacing;
|
||||
text-align: left;
|
||||
color: color-lower-contrast($font-color, 15%);
|
||||
}
|
||||
}
|
||||
|
||||
.msp-help-description {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2024 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>
|
||||
@@ -19,13 +19,14 @@ interface PluginUISpec extends PluginSpec {
|
||||
components?: {
|
||||
controls?: PluginUISpec.LayoutControls
|
||||
remoteState?: 'none' | 'default',
|
||||
structureTools?: React.ComponentClass,
|
||||
structureTools?: React.ComponentClass | React.FC,
|
||||
viewport?: {
|
||||
view?: React.ComponentClass,
|
||||
controls?: React.ComponentClass
|
||||
view?: React.ComponentClass | React.FC,
|
||||
controls?: React.ComponentClass | React.FC,
|
||||
snapshotDescription?: React.ComponentClass | React.FC,
|
||||
},
|
||||
sequenceViewer?: {
|
||||
view?: React.ComponentClass
|
||||
view?: React.ComponentClass | React.FC
|
||||
}
|
||||
hideTaskOverlay?: boolean,
|
||||
disableDragOverlay?: boolean,
|
||||
@@ -34,10 +35,10 @@ interface PluginUISpec extends PluginSpec {
|
||||
|
||||
namespace PluginUISpec {
|
||||
export interface LayoutControls {
|
||||
top?: React.ComponentClass | 'none',
|
||||
left?: React.ComponentClass | 'none',
|
||||
right?: React.ComponentClass | 'none',
|
||||
bottom?: React.ComponentClass | 'none'
|
||||
top?: React.ComponentClass | React.FC | 'none',
|
||||
left?: React.ComponentClass | React.FC | 'none',
|
||||
right?: React.ComponentClass | React.FC | 'none',
|
||||
bottom?: React.ComponentClass | React.FC | 'none'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2022-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { PresetStructureRepresentations } from '../../mol-plugin-state/builder/structure/representation-preset';
|
||||
@@ -70,6 +71,7 @@ export class QuickStyles extends PurePluginUIComponent {
|
||||
radius: 5,
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
blurDepthBias: 0.5,
|
||||
samples: 32,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
@@ -108,6 +110,7 @@ export class QuickStyles extends PurePluginUIComponent {
|
||||
radius: 5,
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
blurDepthBias: 0.5,
|
||||
samples: 32,
|
||||
resolutionScale: 1,
|
||||
color: Color(0x000000),
|
||||
@@ -121,15 +124,15 @@ export class QuickStyles extends PurePluginUIComponent {
|
||||
|
||||
render() {
|
||||
return <div className='msp-flex-row'>
|
||||
<Button noOverflow title='Applies default representation preset. Set outline and occlusion effects to defaults.' onClick={() => this.default()} style={{ width: 'auto' }}>
|
||||
<Button noOverflow title='Applies default representation preset and sets outline and occlusion effects to default' onClick={() => this.default()} style={{ width: 'auto' }}>
|
||||
Default
|
||||
</Button>
|
||||
<Button noOverflow title='Applies no representation preset. Enables outline and occlusion effects. Enables ignore-light representation parameter.' onClick={() => this.stylized()} style={{ width: 'auto' }}>
|
||||
Stylized
|
||||
</Button>
|
||||
<Button noOverflow title='Applies illustrative representation preset. Enables outline and occlusion effects. Enables ignore-light parameter.' onClick={() => this.illustrative()} style={{ width: 'auto' }}>
|
||||
<Button noOverflow title='Applies illustrative representation preset and Stylize it' onClick={() => this.illustrative()} style={{ width: 'auto' }}>
|
||||
Illustrative
|
||||
</Button>
|
||||
<Button noOverflow title='Does not change representation, enables outline and occlusion effects, enables ignore-light representation parameter' onClick={() => this.stylized()} style={{ width: 'auto' }}>
|
||||
Stylize Current
|
||||
</Button>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2024 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>
|
||||
@@ -374,7 +374,7 @@ export class PluginContext {
|
||||
return PluginCommands.State.RemoveObject(this, { state: this.state.data, ref: StateTransform.RootRef });
|
||||
}
|
||||
|
||||
dispose(options?: { doNotForceWebGLContextLoss?: boolean }) {
|
||||
dispose(options?: { doNotForceWebGLContextLoss?: boolean, doNotDisposeCanvas3DContext?: boolean }) {
|
||||
if (this.disposed) return;
|
||||
|
||||
for (const s of this.subs) {
|
||||
@@ -385,7 +385,9 @@ export class PluginContext {
|
||||
this.animationLoop.stop();
|
||||
this.commands.dispose();
|
||||
this.canvas3d?.dispose();
|
||||
this.canvas3dContext?.dispose(options);
|
||||
if (!options?.doNotDisposeCanvas3DContext) {
|
||||
this.canvas3dContext?.dispose(options);
|
||||
}
|
||||
this.ev.dispose();
|
||||
this.state.dispose();
|
||||
this.helpers.substructureParent.dispose();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Jesse Liang <jesse.liang@rcsb.org>
|
||||
@@ -209,6 +209,7 @@ export function defaultImagePassParams(): Partial<ImageProps> {
|
||||
...DefaultCanvas3DParams.multiSample,
|
||||
mode: 'on',
|
||||
sampleLevel: 4,
|
||||
reuseOcclusion: false,
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -221,6 +222,7 @@ export const STYLIZED_POSTPROCESSING: Partial<PostprocessingProps> = {
|
||||
radius: 5,
|
||||
bias: 0.8,
|
||||
blurKernelSize: 15,
|
||||
blurDepthBias: 0.5,
|
||||
resolutionScale: 1,
|
||||
color: ColorNames.black,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2024 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>
|
||||
@@ -116,6 +116,7 @@ class ViewportScreenshotHelper extends PluginComponent {
|
||||
...c.props.multiSample,
|
||||
mode: multisample ? 'on' : 'off',
|
||||
sampleLevel: colorBufferFloat && textureFloat ? 4 : 2,
|
||||
reuseOcclusion: false,
|
||||
},
|
||||
postprocessing: {
|
||||
...c.props.postprocessing,
|
||||
|
||||
Reference in New Issue
Block a user