Compare commits

..

3 Commits

Author SHA1 Message Date
dsehnal
1bd162b977 tweaks 2025-01-04 11:07:55 +01:00
dsehnal
c7fb71738e header 2025-01-04 11:03:01 +01:00
dsehnal
9413481253 Fix plugin interactions when CSS scale is applied 2025-01-04 11:02:37 +01:00
84 changed files with 1336 additions and 3304 deletions

View File

@@ -5,43 +5,18 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
## [v4.11.0] - 2025-01-26
- Fix for tubular helices issue (Fixes #1422)
- Volume UI improvements
- Render all volume entries instead of selecting them one-by-one
- Toggle visibility of all volumes
- More accessible iso value control
- Render all volume entries instead of selecting them one-by-one
- Toggle visibility of all volumes
- More accessible iso value control
- Support wheel event on sliders
- MolViewSpec extension:
- Add validation for discriminated union params
- Primitives: remove triangle_colors, line_colors, have implicit grouping instead; rename many parameters
- UI configuration options
- Support removal of independent selection controls in the viewport
- Support custom selection controls
- Support for custom granularity dropdown options
- Support for custom Sequence Viewer mode options
- Add validation for discriminated union params
- Primitives: remove triangle_colors, line_colors, have implicit grouping instead; rename many parameters
- Add `external-structure` theme that colors any geometry by structure properties
- Support float and half-float data type for direct-volume rendering and GPU isosurface extraction
- Minor documentation updates
- Add support for position-location to `volume-value` color theme
- Add support for color themes to `slice` representation
- Improve/fix palette support in volume color themes
- Fix `Plane3D.projectPoint`
- Fix marking related image rendering issues
- Handle pixels without a group
- Take fog into account
- MolViewSpec extension: Initial support for customizable representation parameters
- Quick Styles section reorganized
- UI color improvements (scrollbar contrast, toggle button hover color)
- Add `overrideWater` param for entity-id color theme
- Renames PDB-Dev to PDB-IHM and adjusts data source
- Fix vertex based themes for spheres shader
- Add volume dot representation
- Add volume-value size theme
- Sequence panel: Mark focused loci (bold+underline)
- Change modifier key behavior in Normal Mode (default = select only, Ctrl/Cmd = add to selection, Shift = extend last selected range)
- Handle Firefox's limit on vertex ids per draw (#1116)
- Fix plugin mouse interactions when CSS `scale` transform is applied
## [v4.10.0] - 2024-12-15
@@ -109,7 +84,7 @@ Note that since we don't clearly distinguish between a public and private interf
- Fix `findPredecessorIndex` bug when repeating values
- MolViewSpec: Support for transparency and custom properties
- MolViewSpec: MVP Support for geometrical primitives (mesh, lines, line, label, distance measurement)
- Mesoscale Explorer: Add support for 4-character PDB IDs (e.g., 8ZZC) in PDB-IHM/PDB-Dev loader
- Mesoscale Explorer: Add support for 4-character PDB IDs (e.g., 8ZZC) in PDB-Dev loader
- Fix Sequence View in Safari 18
- Improve performance of `IndexPairBonds` assignment when operator keys are available
- ModelArchive QualityAssessment extension:
@@ -162,7 +137,7 @@ Note that since we don't clearly distinguish between a public and private interf
- Improve entity-id coloring for structures with multiple models from the same source (#1221)
- Wrap screenshot & image generation in a `Task`
- AlphaFold DB: Add BinaryCIF support when fetching data
- PDB-IHM/PDB-Dev: Add support for 4-character PDB IDs (e.g., 8ZZC)
- PDB-Dev: Add support for 4-character PDB IDs (e.g., 8ZZC)
- Fix polymer-gap visual coloring with cartoon theme
- Add formal-charge color theme (#328)
- Add more coloring options to cartoon theme

2320
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "molstar",
"version": "4.11.0",
"version": "4.10.0",
"description": "A comprehensive macromolecular library.",
"homepage": "https://github.com/molstar/molstar#readme",
"repository": {
@@ -114,8 +114,7 @@
"Eric E <etongfu@outlook.com>",
"Xavier Martinez <xavier.martinez.xm@gmail.com>",
"Alex Chan <smalldirkalex@gmail.com>",
"Simeon Borko <simeon.borko@gmail.com>",
"Ventura Rivera <venturaxrivera@gmail.com>"
"Simeon Borko <simeon.borko@gmail.com>"
],
"license": "MIT",
"devDependencies": {
@@ -123,19 +122,19 @@
"@types/gl": "^6.0.5",
"@types/jest": "^29.5.14",
"@types/pngjs": "^6.0.5",
"@types/react": "^18.3.18",
"@types/react": "^18.3.16",
"@types/react-dom": "^18.3.5",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"benchmark": "^2.1.4",
"concurrently": "^9.1.2",
"concurrently": "^9.1.0",
"cpx2": "^8.0.0",
"crypto-browserify": "^3.12.1",
"css-loader": "^7.1.2",
"eslint": "^8.57.1",
"extra-watch-webpack-plugin": "^1.0.3",
"file-loader": "^6.2.0",
"fs-extra": "^11.3.0",
"fs-extra": "^11.2.0",
"http-server": "^14.1.1",
"jest": "^29.7.0",
"jpeg-js": "^0.4.4",
@@ -144,22 +143,22 @@
"raw-loader": "^4.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sass": "^1.83.4",
"sass": "^1.83.0",
"sass-loader": "^16.0.4",
"simple-git": "^3.27.0",
"stream-browserify": "^3.0.0",
"style-loader": "^4.0.0",
"ts-jest": "^29.2.5",
"typescript": "^5.7.3",
"typescript": "^5.7.2",
"webpack": "^5.97.1",
"webpack-cli": "^6.0.1"
"webpack-cli": "^5.1.4"
},
"dependencies": {
"@types/argparse": "^2.0.17",
"@types/benchmark": "^2.1.5",
"@types/compression": "1.7.5",
"@types/express": "^5.0.0",
"@types/node": "^18.19.74",
"@types/node": "^18.19.68",
"@types/node-fetch": "^2.6.12",
"@types/swagger-ui-dist": "3.30.5",
"argparse": "^2.0.1",
@@ -171,11 +170,11 @@
"immutable": "^5.0.3",
"io-ts": "^2.2.22",
"node-fetch": "^2.7.0",
"react-markdown": "^9.0.3",
"react-markdown": "^9.0.1",
"rxjs": "^7.8.1",
"swagger-ui-dist": "^5.18.2",
"tslib": "^2.8.1",
"util.promisify": "^1.1.3",
"util.promisify": "^1.1.2",
"xhr2": "^0.2.1"
},
"peerDependencies": {

View File

@@ -25,7 +25,7 @@ import { MesoFocusLoci } from './behavior/camera';
import { GraphicsMode, MesoscaleState } from './data/state';
import { MesoSelectLoci } from './behavior/select';
import { Transparency } from '../../mol-gl/webgl/render-item';
import { LoadModel, loadExampleEntry, loadPdb, loadPdbIhm, loadUrl, openState } from './ui/states';
import { LoadModel, loadExampleEntry, loadPdb, loadPdbDev, loadUrl, openState } from './ui/states';
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';
@@ -120,15 +120,8 @@ export class MesoscaleExplorer {
await loadPdb(this.plugin, id);
}
/**
* @deprecated Scheduled for removal in v5. Use {@link loadPdbIhm | loadPdbIhm(id: string)} instead.
*/
async loadPdbDev(id: string) {
await this.loadPdbIhm(id);
}
async loadPdbIhm(id: string) {
await loadPdbIhm(this.plugin, id);
await loadPdbDev(this.plugin, id);
}
static async create(elementOrId: string | HTMLElement, options: Partial<MesoscaleExplorerOptions> = {}) {

View File

@@ -93,15 +93,9 @@
return;
}
var pdbihm = getParam('pdbihm', '[^&]+').trim();
if (pdbihm) {
me.loadPdbIhm(pdbihm);
return;
}
// support for deprecated pdb-dev param
var pdbdev = getParam('pdbdev', '[^&]+').trim();
if (pdbdev) {
me.loadPdbIhm(pdbdev);
me.loadPdbDev(pdbdev);
return;
}
window.addEventListener('unload', () => {

View File

@@ -211,15 +211,15 @@ export async function loadPdb(ctx: PluginContext, id: string) {
await createHierarchy(ctx, data.ref);
}
export async function loadPdbIhm(ctx: PluginContext, id: string) {
export async function loadPdbDev(ctx: PluginContext, id: string) {
await reset(ctx);
let url: string;
// 4 character PDB id, TODO: support extended PDB ID
if (id.match(/^[1-9][A-Z0-9]{3}$/i) !== null) {
url = `https://pdb-ihm.org/bcif/${id.toLowerCase()}.bcif`;
url = `https://pdb-dev.wwpdb.org/bcif/${id.toLowerCase()}.bcif`;
} else {
const nId = id.toUpperCase().startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`;
url = `https://pdb-ihm.org/bcif/${nId.toUpperCase()}.bcif`;
url = `https://pdb-dev.wwpdb.org/bcif/${nId.toUpperCase()}.bcif`;
}
const data = await ctx.builders.data.download({ url, isBinary: true });
await createHierarchy(ctx, data.ref);
@@ -231,7 +231,7 @@ export const LoadDatabase = StateAction.build({
display: { name: 'Database', description: 'Load from Database' },
params: (a, ctx: PluginContext) => {
return {
source: PD.Select('pdb', PD.objectToOptions({ pdb: 'PDB', pdbIhm: 'PDB-IHM' })),
source: PD.Select('pdb', PD.objectToOptions({ pdb: 'PDB', pdbDev: 'PDB-Dev' })),
entry: PD.Text(''),
};
},
@@ -239,8 +239,8 @@ export const LoadDatabase = StateAction.build({
})(({ params }, ctx: PluginContext) => Task.create('Loading from database...', async taskCtx => {
if (params.source === 'pdb') {
await loadPdb(ctx, params.entry);
} else if (params.source === 'pdbIhm') {
await loadPdbIhm(ctx, params.entry);
} else if (params.source === 'pdbDev') {
await loadPdbDev(ctx, params.entry);
}
}));
@@ -426,7 +426,7 @@ export class ExplorerInfo extends PluginUIComponent<{}, { isDisabled: boolean, s
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-IHM databases.', side: 'bottom', 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' } },

View File

@@ -288,21 +288,14 @@ export class Viewer {
}));
}
/**
* @deprecated Scheduled for removal in v5. Use {@link loadPdbIhm | loadPdbIhm(pdbIhm: string)} instead.
*/
loadPdbDev(pdbDev: string) {
return this.loadPdbIhm(pdbDev);
}
loadPdbIhm(pdbIhm: string) {
const params = DownloadStructure.createDefaultParams(this.plugin.state.data.root.obj!, this.plugin);
return this.plugin.runTask(this.plugin.state.data.applyAction(DownloadStructure, {
source: {
name: 'pdb-ihm' as const,
name: 'pdb-dev' as const,
params: {
provider: {
id: pdbIhm,
id: pdbDev,
encoding: 'bcif',
},
options: params.source.params.options,

View File

@@ -111,11 +111,8 @@
var pdb = getParam('pdb', '[^&]+').trim();
if (pdb) viewer.loadPdb(pdb);
var pdbIhm = getParam('pdb-ihm', '[^&]+').trim();
if (pdbIhm) viewer.loadPdbIhm(pdbIhm);
// support for deprecated pdb-dev param
var pdbDev = getParam('pdb-dev', '[^&]+').trim();
if (pdbDev) viewer.loadPdbIhm(pdbDev);
if (pdbDev) viewer.loadPdbDev(pdbDev);
var emdb = getParam('emdb', '[^&]+').trim();
if (emdb) viewer.loadEmdb(emdb);

View File

@@ -285,21 +285,19 @@ export function componentFromXProps(node: MolstarNode<'component_from_uri' | 'co
/** Create props for `StructureRepresentation3D` transformer from a representation node. */
export function representationProps(node: MolstarSubtree<'representation'>): Partial<StateTransformer.Params<StructureRepresentation3D>> {
const alpha = alphaForNode(node);
const params = node.params;
switch (params.type) {
switch (node.params.type) {
case 'cartoon':
return {
type: { name: 'cartoon', params: { alpha, tubularHelices: params.tubular_helices } },
sizeTheme: { name: 'uniform', params: { value: params.size_factor } },
type: { name: 'cartoon', params: { alpha } },
};
case 'ball_and_stick':
return {
type: { name: 'ball-and-stick', params: { sizeFactor: (params.size_factor ?? 1) * 0.5, sizeAspectRatio: 0.5, alpha, ignoreHydrogens: params.ignore_hydrogens } },
type: { name: 'ball-and-stick', params: { sizeFactor: 0.5, sizeAspectRatio: 0.5, alpha } },
};
case 'surface':
return {
type: { name: 'molecular-surface', params: { alpha, ignoreHydrogens: params.ignore_hydrogens } },
sizeTheme: { name: 'physical', params: { scale: params.size_factor } },
type: { name: 'molecular-surface', params: { alpha } },
sizeTheme: { name: 'physical', params: { scale: 1 } },
};
default:
throw new Error('NotImplementedError');

View File

@@ -1,39 +0,0 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { bool, float, OptionalField } from '../generic/field-schema';
import { SimpleParamsSchema, UnionParamsSchema } from '../generic/params-schema';
const Cartoon = {
/** Scales the corresponding visuals */
size_factor: OptionalField(float, 1, 'Scales the corresponding visuals.'),
/** Simplify corkscrew helices to tubes. */
tubular_helices: OptionalField(bool, false, 'Simplify corkscrew helices to tubes.'),
};
const BallAndStick = {
/** Scales the corresponding visuals */
size_factor: OptionalField(float, 1, 'Scales the corresponding visuals.'),
/** Controls whether hydrogen atoms are drawn. */
ignore_hydrogens: OptionalField(bool, false, 'Controls whether hydrogen atoms are drawn.'),
};
const Surface = {
/** Scales the corresponding visuals */
size_factor: OptionalField(float, 1, 'Scales the corresponding visuals.'),
/** Controls whether hydrogen atoms are drawn. */
ignore_hydrogens: OptionalField(bool, false, 'Controls whether hydrogen atoms are drawn.'),
};
export const MVSRepresentationParams = UnionParamsSchema(
'type',
'Representation type',
{
cartoon: SimpleParamsSchema(Cartoon),
ball_and_stick: SimpleParamsSchema(BallAndStick),
surface: SimpleParamsSchema(Surface),
},
);

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2023-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Adam Midlik <midlik@gmail.com>
* @author David Sehnal <david.sehnal@gmail.com>
@@ -8,9 +8,8 @@
import { float, int, list, literal, nullable, OptionalField, RequiredField, str, tuple, union } from '../generic/field-schema';
import { SimpleParamsSchema } from '../generic/params-schema';
import { NodeFor, ParamsOfKind, SubtreeOfKind, TreeFor, TreeSchema, TreeSchemaWithAllRequired } from '../generic/tree-schema';
import { MVSRepresentationParams } from './mvs-tree-representations';
import { MVSPrimitiveParams } from './mvs-tree-primitives';
import { ColorT, ComponentExpressionT, ComponentSelectorT, Matrix, ParseFormatT, SchemaFormatT, SchemaT, StrList, StructureTypeT, Vector3 } from './param-types';
import { ColorT, ComponentExpressionT, ComponentSelectorT, Matrix, ParseFormatT, RepresentationTypeT, SchemaFormatT, SchemaT, StrList, StructureTypeT, Vector3 } from './param-types';
const _DataFromUriParams = {
@@ -143,7 +142,10 @@ export const MVSTreeSchema = TreeSchema({
representation: {
description: 'This node instructs to create a visual representation of a component.',
parent: ['component', 'component_from_uri', 'component_from_source'],
params: MVSRepresentationParams,
params: SimpleParamsSchema({
/** Method of visual representation of the component. */
type: RequiredField(RepresentationTypeT, 'Method of visual representation of the component.'),
}),
},
/** This node instructs to apply color to a visual representation. */
color: {

View File

@@ -45,6 +45,9 @@ export const ComponentExpressionT = iots.partial({
});
export type ComponentExpressionT = ValueFor<typeof ComponentExpressionT>
/** `type` parameter values for `representation` node in MVS tree */
export const RepresentationTypeT = literal('ball_and_stick', 'cartoon', 'surface');
/** `schema` parameter values for `*_from_uri` and `*_from_source` nodes in MVS tree */
export const SchemaT = literal('whole_structure', 'entity', 'chain', 'auth_chain', 'residue', 'auth_residue', 'residue_range', 'auth_residue_range', 'atom', 'auth_atom', 'all_atomic');

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2024 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>
@@ -392,6 +392,8 @@ namespace Canvas3D {
let y = 0;
let width = 128;
let height = 128;
let canvasScaleRatioX = 1;
let canvasScaleRatioY = 1;
let forceNextRender = false;
let currentTime = 0;
@@ -645,7 +647,7 @@ namespace Canvas3D {
function identify(x: number, y: number): PickData | undefined {
const cam = p.camera.stereo.name === 'on' ? stereoCamera : camera;
return webgl.isContextLost ? undefined : pickHelper.identify(x, y, cam);
return webgl.isContextLost ? undefined : pickHelper.identify(x / canvasScaleRatioX, y / canvasScaleRatioY, cam);
}
function commit(isSynchronous: boolean = false) {
@@ -1158,6 +1160,12 @@ namespace Canvas3D {
function updateViewport() {
const oldX = x, oldY = y, oldWidth = width, oldHeight = height;
const canvasRect = canvas?.getBoundingClientRect();
canvasScaleRatioX = (canvasRect?.width ?? gl.drawingBufferWidth) / gl.drawingBufferWidth;
if (!canvasScaleRatioX) canvasScaleRatioX = 1;
canvasScaleRatioY = (canvasRect?.height ?? gl.drawingBufferHeight) / gl.drawingBufferHeight;
if (!canvasScaleRatioY) canvasScaleRatioY = 1;
if (p.viewport.name === 'canvas') {
x = 0;
y = 0;
@@ -1184,7 +1192,7 @@ namespace Canvas3D {
pickHelper.setViewport(x, y, width, height);
renderer.setViewport(x, y, width, height);
Viewport.set(camera.viewport, x, y, width, height);
Viewport.set(controls.viewport, x, y, width, height);
Viewport.set(controls.viewport, x, y, width * canvasScaleRatioX, height * canvasScaleRatioY);
hiZ.setViewport(x, y, width, height);
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 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>
@@ -24,7 +24,6 @@ export type ColorData = {
uColor: ValueCell<Vec3>,
tColor: ValueCell<TextureImage<Uint8Array>>,
tColorGrid: ValueCell<Texture>,
uPaletteDomain: ValueCell<Vec2>,
tPalette: ValueCell<TextureImage<Uint8Array>>,
uColorTexDim: ValueCell<Vec2>,
uColorGridDim: ValueCell<Vec3>,
@@ -37,8 +36,6 @@ export function createColors(locationIt: LocationIterator, positionIt: LocationI
const data = _createColors(locationIt, positionIt, colorTheme, colorData);
if (colorTheme.palette) {
ValueCell.updateIfChanged(data.dUsePalette, true);
const [min, max] = colorTheme.palette.domain || [0, 1];
ValueCell.update(data.uPaletteDomain, Vec2.set(data.uPaletteDomain.ref.value, min, max));
updatePaletteTexture(colorTheme.palette, data.tPalette);
} else {
ValueCell.updateIfChanged(data.dUsePalette, false);
@@ -106,7 +103,6 @@ export function createValueColor(value: Color, colorData?: ColorData): ColorData
uColor: ValueCell.create(Color.toVec3Normalized(Vec3(), value)),
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
tColorGrid: ValueCell.create(createNullTexture()),
uPaletteDomain: ValueCell.create(Vec2.create(0, 1)),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
uColorGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
@@ -135,7 +131,6 @@ export function createTextureColor(colors: TextureImage<Uint8Array>, type: Color
uColor: ValueCell.create(Vec3()),
tColor: ValueCell.create(colors),
tColorGrid: ValueCell.create(createNullTexture()),
uPaletteDomain: ValueCell.create(Vec2.create(0, 1)),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(colors.width, colors.height)),
uColorGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
@@ -238,7 +233,6 @@ export function createGridColor(grid: ColorVolume, type: ColorType, colorData?:
uColor: ValueCell.create(Vec3()),
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
tColorGrid: ValueCell.create(colors),
uPaletteDomain: ValueCell.create(Vec2.create(0, 1)),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(width, height)),
uColorGridDim: ValueCell.create(Vec3.clone(dimension)),
@@ -261,7 +255,6 @@ function createDirectColor(colorData?: ColorData): ColorData {
uColor: ValueCell.create(Vec3()),
tColor: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
tColorGrid: ValueCell.create(createNullTexture()),
uPaletteDomain: ValueCell.create(Vec2.create(0, 1)),
tPalette: ValueCell.create({ array: new Uint8Array(3), width: 1, height: 1 }),
uColorTexDim: ValueCell.create(Vec2.create(1, 1)),
uColorGridDim: ValueCell.create(Vec3.create(1, 1, 1)),

View File

@@ -1,25 +0,0 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Vec3, Mat4 } from '../../../../mol-math/linear-algebra';
import { SegmentedPlane } from '../../../primitive/plane';
import { MeshBuilder } from '../mesh-builder';
const tmpPlaneMat = Mat4.identity();
const tmpVec = Vec3();
function setPlaneMat(m: Mat4, center: Vec3, dirMajor: Vec3, dirMinor: Vec3, scale: Vec3) {
Vec3.add(tmpVec, center, dirMajor);
Mat4.targetTo(m, center, tmpVec, dirMinor);
Mat4.setTranslation(m, center);
Mat4.mul(m, m, Mat4.rotY90);
return Mat4.scale(m, m, scale);
}
export function addPlane(state: MeshBuilder.State, center: Vec3, dirMajor: Vec3, dirMinor: Vec3, scale: Vec3, widthSegments: number, heightSegments: number) {
const plane = SegmentedPlane(widthSegments, heightSegments);
MeshBuilder.addPrimitive(state, setPlaneMat(tmpPlaneMat, center, dirMajor, dirMinor, scale), plane);
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -37,53 +37,4 @@ export function Plane(): Primitive {
export function PlaneCage(): Cage {
return planeCage;
}
//
export function SegmentedPlane(widthSegments: number, heightSegments: number): Primitive {
const widthSegments1 = widthSegments + 1;
const heightSegments1 = heightSegments + 1;
const segmentWidth = 1 / widthSegments;
const segmentHeight = 1 / heightSegments;
const vertices = new Float32Array(widthSegments1 * heightSegments1 * 3);
const normals = new Float32Array(widthSegments1 * heightSegments1 * 3);
const indices = new Uint32Array(widthSegments * heightSegments * 6);
let i = 0;
for (let iy = 0; iy < heightSegments1; ++iy) {
const y = iy * segmentHeight - 0.5;
for (let ix = 0; ix < widthSegments1; ++ix) {
const x = ix * segmentWidth - 0.5;
vertices[i] = x;
vertices[i + 1] = -y;
vertices[i + 2] = 0;
normals[i] = 0;
normals[i + 1] = 0;
normals[i + 2] = 1;
i += 3;
}
}
let j = 0;
for (let iy = 0; iy < heightSegments; ++iy) {
for (let ix = 0; ix < widthSegments; ++ix) {
const a = ix + widthSegments1 * iy;
const b = ix + widthSegments1 * (iy + 1);
const c = (ix + 1) + widthSegments1 * (iy + 1);
const d = (ix + 1) + widthSegments1 * iy;
indices[j] = a;
indices[j + 1] = b;
indices[j + 2] = d;
indices[j + 3] = b;
indices[j + 4] = c;
indices[j + 5] = d;
j += 6;
}
}
return { vertices, normals, indices };
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 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 Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
@@ -203,7 +203,6 @@ export const ColorSchema = {
uColorTexDim: UniformSpec('v2'),
uColorGridDim: UniformSpec('v3'),
uColorGridTransform: UniformSpec('v4'),
uPaletteDomain: UniformSpec('v2'),
tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
tPalette: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
tColorGrid: TextureSpec('texture', 'rgb', 'ubyte', 'linear'),

View File

@@ -29,9 +29,9 @@ export const assign_color_varying = `
vColor.rgb = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb;
#endif
#elif defined(dColorType_vertex)
vColor.rgb = readFromTexture(tColor, vertexId, uColorTexDim).rgb;
vColor.rgb = readFromTexture(tColor, VertexID, uColorTexDim).rgb;
#elif defined(dColorType_vertexInstance)
vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + vertexId, uColorTexDim).rgb;
vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb;
#elif defined(dColorType_volume)
vec3 cgridPos = (uColorGridTransform.w * (position - uColorGridTransform.xyz)) / uColorGridDim;
vColor.rgb = texture3dFrom2dLinear(tColorGrid, cgridPos, uColorGridDim, uColorTexDim).rgb;
@@ -50,7 +50,7 @@ export const assign_color_varying = `
#elif defined(dOverpaintType_groupInstance)
vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
#elif defined(dOverpaintType_vertexInstance)
vOverpaint = readFromTexture(tOverpaint, int(aInstance) * uVertexCount + vertexId, uOverpaintTexDim);
vOverpaint = readFromTexture(tOverpaint, int(aInstance) * uVertexCount + VertexID, uOverpaintTexDim);
#elif defined(dOverpaintType_volumeInstance)
vec3 ogridPos = (uOverpaintGridTransform.w * (vModelPosition - uOverpaintGridTransform.xyz)) / uOverpaintGridDim;
vOverpaint = texture3dFrom2dLinear(tOverpaintGrid, ogridPos, uOverpaintGridDim, uOverpaintTexDim);
@@ -71,7 +71,7 @@ export const assign_color_varying = `
#elif defined(dEmissiveType_groupInstance)
vEmissive = readFromTexture(tEmissive, aInstance * float(uGroupCount) + group, uEmissiveTexDim).a;
#elif defined(dEmissiveType_vertexInstance)
vEmissive = readFromTexture(tEmissive, int(aInstance) * uVertexCount + vertexId, uEmissiveTexDim).a;
vEmissive = readFromTexture(tEmissive, int(aInstance) * uVertexCount + VertexID, uEmissiveTexDim).a;
#elif defined(dEmissiveType_volumeInstance)
vec3 egridPos = (uEmissiveGridTransform.w * (vModelPosition - uEmissiveGridTransform.xyz)) / uEmissiveGridDim;
vEmissive = texture3dFrom2dLinear(tEmissiveGrid, egridPos, uEmissiveGridDim, uEmissiveTexDim).a;
@@ -85,7 +85,7 @@ export const assign_color_varying = `
#elif defined(dSubstanceType_groupInstance)
vSubstance = readFromTexture(tSubstance, aInstance * float(uGroupCount) + group, uSubstanceTexDim);
#elif defined(dSubstanceType_vertexInstance)
vSubstance = readFromTexture(tSubstance, int(aInstance) * uVertexCount + vertexId, uSubstanceTexDim);
vSubstance = readFromTexture(tSubstance, int(aInstance) * uVertexCount + VertexID, uSubstanceTexDim);
#elif defined(dSubstanceType_volumeInstance)
vec3 sgridPos = (uSubstanceGridTransform.w * (vModelPosition - uSubstanceGridTransform.xyz)) / uSubstanceGridDim;
vSubstance = texture3dFrom2dLinear(tSubstanceGrid, sgridPos, uSubstanceGridDim, uSubstanceTexDim);
@@ -102,7 +102,7 @@ export const assign_color_varying = `
#elif defined(dEmissiveType_groupInstance)
vEmissive = readFromTexture(tEmissive, aInstance * float(uGroupCount) + group, uEmissiveTexDim).a;
#elif defined(dEmissiveType_vertexInstance)
vEmissive = readFromTexture(tEmissive, int(aInstance) * uVertexCount + vertexId, uEmissiveTexDim).a;
vEmissive = readFromTexture(tEmissive, int(aInstance) * uVertexCount + VertexID, uEmissiveTexDim).a;
#elif defined(dEmissiveType_volumeInstance)
vec3 egridPos = (uEmissiveGridTransform.w * (vModelPosition - uEmissiveGridTransform.xyz)) / uEmissiveGridDim;
vEmissive = texture3dFrom2dLinear(tEmissiveGrid, egridPos, uEmissiveGridDim, uEmissiveTexDim).a;
@@ -131,7 +131,7 @@ export const assign_color_varying = `
#elif defined(dTransparencyType_groupInstance)
vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a;
#elif defined(dTransparencyType_vertexInstance)
vTransparency = readFromTexture(tTransparency, int(aInstance) * uVertexCount + vertexId, uTransparencyTexDim).a;
vTransparency = readFromTexture(tTransparency, int(aInstance) * uVertexCount + VertexID, uTransparencyTexDim).a;
#elif defined(dTransparencyType_volumeInstance)
vec3 tgridPos = (uTransparencyGridTransform.w * (vModelPosition - uTransparencyGridTransform.xyz)) / uTransparencyGridDim;
vTransparency = texture3dFrom2dLinear(tTransparencyGrid, tgridPos, uTransparencyGridDim, uTransparencyTexDim).a;

View File

@@ -1,6 +1,6 @@
export const assign_group = `
#ifdef dGeometryType_textureMesh
float group = unpackRGBToInt(readFromTexture(tGroup, vertexId, uGeoTexDim).rgb);
float group = unpackRGBToInt(readFromTexture(tGroup, VertexID, uGeoTexDim).rgb);
#else
float group = aGroup;
#endif

View File

@@ -2,7 +2,7 @@ export const assign_position = `
mat4 model = uModel * aTransform;
mat4 modelView = uView * model;
#ifdef dGeometryType_textureMesh
vec3 position = readFromTexture(tPosition, vertexId, uGeoTexDim).xyz;
vec3 position = readFromTexture(tPosition, VertexID, uGeoTexDim).xyz;
#else
vec3 position = aPosition;
#endif

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2025 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>
*/
@@ -38,8 +38,6 @@ uniform float uIsOrtho;
uniform vec3 uCameraDir;
void main() {
int vertexId = VertexID;
#include assign_group
#include assign_color_varying
#include assign_marker_varying

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Michael Krone <michael.krone@uni-tuebingen.de>
@@ -122,7 +122,6 @@ uniform mat4 uCartnToUnit;
#endif
#ifdef dUsePalette
uniform vec2 uPaletteDomain;
uniform sampler2D tPalette;
#endif
@@ -272,8 +271,7 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
#endif
#if defined(dColorType_direct) && defined(dUsePalette)
float paletteValue = (value - uPaletteDomain[0]) / (uPaletteDomain[1] - uPaletteDomain[0]);
material.rgb = texture2D(tPalette, vec2(clamp(paletteValue, 0.0, 1.0), 0.0)).rgb;
material.rgb = texture2D(tPalette, vec2(value, 0.0)).rgb;
#elif defined(dColorType_uniform)
material.rgb = uColor;
#elif defined(dColorType_instance)

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2025 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>
*/
@@ -25,10 +25,6 @@ uniform sampler2D tMarker;
varying vec2 vUv;
varying float vInstance;
#ifdef dUsePalette
uniform sampler2D tPalette;
#endif
#if defined(dInterpolation_catmulrom) || defined(dInterpolation_mitchell) || defined(dInterpolation_bspline)
#define dInterpolation_cubic
#endif
@@ -94,19 +90,6 @@ varying float vInstance;
}
#endif
#if defined(dNeedsMarker)
float getMarker() {
if (uMarker != -1.0) return uMarker;
vec3 packedGroup = texture2D(tGroupTex, vUv).rgb;
if (packedGroup == vec3(0.0)) return 0.0;
float group = unpackRGBToInt(packedGroup);
float marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
return floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
}
#endif
void main() {
#include fade_lod
#include clip_pixel
@@ -116,9 +99,12 @@ void main() {
#else
vec4 imageData = texture2D(tImageTex, vUv);
#endif
if (imageData.a < 0.5) discard;
imageData.a = clamp(imageData.a, 0.0, 1.0);
if (imageData.a > 0.9) imageData.a = 1.0;
imageData.a = uAlpha;
imageData.a *= uAlpha;
if (imageData.a < 0.05)
discard;
float fragmentDepth = gl_FragCoord.z;
@@ -129,7 +115,8 @@ void main() {
}
#if defined(dRenderVariant_pick)
#include check_picking_alpha
if (imageData.a < 0.3)
discard;
#ifdef requiredDrawBuffers
gl_FragColor = vec4(packIntToRGB(float(uObjectId)), 1.0);
gl_FragData[1] = vec4(packIntToRGB(vInstance), 1.0);
@@ -146,41 +133,46 @@ void main() {
}
#endif
#elif defined(dRenderVariant_depth)
if (imageData.a < 0.05)
discard;
if (uRenderMask == MaskOpaque) {
gl_FragColor = packDepthToRGBA(fragmentDepth);
} else if (uRenderMask == MaskTransparent) {
gl_FragColor = packDepthWithAlphaToRGBA(fragmentDepth, imageData.a);
}
#elif defined(dRenderVariant_marking)
float marker = getMarker();
float marker = uMarker;
if (uMarker == -1.0) {
float group = unpackRGBToInt(texture2D(tGroupTex, vUv).rgb);
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
}
if (uMarkingType == 1) {
if (marker > 0.0)
if (marker > 0.0 || imageData.a < 0.05)
discard;
gl_FragColor = packDepthToRGBA(fragmentDepth);
} else {
if (marker == 0.0)
if (marker == 0.0 || imageData.a < 0.05)
discard;
float depthTest = 1.0;
if (uMarkingDepthTest) {
depthTest = (fragmentDepth >= getDepthPacked(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0;
}
bool isHighlight = intMod(marker, 2.0) > 0.1;
float viewZ = depthToViewZ(uIsOrtho, fragmentDepth, uNear, uFar);
float fogFactor = smoothstep(uFogNear, uFogFar, abs(viewZ));
if (fogFactor == 1.0)
discard;
gl_FragColor = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0 - fogFactor);
gl_FragColor = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0);
}
#elif defined(dRenderVariant_emissive)
gl_FragColor = vec4(0.0);
#elif defined(dRenderVariant_color) || defined(dRenderVariant_tracing)
#ifdef dUsePalette
float v = ((imageData.r * 256.0 * 256.0 * 255.0 + imageData.g * 256.0 * 255.0 + imageData.b * 255.0) - 1.0) / 16777215.0;
imageData.rgb = texture2D(tPalette, vec2(v, 0.0)).rgb;
#endif
gl_FragColor = imageData;
float marker = getMarker();
float marker = uMarker;
if (uMarker == -1.0) {
float group = unpackRGBToInt(texture2D(tGroupTex, vUv).rgb);
marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
}
#include apply_marker_color
#if defined(dRenderVariant_color)

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -19,8 +19,6 @@ varying vec2 vUv;
varying float vInstance;
void main() {
int vertexId = VertexID;
#include assign_position
vUv = aUv;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
@@ -40,7 +40,6 @@ void trimSegment(const in vec4 start, inout vec4 end) {
void main(){
float aspect = uViewport.z / uViewport.w;
int vertexId = VertexID;
#include assign_group
#include assign_color_varying

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 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 Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -32,8 +32,6 @@ attribute float aInstance;
varying vec3 vNormal;
void main(){
int vertexId = VertexID;
#include assign_group
#include assign_marker_varying
#include assign_clipping_varying
@@ -42,7 +40,7 @@ void main(){
#include clip_instance
#ifdef dGeometryType_textureMesh
vec3 normal = readFromTexture(tNormal, vertexId, uGeoTexDim).xyz;
vec3 normal = readFromTexture(tNormal, VertexID, uGeoTexDim).xyz;
#else
vec3 normal = aNormal;
#endif

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -24,8 +24,6 @@ attribute float aInstance;
attribute float aGroup;
void main(){
int vertexId = VertexID;
#include assign_group
#include assign_color_varying
#include assign_marker_varying

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -70,9 +70,7 @@ void main(void){
mapping = vec2(1.0, -1.0);
}
int vertexId = VertexID / 6;
vec4 positionGroup = readFromTexture(tPositionGroup, vertexId, uTexDim);
vec4 positionGroup = readFromTexture(tPositionGroup, VertexID / 6, uTexDim);
vec3 position = positionGroup.rgb;
float group = positionGroup.a;

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -38,8 +38,6 @@ varying vec2 vTexCoord;
#include matrix_scale
void main(void){
int vertexId = VertexID;
#include assign_group
#include assign_color_varying
#include assign_marker_varying

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 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 Alexander Rose <alexander.rose@weirdbyte.de>
* @author Gianluca Tomasello <giagitom@gmail.com>
@@ -21,10 +21,6 @@ import { fillSerial } from '../../mol-util/array';
import { deepClone } from '../../mol-util/object';
import { cloneUniformValues, UniformsList } from './uniform';
// Handle Firefox's preference [webgl.max-vert-ids-per-draw] which defaults to 30_000_000
// since FF119, see https://bugzilla.mozilla.org/show_bug.cgi?id=1849433
const MaxDrawCount = 30_000_000;
const getNextRenderItemId = idFactory();
export type DrawMode = 'points' | 'lines' | 'line-strip' | 'line-loop' | 'triangles' | 'triangle-strip' | 'triangle-fan'
@@ -315,16 +311,10 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
}
}
} else {
let offset = 0;
while (true) {
const count = Math.min(drawCount - offset, MaxDrawCount);
if (elementsBuffer) {
instancedArrays.drawElementsInstanced(glDrawMode, count, elementsBuffer._dataType, offset * elementsBuffer._bpe, instanceCount);
} else {
instancedArrays.drawArraysInstanced(glDrawMode, offset, count, instanceCount);
}
offset += count;
if (offset >= drawCount) break;
if (elementsBuffer) {
instancedArrays.drawElementsInstanced(glDrawMode, drawCount, elementsBuffer._dataType, 0, instanceCount);
} else {
instancedArrays.drawArraysInstanced(glDrawMode, 0, drawCount, instanceCount);
}
if (isTimingMode) {
stats.calls.drawInstanced += 1;

View File

@@ -1,10 +1,10 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Mat3, Mat4, Quat, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { Mat3, Mat4, Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
import { ValueCell } from '../../mol-util';
import { GLRenderingContext } from './compat';
import { RenderableSchema } from '../../mol-gl/renderable/schema';
@@ -18,7 +18,6 @@ export type UniformKindValue = {
'v2': Vec2; 'v2[]': number[]
'v3': Vec3; 'v3[]': number[]
'v4': Vec4; 'v4[]': number[]
'q': Quat; 'q[]': number[]
'iv2': Vec2; 'iv2[]': number[]
'iv3': Vec3; 'iv3[]': number[]
'iv4': Vec4; 'iv4[]': number[]
@@ -40,7 +39,6 @@ export function getUniformType(gl: GLRenderingContext, kind: UniformKind) {
case 'v2': case 'v2[]': return gl.FLOAT_VEC2;
case 'v3': case 'v3[]': return gl.FLOAT_VEC3;
case 'v4': case 'v4[]': return gl.FLOAT_VEC4;
case 'q': case 'q[]': return gl.FLOAT_VEC4;
case 'iv2': case 'iv2[]': return gl.INT_VEC2;
case 'iv3': case 'iv3[]': return gl.INT_VEC3;
case 'iv4': case 'iv4[]': return gl.INT_VEC4;
@@ -79,7 +77,6 @@ function getUniformSetter(kind: UniformKind): UniformSetter {
case 'v2': case 'v2[]': return uniform2fv;
case 'v3': case 'v3[]': return uniform3fv;
case 'v4': case 'v4[]': return uniform4fv;
case 'q': case 'q[]': return uniform4fv;
case 'iv2': case 'iv2[]': return uniform2iv;
case 'iv3': case 'iv3[]': return uniform3iv;
case 'iv4': case 'iv4[]': return uniform4iv;
@@ -110,7 +107,6 @@ export function getUniformGlslType(kind: UniformKind): string {
case 'v2': return 'vec2';
case 'v3': return 'vec3';
case 'v4': return 'vec4';
case 'q': return 'vec4';
case 'm3': return 'mat3';
case 'm4': return 'mat4';
}

View File

@@ -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.401, IHM 1.27, MA 1.4.7.
* Code-generated 'BIRD' schema file. Dictionary versions: mmCIF 5.399, IHM 1.27, MA 1.4.6.
*
* @author molstar/ciftools package
*/

View File

@@ -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.401, IHM 1.27, MA 1.4.7.
* Code-generated 'CCD' schema file. Dictionary versions: mmCIF 5.399, IHM 1.27, MA 1.4.6.
*
* @author molstar/ciftools package
*/

View File

@@ -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.401, IHM 1.27, MA 1.4.7.
* Code-generated 'mmCIF' schema file. Dictionary versions: mmCIF 5.399, IHM 1.27, MA 1.4.6.
*
* @author molstar/ciftools package
*/
@@ -705,7 +705,7 @@ export const mmCIF_Schema = {
/**
* An abbreviation that identifies the database.
*/
database_id: Aliased<'alphafolddb' | 'cas' | 'csd' | 'emdb' | 'icsd' | 'modelarchive' | 'mdf' | 'modbase' | 'ndb' | 'nbs' | 'pdb' | 'pdb-dev' | 'pdf' | 'rcsb' | 'swiss-model_repository' | 'ebi' | 'pdbe' | 'bmrb' | 'wwpdb' | 'pdb_acc'>(lstr),
database_id: Aliased<'alphafolddb' | 'cas' | 'csd' | 'emdb' | 'icsd' | 'modelarchive' | 'mdf' | 'modbase' | 'ndb' | 'nbs' | 'pdb' | 'pdf' | 'rcsb' | 'swiss-model_repository' | 'ebi' | 'pdbe' | 'bmrb' | 'wwpdb' | 'pdb_acc'>(lstr),
/**
* The code assigned by the database identified in
* _database_2.database_id.
@@ -884,7 +884,7 @@ export const mmCIF_Schema = {
* (DT) for Thymidine-5'-monophosphate
* (MSE) for Selenomethionine
* (SEP) for Phosphoserine
* (TPO) for Phosphothreonine
* (PTO) for Phosphothreonine
* (PTR) for Phosphotyrosine
* (PCA) for Pyroglutamic acid
* (UNK) for Unknown amino acid
@@ -2098,7 +2098,7 @@ export const mmCIF_Schema = {
/**
* The name of the database containing the related entry.
*/
db_name: Aliased<'BIOISIS' | 'BMCD' | 'BMRB' | 'EMDB' | 'NDB' | 'PDB' | 'PDB-Dev' | 'SASBDB' | 'TargetDB' | 'TargetTrack'>(str),
db_name: str,
/**
* A description of the related entry.
*/
@@ -4939,19 +4939,25 @@ export const mmCIF_Schema = {
*/
ma_model_list: {
/**
* A unique identifier for the structural model being deposited.
* A unique identifier for the model / model group combination.
*/
ordinal_id: int,
/**
* A unique identifier for the structural model being deposited.
* This data item was practically a duplicate of _ma_model_list.ordinal_id
* and has been deprecated with dictionary version 1.4.7.
*/
model_id: int,
/**
* An identifier to group structural models into collections or sets.
* This data item has been deprecated with dictionary version 1.4.7.
* See ma_model_group category.
* A cluster of models and its representative can either be grouped together
* or can be separate groups in the ma_model_list table. The choice between
* the two options should be decided based on how the modeling was carried out
* and how the representative was chosen. If the representative is a member of
* the ensemble (i.e., best scoring model), then it is recommended that the
* representative and the ensemble belong to the same model group. If the
* representative is calculated from the ensemble (i.e., centroid), then it is
* recommended that the representative be separated into a different group.
* If the models do not need to be grouped into collections, then the
* _ma_model_list.model_group_id is the same as _ma_model_list.model_id.
*/
model_group_id: int,
/**
@@ -4960,8 +4966,6 @@ export const mmCIF_Schema = {
model_name: str,
/**
* A decsriptive name for the model group.
* This data item has been deprecated with dictionary version 1.4.7.
* See ma_model_group category.
*/
model_group_name: str,
/**
@@ -5127,11 +5131,11 @@ export const mmCIF_Schema = {
/**
* The type of QA metric.
*/
type: Aliased<'zscore' | 'energy' | 'distance' | 'normalized score' | 'pLDDT' | 'pLDDT in [0,1]' | 'pLDDT all-atom' | 'pLDDT all-atom in [0,1]' | 'pLDDT to polymer' | 'PAE' | 'pTM' | 'ipTM' | 'contact probability' | 'boolean' | 'other'>(str),
type: Aliased<'zscore' | 'energy' | 'distance' | 'normalized score' | 'pLDDT' | 'pLDDT in [0,1]' | 'pLDDT all-atom' | 'pLDDT all-atom in [0,1]' | 'PAE' | 'pTM' | 'ipTM' | 'contact probability' | 'other'>(str),
/**
* 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'>(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
@@ -5150,7 +5154,7 @@ export const mmCIF_Schema = {
ordinal_id: int,
/**
* The identifier for the structural model, for which global QA metric is provided.
* This data item is a pointer to _ma_model_list.ordinal_id
* This data item is a pointer to _ma_model_list.model_id
* in the MA_MODEL_LIST category.
*/
model_id: int,
@@ -5168,10 +5172,6 @@ export const mmCIF_Schema = {
/**
* Data items in the MA_QA_METRIC_LOCAL category captures the
* details of the local QA metrics, calculated at the residue-level.
* Data in this category can be extracted into a separate file which
* is linked to the main file using the categories
* ma_associated_archive_file_details or ma_entry_associated_files
* with file_content set to "QA metrics".
*/
ma_qa_metric_local: {
/**
@@ -5180,7 +5180,7 @@ export const mmCIF_Schema = {
ordinal_id: int,
/**
* The identifier for the structural model, for which local QA metric is provided.
* This data item is a pointer to _ma_model_list.ordinal_id
* This data item is a pointer to _ma_model_list.model_id
* in the MA_MODEL_LIST category.
*/
model_id: int,
@@ -5219,15 +5219,6 @@ export const mmCIF_Schema = {
/**
* Data items in the MA_QA_METRIC_LOCAL_PAIRWISE category captures the
* details of the local QA metrics, calculated at the pairwise residue level.
* In cases where the metric is symmetric, it is enough to store just one value per pair.
* For asymmetric metrics, the order of residues is expected to be meaningful
* (e.g. PAE where PAE_ij is defined by aligning residue i (label_*_1) and measuring
* the error on residue j (label_*_2)).
* In all cases, it is perfectly valid to only provide values for a subset of residue pairs.
* Data in this category is expected to be very large and can be extracted into a
* separate file which is linked to the main file using the categories
* ma_associated_archive_file_details or ma_entry_associated_files with file_content
* set to "QA metrics".
*/
ma_qa_metric_local_pairwise: {
/**
@@ -5236,7 +5227,7 @@ export const mmCIF_Schema = {
ordinal_id: int,
/**
* The identifier for the structural model, for which local QA metric is provided.
* This data item is a pointer to _ma_model_list.ordinal_id
* This data item is a pointer to _ma_model_list.model_id
* in the MA_MODEL_LIST category.
*/
model_id: int,

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 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>
@@ -129,12 +129,6 @@ namespace Box3D {
return out;
}
export function expandUniformly(out: Box3D, box: Box3D, delta: number): Box3D {
Vec3.subScalar(out.min, box.min, delta);
Vec3.addScalar(out.max, box.max, delta);
return out;
}
export function scale(out: Box3D, box: Box3D, scale: number) {
Vec3.scale(out.min, box.min, scale);
Vec3.scale(out.max, box.max, scale);
@@ -224,18 +218,6 @@ namespace Box3D {
Vec3.scale(out, dir, tmin);
return Vec3.set(out, out[0] + x, out[1] + y, out[2] + z);
}
export function center(out: Vec3, box: Box3D): Vec3 {
return Vec3.center(out, box.max, box.min);
}
export function exactEquals(a: Box3D, b: Box3D) {
return Vec3.exactEquals(a.min, b.min) && Vec3.exactEquals(a.max, b.max);
}
export function equals(a: Box3D, b: Box3D) {
return Vec3.equals(a.min, b.min) && Vec3.equals(a.max, b.max);
}
}
export { Box3D };

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2022-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2022-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*
@@ -86,7 +86,7 @@ namespace Plane3D {
}
export function projectPoint(out: Vec3, plane: Plane3D, point: Vec3) {
return Vec3.scaleAndAdd(out, point, plane.normal, -distanceToPoint(plane, point));
return Vec3.scaleAndAdd(out, out, plane.normal, -distanceToPoint(plane, point));
}
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2023 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>
@@ -27,7 +27,6 @@ import { Vec3 } from './vec3';
import { EPSILON } from './common';
import { assertUnreachable, NumberArray } from '../../../mol-util/type-helpers';
import { Euler } from './euler';
import { Mat4 } from './mat4';
interface Quat extends Array<number> { [d: number]: number, '@type': 'quat', length: 4 }
interface ReadonlyQuat extends Array<number> { readonly [d: number]: number, '@type': 'quat', length: 4 }
@@ -283,12 +282,6 @@ namespace Quat {
return out;
}
const m3tmp = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] as unknown as Mat3;
export function fromMat4(out: Quat, m: Mat4) {
Mat3.fromMat4(m3tmp, m);
return fromMat3(out, m3tmp);
}
export function fromEuler(out: Quat, euler: Euler, order: Euler.Order) {
const [x, y, z] = euler;
@@ -374,12 +367,6 @@ namespace Quat {
return out;
}
const m4tmp = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] as unknown as Mat4;
export function fromBasis(out: Quat, x: Vec3, y: Vec3, z: Vec3) {
Mat4.fromBasis(m4tmp, x, y, z);
return fromMat4(out, m4tmp);
}
export function clone(a: Quat) {
const out = zero();
out[0] = a[0];

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2017-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2017-2023 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>
@@ -645,11 +645,6 @@ namespace Vec3 {
return normalize(out, cross(out, triangleNormalTmpAB, triangleNormalTmpAC));
}
const centerTmpV = zero();
export function center(out: Vec3, a: Vec3, b: Vec3): Vec3 {
return Vec3.scaleAndAdd(out, a, Vec3.sub(centerTmpV, b, a), 0.5);
}
export function toString(a: Vec3, precision?: number) {
return `[${a[0].toPrecision(precision)} ${a[1].toPrecision(precision)} ${a[2].toPrecision(precision)}]`;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -31,17 +31,4 @@ export function isDataLocation(x: any): x is DataLocation {
return !!x && x.kind === 'data-location';
}
/**
* A direct Location.
*
* For it, the location is implicitly clear from context and is not explicitly given.
* This is used for themes with direct-volume rendering where the location is the volume
* grid cell itself and coloring is applied in a shader on the GPU.
*/
export const DirectLocation = { kind: 'direct-location' as const };
export type DirectLocation = typeof DirectLocation
export function isDirectLocation(x: any): x is DirectLocation {
return !!x && x.kind === 'direct-location';
}
export type Location = StructureElement.Location | Bond.Location | ShapeGroup.Location | PositionLocation | DataLocation | NullLocation | DirectLocation | Volume.Cell.Location | Volume.Segment.Location
export type Location = StructureElement.Location | Bond.Location | ShapeGroup.Location | PositionLocation | DataLocation | NullLocation | Volume.Segment.Location

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 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>
@@ -8,7 +8,6 @@
import { SpacegroupCell, Box3D, Sphere3D } from '../../mol-math/geometry';
import { Tensor, Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { Histogram, calculateHistogram } from '../../mol-math/histogram';
import { lerp } from '../../mol-math/interpolate';
/** The basic unit cell that contains the grid data. */
interface Grid {
@@ -77,59 +76,6 @@ namespace Grid {
}
return histograms[binCount];
}
export function makeGetTrilinearlyInterpolated(grid: Grid, transform: 'none' | 'relative') {
const cartnToGrid = Grid.getGridToCartesianTransform(grid);
Mat4.invert(cartnToGrid, cartnToGrid);
const gridCoords = Vec3();
const { stats } = grid;
const { dimensions, get } = grid.cells.space;
const data = grid.cells.data;
const [mi, mj, mk] = dimensions;
return function getTrilinearlyInterpolated(position: Vec3): number {
Vec3.copy(gridCoords, position);
Vec3.transformMat4(gridCoords, gridCoords, cartnToGrid);
const i = Math.trunc(gridCoords[0]);
const j = Math.trunc(gridCoords[1]);
const k = Math.trunc(gridCoords[2]);
if (i < 0 || i >= mi || j < 0 || j >= mj || k < 0 || k >= mk) {
return Number.NaN;
}
const u = gridCoords[0] - i;
const v = gridCoords[1] - j;
const w = gridCoords[2] - k;
// Tri-linear interpolation for the value
const ii = Math.min(i + 1, mi - 1);
const jj = Math.min(j + 1, mj - 1);
const kk = Math.min(k + 1, mk - 1);
let a = get(data, i, j, k);
let b = get(data, ii, j, k);
let c = get(data, i, jj, k);
let d = get(data, ii, jj, k);
const x = lerp(lerp(a, b, u), lerp(c, d, u), v);
a = get(data, i, j, kk);
b = get(data, ii, j, kk);
c = get(data, i, jj, kk);
d = get(data, ii, jj, kk);
const y = lerp(lerp(a, b, u), lerp(c, d, u), v);
const value = lerp(x, y, w);
if (transform === 'relative') {
return (value - stats.mean) / stats.sigma;
} else {
return value;
}
};
}
}
export { Grid };

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -220,18 +220,6 @@ export namespace Volume {
export function areLociEqual(a: Loci, b: Loci) { return a.volume === b.volume && OrderedSet.areEqual(a.indices, b.indices); }
export function isLociEmpty(loci: Loci) { return OrderedSet.size(loci.indices) === 0; }
export interface Location {
readonly kind: 'cell-location',
volume: Volume
cell: CellIndex
}
export function Location(volume?: Volume, cell?: CellIndex): Location {
return { kind: 'cell-location', volume: volume as any, cell: cell as any };
}
export function isLocation(x: any): x is Location {
return !!x && x.kind === 'cell-location';
}
const boundaryHelper = new BoundaryHelper('98');
const tmpBoundaryPos = Vec3();
export function getBoundingSphere(volume: Volume, indices: OrderedSet<CellIndex>, boundingSphere?: Sphere3D) {

View File

@@ -63,13 +63,13 @@ const DownloadStructure = StateAction.build({
}, { pivot: 'id' }),
options
}, { isFlat: true, label: 'PDB' }),
'pdb-ihm': PD.Group({
'pdb-dev': PD.Group({
provider: PD.Group({
id: PD.Text('8zzc', { label: 'PDB-IHM Id(s)', description: 'One or more comma/space separated ids.' }),
id: PD.Text('PDBDEV_00000001', { label: 'PDB-Dev Id(s)', description: 'One or more comma/space separated ids.' }),
encoding: PD.Select('bcif', PD.arrayToOptions(['cif', 'bcif'] as const)),
}, { pivot: 'id' }),
options
}, { isFlat: true, label: 'PDB-IHM' }),
}, { isFlat: true, label: 'PDB-Dev' }),
'swissmodel': PD.Group({
id: PD.Text('Q9Y2I8', { label: 'UniProtKB AC(s)', description: 'One or more comma/space separated ACs.' }),
options
@@ -124,22 +124,22 @@ const DownloadStructure = StateAction.build({
);
asTrajectory = !!src.params.options.asTrajectory;
break;
case 'pdb-ihm':
case 'pdb-dev':
const map = (id: string) => id.startsWith('PDBDEV_') ? id : `PDBDEV_${id.padStart(8, '0')}`;
downloadParams = await getDownloadParams(src.params.provider.id,
id => {
// 4 character PDB id, TODO: support extended PDB ID
if (id.match(/^[1-9][A-Z0-9]{3}$/i) !== null) {
return src.params.provider.encoding === 'bcif'
? `https://pdb-ihm.org/bcif/${id.toLowerCase()}.bcif`
: `https://pdb-ihm.org/cif/${id.toLowerCase()}.cif`;
? `https://pdb-dev.wwpdb.org/bcif/${id.toLowerCase()}.bcif`
: `https://pdb-dev.wwpdb.org/cif/${id.toLowerCase()}.cif`;
}
const nId = map(id.toUpperCase());
return src.params.provider.encoding === 'bcif'
? `https://pdb-ihm.org/bcif/${nId}.bcif`
: `https://pdb-ihm.org/cif/${nId}.cif`;
? `https://pdb-dev.wwpdb.org/bcif/${nId}.bcif`
: `https://pdb-dev.wwpdb.org/cif/${nId}.cif`;
},
id => { const nId = id.toUpperCase(); return nId.match(/^[1-9][A-Z0-9]{3}$/) ? `PDB-IHM: ${nId}` : map(nId); },
id => { const nId = id.toUpperCase(); return nId.match(/^[1-9][A-Z0-9]{3}$/) ? `PDB-Dev: ${nId}` : map(nId); },
src.params.provider.encoding === 'bcif'
);
asTrajectory = !!src.params.options.asTrajectory;

View File

@@ -1,9 +1,8 @@
/**
* Copyright (c) 2019-2025 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>
* @author Adam Midlik <midlik@gmail.com>
*/
import { PresetProvider } from '../preset-provider';
@@ -122,7 +121,7 @@ const auto = StructureRepresentationPresetProvider({
params: () => CommonParams,
apply(ref, params, plugin) {
const structure = StateObjectRef.resolveAndCheck(plugin.state.data, ref)?.obj?.data;
if (!structure) return {};
if (!structure) return { };
const thresholds = plugin.config.get(PluginConfig.Structure.SizeThresholds) || Structure.DefaultSizeThresholds;
const size = Structure.getSize(structure, thresholds);
@@ -152,7 +151,7 @@ const empty = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-empty',
display: { name: 'Empty', description: 'Removes all existing representations.' },
async apply(ref, params, plugin) {
return {};
return { };
}
});
@@ -397,9 +396,12 @@ const illustrative = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-illustrative',
display: {
name: 'Illustrative', group: 'Miscellaneous',
description: 'Show everything in spacefill representation with illustrative colors and ignore light.'
description: '...'
},
params: () => CommonParams,
params: () => ({
...CommonParams,
showCarbohydrateSymbol: PD.Boolean(false)
}),
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
@@ -414,47 +416,7 @@ const illustrative = StructureRepresentationPresetProvider({
const { update, builder, typeParams, color } = reprBuilder(plugin, params, structure);
const representations = {
all: builder.buildRepresentation(update, components.all, {
type: 'spacefill',
typeParams: { ...typeParams, ignoreLight: true },
color: 'illustrative',
colorParams: { style: { name: 'entity-id', params: { overrideWater: true } } },
}, { tag: 'all' }),
};
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name ?? color, params.theme?.focus?.params);
return { components, representations };
}
});
const molecularSurface = StructureRepresentationPresetProvider({
id: 'preset-structure-representation-molecular-surface',
display: {
name: 'Molecular Surface', group: 'Miscellaneous',
description: 'Show everything in molecular surface representation with illustrative colors.'
},
params: () => CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
if (!structureCell) return {};
const components = {
all: await presetStaticComponent(plugin, structureCell, 'all'),
branched: undefined
};
const structure = structureCell.obj!.data;
const { update, builder, typeParams, color } = reprBuilder(plugin, params, structure);
const representations = {
all: builder.buildRepresentation(update, components.all, {
type: 'molecular-surface',
typeParams,
color: 'entity-id',
colorParams: { overrideWater: true },
}, { tag: 'all' }),
all: builder.buildRepresentation(update, components.all, { type: 'spacefill', typeParams: { ...typeParams, ignoreLight: true }, color: 'illustrative' }, { tag: 'all' }),
};
await update.commit({ revertOnError: true });
await updateFocusRepr(plugin, structure, params.theme?.focus?.name ?? color, params.theme?.focus?.params);
@@ -512,7 +474,6 @@ export const PresetStructureRepresentations = {
'protein-and-nucleic': proteinAndNucleic,
'coarse-surface': coarseSurface,
illustrative,
'molecular-surface': molecularSurface,
'auto-lod': autoLod,
};
export type PresetStructureRepresentations = typeof PresetStructureRepresentations;

View File

@@ -1,9 +1,8 @@
/**
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2021 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>
* @author Adam Midlik <midlik@gmail.com>
*/
import { EveryLoci, isEmptyLoci, Loci } from '../../mol-model/loci';
@@ -179,9 +178,8 @@ namespace InteractivityManager {
highlightOnlyExtend(current: Representation.Loci, applyGranularity = true) {
const normalized = this.normalizedLoci(current, applyGranularity);
if (StructureElement.Loci.is(normalized.loci)) {
const range = this.ctx.selectionMode ? this.sel.tryGetRange(normalized.loci) : this.ctx.managers.structure.focus.tryGetRange(normalized.loci);
const extended = {
loci: range ?? normalized.loci,
loci: this.sel.tryGetRange(normalized.loci) || normalized.loci,
repr: normalized.repr
};
if (!this.isHighlighted(extended)) {
@@ -295,4 +293,4 @@ namespace InteractivityManager {
}
}
}
}
}

View File

@@ -1,22 +1,20 @@
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020 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>
* @author Adam Midlik <midlik@gmail.com>
*/
import { Sphere3D } from '../../../mol-math/geometry';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { Loci } from '../../../mol-model/loci';
import { Structure, StructureElement } from '../../../mol-model/structure';
import { PluginContext } from '../../../mol-plugin/context';
import { StateSelection } from '../../../mol-state';
import { lociLabel } from '../../../mol-theme/label';
import { arrayRemoveAtInPlace } from '../../../mol-util/array';
import { StatefulPluginComponent } from '../../component';
import { PluginContext } from '../../../mol-plugin/context';
import { arrayRemoveAtInPlace } from '../../../mol-util/array';
import { StructureElement, Structure } from '../../../mol-model/structure';
import { Loci } from '../../../mol-model/loci';
import { lociLabel } from '../../../mol-theme/label';
import { PluginStateObject } from '../../objects';
import { getLociRange } from './selection';
import { StateSelection } from '../../../mol-state';
import { Vec3 } from '../../../mol-math/linear-algebra';
import { Sphere3D } from '../../../mol-math/geometry';
export type FocusEntry = {
label: string
@@ -52,9 +50,6 @@ export class StructureFocusManager extends StatefulPluginComponent<StructureFocu
get current() { return this.state.current; }
get history() { return this.state.history; }
/** Last added or removed loci */
private referenceLoci: StructureElement.Loci | undefined;
private tryAddHistory(entry: FocusEntry) {
if (StructureElement.Loci.isEmpty(entry.loci)) return;
@@ -89,10 +84,6 @@ export class StructureFocusManager extends StatefulPluginComponent<StructureFocu
}
}
tryGetRange(loci: Loci): StructureElement.Loci | undefined {
return getLociRange(this.referenceLoci, loci);
}
setFromLoci(anyLoci: Loci) {
const loci = Loci.normalize(anyLoci);
if (!StructureElement.Loci.is(loci) || StructureElement.Loci.isEmpty(loci)) {
@@ -101,7 +92,6 @@ export class StructureFocusManager extends StatefulPluginComponent<StructureFocu
}
this.set({ loci, label: lociLabel(loci, { reverse: true, hidePrefix: true, htmlStyling: false }) });
this.referenceLoci = loci;
}
addFromLoci(anyLoci: Loci) {
@@ -109,21 +99,6 @@ export class StructureFocusManager extends StatefulPluginComponent<StructureFocu
? StructureElement.Loci.union(anyLoci, this.state.current.loci)
: anyLoci;
this.setFromLoci(union);
const refLoci = Loci.normalize(anyLoci);
this.referenceLoci = StructureElement.Loci.is(refLoci) ? refLoci : undefined;
}
toggleFromLoci(anyLoci: Loci) {
const { kind, loci } = toggleLoci(this.state.current?.loci, anyLoci);
this.setFromLoci(loci);
const refLoci = Loci.normalize(anyLoci);
this.referenceLoci = StructureElement.Loci.is(refLoci) && kind !== 'subtract' ? refLoci : undefined;
}
extendFromLoci(anyLoci: Loci) {
const range = this.tryGetRange(anyLoci) ?? anyLoci;
this.toggleFromLoci(range);
}
clear() {
@@ -131,7 +106,6 @@ export class StructureFocusManager extends StatefulPluginComponent<StructureFocu
this.state.current = undefined;
this.behaviors.current.next(void 0);
}
this.referenceLoci = undefined;
}
getSnapshot(): StructureFocusSnapshot {
@@ -217,17 +191,4 @@ export class StructureFocusManager extends StatefulPluginComponent<StructureFocu
}
});
}
}
/** Return union of `currentLoci` and `newLoci`; or subtract `newLoci` from `currentLoci` if `newLoci` is a subset of `currentLoci`. */
function toggleLoci(currentLoci: StructureElement.Loci | undefined, newLoci: Loci) {
if (currentLoci && StructureElement.Loci.is(newLoci) && newLoci.structure === currentLoci.structure) {
if (StructureElement.Loci.isSubset(currentLoci, newLoci)) {
return { kind: 'subtract', loci: StructureElement.Loci.subtract(currentLoci, newLoci) };
} else {
return { kind: 'add', loci: StructureElement.Loci.union(newLoci, currentLoci) };
}
} else {
return { kind: 'new', loci: newLoci };
}
}
}

View File

@@ -1,9 +1,8 @@
/**
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019-2023 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 { OrderedSet } from '../../../mol-data/int';
@@ -352,9 +351,28 @@ export class StructureSelectionManager extends StatefulPluginComponent<Structure
tryGetRange(loci: Loci): StructureElement.Loci | undefined {
if (!StructureElement.Loci.is(loci)) return;
if (!this.getEntry(loci.structure)) return;
if (loci.elements.length !== 1) return;
const entry = this.getEntry(loci.structure);
if (!entry) return;
return getLociRange(this.referenceLoci, loci);
const xs = loci.elements[0];
if (!xs) return;
const ref = this.referenceLoci;
if (!ref || !StructureElement.Loci.is(ref) || ref.structure !== loci.structure) return;
let e: StructureElement.Loci['elements'][0] | undefined;
for (const _e of ref.elements) {
if (xs.unit === _e.unit) {
e = _e;
break;
}
}
if (!e) return;
if (xs.unit !== e.unit) return;
return getElementRange(loci.structure, e, xs);
}
/** Count of all selected elements */
@@ -551,31 +569,6 @@ function remapSelectionEntry(e: SelectionEntry, s: Structure): SelectionEntry {
return new SelectionEntry(StructureElement.Loci.remap(e.selection, s));
}
/** Return loci spanning the range between `fromLoci` and `toLoci` (including both) if they belong to the same unit in the same structure */
export function getLociRange(fromLoci: Loci | undefined, toLoci: Loci | undefined): StructureElement.Loci | undefined {
if (!StructureElement.Loci.is(fromLoci)) return;
if (!StructureElement.Loci.is(toLoci)) return;
if (fromLoci.structure !== toLoci.structure) return;
if (toLoci.elements.length !== 1) return;
const xs = toLoci.elements[0];
if (!xs) return;
let e: StructureElement.Loci['elements'][0] | undefined;
for (const _e of fromLoci.elements) {
if (xs.unit === _e.unit) {
e = _e;
break;
}
}
if (!e) return;
if (xs.unit !== e.unit) return;
return getElementRange(toLoci.structure, e, xs);
}
/**
* Assumes `ref` and `ext` belong to the same unit in the same structure
*/
@@ -587,4 +580,4 @@ function getElementRange(structure: Structure, ref: StructureElement.Loci['eleme
unit: ref.unit,
indices: OrderedSet.ofRange(min as StructureElement.UnitIndex, max as StructureElement.UnitIndex)
}]);
}
}

View File

@@ -1015,7 +1015,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
const provider = plugin.representation.volume.registry.get(params.type.name);
if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data);
const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data, locationKinds: provider.locationKinds }, params));
repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, params));
const props = params.type.params || {};
await repr.createOrUpdate(props, a.data).runInContext(ctx);
@@ -1024,13 +1024,13 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
},
update({ a, b, oldParams, newParams }, plugin: PluginContext) {
return Task.create('Volume Representation', async ctx => {
const oldProvider = plugin.representation.volume.registry.get(oldParams.type.name);
if (newParams.type.name !== oldParams.type.name) {
const oldProvider = plugin.representation.volume.registry.get(oldParams.type.name);
oldProvider.ensureCustomProperties?.detach(a.data);
return StateTransformer.UpdateResult.Recreate;
}
const props = { ...b.data.repr.props, ...newParams.type.params };
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data, locationKinds: oldProvider.locationKinds }, newParams));
b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, newParams));
await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
b.data.sourceData = a.data;
b.description = VolumeRepresentation3DHelpers.getDescription(props);

View File

@@ -32,7 +32,7 @@ const _Intersect = <svg width='24px' height='24px' viewBox='0 0 24 24'><defs>{ci
export function IntersectSvg() { return _Intersect; }
const _Set = <svg width='24px' height='24px' viewBox='0 0 24 24'><defs>{circleLeft}{circleRight}</defs><g><use href='#circle-left' className='msp-shape-empty' /><use href='#circle-right' className='msp-shape-filled' /></g></svg>;
export function SetSvg() { return _Set; }
const _Molecule = <svg width='17px' height='17px' viewBox='0 0 299.463 299.463' strokeWidth='6px'><g><path d='M256.851,173.832v-48.201c22.916-4.918,34.151-30.668,22.556-50.771c-11.547-20.004-39.486-23.251-55.242-5.844 l-41.746-24.106C189.618,22.603,172.861,0,149.734,0c-23.132,0-39.881,22.609-32.685,44.911L75.305,69.016 C59.522,51.586,31.597,54.88,20.061,74.863c-11.63,20.163-0.298,45.862,22.557,50.769v48.2 c-22.821,4.898-34.195,30.591-22.556,50.771c11.529,19.972,39.454,23.285,55.242,5.845l41.746,24.106 c-7.199,22.308,9.559,44.911,32.685,44.911c23.132,0,39.88-22.609,32.685-44.911l41.745-24.106 c15.817,17.469,43.73,14.099,55.242-5.844c0,0,0-0.001,0.001-0.002c4.587-7.953,5.805-17.213,3.431-26.076 C279.392,185.657,269.129,176.461,256.851,173.832z M249.62,72.088c20.568,0,27.428,27.191,10.008,37.239 c-0.003,0.002-0.006,0.003-0.009,0.005c-10.04,5.81-22.85,1.762-27.877-8.475C225.206,87.548,234.938,72.088,249.62,72.088z M149.734,14.4c11.005,0,19.958,8.954,19.958,19.959c0,11.127-9.077,19.958-19.958,19.958c-10.95,0-19.958-8.9-19.958-19.958 C129.776,23.354,138.729,14.4,149.734,14.4z M39.84,109.328c-17.451-10.067-10.534-37.24,10.01-37.24 c15.311,0,24.922,16.653,17.251,29.942C61.681,111.397,49.517,114.925,39.84,109.328z M59.802,224.702 c-9.535,5.503-21.768,2.229-27.268-7.298c-7.639-13.242,1.887-29.945,17.236-29.945c0.013,0,0.027,0,0.04,0 C70.07,187.48,77.49,214.469,59.802,224.702z M149.734,285.062c-11.005,0-19.958-8.954-19.958-19.958 c0-11.127,9.077-19.958,19.958-19.958c10.954,0,19.958,8.903,19.958,19.958C169.693,276.109,160.74,285.062,149.734,285.062z M216.953,217.982l-41.727,24.095c-13.778-15.22-37.459-14.94-50.983,0l-41.728-24.096c6.196-19.289-5.541-39.835-25.498-44.149 V125.63c19.752-4.268,31.762-24.65,25.498-44.149l41.727-24.095c13.629,15.055,37.32,15.093,50.983,0l41.728,24.096 c-6.196,19.29,5.534,39.835,25.498,44.149v48.202C222.61,178.123,210.721,198.581,216.953,217.982z M266.935,217.404 c-5.501,9.528-17.732,12.802-27.261,7.302c-17.682-10.23-10.301-37.247,10.032-37.247 C264.984,187.459,274.602,204.112,266.935,217.404z' /></g></svg>;
const _Molecule = <svg width='17px' height='17px' viewBox='0 0 299.463 299.463' strokeWidth='6px'><g><path d='M256.851,173.832v-48.201c22.916-4.918,34.151-30.668,22.556-50.771c-11.547-20.004-39.486-23.251-55.242-5.844 l-41.746-24.106C189.618,22.603,172.861,0,149.734,0c-23.132,0-39.881,22.609-32.685,44.911L75.305,69.016 C59.522,51.586,31.597,54.88,20.061,74.863c-11.63,20.163-0.298,45.862,22.557,50.769v48.2 c-22.821,4.898-34.195,30.591-22.556,50.771c11.529,19.972,39.454,23.285,55.242,5.845l41.746,24.106 c-7.199,22.308,9.559,44.911,32.685,44.911c23.132,0,39.88-22.609,32.685-44.911l41.745-24.106 c15.817,17.469,43.73,14.099,55.242-5.844c0,0,0-0.001,0.001-0.002c4.587-7.953,5.805-17.213,3.431-26.076 C279.392,185.657,269.129,176.461,256.851,173.832z M249.62,72.088c20.568,0,27.428,27.191,10.008,37.239 c-0.003,0.002-0.006,0.003-0.009,0.005c-10.04,5.81-22.85,1.762-27.877-8.475C225.206,87.548,234.938,72.088,249.62,72.088z M149.734,14.4c11.005,0,19.958,8.954,19.958,19.959c0,11.127-9.077,19.958-19.958,19.958c-10.95,0-19.958-8.9-19.958-19.958 C129.776,23.354,138.729,14.4,149.734,14.4z M39.84,109.328c-17.451-10.067-10.534-37.24,10.01-37.24 c15.311,0,24.922,16.653,17.251,29.942C61.681,111.397,49.517,114.925,39.84,109.328z M59.802,224.702 c-9.535,5.503-21.768,2.229-27.268-7.298c-7.639-13.242,1.887-29.945,17.236-29.945c0.013,0,0.027,0,0.04,0 C70.07,187.48,77.49,214.469,59.802,224.702z M149.734,285.062c-11.005,0-19.958-8.954-19.958-19.958 c0-11.127,9.077-19.958,19.958-19.958c10.954,0,19.958,8.903,19.958,19.958C169.693,276.109,160.74,285.062,149.734,285.062z M216.953,217.982l-41.727,24.095c-13.778-15.22-37.459-14.94-50.983,0l-41.728-24.096c6.196-19.289-5.541-39.835-25.498-44.149 V125.63c19.752-4.268,31.762-24.65,25.498-44.149l41.727-24.095c13.629,15.055,37.32,15.093,50.983,0l41.728,24.096 c-6.196,19.29,5.534,39.835,25.498,44.149v48.202C222.61,178.123,210.721,198.581,216.953,217.982z M266.935,217.404 c-5.501,9.528-17.732,12.802-27.261,7.302c-17.682-10.23-10.301-37.247,10.032-37.247 C264.984,187.459,274.602,204.112,266.935,217.404z'/></g></svg>;
export function MoleculeSvg() { return _Molecule; }
// The following icons are adapted from https://materialdesignicons.com/ and

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 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>
@@ -695,33 +695,15 @@ const colorGradientInterpolated = memoize1((colors: ColorListEntry[]) => {
const colorGradientBanded = memoize1((colors: ColorListEntry[]) => {
const n = colors.length;
const styles: string[] = [];
const hasOffsets = colors.every(c => Array.isArray(c));
if (hasOffsets) {
const off = colors as [Color, number][];
styles.push(`${Color.toStyle(off[0][0])} ${(100 * off[0][1]).toFixed(2)}%`);
for (let i = 0, il = off.length - 1; i < il; ++i) {
const [c0, o0] = off[i];
const [c1, o1] = off[i + 1];
const o = o0 + (o1 - o0) / 2;
styles.push(
`${Color.toStyle(c0)} ${(100 * o).toFixed(2)}%`,
`${Color.toStyle(c1)} ${(100 * o).toFixed(2)}%`
);
}
styles.push(`${Color.toStyle(off[off.length - 1][0])} ${(100 * off[off.length - 1][1]).toFixed(2)}%`);
} else {
const styles: string[] = [`${colorEntryToStyle(colors[0])} ${100 * (1 / n)}%`];
for (let i = 1, il = n - 1; i < il; ++i) {
styles.push(
`${colorEntryToStyle(colors[i])} ${100 * (i / n)}%`,
`${colorEntryToStyle(colors[i])} ${100 * ((i + 1) / n)}%`
);
}
styles.push(`${colorEntryToStyle(colors[n - 1])} ${100 * ((n - 1) / n)}%`);
const styles: string[] = [`${colorEntryToStyle(colors[0])} ${100 * (1 / n)}%`];
// TODO: does this need to support offsets?
for (let i = 1, il = n - 1; i < il; ++i) {
styles.push(
`${colorEntryToStyle(colors[i])} ${100 * (i / n)}%`,
`${colorEntryToStyle(colors[i])} ${100 * ((i + 1) / n)}%`
);
}
styles.push(`${colorEntryToStyle(colors[n - 1])} ${100 * ((n - 1) / n)}%`);
return `linear-gradient(to right, ${styles.join(', ')})`;
});

View File

@@ -4,7 +4,6 @@
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author Christian Dominguez <christian.99dominguez@gmail.com>
* @author Ventura Rivera <venturaxrivera@gmail.com>
*/
import { List } from 'immutable';
@@ -269,7 +268,6 @@ export class ControlsWrapper extends PluginUIComponent {
export class DefaultViewport extends PluginUIComponent {
render() {
const VPControls = this.plugin.spec.components?.viewport?.controls || ViewportControls;
const SVPControls = this.plugin.spec.components?.selectionTools?.controls || SelectionViewportControls;
const SnapshotDescription = this.plugin.spec.components?.viewport?.snapshotDescription || ViewportSnapshotDescription;
return <>
@@ -280,7 +278,7 @@ export class DefaultViewport extends PluginUIComponent {
<StateSnapshotViewportControls />
<SnapshotDescription />
</div>
<SVPControls />
<SelectionViewportControls />
<VPControls />
<BackgroundTaskProgress />
<div className='msp-highlight-toast-wrapper'>

View File

@@ -3,7 +3,6 @@
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
* @author Ventura Rivera <venturaxrivera@gmail.com>
*/
import * as React from 'react';
@@ -216,12 +215,11 @@ type SequenceViewState = {
modelEntityId: string,
chainGroupId: number,
operatorKey: string,
mode: SequenceViewMode,
sequenceViewModeParam: typeof SequenceViewModeParam,
mode: SequenceViewMode
}
export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceViewMode }, SequenceViewState> {
state: SequenceViewState = { structureOptions: { options: [], all: [] }, structure: Structure.Empty, structureRef: '', modelEntityId: '', chainGroupId: -1, operatorKey: '', mode: 'single', sequenceViewModeParam: SequenceViewModeParam };
state: SequenceViewState = { structureOptions: { options: [], all: [] }, structure: Structure.Empty, structureRef: '', modelEntityId: '', chainGroupId: -1, operatorKey: '', mode: 'single' };
componentDidMount() {
if (this.plugin.state.data.select(StateSelection.Generators.rootsOfType(PSO.Molecule.Structure)).length > 0) this.setState(this.getInitialState());
@@ -243,16 +241,6 @@ export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceView
this.sync();
}
});
const modeOptions = this.plugin.spec.components?.sequenceViewer?.modeOptions;
if (modeOptions) {
const modeSet = new Set(modeOptions);
const sequenceViewModeParam = {
...SequenceViewModeParam,
options: SequenceViewModeParam.options.filter(([firstItem]) => modeSet.has(firstItem)),
};
this.setState({ sequenceViewModeParam: sequenceViewModeParam });
}
}
private sync() {
@@ -312,9 +300,7 @@ export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceView
chainGroupId = this.state.chainGroupId;
operatorKey = this.state.operatorKey;
}
const defaultMode = this.plugin.spec.components?.sequenceViewer?.defaultMode;
const initialMode = this.props.defaultMode ?? defaultMode ?? 'single';
return { structureOptions, structure, structureRef, modelEntityId, chainGroupId, operatorKey, mode: initialMode, sequenceViewModeParam: this.state.sequenceViewModeParam };
return { structureOptions, structure, structureRef, modelEntityId, chainGroupId, operatorKey, mode: this.props.defaultMode ?? 'single' };
}
private get params() {
@@ -327,7 +313,7 @@ export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceView
entity: PD.Select(entityOptions[0][0], entityOptions, { shortLabel: true }),
chain: PD.Select(chainOptions[0][0], chainOptions, { shortLabel: true, twoColumns: true, label: 'Chain' }),
operator: PD.Select(operatorOptions[0][0], operatorOptions, { shortLabel: true, twoColumns: true }),
mode: this.state.sequenceViewModeParam,
mode: SequenceViewModeParam
};
}
@@ -393,11 +379,11 @@ export class SequenceView extends PluginUIComponent<{ defaultMode?: SequenceView
return <div className='msp-sequence'>
<div className='msp-sequence-select'>
<Icon svg={HelpOutlineSvg} style={{ cursor: 'help', position: 'absolute', right: 0, top: 0 }}
title='This shows a single sequence. Use the controls to show a different sequence. &#10;Use Ctrl or Cmd key to add a sequence range to focus; use Shift key to extend last focused/selected range.' />
title='This shows a single sequence. Use the controls to show a different sequence.' />
<span>Sequence of</span>
<PureSelectControl title={`[Structure] ${PD.optionLabel(params.structure, values.structure)}`} param={params.structure} name='structure' value={values.structure} onChange={this.setParamProps} />
<PureSelectControl title={`[Mode]`} param={this.state.sequenceViewModeParam} name='mode' value={values.mode} onChange={this.setParamProps} />
<PureSelectControl title={`[Mode]`} param={SequenceViewModeParam} name='mode' value={values.mode} onChange={this.setParamProps} />
{values.mode === 'single' && <PureSelectControl title={`[Entity] ${PD.optionLabel(params.entity, values.entity)}`} param={params.entity} name='entity' value={values.entity} onChange={this.setParamProps} />}
{values.mode === 'single' && <PureSelectControl title={`[Chain] ${PD.optionLabel(params.chain, values.chain)}`} param={params.chain} name='chain' value={values.chain} onChange={this.setParamProps} />}
{params.operator.options.length > 1 && <>

View File

@@ -1,15 +1,15 @@
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019 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 { Interval, OrderedSet } from '../../mol-data/int';
import { Loci } from '../../mol-model/loci';
import { Structure, StructureElement, StructureProperties } from '../../mol-model/structure';
import { ColorNames } from '../../mol-util/color/names';
import { SequenceWrapper, StructureUnit } from './wrapper';
import { OrderedSet, Interval } from '../../mol-data/int';
import { Loci } from '../../mol-model/loci';
import { ColorNames } from '../../mol-util/color/names';
import { MarkerAction, applyMarkerAction } from '../../mol-util/marker-action';
export class ChainSequenceWrapper extends SequenceWrapper<StructureUnit> {
private label: string;
@@ -26,25 +26,27 @@ export class ChainSequenceWrapper extends SequenceWrapper<StructureUnit> {
return 'msp-sequence-present';
}
override getSeqIndices(loci: Loci): OrderedSet {
mark(loci: Loci, action: MarkerAction) {
let changed = false;
const { structure } = this.data;
if (StructureElement.Loci.is(loci)) {
if (!Structure.areRootsEquivalent(loci.structure, structure)) return Interval.Empty;
if (!Structure.areRootsEquivalent(loci.structure, structure)) return false;
loci = StructureElement.Loci.remap(loci, structure);
for (const e of loci.elements) {
const indices = this.unitIndices.get(e.unit.id);
if (indices) {
if (OrderedSet.isSubset(indices, e.indices)) {
return Interval.ofSingleton(0);
if (applyMarkerAction(this.markerArray, Interval.ofSingleton(0), action)) changed = true;
}
}
}
} else if (Structure.isLoci(loci)) {
if (!Structure.areRootsEquivalent(loci.structure, structure)) return Interval.Empty;
return Interval.ofSingleton(0);
if (!Structure.areRootsEquivalent(loci.structure, structure)) return false;
if (applyMarkerAction(this.markerArray, Interval.ofSingleton(0), action)) changed = true;
}
return Interval.Empty;
return changed;
}
getLoci(seqIdx: number) {
@@ -76,11 +78,12 @@ export class ChainSequenceWrapper extends SequenceWrapper<StructureUnit> {
counts.push(`${elementCount} elements`);
const length = 1;
const markerArray = new Uint8Array(length);
super(data, length);
super(data, markerArray, length);
this.label = `Whole Chain (${counts.join(', ')})`;
this.unitIndices = unitIndices;
this.loci = StructureElement.Loci(this.data.structure, lociElements);
}
}
}

View File

@@ -1,15 +1,15 @@
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019 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 { Interval, OrderedSet } from '../../mol-data/int';
import { Loci } from '../../mol-model/loci';
import { Structure, StructureElement } from '../../mol-model/structure';
import { ColorNames } from '../../mol-util/color/names';
import { SequenceWrapper, StructureUnit } from './wrapper';
import { OrderedSet, Interval } from '../../mol-data/int';
import { Loci } from '../../mol-model/loci';
import { ColorNames } from '../../mol-util/color/names';
import { MarkerAction, applyMarkerAction } from '../../mol-util/marker-action';
export class ElementSequenceWrapper extends SequenceWrapper<StructureUnit> {
private unitIndices: Map<number, Interval<StructureElement.UnitIndex>>;
@@ -24,30 +24,30 @@ export class ElementSequenceWrapper extends SequenceWrapper<StructureUnit> {
return 'msp-sequence-present';
}
override getSeqIndices(loci: Loci): OrderedSet {
mark(loci: Loci, action: MarkerAction) {
let changed = false;
const { structure, units } = this.data;
if (StructureElement.Loci.is(loci)) {
if (!Structure.areRootsEquivalent(loci.structure, structure)) return Interval.Empty;
if (!Structure.areRootsEquivalent(loci.structure, structure)) return false;
loci = StructureElement.Loci.remap(loci, structure);
for (const e of loci.elements) {
const indices = this.unitIndices.get(e.unit.id);
if (indices) {
if (OrderedSet.isSubset(indices, e.indices)) {
// this assumes this SequnceWrapper has only a single unit, otherwise we'd need to collect indices from all units and apply offsets
return e.indices;
if (applyMarkerAction(this.markerArray, e.indices, action)) changed = true;
}
}
}
} else if (Structure.isLoci(loci)) {
if (!Structure.areRootsEquivalent(loci.structure, structure)) return Interval.Empty;
if (!Structure.areRootsEquivalent(loci.structure, structure)) return false;
for (let i = 0, il = units.length; i < il; ++i) {
const indices = this.unitIndices.get(units[i].id)!;
return indices;
if (applyMarkerAction(this.markerArray, indices, action)) changed = true;
}
}
return Interval.Empty;
return changed;
}
getLoci(seqIdx: number) {
@@ -79,9 +79,10 @@ export class ElementSequenceWrapper extends SequenceWrapper<StructureUnit> {
unitIndices.set(unit.id, indices);
lociElements.push({ unit, indices });
}
const markerArray = new Uint8Array(length);
super(data, length);
super(data, markerArray, length);
this.unitIndices = unitIndices;
}
}
}

View File

@@ -1,15 +1,15 @@
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019 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 { Interval, OrderedSet, Segmentation, SortedArray } from '../../mol-data/int';
import { Loci } from '../../mol-model/loci';
import { ResidueIndex, Structure, StructureElement, Unit } from '../../mol-model/structure';
import { ColorNames } from '../../mol-util/color/names';
import { Structure, StructureElement, ResidueIndex, Unit } from '../../mol-model/structure';
import { SequenceWrapper, StructureUnit } from './wrapper';
import { OrderedSet, Segmentation, Interval, SortedArray } from '../../mol-data/int';
import { Loci } from '../../mol-model/loci';
import { ColorNames } from '../../mol-util/color/names';
import { MarkerAction, applyMarkerAction } from '../../mol-util/marker-action';
export class HeteroSequenceWrapper extends SequenceWrapper<StructureUnit> {
private readonly unitMap: Map<number, Unit>;
@@ -28,29 +28,29 @@ export class HeteroSequenceWrapper extends SequenceWrapper<StructureUnit> {
return 'msp-sequence-present';
}
override getSeqIndices(loci: Loci): OrderedSet {
mark(loci: Loci, action: MarkerAction) {
let changed = false;
const { structure } = this.data;
if (StructureElement.Loci.is(loci)) {
if (!Structure.areRootsEquivalent(loci.structure, structure)) return Interval.Empty;
if (!Structure.areRootsEquivalent(loci.structure, structure)) return false;
loci = StructureElement.Loci.remap(loci, structure);
const out: number[] = [];
for (const e of loci.elements) {
const unit = this.unitMap.get(e.unit.id);
if (unit) {
const { index: residueIndex } = e.unit.model.atomicHierarchy.residueAtomSegments;
OrderedSet.forEach(e.indices, v => {
const seqIdx = this.sequenceIndices.get(residueIndex[unit.elements[v]]);
if (seqIdx !== undefined) out.push(seqIdx);
if (seqIdx !== undefined && applyMarkerAction(this.markerArray, Interval.ofSingleton(seqIdx), action)) changed = true;
});
}
}
return SortedArray.deduplicate(SortedArray.ofSortedArray(out));
} else if (Structure.isLoci(loci)) {
if (!Structure.areRootsEquivalent(loci.structure, structure)) return Interval.Empty;
return Interval.ofBounds(0, this.length);
if (!Structure.areRootsEquivalent(loci.structure, structure)) return false;
if (applyMarkerAction(this.markerArray, Interval.ofBounds(0, this.length), action)) changed = true;
}
return Interval.Empty;
return changed;
}
getLoci(seqIdx: number) {
@@ -86,8 +86,9 @@ export class HeteroSequenceWrapper extends SequenceWrapper<StructureUnit> {
}
const length = sequence.length;
const markerArray = new Uint8Array(length);
super(data, length);
super(data, markerArray, length);
this.unitMap = new Map();
for (const unit of data.units) this.unitMap.set(unit.id, unit);
@@ -97,4 +98,4 @@ export class HeteroSequenceWrapper extends SequenceWrapper<StructureUnit> {
this.residueIndices = residueIndices;
this.seqToUnit = seqToUnit;
}
}
}

View File

@@ -1,17 +1,17 @@
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019 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 { Interval, OrderedSet, SortedArray } from '../../mol-data/int';
import { StructureSelection, StructureQuery, Structure, Queries, StructureProperties as SP, StructureElement, Unit } from '../../mol-model/structure';
import { SequenceWrapper, StructureUnit } from './wrapper';
import { OrderedSet, Interval, SortedArray } from '../../mol-data/int';
import { Loci } from '../../mol-model/loci';
import { Sequence } from '../../mol-model/sequence';
import { Queries, StructureProperties as SP, Structure, StructureElement, StructureQuery, StructureSelection, Unit } from '../../mol-model/structure';
import { MissingResidues } from '../../mol-model/structure/model/properties/common';
import { ColorNames } from '../../mol-util/color/names';
import { SequenceWrapper, StructureUnit } from './wrapper';
import { MarkerAction, applyMarkerAction, applyMarkerActionAtPosition } from '../../mol-util/marker-action';
export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
private readonly unitMap: Map<number, Unit>;
@@ -41,30 +41,28 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
: 'msp-sequence-present';
}
override getSeqIndices(loci: Loci): OrderedSet {
mark(loci: Loci, action: MarkerAction): boolean {
let changed = false;
const { structure } = this.data;
const index = (seqId: number) => this.sequence.index(seqId);
if (StructureElement.Loci.is(loci)) {
if (!Structure.areRootsEquivalent(loci.structure, structure)) return Interval.Empty;
if (!Structure.areRootsEquivalent(loci.structure, structure)) return false;
loci = StructureElement.Loci.remap(loci, structure);
const out: number[] = [];
for (const e of loci.elements) {
if (!this.unitMap.has(e.unit.id)) continue;
if (Unit.isAtomic(e.unit)) {
collectSeqIdxAtomic(out, e, index);
changed = applyMarkerAtomic(e, action, this.markerArray, index) || changed;
} else {
collectSeqIdxCoarse(out, e, index);
changed = applyMarkerCoarse(e, action, this.markerArray, index) || changed;
}
}
return SortedArray.deduplicate(SortedArray.ofSortedArray(out));
} else if (Structure.isLoci(loci)) {
if (!Structure.areRootsEquivalent(loci.structure, structure)) return Interval.Empty;
return this.observed;
if (!Structure.areRootsEquivalent(loci.structure, structure)) return false;
if (applyMarkerAction(this.markerArray, this.observed, action)) changed = true;
}
return Interval.Empty;
return changed;
}
getLoci(seqIdx: number) {
@@ -77,8 +75,9 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> {
const entitySeq = data.units[0].model.sequence.byEntityKey[SP.entity.key(l)];
const length = entitySeq.sequence.length;
const markerArray = new Uint8Array(length);
super(data, length);
super(data, markerArray, length);
this.unitMap = new Map();
for (const unit of data.units) this.unitMap.set(unit.id, unit);
@@ -118,20 +117,19 @@ function createResidueQuery(chainGroupId: number, operatorName: string, label_se
});
}
function collectSeqIdxAtomic(out: number[], e: StructureElement.Loci.Element, index: (seqId: number) => number) {
function applyMarkerAtomic(e: StructureElement.Loci.Element, action: MarkerAction, markerArray: Uint8Array, index: (seqId: number) => number) {
const { model, elements } = e.unit;
const { index: residueIndex } = model.atomicHierarchy.residueAtomSegments;
const { label_seq_id } = model.atomicHierarchy.residues;
OrderedSet.forEachSegment(e.indices, i => residueIndex[elements[i]], rI => {
const seqId = label_seq_id.value(rI);
const seqIdx = index(seqId);
out.push(seqIdx);
applyMarkerActionAtPosition(markerArray, index(seqId), action);
});
return true;
}
function collectSeqIdxCoarse(out: number[], e: StructureElement.Loci.Element, index: (seqId: number) => number) {
function applyMarkerCoarse(e: StructureElement.Loci.Element, action: MarkerAction, markerArray: Uint8Array, index: (seqId: number) => number) {
const { model, elements } = e.unit;
const begin = Unit.isSpheres(e.unit) ? model.coarseHierarchy.spheres.seq_id_begin : model.coarseHierarchy.gaussians.seq_id_begin;
const end = Unit.isSpheres(e.unit) ? model.coarseHierarchy.spheres.seq_id_end : model.coarseHierarchy.gaussians.seq_id_end;
@@ -139,8 +137,8 @@ function collectSeqIdxCoarse(out: number[], e: StructureElement.Loci.Element, in
OrderedSet.forEach(e.indices, i => {
const eI = elements[i];
for (let s = index(begin.value(eI)), e = index(end.value(eI)); s <= e; s++) {
out.push(s);
applyMarkerActionAtPosition(markerArray, s, action);
}
});
return true;
}
}

View File

@@ -1,26 +1,21 @@
/**
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2020 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>
* @author Adam Midlik <midlik@gmail.com>
*/
import * as React from 'react';
import { Subject } from 'rxjs';
import { throttleTime } from 'rxjs/operators';
import { OrderedSet } from '../../mol-data/int';
import { EveryLoci } from '../../mol-model/loci';
import { StructureElement, StructureProperties, Unit } from '../../mol-model/structure';
import { PluginCommands } from '../../mol-plugin/commands';
import { Representation } from '../../mol-repr/representation';
import { Color } from '../../mol-util/color';
import { ButtonsType, getButton, getButtons, getModifiers, ModifiersKeys } from '../../mol-util/input/input-observer';
import { MarkerAction } from '../../mol-util/marker-action';
import { PluginUIComponent } from '../base';
import { SequenceWrapper } from './wrapper';
type SequenceProps = {
sequenceWrapper: SequenceWrapper.Any,
sequenceNumberPeriod?: number,
@@ -30,18 +25,11 @@ type SequenceProps = {
/** Note, if this is changed, the CSS for `msp-sequence-number` needs adjustment too */
const MaxSequenceNumberSize = 5;
const DefaultMarkerColors = {
selected: 'rgb(51, 255, 25)',
highlighted: 'rgb(255, 102, 153)',
focused: '',
};
// TODO: this is somewhat inefficient and should be done using a canvas.
export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
protected parentDiv = React.createRef<HTMLDivElement>();
protected lastMouseOverSeqIdx = -1;
protected highlightQueue = new Subject<{ seqIdx: number, buttons: number, button: number, modifiers: ModifiersKeys }>();
protected markerColors = { ...DefaultMarkerColors };
protected lociHighlightProvider = (loci: Representation.Loci, action: MarkerAction) => {
const changed = this.props.sequenceWrapper.markResidue(loci.loci, action);
@@ -71,33 +59,6 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
const loci = this.getLoci(e.seqIdx < 0 ? void 0 : e.seqIdx);
this.hover(loci, e.buttons, e.button, e.modifiers);
});
this.subscribe(this.plugin.managers.structure.focus.behaviors.current, focus => {
this.updateFocus(focus?.loci);
this.updateMarker();
});
this.updateColors();
PluginCommands.Canvas3D.SetSettings.subscribe(this.plugin, () => {
this.updateColors();
this.updateMarker();
});
}
updateColors() {
if (this.plugin.canvas3d) {
this.markerColors.highlighted = Color.toHexStyle(this.plugin.canvas3d.props.renderer.highlightColor);
this.markerColors.selected = Color.toHexStyle(this.plugin.canvas3d.props.renderer.selectColor);
} else {
this.markerColors.highlighted = DefaultMarkerColors.highlighted;
this.markerColors.selected = DefaultMarkerColors.selected;
}
}
updateFocus(loci: StructureElement.Loci | undefined) {
this.props.sequenceWrapper.markResidue(EveryLoci, 'unfocus');
if (loci) {
this.props.sequenceWrapper.markResidue(loci, 'focus');
}
}
componentWillUnmount() {
@@ -127,18 +88,6 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
const ev = { current: Representation.Loci.Empty, buttons, button, modifiers };
if (loci !== undefined && !StructureElement.Loci.isEmpty(loci)) {
ev.current = { loci };
if (this.mouseDownLoci) {
const ref = this.mouseDownLoci.elements[0];
const ext = loci.elements[0];
const min = Math.min(OrderedSet.min(ref.indices), OrderedSet.min(ext.indices));
const max = Math.max(OrderedSet.max(ref.indices), OrderedSet.max(ext.indices));
const range = StructureElement.Loci(loci.structure, [{
unit: ref.unit,
indices: OrderedSet.ofRange(min as StructureElement.UnitIndex, max as StructureElement.UnitIndex)
}]);
ev.current = { loci: range };
}
}
this.plugin.behaviors.interaction.hover.next(ev);
}
@@ -162,6 +111,11 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
const seqIdx = this.getSeqIdx(e);
const loci = this.getLoci(seqIdx);
const buttons = getButtons(e.nativeEvent);
const button = getButton(e.nativeEvent);
const modifiers = getModifiers(e.nativeEvent);
this.click(loci, buttons, button, modifiers);
this.mouseDownLoci = loci;
};
@@ -174,51 +128,44 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
const seqIdx = this.getSeqIdx(e);
const loci = this.getLoci(seqIdx);
if (loci) {
if (loci && !StructureElement.Loci.areEqual(this.mouseDownLoci, loci)) {
const buttons = getButtons(e.nativeEvent);
const button = getButton(e.nativeEvent);
const modifiers = getModifiers(e.nativeEvent);
let range = loci;
if (!StructureElement.Loci.areEqual(this.mouseDownLoci, loci)) {
const ref = this.mouseDownLoci.elements[0];
const ext = loci.elements[0];
const min = Math.min(OrderedSet.min(ref.indices), OrderedSet.min(ext.indices));
const max = Math.max(OrderedSet.max(ref.indices), OrderedSet.max(ext.indices));
const ref = this.mouseDownLoci.elements[0];
const ext = loci.elements[0];
const min = Math.min(OrderedSet.min(ref.indices), OrderedSet.min(ext.indices));
const max = Math.max(OrderedSet.max(ref.indices), OrderedSet.max(ext.indices));
range = StructureElement.Loci(loci.structure, [{
unit: ref.unit,
indices: OrderedSet.ofRange(min as StructureElement.UnitIndex, max as StructureElement.UnitIndex)
}]);
}
const range = StructureElement.Loci(loci.structure, [{
unit: ref.unit,
indices: OrderedSet.ofRange(min as StructureElement.UnitIndex, max as StructureElement.UnitIndex)
}]);
this.click(range, buttons, button, modifiers);
this.click(StructureElement.Loci.subtract(range, this.mouseDownLoci), buttons, button, modifiers);
}
this.mouseDownLoci = undefined;
};
protected getBackgroundColor(seqIdx: number) {
const seqWrapper = this.props.sequenceWrapper;
if (seqWrapper.isHighlighted(seqIdx)) return this.markerColors.highlighted;
if (seqWrapper.isSelected(seqIdx)) return this.markerColors.selected;
if (seqWrapper.isFocused(seqIdx)) return this.markerColors.focused;
return '';
protected getBackgroundColor(marker: number) {
// TODO: make marker color configurable
if (typeof marker === 'undefined') console.error('unexpected marker value');
return marker === 0
? ''
: marker % 2 === 0
? 'rgb(51, 255, 25)' // selected
: 'rgb(255, 102, 153)'; // highlighted
}
protected getResidueClass(seqIdx: number, label: string) {
const seqWrapper = this.props.sequenceWrapper;
const classes = [seqWrapper.residueClass(seqIdx)];
if (label.length > 1) {
classes.push(seqIdx === 0 ? 'msp-sequence-residue-long-begin' : 'msp-sequence-residue-long');
}
if (seqWrapper.isHighlighted(seqIdx)) classes.push('msp-sequence-residue-highlighted');
if (seqWrapper.isSelected(seqIdx)) classes.push('msp-sequence-residue-selected');
if (seqWrapper.isFocused(seqIdx)) classes.push('msp-sequence-residue-focused');
return classes.join(' ');
return label.length > 1
? this.props.sequenceWrapper.residueClass(seqIdx) + (seqIdx === 0 ? ' msp-sequence-residue-long-begin' : ' msp-sequence-residue-long')
: this.props.sequenceWrapper.residueClass(seqIdx);
}
protected residue(seqIdx: number, label: string) {
return <span key={seqIdx} data-seqid={seqIdx} style={{ backgroundColor: this.getBackgroundColor(seqIdx) }} className={this.getResidueClass(seqIdx, label)}>{`\u200b${label}\u200b`}</span>;
protected residue(seqIdx: number, label: string, marker: number) {
return <span key={seqIdx} data-seqid={seqIdx} style={{ backgroundColor: this.getBackgroundColor(marker) }} className={this.getResidueClass(seqIdx, label)}>{`\u200b${label}\u200b`}</span>;
}
protected getSequenceNumberClass(seqIdx: number, seqNum: string, label: string) {
@@ -261,23 +208,30 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
protected updateMarker() {
if (!this.parentDiv.current) return;
const xs = this.parentDiv.current.children;
const { markerArray } = this.props.sequenceWrapper;
const hasNumbers = !this.props.hideSequenceNumbers, period = this.sequenceNumberPeriod;
const seqWrapper = this.props.sequenceWrapper;
const seqLength = seqWrapper.length;
// let first: HTMLSpanElement | undefined;
let o = 0;
for (let i = 0; i < seqLength; i++) {
if (hasNumbers && i % period === 0 && i < seqLength) o++;
for (let i = 0, il = markerArray.length; i < il; i++) {
if (hasNumbers && i % period === 0 && i < il) o++;
// o + 1 to account for help icon
const span = xs[o] as HTMLSpanElement;
if (!span) return;
o++;
const className = this.getResidueClass(i, seqWrapper.residueLabel(i));
if (span.className !== className) span.className = className;
const backgroundColor = this.getBackgroundColor(i);
// if (!first && markerArray[i] > 0) {
// first = span;
// }
const backgroundColor = this.getBackgroundColor(markerArray[i]);
if (span.style.backgroundColor !== backgroundColor) span.style.backgroundColor = backgroundColor;
}
// if (first) {
// first.scrollIntoView({ block: 'nearest' });
// }
}
mouseMove = (e: React.MouseEvent) => {
@@ -301,7 +255,7 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
this.lastMouseOverSeqIdx = seqIdx;
if (this.mouseDownLoci !== undefined) {
const loci = this.getLoci(seqIdx);
this.hover(loci, ButtonsType.Flag.None, ButtonsType.Flag.None, modifiers);
this.hover(loci, ButtonsType.Flag.None, ButtonsType.Flag.None, { ...modifiers, shift: true });
} else {
this.highlightQueue.next({ seqIdx, buttons, button, modifiers });
}
@@ -332,12 +286,9 @@ export class Sequence<P extends SequenceProps> extends PluginUIComponent<P> {
if (hasNumbers && i % period === 0 && i < il) {
elems[elems.length] = this.getSequenceNumberSpan(i, label);
}
elems[elems.length] = this.residue(i, label);
elems[elems.length] = this.residue(i, label, sw.markerArray[i]);
}
// ensure the focus markers are updated after sequenceRender is recreated
this.updateFocus(this.plugin.managers.structure.focus.behaviors.current.value?.loci);
// calling .updateMarker here is neccesary to ensure existing
// residue spans are updated as react won't update them
this.updateMarker();

View File

@@ -1,15 +1,14 @@
/**
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2019 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 { Interval, OrderedSet } from '../../mol-data/int';
import { Interval } from '../../mol-data/int';
import { Loci, isEveryLoci } from '../../mol-model/loci';
import { Structure, StructureElement, Unit } from '../../mol-model/structure';
import { Color } from '../../mol-util/color';
import { MarkerAction, applyMarkerAction } from '../../mol-util/marker-action';
import { StructureElement, Structure, Unit } from '../../mol-model/structure';
import { Color } from '../../mol-util/color';
export type StructureUnit = { structure: Structure, units: Unit[] }
@@ -22,19 +21,9 @@ abstract class SequenceWrapper<D> {
abstract getLoci(seqIdx: number): StructureElement.Loci
/** Return list of sequence viewer positions that correspond to `loci` */
abstract getSeqIndices(loci: Loci): OrderedSet;
mark(loci: Loci, action: MarkerAction): boolean {
const seqIdxs = this.getSeqIndices(loci);
if (OrderedSet.size(seqIdxs) === 0) return false;
return applyMarkerAction(this.markerArray, seqIdxs, action);
}
markResidue(loci: Loci, action: MarkerAction | 'focus' | 'unfocus'): boolean {
if (action === 'focus') return this.markResidueFocus(loci, true);
if (action === 'unfocus') return this.markResidueFocus(loci, false);
abstract mark(loci: Loci, action: MarkerAction): boolean;
markResidue(loci: Loci, action: MarkerAction) {
if (isEveryLoci(loci)) {
return applyMarkerAction(this.markerArray, Interval.ofLength(this.length), action);
} else {
@@ -42,42 +31,11 @@ abstract class SequenceWrapper<D> {
}
}
private markResidueFocus(loci: Loci, focusState: boolean) {
const value = focusState ? 1 : 0;
if (isEveryLoci(loci)) {
this.focusMarkerArray.fill(value, 0, this.length);
return true;
} else {
const seqIdxs = this.getSeqIndices(loci);
OrderedSet.forEach(seqIdxs, seqIdx => this.focusMarkerArray[seqIdx] = value);
return OrderedSet.size(seqIdxs) > 0;
}
}
constructor(readonly data: D, readonly markerArray: Uint8Array, readonly length: number) {
/** Return true if the position `seqIndex` in sequence view is highlighted */
isHighlighted(seqIndex: number): boolean {
return !!(this.markerArray[seqIndex] & 1);
}
/** Return true if the position `seqIndex` in sequence view is selected */
isSelected(seqIndex: number): boolean {
return !!(this.markerArray[seqIndex] & 2);
}
/** Return true if the position `seqIndex` in sequence view is focused */
isFocused(seqIndex: number): boolean {
return !!(this.focusMarkerArray[seqIndex]);
}
/** Markers for "highlighted" and "selected" (2 bits per position) */
readonly markerArray: Uint8Array;
/** Markers for "focused" (1 bit per position) */
readonly focusMarkerArray: Uint8Array;
constructor(readonly data: D, readonly length: number) {
this.markerArray = new Uint8Array(length);
this.focusMarkerArray = new Uint8Array(length);
}
}
namespace SequenceWrapper {
export type Any = SequenceWrapper<any>
}
}

View File

@@ -215,12 +215,8 @@
color: $msp-btn-link-toggle-off-font-color !important;
}
.msp-btn-link-toggle-on:hover {
color: $msp-btn-link-toggle-on-hover-font-color !important;
}
.msp-btn-link-toggle-off:hover {
color: $msp-btn-link-toggle-off-hover-font-color !important;
.msp-btn-link-toggle-off:hover, .msp-btn-link-toggle-on:hover {
color: $hover-font-color !important;
}
@mixin msp-btn($name, $font, $bg) {

View File

@@ -1,5 +1,4 @@
@use "sass:math";
@use "sass:color";
.msp-control-row {
position: relative;
@@ -333,7 +332,7 @@
.msp-help:hover span {
display: inline-block;
background: linear-gradient($default-background, color.change($default-background, $alpha: 0.8));
background: linear-gradient($default-background, change-color($default-background, $alpha: 0.8));
}
.msp-help-text {

View File

@@ -8,7 +8,6 @@
}
$sequence-select-height: 24px;
.msp-sequence-select {
position: relative;
height: $sequence-select-height;
@@ -85,11 +84,6 @@ $sequence-select-height: 24px;
margin: 0em 0.2em 0em 0em;
}
.msp-sequence-residue-focused {
font-weight: bold;
text-decoration: underline;
}
.msp-sequence-label {
color: $sequence-number-color;
font-size: 90%;
@@ -97,7 +91,7 @@ $sequence-select-height: 24px;
padding-bottom: 1em;
padding-left: 0.2em;
}
.msp-sequence-number {
color: $sequence-number-color;
word-break: keep-all;

View File

@@ -21,11 +21,9 @@
}
::-webkit-scrollbar-thumb {
border-radius: 10px;
border-radius: 0;
//-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.9);
background-color: color-increase-contrast($control-background, 16%);
border: solid 1px transparent;
background-clip: content-box;
background-color: color-lower-contrast($control-background, 8%);
}
@import 'components/controls-base';

View File

@@ -1,5 +1,3 @@
@use "sass:color";
// measures
$control-label-width: 110px;
@@ -55,8 +53,6 @@ $msp-form-control-background: color-lower-contrast($default-background, 2.5%);
$msp-btn-link-font-color: $font-color;
$msp-btn-link-toggle-on-font-color: $font-color;
$msp-btn-link-toggle-off-font-color: color-lower-contrast($font-color, 33%);
$msp-btn-link-toggle-on-hover-font-color: $hover-font-color;
$msp-btn-link-toggle-off-hover-font-color: color.adjust(color-lower-contrast($hover-font-color, 25%), $saturation: -30%, $space: hsl);
// used for "actions" -- i.e. + in selection
$msp-btn-remove-font-color: $font-color;

View File

@@ -1,9 +1,8 @@
/**
* Copyright (c) 2018-2025 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 Ventura Rivera <venturaxrivera@gmail.com>
*/
@@ -12,8 +11,6 @@ import { CreateVolumeStreamingBehavior } from '../mol-plugin/behavior/dynamic/vo
import { DefaultPluginSpec, PluginSpec } from '../mol-plugin/spec';
import { StateAction, StateTransformer } from '../mol-state';
import { VolumeStreamingCustomControls } from './custom/volume';
import { Loci } from '../mol-model/loci';
import { SequenceViewMode } from './sequence';
export { PluginUISpec };
@@ -30,28 +27,9 @@ interface PluginUISpec extends PluginSpec {
},
sequenceViewer?: {
view?: React.ComponentClass | React.FC
modeOptions?: SequenceViewMode[],
defaultMode?: SequenceViewMode,
}
hideTaskOverlay?: boolean,
disableDragOverlay?: boolean,
selectionTools?: {
controls?: React.ComponentClass | React.FC,
granularityOptions?: Loci.Granularity[],
hide?: {
granularity?: boolean,
union?: boolean,
subtract?: boolean,
intersect?: boolean,
set?: boolean,
theme?: boolean,
componentAdd?: boolean,
componentRemove?: boolean,
undo?: boolean,
help?: boolean,
cancel?: boolean,
},
},
},
}

View File

@@ -1,21 +1,19 @@
/**
* Copyright (c) 2022-2025 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>
* @author Adam Midlik <midlik@gmail.com>
*/
import { PostprocessingParams } from '../../mol-canvas3d/passes/postprocessing';
import { PresetStructureRepresentations } from '../../mol-plugin-state/builder/structure/representation-preset';
import { PluginConfig } from '../../mol-plugin/config';
import { PluginContext } from '../../mol-plugin/context';
import { Color } from '../../mol-util/color';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { CollapsableControls, PurePluginUIComponent } from '../base';
import { Button } from '../controls/common';
import { MagicWandSvg } from '../controls/icons';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { PostprocessingParams } from '../../mol-canvas3d/passes/postprocessing';
import { PluginConfig } from '../../mol-plugin/config';
import { StructureComponentManager } from '../../mol-plugin-state/manager/structure/component';
export class StructureQuickStylesControls extends CollapsableControls {
defaultState() {
@@ -33,118 +31,65 @@ export class StructureQuickStylesControls extends CollapsableControls {
}
}
export class QuickStyles extends PurePluginUIComponent {
async default() {
const { structures } = this.plugin.managers.structure.hierarchy.selection;
const preset = this.plugin.config.get(PluginConfig.Structure.DefaultRepresentationPreset) || PresetStructureRepresentations.auto.id;
const provider = this.plugin.builders.structure.representation.resolveProvider(preset);
await this.plugin.managers.structure.component.applyPreset(structures, provider);
type PresetName = 'default' | 'cartoon' | 'spacefill' | 'surface';
type StyleName = 'default' | 'illustrative';
this.plugin.managers.structure.component.setOptions(PD.getDefaultValues(StructureComponentManager.OptionsParams));
interface QuickStylesState {
busy: boolean,
style: StyleName,
}
export class QuickStyles extends PurePluginUIComponent<{}, QuickStylesState> {
state: QuickStylesState = { busy: false, style: 'default' };
async applyRepresentation(preset: PresetName) {
this.setState({ busy: true });
await applyRepresentationPreset(this.plugin, preset);
await applyStyle(this.plugin, this.state.style); // reapplying current style is desired because some presets come with weird params (namely spacefill comes with ignoreLight:true)
this.setState({ busy: false });
}
async applyStyle(style: StyleName) {
this.setState({ busy: true });
await applyStyle(this.plugin, style);
this.setState({ busy: false, style });
}
render() {
return <>
<NoncollapsableGroup title='Apply Representation'>
<div className='msp-flex-row'>
<Button title='Applies default representation preset (depends on structure size)'
onClick={() => this.applyRepresentation('default')} disabled={this.state.busy} >
Default
</Button>
<Button title='Applies cartoon polymer and ball-and-stick ligand representation preset'
onClick={() => this.applyRepresentation('cartoon')} disabled={this.state.busy} >
Cartoon
</Button>
<Button title='Applies spacefill representation preset'
onClick={() => this.applyRepresentation('spacefill')} disabled={this.state.busy} >
Spacefill
</Button>
<Button title='Applies molecular surface representation preset'
onClick={() => this.applyRepresentation('surface')} disabled={this.state.busy} >
Surface
</Button>
</div>
</NoncollapsableGroup>
<NoncollapsableGroup title='Apply Style'>
<div className='msp-flex-row'>
<Button title='Applies default appearance (no outline, no ignore-light)'
onClick={() => this.applyStyle('default')} disabled={this.state.busy} >
Default
</Button>
<Button title='Applies illustrative appearance (outline, ignore-light)'
onClick={() => this.applyStyle('illustrative')} disabled={this.state.busy} >
Illustrative
</Button>
</div>
</NoncollapsableGroup>
</>;
}
}
/** Visually imitates `ControlGroup` but is always expanded */
function NoncollapsableGroup(props: { title: string, children: any }): JSX.Element {
return <div className='msp-control-group-wrapper'>
<div className='msp-control-group-header'><div><b>{props.title}</b></div></div>
{props.children}
</div>;
}
async function applyRepresentationPreset(plugin: PluginContext, preset: PresetName) {
const { structures } = plugin.managers.structure.hierarchy.selection;
switch (preset) {
case 'default':
const defaultPreset = plugin.config.get(PluginConfig.Structure.DefaultRepresentationPreset) || PresetStructureRepresentations.auto.id;
const provider = plugin.builders.structure.representation.resolveProvider(defaultPreset);
await plugin.managers.structure.component.applyPreset(structures, provider);
break;
case 'spacefill':
await plugin.managers.structure.component.applyPreset(structures, PresetStructureRepresentations.illustrative);
break;
case 'cartoon':
await plugin.managers.structure.component.applyPreset(structures, PresetStructureRepresentations['polymer-and-ligand']);
break;
case 'surface':
await plugin.managers.structure.component.applyPreset(structures, PresetStructureRepresentations['molecular-surface']);
break;
}
}
async function applyStyle(plugin: PluginContext, style: StyleName) {
if (style === 'default') {
await plugin.managers.structure.component.setOptions({ ...plugin.managers.structure.component.state.options, ignoreLight: false });
if (plugin.canvas3d) {
if (this.plugin.canvas3d) {
const p = PD.getDefaultValues(PostprocessingParams);
plugin.canvas3d.setProps({
postprocessing: { outline: p.outline, occlusion: p.occlusion, shadow: p.shadow }
this.plugin.canvas3d.setProps({
postprocessing: { outline: p.outline, occlusion: p.occlusion }
});
}
}
if (style === 'illustrative') {
await plugin.managers.structure.component.setOptions({ ...plugin.managers.structure.component.state.options, ignoreLight: true });
async illustrative() {
const { structures } = this.plugin.managers.structure.hierarchy.selection;
await this.plugin.managers.structure.component.applyPreset(structures, PresetStructureRepresentations.illustrative);
if (plugin.canvas3d) {
const pp = plugin.canvas3d.props.postprocessing;
plugin.canvas3d.setProps({
if (this.plugin.canvas3d) {
this.plugin.canvas3d.setProps({
postprocessing: {
outline: {
name: 'on',
params: {
scale: 1,
color: Color(0x000000),
threshold: 0.25,
includeTransparent: true,
}
},
occlusion: {
name: 'on',
params: {
multiScale: { name: 'off', params: {} },
radius: 5,
bias: 0.8,
blurKernelSize: 15,
blurDepthBias: 0.5,
samples: 32,
resolutionScale: 1,
color: Color(0x000000),
transparentThreshold: 0.4,
}
},
shadow: { name: 'off', params: {} },
}
});
}
}
async stylized() {
this.plugin.managers.structure.component.setOptions({ ...this.plugin.managers.structure.component.state.options, ignoreLight: true });
if (this.plugin.canvas3d) {
const pp = this.plugin.canvas3d.props.postprocessing;
this.plugin.canvas3d.setProps({
postprocessing: {
outline: {
name: 'on',
@@ -178,4 +123,18 @@ async function applyStyle(plugin: PluginContext, style: StyleName) {
});
}
}
}
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 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>;
}
}

View File

@@ -5,7 +5,6 @@
* @author David Sehnal <david.sehnal@gmail.com>
* @author Jason Pattle <jpattle.exscientia.co.uk>
* @author Ludovic Autin <ludovic.autin@gmail.com>
* @author Ventura Rivera <venturaxrivera@gmail.com>
*/
import * as React from 'react';
@@ -62,8 +61,6 @@ interface StructureSelectionActionsControlsState {
action?: StructureSelectionModifier | 'theme' | 'add-component' | 'help',
helper?: SelectionHelperType,
structureSelectionParams?: typeof StructureSelectionParams,
}
const ActionHeader = new Map<StructureSelectionModifier, string>([
@@ -81,8 +78,6 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
isEmpty: true,
isBusy: false,
canUndo: false,
structureSelectionParams: StructureSelectionParams,
};
componentDidMount() {
@@ -107,20 +102,6 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
this.subscribe(this.plugin.state.data.events.historyUpdated, ({ state }) => {
this.setState({ canUndo: state.canUndo });
});
// Update structureSelectionParams state if there are custom-defined granularityOptions
const granularityOptions = this.plugin.spec.components?.selectionTools?.granularityOptions;
if (granularityOptions) {
const granularitySet = new Set((granularityOptions));
const structureSelectionParams = {
...StructureSelectionParams,
granularity: {
...StructureSelectionParams.granularity,
options: StructureSelectionParams.granularity.options.filter(([firstItem]) => granularitySet.has(firstItem)),
},
};
this.setState({ structureSelectionParams: structureSelectionParams });
}
}
get isDisabled() {
@@ -237,7 +218,6 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
render() {
const granularity = this.plugin.managers.interactivity.props.granularity;
const hide = this.plugin.spec.components?.selectionTools?.hide;
const undoTitle = this.state.canUndo
? `Undo ${this.plugin.state.data.latestUndoLabel}`
: 'Some mistakes of the past can be undone.';
@@ -283,19 +263,19 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
return <>
<div className='msp-flex-row' style={{ background: 'none' }}>
{(!hide?.granularity) && <PureSelectControl title={`Picking Level for selecting and highlighting`} param={this.state.structureSelectionParams.granularity} name='granularity' value={granularity} onChange={this.setGranuality} isDisabled={this.isDisabled} />}
{(!hide?.union) && <ToggleButton icon={UnionSvg} title={`${ActionHeader.get('add')}. Hold shift key to keep menu open.`} toggle={this.toggleAdd} isSelected={this.state.action === 'add'} disabled={this.isDisabled} />}
{(!hide?.subtract) && <ToggleButton icon={SubtractSvg} title={`${ActionHeader.get('remove')}. Hold shift key to keep menu open.`} toggle={this.toggleRemove} isSelected={this.state.action === 'remove'} disabled={this.isDisabled} />}
{(!hide?.intersect) && <ToggleButton icon={IntersectSvg} title={`${ActionHeader.get('intersect')}. Hold shift key to keep menu open.`} toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} />}
{(!hide?.set) && <ToggleButton icon={SetSvg} title={`${ActionHeader.get('set')}. Hold shift key to keep menu open.`} toggle={this.toggleSet} isSelected={this.state.action === 'set'} disabled={this.isDisabled} />}
<PureSelectControl title={`Picking Level for selecting and highlighting`} param={StructureSelectionParams.granularity} name='granularity' value={granularity} onChange={this.setGranuality} isDisabled={this.isDisabled} />
<ToggleButton icon={UnionSvg} title={`${ActionHeader.get('add')}. Hold shift key to keep menu open.`} toggle={this.toggleAdd} isSelected={this.state.action === 'add'} disabled={this.isDisabled} />
<ToggleButton icon={SubtractSvg} title={`${ActionHeader.get('remove')}. Hold shift key to keep menu open.`} toggle={this.toggleRemove} isSelected={this.state.action === 'remove'} disabled={this.isDisabled} />
<ToggleButton icon={IntersectSvg} title={`${ActionHeader.get('intersect')}. Hold shift key to keep menu open.`} toggle={this.toggleIntersect} isSelected={this.state.action === 'intersect'} disabled={this.isDisabled} />
<ToggleButton icon={SetSvg} title={`${ActionHeader.get('set')}. Hold shift key to keep menu open.`} toggle={this.toggleSet} isSelected={this.state.action === 'set'} disabled={this.isDisabled} />
{(!hide?.theme) && <ToggleButton icon={BrushSvg} title='Apply Theme to Selection' toggle={this.toggleTheme} isSelected={this.state.action === 'theme'} disabled={this.isDisabled} style={{ marginLeft: '10px' }} />}
{(!hide?.componentAdd) && <ToggleButton icon={CubeOutlineSvg} title='Create Component of Selection with Representation' toggle={this.toggleAddComponent} isSelected={this.state.action === 'add-component'} disabled={this.isDisabled} />}
{(!hide?.componentRemove) && <IconButton svg={RemoveSvg} title='Remove/subtract Selection from all Components' onClick={this.subtract} disabled={this.isDisabled} />}
{(!hide?.undo) && <IconButton svg={RestoreSvg} onClick={this.undo} disabled={!this.state.canUndo || this.isDisabled} title={undoTitle} />}
<ToggleButton icon={BrushSvg} title='Apply Theme to Selection' toggle={this.toggleTheme} isSelected={this.state.action === 'theme'} disabled={this.isDisabled} style={{ marginLeft: '10px' }} />
<ToggleButton icon={CubeOutlineSvg} title='Create Component of Selection with Representation' toggle={this.toggleAddComponent} isSelected={this.state.action === 'add-component'} disabled={this.isDisabled} />
<IconButton svg={RemoveSvg} title='Remove/subtract Selection from all Components' onClick={this.subtract} disabled={this.isDisabled} />
<IconButton svg={RestoreSvg} onClick={this.undo} disabled={!this.state.canUndo || this.isDisabled} title={undoTitle} />
{(!hide?.help) && <ToggleButton icon={HelpOutlineSvg} title='Show/hide help' toggle={this.toggleHelp} style={{ marginLeft: '10px' }} isSelected={this.state.action === 'help'} />}
{((!hide?.cancel) && this.plugin.config.get(PluginConfig.Viewport.ShowSelectionMode)) && (<IconButton svg={CancelOutlinedSvg} title='Turn selection mode off' onClick={this.turnOff} />)}
<ToggleButton icon={HelpOutlineSvg} title='Show/hide help' toggle={this.toggleHelp} style={{ marginLeft: '10px' }} isSelected={this.state.action === 'help'} />
{this.plugin.config.get(PluginConfig.Viewport.ShowSelectionMode) && (<IconButton svg={CancelOutlinedSvg} title='Turn selection mode off' onClick={this.turnOff} />)}
</div>
{children}
</>;

View File

@@ -28,7 +28,7 @@ export const DefaultClickResetCameraOnEmpty = Binding([
export const DefaultClickResetCameraOnEmptySelectMode = Binding([
Trigger(B.Flag.Secondary, M.create()),
Trigger(B.Flag.Primary, M.create({ control: true }))
], 'Reset camera focus (Selection Mode)', 'Click on nothing using ${triggers}');
], 'Reset camera focus', 'Click on nothing using ${triggers}');
type FocusLociBindings = {
clickCenterFocus: Binding
@@ -45,7 +45,7 @@ export const DefaultFocusLociBindings: FocusLociBindings = {
clickCenterFocusSelectMode: Binding([
Trigger(B.Flag.Secondary, M.create()),
Trigger(B.Flag.Primary, M.create({ control: true }))
], 'Camera center and focus (Selection Mode)', 'Click element using ${triggers}'),
], 'Camera center and focus', 'Click element using ${triggers}'),
clickResetCameraOnEmpty: DefaultClickResetCameraOnEmpty,
clickResetCameraOnEmptySelectMode: DefaultClickResetCameraOnEmptySelectMode,
};
@@ -158,7 +158,7 @@ const DefaultCameraControlsBindings = {
keyRockAnimation: Binding([Key('O')], 'Rock Animation', 'Press ${triggers}'),
keyToggleFlyMode: Binding([Key('Space', M.create({ shift: true }))], 'Toggle Fly Mode', 'Press ${triggers}'),
keyResetView: Binding([Key('T')], 'Reset View', 'Press ${triggers}'),
keyGlobalIllumination: Binding([Key('G')], 'Global Illumination', 'Press ${triggers}'),
keyGlobalIllumination: Binding([Key('G')], 'Gobal Illumination', 'Press ${triggers}'),
};
const CameraControlsParams = {
bindings: PD.Value(DefaultCameraControlsBindings, { isHidden: true }),

View File

@@ -1,10 +1,9 @@
/**
* Copyright (c) 2018-2025 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 Jason Pattle <jpattle.exscientia.co.uk>
* @author Adam Midlik <midlik@gmail.com>
*/
import { MarkerAction } from '../../../mol-util/marker-action';
@@ -96,9 +95,9 @@ export const HighlightLoci = PluginBehavior.create({
export const DefaultSelectLociBindings = {
clickSelect: Binding.Empty,
clickToggleExtend: Binding([Trigger(B.Flag.Primary, M.create({ shift: true }))], 'Toggle extended selection', 'Click on element using ${triggers} to extend selection along polymer'),
clickSelectOnly: Binding.Empty,
clickToggle: Binding([Trigger(B.Flag.Primary, M.create())], 'Toggle selection', 'Click on element using ${triggers}'),
clickToggleExtend: Binding([Trigger(B.Flag.Primary, M.create({ shift: true }))], 'Toggle extended selection', 'Click on element using ${triggers} to extend selection along polymer'),
clickDeselect: Binding.Empty,
clickDeselectAllOnEmpty: Binding([Trigger(B.Flag.Primary, M.create())], 'Deselect all', 'Click on nothing using ${triggers}'),
};
@@ -243,21 +242,14 @@ export const DefaultFocusLociBindings = {
Trigger(B.Flag.Primary, M.create()),
], 'Representation Focus', 'Click element using ${triggers}'),
clickFocusAdd: Binding([
Trigger(B.Flag.Primary, M.create({ control: true })),
Trigger(B.Flag.Primary, M.create({ meta: true })),
], 'Representation Focus Add', 'Click element using ${triggers}'),
clickFocusExtend: Binding([
Trigger(B.Flag.Primary, M.create({ shift: true })),
], 'Representation Focus Extend', 'Click on element using ${triggers}'),
], 'Representation Focus Add', 'Click element using ${triggers}'),
clickFocusSelectMode: Binding([
// default is empty
], 'Representation Focus (Selection Mode)', 'Click element using ${triggers}'),
], 'Representation Focus', 'Click element using ${triggers}'),
clickFocusAddSelectMode: Binding([
// default is empty
], 'Representation Focus Add (Selection Mode)', 'Click element using ${triggers}'),
clickFocusExtendSelectMode: Binding([
// default is empty
], 'Representation Focus Extend (Selection Mode)', 'Click on element using ${triggers}'),
], 'Representation Focus Add', 'Click element using ${triggers}'),
};
const FocusLociParams = {
bindings: PD.Value(DefaultFocusLociBindings, { isHidden: true }),
@@ -270,19 +262,11 @@ export const FocusLoci = PluginBehavior.create<FocusLociProps>({
ctor: class extends PluginBehavior.Handler<FocusLociProps> {
register(): void {
this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, button, modifiers }) => {
const { clickFocus, clickFocusAdd, clickFocusExtend, clickFocusSelectMode, clickFocusAddSelectMode, clickFocusExtendSelectMode } = this.params.bindings;
const { clickFocus, clickFocusAdd, clickFocusSelectMode, clickFocusAddSelectMode } = this.params.bindings;
const binding = this.ctx.selectionMode ? clickFocusSelectMode : clickFocus;
const matched = Binding.match(binding, button, modifiers);
const bindingAdd = this.ctx.selectionMode ? clickFocusAddSelectMode : clickFocusAdd;
const matchedAdd = Binding.match(bindingAdd, button, modifiers);
const bindingExtend = this.ctx.selectionMode ? clickFocusExtendSelectMode : clickFocusExtend;
const matchedExtend = Binding.match(bindingExtend, button, modifiers);
if (!matched && !matchedAdd && !matchedExtend) return;
// Support snapshot key property, in which case ignore the focus functionality
const snapshotKey = current.repr?.props?.snapshotKey?.trim() ?? '';
if (!this.ctx.selectionMode && matched && snapshotKey) {
@@ -294,6 +278,10 @@ export const FocusLoci = PluginBehavior.create<FocusLociProps>({
const { granularity } = this.ctx.managers.interactivity.props;
if (granularity !== 'residue' && granularity !== 'element') return;
const bindingAdd = this.ctx.selectionMode ? clickFocusAddSelectMode : clickFocusAdd;
const matchedAdd = Binding.match(bindingAdd, button, modifiers);
if (!matched && !matchedAdd) return;
const loci = Loci.normalize(current.loci, 'residue');
const entry = this.ctx.managers.structure.focus.current;
if (entry && Loci.areEqual(entry.loci, loci)) {
@@ -302,13 +290,9 @@ export const FocusLoci = PluginBehavior.create<FocusLociProps>({
if (matched) {
this.ctx.managers.structure.focus.setFromLoci(loci);
} else {
if (matchedExtend) {
this.ctx.managers.structure.focus.extendFromLoci(loci);
} else { // matchedAdd
this.ctx.managers.structure.focus.toggleFromLoci(loci);
}
this.ctx.managers.structure.focus.addFromLoci(loci);
// focus-add and focus-extend is not handled in camera behavior, doing it here
// focus-add is not handled in camera behavior, doing it here
const current = this.ctx.managers.structure.focus.current?.loci;
if (current) this.ctx.managers.camera.focusLoci(current);
}
@@ -318,4 +302,4 @@ export const FocusLoci = PluginBehavior.create<FocusLociProps>({
},
params: () => FocusLociParams,
display: { name: 'Representation Focus Loci on Canvas' }
});
});

View File

@@ -27,7 +27,6 @@ import { SetUtils } from '../mol-util/set';
import { cantorPairing } from '../mol-data/util';
import { Substance } from '../mol-theme/substance';
import { Emissive } from '../mol-theme/emissive';
import { Location } from '../mol-model/location';
export type RepresentationProps = { [k: string]: any }
@@ -58,13 +57,12 @@ export interface RepresentationProvider<D = any, P extends PD.Params = any, S ex
}
readonly getData?: (data: D, props: PD.Values<P>) => D
readonly mustRecreate?: (oldProps: PD.Values<P>, newProps: PD.Values<P>) => boolean
readonly locationKinds?: ReadonlyArray<Location['kind']>
}
export namespace RepresentationProvider {
export type ParamValues<R extends RepresentationProvider<any, any, any>> = R extends RepresentationProvider<any, infer P, any> ? PD.Values<P> : never;
export function getDefaultParams<R extends RepresentationProvider<D, any, any>, D>(r: R, ctx: ThemeRegistryContext, data: D) {
export function getDetaultParams<R extends RepresentationProvider<D, any, any>, D>(r: R, ctx: ThemeRegistryContext, data: D) {
return PD.getDefaultValues(r.getParams(ctx, data));
}
}

View File

@@ -83,22 +83,24 @@ function createPolymerTraceMesh(ctx: VisualContext, unit: Unit, structure: Struc
const hasRoundCap = isHelix && tubularHelices && roundCap;
let segmentCount = linearSegments;
if (v.initial) {
segmentCount = Math.max(Math.round(linearSegments * shift), 1);
const offset = linearSegments - segmentCount;
curvePoints.copyWithin(0, offset * 3);
binormalVectors.copyWithin(0, offset * 3);
normalVectors.copyWithin(0, offset * 3);
Vec3.fromArray(tmpV1, curvePoints, 3);
Vec3.normalize(tmpV1, Vec3.sub(tmpV1, v.p2, tmpV1));
Vec3.scaleAndAdd(tmpV1, v.p2, tmpV1, w1 * OverhangFactor);
Vec3.toArray(tmpV1, curvePoints, 0);
} else if (v.final) {
segmentCount = Math.max(Math.round(linearSegments * (1 - shift)), 1);
Vec3.fromArray(tmpV1, curvePoints, segmentCount * 3 - 3);
Vec3.normalize(tmpV1, Vec3.sub(tmpV1, v.p2, tmpV1));
Vec3.scaleAndAdd(tmpV1, v.p2, tmpV1, w1 * OverhangFactor);
Vec3.toArray(tmpV1, curvePoints, segmentCount * 3);
if (!hasRoundCap) {
if (v.initial) {
segmentCount = Math.max(Math.round(linearSegments * shift), 1);
const offset = linearSegments - segmentCount;
curvePoints.copyWithin(0, offset * 3);
binormalVectors.copyWithin(0, offset * 3);
normalVectors.copyWithin(0, offset * 3);
Vec3.fromArray(tmpV1, curvePoints, 3);
Vec3.normalize(tmpV1, Vec3.sub(tmpV1, v.p2, tmpV1));
Vec3.scaleAndAdd(tmpV1, v.p2, tmpV1, w1 * OverhangFactor);
Vec3.toArray(tmpV1, curvePoints, 0);
} else if (v.final) {
segmentCount = Math.max(Math.round(linearSegments * (1 - shift)), 1);
Vec3.fromArray(tmpV1, curvePoints, segmentCount * 3 - 3);
Vec3.normalize(tmpV1, Vec3.sub(tmpV1, v.p2, tmpV1));
Vec3.scaleAndAdd(tmpV1, v.p2, tmpV1, w1 * OverhangFactor);
Vec3.toArray(tmpV1, curvePoints, segmentCount * 3);
}
}
if (v.initial === true && v.final === true) {

View File

@@ -281,7 +281,7 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
const isHelixNext3 = isHelixSS(ssNext3);
// handle positions for tubular helices
if (this.helixOrientationCenters && !(isHelix && value.secStrucFirst && value.secStrucLast)) {
if (this.helixOrientationCenters && !(value.secStrucFirst && value.secStrucLast)) {
if (isHelix !== isHelixPrev1) {
if (isHelix) {
Vec3.copy(this.p0, this.p3);
@@ -353,7 +353,7 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
this.setFromToVector(this.d34, residueIndexNext2, ssNext2);
}
const helixFlag = isHelix && this.helixOrientationCenters;
const helixFlag = isHelix && this.helixOrientationCenters && !(value.secStrucFirst && value.secStrucLast);
// extend termini
const f = 1.5;

View File

@@ -172,7 +172,7 @@ export function DirectVolumeVisual(materialId: number): VolumeVisual<DirectVolum
geometryUtils: DirectVolume.Utils,
dispose: (geometry: DirectVolume) => {
geometry.gridTexture.ref.value.destroy();
},
}
}, materialId);
}
@@ -189,6 +189,5 @@ export const DirectVolumeRepresentationProvider = VolumeRepresentationProvider({
defaultValues: PD.getDefaultValues(DirectVolumeParams),
defaultColorTheme: { name: 'volume-value' },
defaultSizeTheme: { name: 'uniform' },
locationKinds: ['position-location', 'direct-location'],
isApplicable: (volume: Volume) => !Volume.isEmpty(volume) && !Volume.Segmentation.get(volume)
});

View File

@@ -1,274 +0,0 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { Grid, Volume } from '../../mol-model/volume';
import { VisualContext } from '../visual';
import { Theme, ThemeRegistryContext } from '../../mol-theme/theme';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider, VolumeKey } from './representation';
import { VisualUpdateState } from '../util';
import { RepresentationContext, RepresentationParamsGetter, Representation } from '../representation';
import { PickingId } from '../../mol-geo/geometry/picking';
import { EmptyLoci, Loci } from '../../mol-model/loci';
import { Interval } from '../../mol-data/int';
import { createVolumeCellLocationIterator, eachVolumeLoci } from './util';
import { WebGLContext } from '../../mol-gl/webgl/context';
import { BaseGeometry } from '../../mol-geo/geometry/base';
import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { SpheresBuilder } from '../../mol-geo/geometry/spheres/spheres-builder';
import { Vec3 } from '../../mol-math/linear-algebra/3d/vec3';
import { addSphere } from '../../mol-geo/geometry/mesh/builder/sphere';
import { sphereVertexCount } from '../../mol-geo/primitive/sphere';
import { Points } from '../../mol-geo/geometry/points/points';
import { PointsBuilder } from '../../mol-geo/geometry/points/points-builder';
export const VolumeDotParams = {
isoValue: Volume.IsoValueParam,
};
export type VolumeDotParams = typeof VolumeDotParams
export type VolumeDotProps = PD.Values<VolumeDotParams>
//
export const VolumeSphereParams = {
...Spheres.Params,
...Mesh.Params,
...VolumeDotParams,
tryUseImpostor: PD.Boolean(true),
detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
};
export type VolumeSphereParams = typeof VolumeSphereParams
export type VolumeSphereProps = PD.Values<VolumeSphereParams>
export function VolumeSphereVisual(materialId: number, volume: Volume, key: number, props: PD.Values<VolumeSphereParams>, webgl?: WebGLContext) {
return props.tryUseImpostor && webgl && webgl.extensions.fragDepth && webgl.extensions.textureFloat
? VolumeSphereImpostorVisual(materialId)
: VolumeSphereMeshVisual(materialId);
}
export function VolumeSphereImpostorVisual(materialId: number): VolumeVisual<VolumeSphereParams> {
return VolumeVisual<Spheres, VolumeSphereParams>({
defaultProps: PD.getDefaultValues(VolumeSphereParams),
createGeometry: createVolumeSphereImpostor,
createLocationIterator: createVolumeCellLocationIterator,
getLoci: getDotLoci,
eachLocation: eachDot,
setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<VolumeSphereParams>, currentProps: PD.Values<VolumeSphereParams>) => {
state.createGeometry = (
!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)
);
},
geometryUtils: Spheres.Utils,
mustRecreate: (volumekey: VolumeKey, props: PD.Values<VolumeSphereParams>, webgl?: WebGLContext) => {
return !props.tryUseImpostor || !webgl;
}
}, materialId);
}
export function VolumeSphereMeshVisual(materialId: number): VolumeVisual<VolumeSphereParams> {
return VolumeVisual<Mesh, VolumeSphereParams>({
defaultProps: PD.getDefaultValues(VolumeSphereParams),
createGeometry: createVolumeSphereMesh,
createLocationIterator: createVolumeCellLocationIterator,
getLoci: getDotLoci,
eachLocation: eachDot,
setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<VolumeSphereParams>, currentProps: PD.Values<VolumeSphereParams>) => {
state.createGeometry = (
!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats) ||
newProps.sizeFactor !== currentProps.sizeFactor ||
newProps.detail !== currentProps.detail
);
},
geometryUtils: Mesh.Utils,
mustRecreate: (volumekey: VolumeKey, props: PD.Values<VolumeSphereParams>, webgl?: WebGLContext) => {
return props.tryUseImpostor && !!webgl;
}
}, materialId);
}
export function createVolumeSphereImpostor(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: VolumeSphereProps, spheres?: Spheres): Spheres {
const { cells: { space, data }, stats } = volume.grid;
const gridToCartn = Grid.getGridToCartesianTransform(volume.grid);
const isoVal = Volume.IsoValue.toAbsolute(props.isoValue, stats).absoluteValue;
const p = Vec3();
const [xn, yn, zn] = space.dimensions;
const count = Math.ceil((xn * yn * zn) / 10);
const builder = SpheresBuilder.create(count, Math.ceil(count / 2), spheres);
for (let z = 0; z < zn; ++z) {
for (let y = 0; y < yn; ++y) {
for (let x = 0; x < xn; ++x) {
if (space.get(data, x, y, z) < isoVal) continue;
Vec3.set(p, x, y, z);
Vec3.transformMat4(p, p, gridToCartn);
builder.add(p[0], p[1], p[2], space.dataOffset(x, y, z));
}
}
}
const s = builder.getSpheres();
s.setBoundingSphere(Volume.Isosurface.getBoundingSphere(volume, props.isoValue));
return s;
}
export function createVolumeSphereMesh(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: VolumeSphereProps, mesh?: Mesh): Mesh {
const { detail, sizeFactor } = props;
const { cells: { space, data }, stats } = volume.grid;
const gridToCartn = Grid.getGridToCartesianTransform(volume.grid);
const isoVal = Volume.IsoValue.toAbsolute(props.isoValue, stats).absoluteValue;
const p = Vec3();
const [xn, yn, zn] = space.dimensions;
const count = (xn * yn * zn) / 10;
const vertexCount = count * sphereVertexCount(detail);
const builderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh);
const l = Volume.Cell.Location(volume);
const themeSize = theme.size.size;
for (let z = 0; z < zn; ++z) {
for (let y = 0; y < yn; ++y) {
for (let x = 0; x < xn; ++x) {
if (space.get(data, x, y, z) < isoVal) continue;
Vec3.set(p, x, y, z);
Vec3.transformMat4(p, p, gridToCartn);
builderState.currentGroup = space.dataOffset(x, y, z);
l.cell = builderState.currentGroup as Volume.CellIndex;
const size = themeSize(l);
addSphere(builderState, p, size * sizeFactor, detail);
}
}
}
const m = MeshBuilder.getMesh(builderState);
m.setBoundingSphere(Volume.Isosurface.getBoundingSphere(volume, props.isoValue));
return m;
}
//
export const VolumePointParams = {
...Points.Params,
...VolumeDotParams,
};
export type VolumePointParams = typeof VolumePointParams
export type VolumePointProps = PD.Values<VolumePointParams>
export function VolumePointVisual(materialId: number): VolumeVisual<VolumePointParams> {
return VolumeVisual<Points, VolumePointParams>({
defaultProps: PD.getDefaultValues(VolumePointParams),
createGeometry: createVolumePoint,
createLocationIterator: createVolumeCellLocationIterator,
getLoci: getDotLoci,
eachLocation: eachDot,
setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<VolumePointParams>, currentProps: PD.Values<VolumePointParams>) => {
state.createGeometry = (
!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)
);
},
geometryUtils: Points.Utils,
}, materialId);
}
export function createVolumePoint(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: VolumePointProps, points?: Points): Points {
const { cells: { space, data }, stats } = volume.grid;
const gridToCartn = Grid.getGridToCartesianTransform(volume.grid);
const isoVal = Volume.IsoValue.toAbsolute(props.isoValue, stats).absoluteValue;
const p = Vec3();
const [xn, yn, zn] = space.dimensions;
const count = Math.ceil((xn * yn * zn) / 10);
const builder = PointsBuilder.create(count, Math.ceil(count / 2), points);
for (let z = 0; z < zn; ++z) {
for (let y = 0; y < yn; ++y) {
for (let x = 0; x < xn; ++x) {
if (space.get(data, x, y, z) < isoVal) continue;
Vec3.set(p, x, y, z);
Vec3.transformMat4(p, p, gridToCartn);
builder.add(p[0], p[1], p[2], space.dataOffset(x, y, z));
}
}
}
const pt = builder.getPoints();
pt.setBoundingSphere(Volume.Isosurface.getBoundingSphere(volume, props.isoValue));
return pt;
}
//
function getLoci(volume: Volume, props: VolumeDotProps) {
return Volume.Isosurface.Loci(volume, props.isoValue);
}
function getDotLoci(pickingId: PickingId, volume: Volume, key: number, props: VolumeDotProps, id: number) {
const { objectId, groupId } = pickingId;
if (id === objectId) {
const granularity = Volume.PickingGranularity.get(volume);
if (granularity === 'volume') {
return Volume.Loci(volume);
} else if (granularity === 'object') {
return Volume.Isosurface.Loci(volume, props.isoValue);
} else {
return Volume.Cell.Loci(volume, Interval.ofSingleton(groupId as Volume.CellIndex));
}
}
return EmptyLoci;
}
function eachDot(loci: Loci, volume: Volume, key: number, props: VolumeDotProps, apply: (interval: Interval) => boolean) {
return eachVolumeLoci(loci, volume, { isoValue: props.isoValue }, apply);
}
//
const DotVisuals = {
'sphere': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, VolumeSphereParams>) => VolumeRepresentation('Dot sphere', ctx, getParams, VolumeSphereVisual, getLoci),
'point': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, VolumePointParams>) => VolumeRepresentation('Dot point', ctx, getParams, VolumePointVisual, getLoci),
};
export const DotParams = {
...VolumeSphereParams,
...VolumePointParams,
visuals: PD.MultiSelect(['sphere'], PD.objectToOptions(DotVisuals)),
bumpFrequency: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }, BaseGeometry.ShadingCategory),
};
export type DotParams = typeof DotParams
export function getDotParams(ctx: ThemeRegistryContext, volume: Volume) {
const p = PD.clone(DotParams);
p.isoValue = Volume.createIsoValueParam(Volume.IsoValue.relative(2), volume.grid.stats);
return p;
}
export type DotRepresentation = VolumeRepresentation<DotParams>
export function DotRepresentation(ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, DotParams>): DotRepresentation {
return Representation.createMulti('Dot', ctx, getParams, Representation.StateBuilder, DotVisuals as unknown as Representation.Def<Volume, DotParams>);
}
export const DotRepresentationProvider = VolumeRepresentationProvider({
name: 'dot',
label: 'Dot',
description: 'Displays dots of volumetric data.',
factory: DotRepresentation,
getParams: getDotParams,
defaultValues: PD.getDefaultValues(DotParams),
defaultColorTheme: { name: 'uniform' },
defaultSizeTheme: { name: 'uniform' },
isApplicable: (volume: Volume) => !Volume.isEmpty(volume) && !Volume.Segmentation.get(volume)
});

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -11,7 +11,6 @@ import { objectForEach } from '../../mol-util/object';
import { SliceRepresentationProvider } from './slice';
import { DirectVolumeRepresentationProvider } from './direct-volume';
import { SegmentRepresentationProvider } from './segment';
import { DotRepresentationProvider } from './dot';
export class VolumeRepresentationRegistry extends RepresentationRegistry<Volume, Representation.State> {
constructor() {
@@ -25,11 +24,10 @@ export class VolumeRepresentationRegistry extends RepresentationRegistry<Volume,
export namespace VolumeRepresentationRegistry {
export const BuiltIn = {
'direct-volume': DirectVolumeRepresentationProvider,
'dot': DotRepresentationProvider,
'isosurface': IsosurfaceRepresentationProvider,
'segment': SegmentRepresentationProvider,
'slice': SliceRepresentationProvider,
'direct-volume': DirectVolumeRepresentationProvider,
'segment': SegmentRepresentationProvider,
};
type _BuiltIn = typeof BuiltIn

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 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 Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -35,7 +35,6 @@ import { isPromiseLike } from '../../mol-util/type-helpers';
import { Substance } from '../../mol-theme/substance';
import { createMarkers } from '../../mol-geo/geometry/marker-data';
import { Emissive } from '../../mol-theme/emissive';
import { SizeTheme } from '../../mol-theme/size';
export type VolumeKey = { volume: Volume, key: number }
export interface VolumeVisual<P extends VolumeParams> extends Visual<VolumeKey, P> { }
@@ -110,26 +109,15 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
setUpdateState(updateState, volume, newProps, currentProps, newTheme, currentTheme);
if (!ColorTheme.areEqual(theme.color, currentTheme.color)) {
updateState.updateColor = true;
}
if (!ColorTheme.areEqual(theme.color, currentTheme.color)) updateState.updateColor = true;
if (!SizeTheme.areEqual(theme.size, currentTheme.size)) {
updateState.updateSize = true;
if (updateState.createGeometry) {
updateState.updateColor = true;
}
if (newProps.instanceGranularity !== currentProps.instanceGranularity) {
updateState.updateTransform = true;
}
if (updateState.updateSize && !('uSize' in renderObject!.values)) {
updateState.createGeometry = true;
}
if (updateState.createGeometry) {
updateState.updateColor = true;
updateState.updateSize = true;
}
}
function update(newGeometry?: G) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -9,7 +9,7 @@ import { Image } from '../../mol-geo/geometry/image/image';
import { ThemeRegistryContext, Theme } from '../../mol-theme/theme';
import { Grid, Volume } from '../../mol-model/volume';
import { VolumeVisual, VolumeRepresentation, VolumeRepresentationProvider } from './representation';
import { LocationIterator, PositionLocation } from '../../mol-geo/util/location-iterator';
import { LocationIterator } from '../../mol-geo/util/location-iterator';
import { VisualUpdateState } from '../util';
import { NullLocation } from '../../mol-model/location';
import { RepresentationContext, RepresentationParamsGetter } from '../representation';
@@ -23,20 +23,17 @@ import { Color } from '../../mol-util/color';
import { ColorTheme } from '../../mol-theme/color';
import { packIntToRGBArray } from '../../mol-util/number-packing';
import { eachVolumeLoci } from './util';
import { Vec3 } from '../../mol-math/linear-algebra/3d/vec3';
import { equalEps } from '../../mol-math/linear-algebra/3d/common';
export async function createImage(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: PD.Values<SliceParams>, image?: Image) {
const { dimension: { name: dim }, isoValue } = props;
const { cells: { space, data }, stats } = volume.grid;
const { min, max } = stats;
const isoVal = Volume.IsoValue.toAbsolute(isoValue, stats).absoluteValue;
const { space, data } = volume.grid.cells;
const { min, max } = volume.grid.stats;
const isoVal = Volume.IsoValue.toAbsolute(isoValue, volume.grid.stats).absoluteValue;
const isUniform = theme.color.granularity === 'uniform';
const color = 'color' in theme.color && theme.color.color
? theme.color.color
: () => Color(0xffffff);
// TODO more color themes
const color = 'color' in theme.color ? theme.color.color(NullLocation, false) : Color(0xffffff);
const [r, g, b] = Color.toRgbNormalized(color);
const {
width, height,
@@ -54,52 +51,17 @@ export async function createImage(ctx: VisualContext, volume: Volume, key: numbe
const imageArray = new Uint8Array(width * height * 4);
const groupArray = getPackedGroupArray(volume.grid, props);
const gridToCartn = Grid.getGridToCartesianTransform(volume.grid);
const l = PositionLocation(Vec3(), Vec3());
let v: (ix: number, iy: number, iz: number) => number;
if (equalEps(isoVal, stats.min, 0.001)) {
v = (_ix, _iy, _iz) => 255;
} else if (equalEps(isoVal, stats.max, 0.001)) {
v = (_ix, _iy, _iz) => 0;
} else {
v = (ix, iy, iz) => {
return (
(space.get(data, ix, iy, iz) >= isoVal ? 1 : 0) * 2 +
(space.get(data, ix + 1, iy, iz) >= isoVal ? 1 : 0) +
(space.get(data, ix - 1, iy, iz) >= isoVal ? 1 : 0) +
(space.get(data, ix, iy + 1, iz) >= isoVal ? 1 : 0) +
(space.get(data, ix, iy - 1, iz) >= isoVal ? 1 : 0) +
(space.get(data, ix, iy, iz + 1) >= isoVal ? 1 : 0) +
(space.get(data, ix, iy, iz - 1) >= isoVal ? 1 : 0) +
(space.get(data, ix + 1, iy + 1, iz + 1) >= isoVal ? 1 : 0) +
(space.get(data, ix - 1, iy + 1, iz + 1) >= isoVal ? 1 : 0) +
(space.get(data, ix + 1, iy - 1, iz + 1) >= isoVal ? 1 : 0) +
(space.get(data, ix + 1, iy + 1, iz - 1) >= isoVal ? 1 : 0) +
(space.get(data, ix - 1, iy - 1, iz + 1) >= isoVal ? 1 : 0) +
(space.get(data, ix - 1, iy + 1, iz - 1) >= isoVal ? 1 : 0) +
(space.get(data, ix + 1, iy - 1, iz - 1) >= isoVal ? 1 : 0) +
(space.get(data, ix - 1, iy - 1, iz - 1) >= isoVal ? 1 : 0)
) / 16 * 255;
};
}
let i = 0;
for (let iy = y0; iy < ny; ++iy) {
for (let ix = x0; ix < nx; ++ix) {
for (let iz = z0; iz < nz; ++iz) {
Vec3.set(l.position, ix, iy, iz);
Vec3.transformMat4(l.position, l.position, gridToCartn);
Color.toArray(color(l, false), imageArray, i);
const val = space.get(data, ix, iy, iz);
const normVal = (val - min) / (max - min);
if (isUniform) {
const val = space.get(data, ix, iy, iz);
const normVal = (val - min) / (max - min);
imageArray[i] *= normVal * 2;
imageArray[i + 1] *= normVal * 2;
imageArray[i + 2] *= normVal * 2;
}
imageArray[i + 3] = v(ix, iy, iz);
imageArray[i] = r * normVal * 2 * 255;
imageArray[i + 1] = g * normVal * 2 * 255;
imageArray[i + 2] = b * normVal * 2 * 255;
imageArray[i + 3] = val >= isoVal ? 255 : 0;
i += 4;
}

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2020-2025 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>
*/
@@ -14,7 +14,6 @@ import { SetUtils } from '../../mol-util/set';
import { Box3D } from '../../mol-math/geometry';
import { toHalfFloat } from '../../mol-util/number-conversion';
import { clamp } from '../../mol-math/interpolate';
import { LocationIterator } from '../../mol-geo/util/location-iterator';
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
const v3set = Vec3.set;
@@ -78,18 +77,6 @@ export function eachVolumeLoci(loci: Loci, volume: Volume, props: { isoValue?: V
return changed;
}
export function createVolumeCellLocationIterator(volume: Volume): LocationIterator {
const [xn, yn, zn] = volume.grid.cells.space.dimensions;
const groupCount = xn * yn * zn;
const instanceCount = 1;
const location = Volume.Cell.Location(volume);
const getLocation = (groupIndex: number, _instanceIndex: number) => {
location.cell = groupIndex as Volume.CellIndex;
return location;
};
return LocationIterator(groupCount, instanceCount, 1, getLocation);
}
//
export function getVolumeTexture2dLayout(dim: Vec3, padding = 0) {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 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 Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -35,7 +35,7 @@ import { OperatorHklColorThemeProvider } from './color/operator-hkl';
import { PartialChargeColorThemeProvider } from './color/partial-charge';
import { AtomIdColorThemeProvider } from './color/atom-id';
import { EntityIdColorThemeProvider } from './color/entity-id';
import type { Texture, TextureFilter } from '../mol-gl/webgl/texture';
import { Texture, TextureFilter } from '../mol-gl/webgl/texture';
import { VolumeValueColorThemeProvider } from './color/volume-value';
import { Vec3, Vec4 } from '../mol-math/linear-algebra';
import { ModelIndexColorThemeProvider } from './color/model-index';
@@ -46,10 +46,6 @@ import { ColorThemeCategory } from './color/categories';
import { CartoonColorThemeProvider } from './color/cartoon';
import { FormalChargeColorThemeProvider } from './color/formal-charge';
import { ExternalStructureColorThemeProvider } from './color/external-structure';
import { ColorListEntry } from '../mol-util/color/color';
import { getPrecision } from '../mol-util/number';
import { SortedArray } from '../mol-data/int/sorted-array';
import { normalize } from '../mol-math/interpolate';
export type LocationColor = (location: Location, isSecondary: boolean) => Color
@@ -98,55 +94,8 @@ namespace ColorTheme {
export const Category = ColorThemeCategory;
export interface Palette {
colors: Color[],
filter?: TextureFilter,
domain?: [number, number],
}
export function Palette(list: ColorListEntry[], kind: 'set' | 'interpolate', domain?: [number, number]): Palette {
const colors: Color[] = [];
const hasOffsets = list.every(c => Array.isArray(c));
if (hasOffsets) {
let maxPrecision = 0;
for (const e of list) {
if (Array.isArray(e)) {
const p = getPrecision(e[1]);
if (p > maxPrecision) maxPrecision = p;
}
}
const count = Math.pow(10, maxPrecision);
const sorted = [...list] as [Color, number][];
sorted.sort((a, b) => a[1] - b[1]);
const src = sorted.map(c => c[0]);
const values = SortedArray.ofSortedArray(sorted.map(c => c[1]));
const _off: number[] = [];
for (let i = 0, il = values.length - 1; i < il; ++i) {
_off.push(values[i] + (values[i + 1] - values[i]) / 2);
}
_off.push(values[values.length - 1]);
const off = SortedArray.ofSortedArray(_off);
for (let i = 0, il = Math.max(count, list.length); i < il; ++i) {
const t = normalize(i, 0, count - 1);
const j = SortedArray.findPredecessorIndex(off, t);
colors[i] = src[j];
}
} else {
for (const e of list) {
if (Array.isArray(e)) colors.push(e[0]);
else colors.push(e);
}
}
return {
colors,
filter: kind === 'set' ? 'nearest' : 'linear',
domain,
};
colors: Color[],
}
export const PaletteScale = (1 << 24) - 1;

View File

@@ -1,8 +1,7 @@
/**
* Copyright (c) 2021-2025 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 Alexander Rose <alexander.rose@weirdbyte.de>
* @author Adam Midlik <midlik@gmail.com>
*/
import { StructureProperties, StructureElement, Bond, Structure, Unit } from '../../mol-model/structure';
@@ -16,16 +15,12 @@ import { TableLegend, ScaleLegend } from '../../mol-util/legend';
import { ColorThemeCategory } from './categories';
import { ModelFormat } from '../../mol-model-formats/format';
const DefaultList = 'many-distinct';
const DefaultColor = Color(0xFAFAFA);
const DefaultWaterColor = Color(0xFF0D0D);
const Description = 'Gives every chain a color based on its `label_entity_id` value.';
export const EntityIdColorThemeParams = {
...getPaletteParams({ type: 'colors', colorList: DefaultList }),
overrideWater: PD.Boolean(false, { description: 'Override the color for water molecules.' }),
waterColor: PD.Color(DefaultWaterColor, { hideIf: p => !p.overrideWater, description: 'Color for water molecules (if overrideWater is true).' }),
};
export type EntityIdColorThemeParams = typeof EntityIdColorThemeParams
export function getEntityIdColorThemeParams(ctx: ThemeDataContext) {
@@ -99,25 +94,20 @@ export function EntityIdColorTheme(ctx: ThemeDataContext, props: PD.Values<Entit
legend = palette.legend;
color = (location: Location): Color => {
let structElemLoc: StructureElement.Location;
let serial: number | undefined = undefined;
if (StructureElement.Location.is(location)) {
structElemLoc = location;
const entityId = getEntityId(location);
const sourceSerial = sourceSerialMap.get(location.unit.model.sourceData) ?? -1;
const k = key(entityId, sourceSerial);
serial = entityIdSerialMap.get(k);
} else if (Bond.isLocation(location)) {
l.unit = location.aUnit;
l.element = location.aUnit.elements[location.aIndex];
structElemLoc = l;
} else {
return DefaultColor;
const entityId = getEntityId(l);
const sourceSerial = sourceSerialMap.get(l.unit.model.sourceData) ?? -1;
const k = key(entityId, sourceSerial);
serial = entityIdSerialMap.get(k);
}
const entityId = getEntityId(structElemLoc);
const sourceSerial = sourceSerialMap.get(structElemLoc.unit.model.sourceData) ?? -1;
if (props.overrideWater) {
const entities = structElemLoc.unit.model.entities;
const entityType = entities.data.type.value(entities.getEntityIndex(entityId));
if (entityType === 'water') return props.waterColor;
}
const k = key(entityId, sourceSerial);
const serial = entityIdSerialMap.get(k);
return serial === undefined ? DefaultColor : palette.color(serial);
};
} else {

View File

@@ -1,21 +1,20 @@
/**
* Copyright (c) 2024-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Cai Huiyu <szmun.caihy@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { Color, ColorScale } from '../../mol-util/color';
import { Location } from '../../mol-model/location';
import { ColorTheme, LocationColor } from '../color';
import type { ColorTheme } from '../color';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ThemeDataContext } from '../theme';
import { Grid, Volume } from '../../mol-model/volume';
import { type PluginContext } from '../../mol-plugin/context';
import { isPositionLocation } from '../../mol-geo/util/location-iterator';
import { Vec3 } from '../../mol-math/linear-algebra';
import { clamp } from '../../mol-math/interpolate';
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
import { lerp } from '../../mol-math/interpolate';
import { ColorThemeCategory } from './categories';
const Description = `Assigns a color based on volume value at a given vertex.`;
@@ -31,7 +30,7 @@ export const ExternalVolumeColorThemeParams = {
coloring: PD.MappedStatic('absolute-value', {
'absolute-value': PD.Group({
domain: PD.MappedStatic('auto', {
custom: PD.Interval([-1, 1], { step: 0.001 }),
custom: PD.Interval([-1, 1]),
auto: PD.Group({
symmetric: PD.Boolean(false, { description: 'If true the automatic range is determined as [-|max|, |max|].' })
})
@@ -40,7 +39,7 @@ export const ExternalVolumeColorThemeParams = {
}),
'relative-value': PD.Group({
domain: PD.MappedStatic('auto', {
custom: PD.Interval([-1, 1], { step: 0.001 }),
custom: PD.Interval([-1, 1]),
auto: PD.Group({
symmetric: PD.Boolean(false, { description: 'If true the automatic range is determined as [-|max|, |max|].' })
})
@@ -50,7 +49,6 @@ export const ExternalVolumeColorThemeParams = {
}),
defaultColor: PD.Color(Color(0xcccccc)),
normalOffset: PD.Numeric(0., { min: 0, max: 20, step: 0.1 }, { description: 'Offset vertex position along its normal by given amount.' }),
usePalette: PD.Boolean(false, { description: 'Use a palette to color at the pixel level.' }),
};
export type ExternalVolumeColorThemeParams = typeof ExternalVolumeColorThemeParams
@@ -65,11 +63,7 @@ export function ExternalVolumeColorTheme(ctx: ThemeDataContext, props: PD.Values
// NOTE: this will currently be slow for with GPU/texture meshes due to slow iteration
// TODO: create texture to be able to do the sampling on the GPU
let color: LocationColor;
let palette: ColorTheme.Palette | undefined;
const { normalOffset, defaultColor, usePalette } = props;
let color;
if (volume) {
const coloring = props.coloring.params;
const { stats } = volume.grid;
@@ -81,7 +75,7 @@ export function ExternalVolumeColorTheme(ctx: ThemeDataContext, props: PD.Values
domain[1] = (domain[1] - stats.mean) / stats.sigma;
}
if (coloring.domain.name === 'auto' && coloring.domain.params.symmetric) {
if (props.coloring.params.domain.name === 'auto' && props.coloring.params.domain.params.symmetric) {
const max = Math.max(Math.abs(domain[0]), Math.abs(domain[1]));
domain[0] = -max;
domain[1] = max;
@@ -89,37 +83,67 @@ export function ExternalVolumeColorTheme(ctx: ThemeDataContext, props: PD.Values
const scale = ColorScale.create({ domain, listOrName: coloring.list.colors });
const position = Vec3();
const getTrilinearlyInterpolated = Grid.makeGetTrilinearlyInterpolated(volume.grid, isRelative ? 'relative' : 'none');
const cartnToGrid = Grid.getGridToCartesianTransform(volume.grid);
Mat4.invert(cartnToGrid, cartnToGrid);
const gridCoords = Vec3();
const { dimensions, get } = volume.grid.cells.space;
const data = volume.grid.cells.data;
const [mi, mj, mk] = dimensions;
color = (location: Location): Color => {
if (!isPositionLocation(location)) {
return defaultColor;
return props.defaultColor;
}
// Offset the vertex position along its normal
if (normalOffset > 0) {
Vec3.scaleAndAdd(position, location.position, location.normal, normalOffset);
} else {
Vec3.copy(position, location.position);
Vec3.copy(gridCoords, location.position);
if (props.normalOffset > 0) {
Vec3.scaleAndAdd(gridCoords, gridCoords, location.normal, props.normalOffset);
}
const value = getTrilinearlyInterpolated(position);
if (isNaN(value)) return defaultColor;
Vec3.transformMat4(gridCoords, gridCoords, cartnToGrid);
if (usePalette) {
return (clamp((value - domain[0]) / (domain[1] - domain[0]), 0, 1) * ColorTheme.PaletteScale) as Color;
} else {
return scale.color(value);
const i = Math.floor(gridCoords[0]);
const j = Math.floor(gridCoords[1]);
const k = Math.floor(gridCoords[2]);
if (i < 0 || i >= mi || j < 0 || j >= mj || k < 0 || k >= mk) {
return props.defaultColor;
}
const u = gridCoords[0] - i;
const v = gridCoords[1] - j;
const w = gridCoords[2] - k;
// Tri-linear interpolation for the value
const ii = Math.min(i + 1, mi - 1);
const jj = Math.min(j + 1, mj - 1);
const kk = Math.min(k + 1, mk - 1);
let a = get(data, i, j, k);
let b = get(data, ii, j, k);
let c = get(data, i, jj, k);
let d = get(data, ii, jj, k);
const x = lerp(lerp(a, b, u), lerp(c, d, u), v);
a = get(data, i, j, kk);
b = get(data, ii, j, kk);
c = get(data, i, jj, kk);
d = get(data, ii, jj, kk);
const y = lerp(lerp(a, b, u), lerp(c, d, u), v);
let value = lerp(x, y, w);
if (isRelative) {
value = (value - stats.mean) / stats.sigma;
}
return scale.color(value);
};
palette = usePalette ? {
colors: coloring.list.colors.map(e => Array.isArray(e) ? e[0] : e),
filter: (coloring.list.kind === 'set' ? 'nearest' : 'linear') as 'nearest' | 'linear'
} : undefined;
} else {
color = () => defaultColor;
color = () => props.defaultColor;
}
return {
@@ -127,7 +151,6 @@ export function ExternalVolumeColorTheme(ctx: ThemeDataContext, props: PD.Values
granularity: 'vertex',
preferSmoothing: true,
color,
palette,
props,
description: Description,
// TODO: figure out how to do legend for this

View File

@@ -1,20 +1,17 @@
/**
* Copyright (c) 2021-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2021-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { ColorTheme } from '../color';
import type { ColorTheme } from '../color';
import { Color, ColorScale } from '../../mol-util/color';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ThemeDataContext } from '../theme';
import { ColorNames } from '../../mol-util/color/names';
import { Location } from '../../mol-model/location';
import { ColorTypeDirect } from '../../mol-geo/geometry/color-data';
import { Volume } from '../../mol-model/volume/volume';
import { ColorThemeCategory } from './categories';
import { clamp, normalize } from '../../mol-math/interpolate';
import { Color } from '../../mol-util/color/color';
import { Grid } from '../../mol-model/volume/grid';
import { isPositionLocation } from '../../mol-geo/util/location-iterator';
const Description = 'Assign color based on the given value of a volume cell.';
@@ -29,90 +26,30 @@ export const VolumeValueColorThemeParams = {
[ColorNames.white, 1]
]
}, { offsets: true, isEssential: true }),
domain: PD.MappedStatic('auto', {
custom: PD.Interval([-1, 1], { step: 0.001 }),
auto: PD.Group({
symmetric: PD.Boolean(false, { description: 'If true the automatic range is determined as [-|max|, |max|].' })
})
}),
isRelative: PD.Boolean(false, { description: 'If true the value is treated as relative to the volume mean and sigma.' }),
defaultColor: PD.Color(Color(0xcccccc)),
};
export type VolumeValueColorThemeParams = typeof VolumeValueColorThemeParams
export function getVolumeValueColorThemeParams(ctx: ThemeDataContext) {
return VolumeValueColorThemeParams; // TODO return copy
}
export function VolumeValueColorTheme(ctx: ThemeDataContext, props: PD.Values<VolumeValueColorThemeParams>): ColorTheme<VolumeValueColorThemeParams, any> {
if (ctx.volume) {
const { min, max, mean, sigma } = ctx.volume.grid.stats;
const domain: [number, number] = props.domain.name === 'custom' ? props.domain.params : [min, max];
const { colorList } = props;
export function VolumeValueColorTheme(ctx: ThemeDataContext, props: PD.Values<VolumeValueColorThemeParams>): ColorTheme<VolumeValueColorThemeParams, ColorTypeDirect> {
const scale = ColorScale.create({ domain: [0, 1], listOrName: props.colorList.colors });
if (props.domain.name === 'auto' && props.isRelative) {
domain[0] = (domain[0] - mean) / sigma;
domain[1] = (domain[1] - mean) / sigma;
}
if (props.domain.name === 'auto' && props.domain.params.symmetric) {
const max = Math.max(Math.abs(domain[0]), Math.abs(domain[1]));
domain[0] = -max;
domain[1] = max;
}
if (ctx.locationKinds?.includes('direct-location')) {
// this is for direct-volume rendering where the location is the volume grid cell
// and we only need to provide the domain and palette here
const normalizedDomain = [
normalize(domain[0], min, max),
normalize(domain[1], min, max)
] as [number, number];
const palette = ColorTheme.Palette(colorList.colors, colorList.kind, normalizedDomain);
return {
factory: VolumeValueColorTheme as any,
granularity: 'direct',
props,
description: Description,
palette,
};
} else {
const getTrilinearlyInterpolated = Grid.makeGetTrilinearlyInterpolated(ctx.volume.grid, 'none');
const color = (location: Location): Color => {
if (!isPositionLocation(location)) {
return props.defaultColor;
}
const value = getTrilinearlyInterpolated(location.position);
if (isNaN(value)) return props.defaultColor;
return (clamp((value - domain[0]) / (domain[1] - domain[0]), 0, 1) * ColorTheme.PaletteScale) as Color;
};
const palette = ColorTheme.Palette(colorList.colors, colorList.kind);
return {
factory: VolumeValueColorTheme as any,
granularity: 'vertex',
preferSmoothing: true,
color,
palette,
props,
description: Description,
};
}
} else {
return {
factory: VolumeValueColorTheme as any,
granularity: 'uniform',
color: () => props.defaultColor,
props,
description: Description,
};
const colors: Color[] = [];
for (let i = 0; i < 256; ++i) {
colors[i] = scale.color(i / 255);
}
const palette: ColorTheme.Palette = { colors, filter: 'linear' };
return {
factory: VolumeValueColorTheme,
granularity: 'direct',
props: props,
description: Description,
legend: scale.legend,
palette,
};
}
export const VolumeValueColorThemeProvider: ColorTheme.Provider<VolumeValueColorThemeParams, 'volume-value'> = {

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -12,7 +12,6 @@ import { PhysicalSizeThemeProvider } from './size/physical';
import { deepEqual } from '../mol-util';
import { ShapeGroupSizeThemeProvider } from './size/shape-group';
import { UncertaintySizeThemeProvider } from './size/uncertainty';
import { VolumeValueSizeThemeProvider } from './size/volume-value';
export { SizeTheme };
interface SizeTheme<P extends PD.Params> {
@@ -45,8 +44,7 @@ namespace SizeTheme {
'physical': PhysicalSizeThemeProvider,
'shape-group': ShapeGroupSizeThemeProvider,
'uncertainty': UncertaintySizeThemeProvider,
'uniform': UniformSizeThemeProvider,
'volume-value': VolumeValueSizeThemeProvider,
'uniform': UniformSizeThemeProvider
};
type _BuiltIn = typeof BuiltIn
export type BuiltIn = keyof _BuiltIn

View File

@@ -1,63 +0,0 @@
/**
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import type { SizeTheme } from '../size';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
import { ThemeDataContext } from '../../mol-theme/theme';
import { LocationSize } from '../../mol-geo/geometry/size-data';
import { Volume } from '../../mol-model/volume/volume';
import { Location } from '../../mol-model/location';
const Description = 'Assign size based on the given value of a volume cell.';
export const VolumeValueSizeThemeParams = {
scale: PD.Numeric(1, { min: 0.1, max: 5, step: 0.1 }),
};
export type VolumeValueSizeThemeParams = typeof VolumeValueSizeThemeParams
export function getVolumeValueSizeThemeParams(ctx: ThemeDataContext) {
return VolumeValueSizeThemeParams; // TODO return copy
}
export function VolumeValueSizeTheme(ctx: ThemeDataContext, props: PD.Values<VolumeValueSizeThemeParams>): SizeTheme<VolumeValueSizeThemeParams> {
if (ctx.volume) {
const { data } = ctx.volume.grid.cells;
const isLocation = Volume.Cell.isLocation;
const size: LocationSize = (location: Location): number => {
if (isLocation(location)) {
return data[location.cell] * props.scale;
} else {
return 0;
}
};
return {
factory: VolumeValueSizeTheme,
granularity: 'group',
size,
props,
description: Description
};
} else {
return {
factory: VolumeValueSizeTheme,
granularity: 'uniform',
size: () => props.scale,
props,
description: Description
};
}
}
export const VolumeValueSizeThemeProvider: SizeTheme.Provider<VolumeValueSizeThemeParams, 'volume-value'> = {
name: 'volume-value',
label: 'Volume Value',
category: '',
factory: VolumeValueSizeTheme,
getParams: getVolumeValueSizeThemeParams,
defaultValues: PD.getDefaultValues(VolumeValueSizeThemeParams),
isApplicable: (ctx: ThemeDataContext) => !!ctx.volume && !Volume.Segmentation.get(ctx.volume),
};

View File

@@ -1,5 +1,5 @@
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
@@ -13,7 +13,6 @@ import { Shape } from '../mol-model/shape';
import { CustomProperty } from '../mol-model-props/common/custom-property';
import { objectForEach } from '../mol-util/object';
import { ColorType } from '../mol-geo/geometry/color-data';
import { Location } from '../mol-model/location';
export interface ThemeRegistryContext {
colorThemeRegistry: ColorTheme.Registry
@@ -25,8 +24,6 @@ export interface ThemeDataContext {
structure?: Structure
volume?: Volume
shape?: Shape
/** Hint to request support for specific kinds of locations */
locationKinds?: ReadonlyArray<Location['kind']>
}
export { Theme };