mirror of
https://github.com/molstar/molstar.git
synced 2026-06-05 14:04:36 +08:00
Compare commits
3 Commits
v4.11.0
...
support-sc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bd162b977 | ||
|
|
c7fb71738e | ||
|
|
9413481253 |
41
CHANGELOG.md
41
CHANGELOG.md
@@ -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
2320
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
23
package.json
23
package.json
@@ -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": {
|
||||
|
||||
@@ -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> = {}) {
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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' } },
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
);
|
||||
@@ -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: {
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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'),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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)}]`;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 };
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}]);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(', ')})`;
|
||||
});
|
||||
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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. 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 && <>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
</>;
|
||||
|
||||
@@ -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 }),
|
||||
|
||||
@@ -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' }
|
||||
});
|
||||
});
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
@@ -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)
|
||||
});
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'> = {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user