mirror of
https://github.com/molstar/molstar.git
synced 2026-06-07 23:34:23 +08:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d993082f24 | ||
|
|
5eaa73d56d | ||
|
|
fc3c7997ea | ||
|
|
b3aecf8de4 | ||
|
|
f3581e62ef | ||
|
|
88e7fe508f | ||
|
|
194092ed67 | ||
|
|
e96157c890 | ||
|
|
a028c1ef42 | ||
|
|
8ba19f0be4 | ||
|
|
bccc68f6df | ||
|
|
026a05d03d | ||
|
|
2b4741c8ee | ||
|
|
7960ee06d4 | ||
|
|
f73f5af131 | ||
|
|
3123110aa4 | ||
|
|
154063638d | ||
|
|
a720b98365 | ||
|
|
d4a2937e0b | ||
|
|
b0ca7ffbb7 | ||
|
|
c42b738abe | ||
|
|
ab0d0fec53 | ||
|
|
8d96131962 | ||
|
|
95bbcd8b24 | ||
|
|
a21f5c2c23 | ||
|
|
94b7b1281c | ||
|
|
16dba586df | ||
|
|
72b761f959 | ||
|
|
943d81cbf9 | ||
|
|
ea612c3acb | ||
|
|
a1308645e5 | ||
|
|
794b705184 | ||
|
|
66264abe50 |
17
CHANGELOG.md
17
CHANGELOG.md
@@ -5,6 +5,23 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v5.4.0] - 2025-11-16
|
||||
- Fix ugly camera clipping in snapshot transitions
|
||||
- Add viewport button to toggle illumination mode
|
||||
- Fix bounding sphere computation for 3D text
|
||||
- Structure bounding sphere includes atom VDW radii / coarse sphere radii
|
||||
- Relax camera limits to allow focusing any selection with >1 atom
|
||||
- MolViewSpec
|
||||
- Fix `appendSnapshots` when loading MVSX
|
||||
- Fix all-selector color not applying on substructure
|
||||
- Fix primitives in root not being transformed with reference structure
|
||||
- Color themes do not prefer smoothing (improves performance in animations)
|
||||
- Allow canvas background interpolation
|
||||
- Fix `direct-volume` not drawn in illumination mode
|
||||
- Fix default trackball animated spin speed
|
||||
- Use `PluginCommands` to set canvas3d props in camera behavior
|
||||
- Add support for Input Method Editor (IME) to text params input
|
||||
|
||||
## [v5.3.0] - 2025-11-5
|
||||
- Update loading message in MVS Stories Viewer
|
||||
- Add `Canvas3D.setAttribs`
|
||||
|
||||
6281
package-lock.json
generated
6281
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "5.3.0",
|
||||
"version": "5.4.0",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
@@ -131,36 +131,36 @@
|
||||
"@types/gl": "^6.0.5",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"@types/react": "^18.3.24",
|
||||
"@types/react": "^18.3.26",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"@types/webxr": "^0.5.23",
|
||||
"@typescript-eslint/eslint-plugin": "^8.44.1",
|
||||
"@typescript-eslint/parser": "^8.44.1",
|
||||
"@types/webxr": "^0.5.24",
|
||||
"@typescript-eslint/eslint-plugin": "^8.46.4",
|
||||
"@typescript-eslint/parser": "^8.46.4",
|
||||
"benchmark": "^2.1.4",
|
||||
"concurrently": "^9.2.1",
|
||||
"cpx2": "^8.0.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"esbuild": "^0.25.10",
|
||||
"esbuild": "^0.27.0",
|
||||
"esbuild-jest-transform": "^2.0.1",
|
||||
"esbuild-sass-plugin": "^3.3.1",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint": "^9.39.1",
|
||||
"fs-extra": "^11.3.2",
|
||||
"http-server": "^14.1.1",
|
||||
"jest": "^30.2.0",
|
||||
"jpeg-js": "^0.4.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"sass": "^1.93.2",
|
||||
"simple-git": "^3.28.0",
|
||||
"sass": "^1.94.0",
|
||||
"simple-git": "^3.30.0",
|
||||
"tsc-alias": "^1.8.16",
|
||||
"typescript": "^5.9.2"
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/argparse": "^2.0.17",
|
||||
"@types/benchmark": "^2.1.5",
|
||||
"@types/compression": "1.8.1",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/node": "^20.19.17",
|
||||
"@types/express": "^5.0.5",
|
||||
"@types/node": "^20.19.25",
|
||||
"@types/node-fetch": "^2.6.13",
|
||||
"@types/swagger-ui-dist": "3.30.6",
|
||||
"argparse": "^2.0.1",
|
||||
@@ -168,14 +168,14 @@
|
||||
"cors": "^2.8.5",
|
||||
"express": "^5.1.0",
|
||||
"h264-mp4-encoder": "^1.0.12",
|
||||
"immutable": "^5.1.3",
|
||||
"immutable": "^5.1.4",
|
||||
"io-ts": "^2.2.22",
|
||||
"mutative": "^1.3.0",
|
||||
"node-fetch": "^2.7.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"swagger-ui-dist": "^5.29.0",
|
||||
"swagger-ui-dist": "^5.30.2",
|
||||
"tslib": "^2.8.1",
|
||||
"util.promisify": "^1.1.3"
|
||||
},
|
||||
|
||||
@@ -531,7 +531,7 @@ export class Viewer {
|
||||
} else if (format === 'mvsx') {
|
||||
const data = await this.plugin.runTask(this.plugin.fetch({ url, type: 'binary' }));
|
||||
await this.plugin.runTask(Task.create('Load MVSX file', async ctx => {
|
||||
const parsed = await loadMVSX(this.plugin, ctx, data);
|
||||
const parsed = await loadMVSX(this.plugin, ctx, data, { doNotClearAssets: options?.appendSnapshots });
|
||||
await loadMVS(this.plugin, parsed.mvsData, { sanityChecks: true, sourceUrl: parsed.sourceUrl, ...options });
|
||||
}));
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
@@ -33,7 +33,7 @@ import { MVSTreeSchema } from './tree/mvs/mvs-tree';
|
||||
|
||||
|
||||
const DefaultFocusOptions = {
|
||||
minRadius: 5,
|
||||
minRadius: 1,
|
||||
extraRadius: 0,
|
||||
};
|
||||
const DefaultCanvasBackgroundColor = ColorNames.white;
|
||||
|
||||
@@ -117,7 +117,7 @@ export function MVSAnnotationColorTheme(ctx: ThemeDataContext, props: MVSAnnotat
|
||||
return {
|
||||
factory: MVSAnnotationColorTheme,
|
||||
granularity: 'groupInstance',
|
||||
preferSmoothing: true,
|
||||
preferSmoothing: false,
|
||||
color: color,
|
||||
props: props,
|
||||
description: 'Assigns colors based on custom MolViewSpec annotation data.',
|
||||
|
||||
@@ -112,11 +112,18 @@ export const MVSXFormatProvider: DataFormatProvider<{}, StateObjectRef<Mvs>, any
|
||||
* add all contained files to `plugin`'s asset manager,
|
||||
* and parse the main file in the archive as MVSJ.
|
||||
* Return parsed MVS data and `sourceUrl` for resolution of relative URIs. */
|
||||
export async function loadMVSX(plugin: PluginContext, runtimeCtx: RuntimeContext, data: Uint8Array<ArrayBuffer>, mainFilePath: string = 'index.mvsj'): Promise<{ mvsData: MVSData, sourceUrl: string }> {
|
||||
export async function loadMVSX(plugin: PluginContext, runtimeCtx: RuntimeContext, data: Uint8Array<ArrayBuffer>, mainFilePathOrOptions?: string | { mainFilePath?: string, doNotClearAssets?: boolean }): Promise<{ mvsData: MVSData, sourceUrl: string }> {
|
||||
// TODO: on next major version, streamline mainFilePathOrOptions
|
||||
if (typeof mainFilePathOrOptions === 'string') {
|
||||
mainFilePathOrOptions = { mainFilePath: mainFilePathOrOptions };
|
||||
}
|
||||
const mainFilePath = mainFilePathOrOptions?.mainFilePath ?? 'index.mvsj';
|
||||
const doNotClearAssets = mainFilePathOrOptions?.doNotClearAssets ?? false;
|
||||
|
||||
// Ensure at most one generation of MVSX file assets exists in the asset manager.
|
||||
// Hopefully, this is a reasonable compromise to ensure MVSX files work in multi-snapshot
|
||||
// states.
|
||||
clearMVSXFileAssets(plugin);
|
||||
if (!doNotClearAssets) clearMVSXFileAssets(plugin);
|
||||
|
||||
const archiveId = `ni,MurmurHash3_128;${murmurHash3_128_fromBytes(data, 42)}`;
|
||||
let files: { [path: string]: Uint8Array<ArrayBuffer> };
|
||||
@@ -160,7 +167,7 @@ export async function loadMVSData(plugin: PluginContext, data: MVSData | StringL
|
||||
throw new Error("loadMvsData: if `format` is 'mvsx', then `data` must be a Uint8Array or a base64-encoded string prefixed with 'base64,'.");
|
||||
}
|
||||
await plugin.runTask(Task.create('Load MVSX file', async ctx => {
|
||||
const parsed = await loadMVSX(plugin, ctx, data as Uint8Array<ArrayBuffer>);
|
||||
const parsed = await loadMVSX(plugin, ctx, data as Uint8Array<ArrayBuffer>, { doNotClearAssets: options?.appendSnapshots });
|
||||
await loadMVS(plugin, parsed.mvsData, { sanityChecks: true, ...options, sourceUrl: parsed.sourceUrl });
|
||||
}));
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
@@ -72,7 +72,7 @@ export const DefaultMultilayerColorThemeProps: MultilayerColorThemeProps = { lay
|
||||
* If a nested theme provider has `ensureCustomProperties` methods, these will not be called automatically
|
||||
* (the caller must ensure that any required custom properties be attached). */
|
||||
function makeMultilayerColorTheme(ctx: ThemeDataContext, props: MultilayerColorThemeProps, colorThemeRegistry: ColorTheme.Registry): ColorTheme<MultilayerColorThemeParams> {
|
||||
const { colorLayers, granularity } = makeLayers(ctx, props, colorThemeRegistry);
|
||||
const { colorLayers, granularity, preferSmoothing } = makeLayers(ctx, props, colorThemeRegistry);
|
||||
|
||||
function structureElementColor(loc: StructureElement.Location, isSecondary: boolean): Color {
|
||||
for (const layer of colorLayers) {
|
||||
@@ -101,7 +101,7 @@ function makeMultilayerColorTheme(ctx: ThemeDataContext, props: MultilayerColorT
|
||||
return {
|
||||
factory: (ctx_, props_) => makeMultilayerColorTheme(ctx_, props_, colorThemeRegistry),
|
||||
granularity,
|
||||
preferSmoothing: true,
|
||||
preferSmoothing,
|
||||
color: color,
|
||||
props: props,
|
||||
description: 'Combines colors from multiple color themes.',
|
||||
@@ -136,6 +136,7 @@ interface ColorLayer {
|
||||
function makeLayers(ctx: ThemeDataContext, props: MultilayerColorThemeProps, colorThemeRegistry: ColorTheme.Registry) {
|
||||
const colorLayers: ColorLayer[] = [];
|
||||
let granularityFlags = 0;
|
||||
let preferSmoothing = false;
|
||||
for (let i = props.layers.length - 1; i >= 0; i--) { // iterate from the end to get top layer first, bottom layer last
|
||||
const layer = props.layers[i];
|
||||
const themeProvider = colorThemeRegistry.get(layer.theme.name);
|
||||
@@ -175,8 +176,9 @@ function makeLayers(ctx: ThemeDataContext, props: MultilayerColorThemeProps, col
|
||||
default:
|
||||
console.warn(`Skipping color theme '${layer.theme.name}', cannot process granularity '${theme.granularity}'`);
|
||||
}
|
||||
if (theme.preferSmoothing) preferSmoothing = true;
|
||||
}
|
||||
return { colorLayers, granularity: granularityNameFromFlags(granularityFlags) };
|
||||
return { colorLayers, granularity: granularityNameFromFlags(granularityFlags), preferSmoothing };
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -464,12 +464,15 @@ const MolstarLoadingActions: LoadingActions<MolstarTree, MolstarLoadingContext>
|
||||
const refs = getPrimitiveStructureRefs(tree);
|
||||
const clip = clippingForNode(tree);
|
||||
const data = UpdateTarget.apply(updateParent, MVSInlinePrimitiveData, { node: tree as any });
|
||||
UpdateTarget.setMvsDependencies(data, refs); // MVSInlinePrimitiveData must depend on `refs` because it caches positions
|
||||
return applyPrimitiveVisuals(data, refs, clip);
|
||||
},
|
||||
primitives_from_uri(updateParent: UpdateTarget, tree: MolstarNode<'primitives_from_uri'>, context: MolstarLoadingContext): UpdateTarget {
|
||||
const data = UpdateTarget.apply(updateParent, MVSDownloadPrimitiveData, { uri: tree.params.uri, format: tree.params.format });
|
||||
const refs = new Set(tree.params.references);
|
||||
const clip = clippingForNode(tree);
|
||||
return applyPrimitiveVisuals(data, new Set(tree.params.references), clip);
|
||||
const data = UpdateTarget.apply(updateParent, MVSDownloadPrimitiveData, { uri: tree.params.uri, format: tree.params.format });
|
||||
UpdateTarget.setMvsDependencies(data, refs); // MVSInlinePrimitiveData must depend on `refs` because it caches positions
|
||||
return applyPrimitiveVisuals(data, refs, clip);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ export const Canvas3DParams = {
|
||||
cameraClipping: PD.Group({
|
||||
radius: PD.Numeric(100, { min: 0, max: 99, step: 1 }, { label: 'Clipping', description: 'How much of the scene to show.' }),
|
||||
far: PD.Boolean(true, { description: 'Hide scene in the distance' }),
|
||||
minNear: PD.Numeric(5, { min: 0.1, max: 100, step: 0.1 }, { description: 'Note, may cause performance issues rendering impostors when set too small and cause issues with outline rendering when too close to 0.' }),
|
||||
minNear: PD.Numeric(1, { min: 0.1, max: 100, step: 0.1 }, { description: 'Minimal allowed distance of near clipping plane from the camera. Note, may cause performance issues rendering impostors when set too small and cause issues with outline rendering when too close to 0.' }),
|
||||
}, { pivot: 'radius' }),
|
||||
viewport: PD.MappedStatic('canvas', {
|
||||
canvas: PD.Group({}),
|
||||
|
||||
@@ -75,7 +75,7 @@ export const TrackballControlsParams = {
|
||||
animate: PD.MappedStatic('off', {
|
||||
off: PD.EmptyGroup(),
|
||||
spin: PD.Group({
|
||||
speed: PD.Numeric(0.3, { min: -5, max: 5, step: 0.1 }, { description: 'Number of rotations per second' }),
|
||||
speed: PD.Numeric(0.1, { min: -2, max: 2, step: 0.01 }, { description: 'Number of rotations per second' }),
|
||||
}, { description: 'Spin the 3D scene around the x-axis in view space' }),
|
||||
rock: PD.Group({
|
||||
speed: PD.Numeric(0.3, { min: -5, max: 5, step: 0.1 }, { description: 'Number of oscilations per second' }),
|
||||
|
||||
@@ -163,7 +163,7 @@ export class IlluminationPass {
|
||||
const { gl, state } = this.webgl;
|
||||
|
||||
const markingEnabled = MarkingPass.isEnabled(props.marking);
|
||||
const hasTransparent = scene.opacityAverage < 1;
|
||||
const hasTransparent = scene.opacityAverage < 1 || scene.volumes.renderables.length > 0;
|
||||
const hasMarking = markingEnabled && scene.markerAverage > 0;
|
||||
|
||||
this.transparentTarget.bind();
|
||||
@@ -347,7 +347,7 @@ export class IlluminationPass {
|
||||
const dofEnabled = DofPass.isEnabled(props.postprocessing);
|
||||
|
||||
const markingEnabled = MarkingPass.isEnabled(props.marking);
|
||||
const hasTransparent = scene.opacityAverage < 1;
|
||||
const hasTransparent = scene.opacityAverage < 1 || scene.volumes.renderables.length > 0;
|
||||
const hasMarking = markingEnabled && scene.markerAverage > 0;
|
||||
|
||||
let needsUpdateCompose = false;
|
||||
|
||||
@@ -122,7 +122,9 @@ export function createValueColor(value: Color, colorData?: ColorData): ColorData
|
||||
|
||||
/** Creates color uniform */
|
||||
function createUniformColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData {
|
||||
return createValueColor(color(NullLocation, false), colorData);
|
||||
locationIt.reset();
|
||||
const loc = locationIt.hasNext ? locationIt.move() : { location: NullLocation, isSecondary: false };
|
||||
return createValueColor(color(loc.location, loc.isSecondary), colorData);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
@@ -77,7 +77,9 @@ export function createValueSize(value: number, sizeData?: SizeData): SizeData {
|
||||
|
||||
/** Creates size uniform */
|
||||
export function createUniformSize(locationIt: LocationIterator, sizeFn: LocationSize, sizeData?: SizeData): SizeData {
|
||||
return createValueSize(sizeFn(NullLocation), sizeData);
|
||||
locationIt.reset();
|
||||
const location = locationIt.hasNext ? locationIt.move().location : NullLocation;
|
||||
return createValueSize(sizeFn(location), sizeData);
|
||||
}
|
||||
|
||||
export function createTextureSize(sizes: TextureImage<Uint8Array>, type: SizeType, sizeData?: SizeData): SizeData {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
@@ -333,5 +334,5 @@ function getPadding(mappings: Float32Array, depths: Float32Array, charCount: num
|
||||
const d = Math.abs(depths[i]);
|
||||
if (d > maxDepth) maxDepth = d;
|
||||
}
|
||||
return scale * Math.max(maxDepth, maxOffset);
|
||||
return Math.max(maxDepth, scale * maxOffset);
|
||||
}
|
||||
@@ -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.406, IHM 1.28, MA 1.4.7.
|
||||
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.407, IHM 1.28, MA 1.4.8.
|
||||
*
|
||||
* @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.406, IHM 1.28, MA 1.4.7.
|
||||
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.407, IHM 1.28, MA 1.4.8.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
|
||||
@@ -40,7 +40,7 @@ export const CifCore_Schema = {
|
||||
* in exceptional circumstances (Brock, 2025) may be reported as
|
||||
* non-integral.
|
||||
*
|
||||
* Reference: Brock, C. P. (2025). Acta Cryst. A81, nnn-nnn.
|
||||
* Reference: Brock, C. P. (2025). Acta Cryst. A81, 405-408.
|
||||
*/
|
||||
formula_units_z: float,
|
||||
/**
|
||||
@@ -336,7 +336,12 @@ export const CifCore_Schema = {
|
||||
*/
|
||||
site_symmetry_2: str,
|
||||
/**
|
||||
* Bond valence calculated from the bond distance.
|
||||
* Valence assigned to the bond between the sites identified by
|
||||
* _geom_bond.id calculated according to the bond-valence model
|
||||
* (Brown, 2002) from the bond distance.
|
||||
*
|
||||
* Ref: Brown, I. D. (2002). The Chemical Bond in Inorganic Chemistry:
|
||||
* the Bond-Valence Model, eq. (3.1). Oxford: Oxford University Press.
|
||||
*/
|
||||
valence: float,
|
||||
},
|
||||
|
||||
@@ -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.406, IHM 1.28, MA 1.4.7.
|
||||
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.407, IHM 1.28, MA 1.4.8.
|
||||
*
|
||||
* @author molstar/ciftools package
|
||||
*/
|
||||
@@ -5078,7 +5078,7 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* The type of data held in the dataset.
|
||||
*/
|
||||
content_type: Aliased<'target' | 'template structure' | 'polymeric template library' | 'spatial restraints' | 'target-template alignment' | 'coevolution MSA' | 'model coordinates' | 'input structure' | 'reference database' | 'other'>(str),
|
||||
content_type: Aliased<'target' | 'template structure' | 'polymeric template library' | 'spatial restraints' | 'target-template alignment' | 'coevolution MSA' | 'model coordinates' | 'input structure' | 'reference database' | 'intermediate backbone' | 'intermediate sequence' | 'model quality assessment scores' | 'energy estimate' | 'experimental validation' | 'other'>(str),
|
||||
/**
|
||||
* Details for other content types.
|
||||
*/
|
||||
@@ -5131,7 +5131,7 @@ export const mmCIF_Schema = {
|
||||
/**
|
||||
* The mode of calculation of the QA metric.
|
||||
*/
|
||||
mode: Aliased<'local' | 'global' | 'local-pairwise' | 'per-feature' | 'per-feature-pair'>(str),
|
||||
mode: Aliased<'local' | 'global' | 'local-pairwise' | 'per-feature' | 'per-feature-pair' | 'dihedral'>(str),
|
||||
/**
|
||||
* Identifier to the set of software used to calculate the QA metric.
|
||||
* This data item is a pointer to the _ma_software_group.group_id in the
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
/**
|
||||
* Copyright (c) 2017-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2017-2025 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 Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { UUID } from '../../../mol-util/uuid';
|
||||
import { StructureSequence } from './properties/sequence';
|
||||
import { AtomicHierarchy, AtomicConformation, AtomicRanges } from './properties/atomic';
|
||||
import { AtomicHierarchy, AtomicConformation, AtomicRanges, VdwRadius } from './properties/atomic';
|
||||
import { CoarseHierarchy, CoarseConformation } from './properties/coarse';
|
||||
import { Entities, ChemicalComponentMap, MissingResidues, StructAsymMap } from './properties/common';
|
||||
import { CustomProperties } from '../../custom-property';
|
||||
@@ -184,6 +185,20 @@ export namespace Model {
|
||||
return center;
|
||||
}
|
||||
|
||||
const AtomicRadiiProp = '__AtomicRadii__';
|
||||
/** Get array of atomic radii for all atoms in the model (cached). */
|
||||
export function getAtomicRadii(model: Model): Float32Array {
|
||||
if (model._dynamicPropertyData[AtomicRadiiProp]) return model._dynamicPropertyData[AtomicRadiiProp];
|
||||
const nAtoms = model.atomicHierarchy.atoms._rowCount;
|
||||
const type_symbol = model.atomicHierarchy.atoms.type_symbol.value;
|
||||
const radii = new Float32Array(nAtoms);
|
||||
for (let i = 0; i < nAtoms; i++) {
|
||||
radii[i] = VdwRadius(type_symbol(i));
|
||||
}
|
||||
model._dynamicPropertyData[AtomicRadiiProp] = radii;
|
||||
return radii;
|
||||
}
|
||||
|
||||
function invertIndex(xs: Column<number>) {
|
||||
const invertedIndex = new Int32Array(xs.rowCount);
|
||||
let isIdentity = false;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2025s 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>
|
||||
@@ -98,6 +98,9 @@ export const UnknownSaccharideComponent: SaccharideComponent = {
|
||||
type: SaccharideType.Unknown
|
||||
};
|
||||
|
||||
/**
|
||||
* Unless stated otherwise, these follow SNFG recommendations.
|
||||
*/
|
||||
const Monosaccharides: SaccharideComponent[] = [
|
||||
{ abbr: 'Glc', name: 'Glucose', color: SaccharideColors.Blue, type: SaccharideType.Hexose },
|
||||
{ abbr: 'Man', name: 'Mannose', color: SaccharideColors.Green, type: SaccharideType.Hexose },
|
||||
@@ -125,7 +128,7 @@ const Monosaccharides: SaccharideComponent[] = [
|
||||
{ abbr: 'AllN', name: 'Allosamine', color: SaccharideColors.Purple, type: SaccharideType.Hexosamine },
|
||||
{ abbr: 'TalN', name: 'Talosamine', color: SaccharideColors.LightBlue, type: SaccharideType.Hexosamine },
|
||||
{ abbr: 'IdoN', name: 'Idosamine', color: SaccharideColors.Brown, type: SaccharideType.Hexosamine },
|
||||
{ abbr: 'GlcNS', name: 'N-sulfo Glucosamine', color: SaccharideColors.Blue, type: SaccharideType.Hexosamine },
|
||||
{ abbr: 'GlcNS', name: 'N-sulfo Glucosamine', color: SaccharideColors.Blue, type: SaccharideType.Hexosamine }, // from GLYCAM
|
||||
|
||||
{ abbr: 'GlcA', name: 'Glucuronic Acid', color: SaccharideColors.Blue, type: SaccharideType.Hexuronate },
|
||||
{ abbr: 'ManA', name: 'Mannuronic Acid', color: SaccharideColors.Green, type: SaccharideType.Hexuronate },
|
||||
@@ -148,7 +151,7 @@ const Monosaccharides: SaccharideComponent[] = [
|
||||
{ abbr: '6dAltNAc', name: 'N-Acetyl 6-Deoxy Altrosamine', color: SaccharideColors.Pink, type: SaccharideType.DeoxyhexNAc },
|
||||
{ abbr: '6dTalNAc', name: 'N-Acetyl 6-Deoxy Talosamine', color: SaccharideColors.LightBlue, type: SaccharideType.DeoxyhexNAc },
|
||||
{ abbr: 'FucNAc', name: 'N-Acetyl Fucosamine', color: SaccharideColors.Red, type: SaccharideType.DeoxyhexNAc },
|
||||
{ abbr: 'AAT', name: 'AAT', color: SaccharideColors.Red, type: SaccharideType.DeoxyhexNAc },
|
||||
{ abbr: 'AAT', name: 'AAT', color: SaccharideColors.Red, type: SaccharideType.DeoxyhexNAc }, // from GLYCAM
|
||||
|
||||
{ abbr: 'Oli', name: 'Olivose', color: SaccharideColors.Blue, type: SaccharideType.DiDeoxyhexose },
|
||||
{ abbr: 'Tyv', name: 'Tyvelose', color: SaccharideColors.Green, type: SaccharideType.DiDeoxyhexose },
|
||||
@@ -181,8 +184,8 @@ const Monosaccharides: SaccharideComponent[] = [
|
||||
{ abbr: 'MurNAc', name: 'N-Acetyl Muramic Acid', color: SaccharideColors.Purple, type: SaccharideType.Unknown },
|
||||
{ abbr: 'MurNGc', name: 'N-Glycolyl Muramic Acid', color: SaccharideColors.LightBlue, type: SaccharideType.Unknown },
|
||||
{ abbr: 'Mur', name: 'Muramic Acid', color: SaccharideColors.Brown, type: SaccharideType.Unknown },
|
||||
{ abbr: 'K3O', name: 'D-glycero-a-D-talo-oct-2-Ulosonic Acid', color: SaccharideColors.Red, type: SaccharideType.Unknown },
|
||||
{ abbr: 'dUA', name: '4-deoxy-4,5-didehydro iduronic acid', color: SaccharideColors.Secondary, type: SaccharideType.Unknown },
|
||||
{ abbr: 'K3O', name: 'D-glycero-a-D-talo-oct-2-Ulosonic Acid', color: SaccharideColors.Red, type: SaccharideType.Unknown }, // from GLYCAM
|
||||
{ abbr: 'dUA', name: '4-deoxy-4,5-didehydro iduronic acid', color: SaccharideColors.Secondary, type: SaccharideType.Unknown }, // from GLYCAM
|
||||
|
||||
{ abbr: 'Api', name: 'Apicose', color: SaccharideColors.Green, type: SaccharideType.Assigned },
|
||||
{ abbr: 'Fru', name: 'Fructose', color: SaccharideColors.Green, type: SaccharideType.Assigned },
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Adam Midlik <midlik@gmail.com>
|
||||
*/
|
||||
|
||||
import { SymmetryOperator } from '../../../mol-math/geometry/symmetry-operator';
|
||||
import { Model } from '../model';
|
||||
import { GridLookup3D, Lookup3D, Spacegroup } from '../../../mol-math/geometry';
|
||||
import { IntraUnitBonds, computeIntraUnitBonds } from './unit/bonds';
|
||||
import { VdwRadius } from '../model/properties/atomic';
|
||||
import { CoarseElements, CoarseSphereConformation, CoarseGaussianConformation } from '../model/properties/coarse';
|
||||
import { BitFlags } from '../../../mol-util';
|
||||
import { UnitRings } from './unit/rings';
|
||||
@@ -46,7 +48,7 @@ namespace Unit {
|
||||
|
||||
export function create<K extends Kind>(id: number, invariantId: number, chainGroupId: number, traits: Traits, kind: Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set, props?: K extends Kind.Atomic ? AtomicProperties : CoarseProperties): Unit {
|
||||
switch (kind) {
|
||||
case Kind.Atomic: return new Atomic(id, invariantId, chainGroupId, traits, model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation), props ?? AtomicProperties());
|
||||
case Kind.Atomic: return new Atomic(id, invariantId, chainGroupId, traits, model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation, getAtomicRadiusFunc(model)), props ?? AtomicProperties());
|
||||
case Kind.Spheres: return createCoarse(id, invariantId, chainGroupId, traits, model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres, getSphereRadiusFunc(model)), props ?? CoarseProperties());
|
||||
case Kind.Gaussians: return createCoarse(id, invariantId, chainGroupId, traits, model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians, getGaussianRadiusFunc(model)), props ?? CoarseProperties());
|
||||
}
|
||||
@@ -182,6 +184,11 @@ namespace Unit {
|
||||
return {};
|
||||
}
|
||||
|
||||
function getAtomicRadiusFunc(model: Model) {
|
||||
const type_symbol = model.atomicHierarchy.atoms.type_symbol.value;
|
||||
return (i: ElementIndex) => VdwRadius(type_symbol(i));
|
||||
}
|
||||
|
||||
function getSphereRadiusFunc(model: Model) {
|
||||
const r = model.coarseConformation.spheres.radius;
|
||||
return (i: ElementIndex) => r[i];
|
||||
@@ -270,7 +277,7 @@ namespace Unit {
|
||||
}
|
||||
|
||||
const conformation = (this.model.atomicConformation !== model.atomicConformation || operator !== this.conformation.operator)
|
||||
? SymmetryOperator.createMapping<ElementIndex>(operator, model.atomicConformation)
|
||||
? SymmetryOperator.createMapping<ElementIndex>(operator, model.atomicConformation, getAtomicRadiusFunc(model))
|
||||
: this.conformation;
|
||||
return new Atomic(this.id, this.invariantId, this.chainGroupId, this.traits, model, this.elements, conformation, props);
|
||||
}
|
||||
@@ -278,9 +285,10 @@ namespace Unit {
|
||||
get boundary() {
|
||||
if (this.props.boundary) return this.props.boundary;
|
||||
const { x, y, z } = this.model.atomicConformation;
|
||||
const radius = Model.getAtomicRadii(this.model);
|
||||
this.props.boundary = Traits.is(this.traits, Trait.FastBoundary)
|
||||
? getFastBoundary({ x, y, z, indices: this.elements })
|
||||
: getBoundary({ x, y, z, indices: this.elements });
|
||||
? getFastBoundary({ x, y, z, radius, indices: this.elements })
|
||||
: getBoundary({ x, y, z, radius, indices: this.elements });
|
||||
return this.props.boundary;
|
||||
}
|
||||
|
||||
@@ -456,11 +464,11 @@ namespace Unit {
|
||||
|
||||
get boundary() {
|
||||
if (this.props.boundary) return this.props.boundary;
|
||||
// TODO: support sphere radius?
|
||||
const { x, y, z } = this.getCoarseConformation();
|
||||
const radius = this.getCoarseRadii();
|
||||
this.props.boundary = Traits.is(this.traits, Trait.FastBoundary)
|
||||
? getFastBoundary({ x, y, z, indices: this.elements })
|
||||
: getBoundary({ x, y, z, indices: this.elements });
|
||||
? getFastBoundary({ x, y, z, radius, indices: this.elements })
|
||||
: getBoundary({ x, y, z, radius, indices: this.elements });
|
||||
return this.props.boundary;
|
||||
}
|
||||
|
||||
@@ -494,6 +502,10 @@ namespace Unit {
|
||||
return getCoarseConformation(this.kind, this.model);
|
||||
}
|
||||
|
||||
private getCoarseRadii() {
|
||||
return getCoarseRadii(this.kind, this.model);
|
||||
}
|
||||
|
||||
constructor(id: number, invariantId: number, chainGroupId: number, traits: Traits, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: CoarseProperties) {
|
||||
this.kind = kind;
|
||||
this.objectPrimitive = kind === Kind.Spheres ? 'sphere' : 'gaussian';
|
||||
@@ -514,6 +526,10 @@ namespace Unit {
|
||||
return kind === Kind.Spheres ? model.coarseConformation.spheres : model.coarseConformation.gaussians;
|
||||
}
|
||||
|
||||
function getCoarseRadii(kind: Kind, model: Model) {
|
||||
return kind === Kind.Spheres ? model.coarseConformation.spheres.radius : undefined; // Zero radius for gaussians
|
||||
}
|
||||
|
||||
interface CoarseProperties extends BaseProperties { }
|
||||
|
||||
function CoarseProperties(): CoarseProperties {
|
||||
|
||||
@@ -4,23 +4,30 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { Canvas3DParams } from '../../../mol-canvas3d/canvas3d';
|
||||
import { PluginContext } from '../../../mol-plugin/context';
|
||||
import { PluginState } from '../../../mol-plugin/state';
|
||||
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
|
||||
import { PluginStateSnapshotManager } from '../../manager/snapshots';
|
||||
import { PluginStateAnimation } from '../model';
|
||||
|
||||
|
||||
async function setPartialSnapshot(plugin: PluginContext, entry: Partial<PluginStateSnapshotManager.Entry['snapshot']>, first = false) {
|
||||
if (entry.data) {
|
||||
await plugin.runTask(plugin.state.data.setSnapshot(entry.data));
|
||||
// update the canvas3d trackball with the snapshot
|
||||
if (entry.canvas3d?.props?.trackball) {
|
||||
plugin.canvas3d?.setProps({
|
||||
trackball: entry.canvas3d?.props?.trackball
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (entry.canvas3d?.props) {
|
||||
const settings = PD.normalizeParams(Canvas3DParams, entry.canvas3d.props, 'children');
|
||||
if (entry.camera?.current || entry.camera?.focus) {
|
||||
// Avoid multiple camera transitions (creates ugly cases when camera in old and new snapshot is the same)
|
||||
settings.camera = undefined;
|
||||
settings.cameraClipping = undefined;
|
||||
settings.cameraFog = undefined;
|
||||
}
|
||||
plugin.canvas3d?.setProps(settings);
|
||||
}
|
||||
if (entry.camera?.current) {
|
||||
plugin.canvas3d?.requestCameraReset({
|
||||
snapshot: entry.camera.current,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2025 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>
|
||||
@@ -25,7 +25,7 @@ import { changeCameraRotation, structureLayingTransform } from './focus-camera/o
|
||||
|
||||
// TODO: make this customizable somewhere?
|
||||
const DefaultCameraFocusOptions = {
|
||||
minRadius: 5,
|
||||
minRadius: 1,
|
||||
extraRadius: 4,
|
||||
durationMs: 250,
|
||||
};
|
||||
|
||||
@@ -195,6 +195,8 @@ const _Warning = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M1
|
||||
export function WarningSvg() { return _Warning; }
|
||||
const _ContentCut = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M 9.64 7.64 c 0.23 -0.5 0.36 -1.05 0.36 -1.64 c 0 -2.21 -1.79 -4 -4 -4 S 2 3.79 2 6 s 1.79 4 4 4 c 0.59 0 1.14 -0.13 1.64 -0.36 L 10 12 l -2.36 2.36 C 7.14 14.13 6.59 14 6 14 c -2.21 0 -4 1.79 -4 4 s 1.79 4 4 4 s 4 -1.79 4 -4 c 0 -0.59 -0.13 -1.14 -0.36 -1.64 L 12 14 l 7 7 h 3 v -1 L 9.64 7.64 Z M 6 8 c -1.1 0 -2 -0.89 -2 -2 s 0.9 -2 2 -2 s 2 0.89 2 2 s -0.9 2 -2 2 Z m 0 12 c -1.1 0 -2 -0.89 -2 -2 s 0.9 -2 2 -2 s 2 0.89 2 2 s -0.9 2 -2 2 Z m 6 -7.5 c -0.28 0 -0.5 -0.22 -0.5 -0.5 s 0.22 -0.5 0.5 -0.5 s 0.5 0.22 0.5 0.5 s -0.22 0.5 -0.5 0.5 Z M 19 3 l -6 6 l 2 2 l 7 -7 V 3 Z' /></svg>;
|
||||
export function ContentCutSvg() { return _ContentCut; }
|
||||
const _LightMode = <svg width='24px' height='24px' viewBox='0 0 24 24'><path d='M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0 c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2 c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1 C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06 c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41 l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41 c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36 c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z' /></svg>;
|
||||
export function LightModeSvg() { return _LightMode; }
|
||||
|
||||
// The following icons are adapted from https://icons.getbootstrap.com/ and
|
||||
// licensed with https://github.com/twbs/bootstrap/blob/main/LICENSE
|
||||
|
||||
@@ -418,12 +418,19 @@ export class TextControl extends SimpleParam<PD.Text> {
|
||||
|
||||
function TextCtrl({ props, placeholder, update }: { props: ParamProps<PD.Text>, placeholder: string, update: (v: string) => any }) {
|
||||
const [value, setValue] = React.useState(props.value);
|
||||
const [composition, setComposition] = React.useState(false);
|
||||
React.useEffect(() => setValue(props.value), [props.value]);
|
||||
|
||||
const onChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
if (props.param.disableInteractiveUpdates) setValue(e.target.value);
|
||||
if (composition || props.param.disableInteractiveUpdates) setValue(e.target.value);
|
||||
else update(e.target.value);
|
||||
};
|
||||
const onCompositionEnd = (e: React.CompositionEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
setComposition(false);
|
||||
const target = e.target as EventTarget & (HTMLInputElement | HTMLTextAreaElement);
|
||||
if (props.param.disableInteractiveUpdates) setValue(target.value);
|
||||
else update(target.value);
|
||||
};
|
||||
const onBlur = (e: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||
if (props.param.disableInteractiveUpdates) update(e.target.value);
|
||||
};
|
||||
@@ -443,13 +450,15 @@ function TextCtrl({ props, placeholder, update }: { props: ParamProps<PD.Text>,
|
||||
return <div className='msp-control-text-area-wrapper'>
|
||||
<textarea
|
||||
value={value ?? ''} placeholder={placeholder} disabled={props.isDisabled}
|
||||
onChange={onChange} onBlur={onBlur} onKeyDown={onKeyDown}
|
||||
onChange={onChange} onBlur={onBlur} onKeyDown={onKeyDown}
|
||||
onCompositionStart={() => setComposition(true)} onCompositionEnd={onCompositionEnd}
|
||||
/>
|
||||
</div>;
|
||||
} else {
|
||||
return <input type='text'
|
||||
value={value ?? ''} placeholder={placeholder} disabled={props.isDisabled}
|
||||
onChange={onChange} onBlur={onBlur} onKeyDown={onKeyDown}
|
||||
onChange={onChange} onBlur={onBlur} onKeyDown={onKeyDown}
|
||||
onCompositionStart={() => setComposition(true)} onCompositionEnd={onCompositionEnd}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2025 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>
|
||||
@@ -14,7 +14,7 @@ import { PluginConfig } from '../mol-plugin/config';
|
||||
import { ParamDefinition as PD } from '../mol-util/param-definition';
|
||||
import { PluginUIComponent } from './base';
|
||||
import { Button, ControlGroup, IconButton } from './controls/common';
|
||||
import { AspectRatioSvg, AutorenewSvg, BuildOutlinedSvg, CameraOutlinedSvg, CloseSvg, FullscreenSvg, HeadsetVRSvg, TuneSvg } from './controls/icons';
|
||||
import { AspectRatioSvg, AutorenewSvg, BuildOutlinedSvg, CameraOutlinedSvg, CloseSvg, FullscreenSvg, HeadsetVRSvg, LightModeSvg, TuneSvg } from './controls/icons';
|
||||
import { ToggleSelectionModeButton } from './structure/selection';
|
||||
import { ViewportCanvas } from './viewport/canvas';
|
||||
import { DownloadScreenshotControls } from './viewport/screenshot';
|
||||
@@ -85,6 +85,19 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
|
||||
}
|
||||
};
|
||||
|
||||
toggleIllumination = () => {
|
||||
if (!this.plugin.canvas3d) return;
|
||||
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, {
|
||||
settings: {
|
||||
illumination: {
|
||||
...this.plugin.canvas3d.props.illumination,
|
||||
enabled: !this.plugin.canvas3d.props.illumination.enabled
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
setSettings = (p: { param: PD.Base<any>, name: string, value: any }) => {
|
||||
PluginCommands.Canvas3D.SetSettings(this.plugin, { settings: { [p.name]: p.value } });
|
||||
};
|
||||
@@ -178,6 +191,7 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
|
||||
&& this.plugin.config.get(PluginConfig.Viewport.ShowToggleFullscreen)
|
||||
&& this.icon(AspectRatioSvg, this.toggleFullscreen, 'Toggle Full Screen', this.plugin.layout.state.expandToFullscreen)}
|
||||
{this.plugin.config.get(PluginConfig.Viewport.ShowSettings) && this.icon(TuneSvg, this.toggleSettingsExpanded, 'Settings / Controls Info', this.state.isSettingsExpanded)}
|
||||
{this.plugin.config.get(PluginConfig.Viewport.ShowIllumination) && this.icon(LightModeSvg, this.toggleIllumination, 'Illumination', this.plugin.canvas3d?.props.illumination.enabled || false)}
|
||||
{xr && this.icon(HeadsetVRSvg, this.toggleXR, xrTitle, xrIsPresenting, !xrIsSupported)}
|
||||
</div>
|
||||
{this.plugin.config.get(PluginConfig.Viewport.ShowSelectionMode) && <div>
|
||||
|
||||
@@ -190,13 +190,21 @@ export const CameraControls = PluginBehavior.create<CameraControlsProps>({
|
||||
if (Binding.matchKey(b.keySpinAnimation, code, modifiers, key)) {
|
||||
const name = tp.animate.name !== 'spin' ? 'spin' : 'off';
|
||||
if (name === 'off') {
|
||||
this.ctx.canvas3d.setProps({
|
||||
trackball: { animate: { name, params: {} } }
|
||||
PluginCommands.Canvas3D.SetSettings(this.ctx, {
|
||||
settings: {
|
||||
trackball: {
|
||||
...tp,
|
||||
animate: { name, params: {} }
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.ctx.canvas3d.setProps({
|
||||
trackball: { animate: {
|
||||
name, params: { speed: 1 } }
|
||||
PluginCommands.Canvas3D.SetSettings(this.ctx, {
|
||||
settings: {
|
||||
trackball: {
|
||||
...tp,
|
||||
animate: { name, params: { speed: 0.1 } }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -205,13 +213,21 @@ export const CameraControls = PluginBehavior.create<CameraControlsProps>({
|
||||
if (Binding.matchKey(b.keyRockAnimation, code, modifiers, key)) {
|
||||
const name = tp.animate.name !== 'rock' ? 'rock' : 'off';
|
||||
if (name === 'off') {
|
||||
this.ctx.canvas3d.setProps({
|
||||
trackball: { animate: { name, params: {} } }
|
||||
PluginCommands.Canvas3D.SetSettings(this.ctx, {
|
||||
settings: {
|
||||
trackball: {
|
||||
...tp,
|
||||
animate: { name, params: {} }
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.ctx.canvas3d.setProps({
|
||||
trackball: { animate: {
|
||||
name, params: { speed: 0.3, angle: 10 } }
|
||||
PluginCommands.Canvas3D.SetSettings(this.ctx, {
|
||||
settings: {
|
||||
trackball: {
|
||||
...tp,
|
||||
animate: { name, params: { speed: 0.3, angle: 10 } }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -220,8 +236,13 @@ export const CameraControls = PluginBehavior.create<CameraControlsProps>({
|
||||
if (Binding.matchKey(b.keyToggleFlyMode, code, modifiers, key)) {
|
||||
const flyMode = !tp.flyMode;
|
||||
|
||||
this.ctx.canvas3d.setProps({
|
||||
trackball: { flyMode }
|
||||
PluginCommands.Canvas3D.SetSettings(this.ctx, {
|
||||
settings: {
|
||||
trackball: {
|
||||
...tp,
|
||||
flyMode
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (this.ctx.canvas3dContext?.canvas) {
|
||||
@@ -234,10 +255,12 @@ export const CameraControls = PluginBehavior.create<CameraControlsProps>({
|
||||
}
|
||||
|
||||
if (Binding.matchKey(b.keyGlobalIllumination, code, modifiers, key)) {
|
||||
this.ctx.canvas3d.setProps({
|
||||
illumination: {
|
||||
...ip,
|
||||
enabled: !ip.enabled,
|
||||
PluginCommands.Canvas3D.SetSettings(this.ctx, {
|
||||
settings: {
|
||||
illumination: {
|
||||
...ip,
|
||||
enabled: !ip.enabled
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ export const PluginConfig = {
|
||||
ShowAnimation: item('viewer.show-animation-button', true),
|
||||
ShowTrajectoryControls: item('viewer.show-trajectory-controls', true),
|
||||
ShowScreenshotControls: item('viewer.show-screenshot-controls', true),
|
||||
ShowIllumination: item('viewer.show-illumination-button', true),
|
||||
ShowXR: item<'auto' | 'always' | 'never'>('viewer.show-xr', 'always'),
|
||||
},
|
||||
Download: {
|
||||
|
||||
@@ -89,7 +89,13 @@ class PluginState extends PluginComponent {
|
||||
if (snapshot.behaviour) await this.plugin.runTask(this.behaviors.setSnapshot(snapshot.behaviour));
|
||||
if (snapshot.data) await this.plugin.runTask(this.data.setSnapshot(snapshot.data));
|
||||
if (snapshot.canvas3d?.props) {
|
||||
const settings = PD.normalizeParams(Canvas3DParams, snapshot.canvas3d.props, 'children');
|
||||
const settings: Partial<Canvas3DProps> = PD.normalizeParams(Canvas3DParams, snapshot.canvas3d.props, 'children');
|
||||
if (snapshot.camera?.current || snapshot.camera?.focus) {
|
||||
// Avoid multiple camera transitions (creates ugly cases when camera in old and new snapshot is the same)
|
||||
settings.camera = undefined;
|
||||
settings.cameraClipping = undefined;
|
||||
settings.cameraFog = undefined;
|
||||
}
|
||||
await PluginCommands.Canvas3D.SetSettings(this.plugin, { settings });
|
||||
}
|
||||
if (snapshot.canvas3dContext?.props) {
|
||||
|
||||
Reference in New Issue
Block a user