molstar
Version:
A comprehensive macromolecular library.
617 lines (616 loc) • 27.1 kB
JavaScript
/**
* Copyright (c) 2023-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
import { PluginStateObject as PSO, PluginStateTransform } from '../../../mol-plugin-state/objects';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Task } from '../../../mol-task';
import { Color } from '../../../mol-util/color';
import { Spheres } from '../../../mol-geo/geometry/spheres/spheres';
import { Clip } from '../../../mol-util/clip';
import { escapeRegExp, stringToWords } from '../../../mol-util/string';
import { Mat4, Vec3 } from '../../../mol-math/linear-algebra';
import { ParamMapping } from '../../../mol-util/param-mapping';
import { distinctColors } from '../../../mol-util/color/distinct';
import { Hcl } from '../../../mol-util/color/spaces/hcl';
import { StateObjectRef, StateSelection } from '../../../mol-state';
import { ShapeRepresentation3D, StructureRepresentation3D } from '../../../mol-plugin-state/transforms/representation';
import { assertUnreachable } from '../../../mol-util/type-helpers';
import { saturate } from '../../../mol-math/interpolate';
import { Material } from '../../../mol-util/material';
function getHueRange(hue, variability) {
let min = hue - variability;
const minOverflow = (min < 0 ? -min : 0);
let max = hue + variability;
if (max > 360)
min -= max - 360;
max += minOverflow;
return [Math.max(0, min), Math.min(360, max)];
}
function getGrayscaleColors(count, luminance, variability) {
const out = [];
for (let i = 0; i < count; ++i) {
const l = saturate(luminance / 100);
const v = saturate(variability / 180) * Math.random();
const s = Math.random() > 0.5 ? 1 : -1;
const d = Math.abs(l + s * v) % 1;
out[i] = Color.fromNormalizedRgb(d, d, d);
}
return out;
}
export function getDistinctGroupColors(count, color, variability, shift, props) {
const hcl = Hcl.fromColor(Hcl(), color);
if (isNaN(hcl[0])) {
return getGrayscaleColors(count, hcl[2], variability);
}
if (count === 1) {
hcl[1] = 65;
hcl[2] = 55;
return [Hcl.toColor(hcl)];
}
const colors = distinctColors(count, {
hue: getHueRange(hcl[0], variability),
chroma: [30, 100],
luminance: [50, 100],
clusteringStepCount: 0,
minSampleCount: 1000,
sampleCountFactor: 100,
sort: 'none',
...props,
});
if (shift !== 0) {
const offset = Math.floor(shift / 100 * count);
return [...colors.slice(offset), ...colors.slice(0, offset)];
}
else {
return colors;
}
}
const Colors = [0x377eb8, 0xe41a1c, 0x4daf4a, 0x984ea3, 0xff7f00, 0xffff33, 0xa65628, 0xf781bf];
export function getDistinctBaseColors(count, shift, props) {
let colors;
if (count <= Colors.length) {
colors = Colors.slice(0, count).map(e => Array.isArray(e) ? e[0] : e);
}
else {
colors = distinctColors(count, {
hue: [1, 360],
chroma: [25, 100],
luminance: [30, 100],
clusteringStepCount: 0,
minSampleCount: 1000,
sampleCountFactor: 100,
sort: 'none',
...props,
});
}
if (shift !== 0) {
const offset = Math.floor(shift / 100 * count);
return [...colors.slice(offset), ...colors.slice(0, offset)];
}
else {
return colors;
}
}
export const ColorParams = {
type: PD.Select('generate', PD.arrayToOptions(['generate', 'uniform', 'custom'])),
illustrative: PD.Boolean(false, { description: 'Illustrative style', hideIf: p => p.type === 'custom' }),
value: PD.Color(Color(0xFFFFFF), { hideIf: p => p.type === 'custom' }),
variability: PD.Numeric(20, { min: 1, max: 180, step: 1 }, { hideIf: p => p.type !== 'generate' }),
shift: PD.Numeric(0, { min: 0, max: 100, step: 1 }, { hideIf: p => !p.type.includes('generate') }),
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }, { hideIf: p => p.type === 'custom' }),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
};
export const ColorValueParam = PD.Color(Color(0xFFFFFF));
export const RootParams = {
type: PD.Select('custom', PD.arrayToOptions(['group-generate', 'group-uniform', 'generate', 'uniform', 'custom'])),
illustrative: PD.Boolean(false, { description: 'Illustrative style', hideIf: p => p.type === 'custom' }),
value: PD.Color(Color(0xFFFFFF), { hideIf: p => p.type !== 'uniform' }),
variability: PD.Numeric(20, { min: 1, max: 180, step: 1 }, { hideIf: p => p.type !== 'group-generate' }),
shift: PD.Numeric(0, { min: 0, max: 100, step: 1 }, { hideIf: p => !p.type.includes('generate') }),
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }, { hideIf: p => p.type === 'custom' }),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }, { hideIf: p => p.type === 'custom' }),
};
export const LightnessParams = {
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
};
export const DimLightness = 6;
export const IllustrativeParams = {
illustrative: PD.Boolean(false, { description: 'Illustrative style' }),
};
export const OpacityParams = {
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
};
export const EmissiveParams = {
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
};
export const celShaded = {
celShaded: PD.Boolean(false, { description: 'Cel Shading light for stylized rendering of representations' })
};
export const PatternParams = {
frequency: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
amplitude: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
};
export const StyleParams = {
ignoreLight: PD.Boolean(false, { description: 'Ignore light for stylized rendering of representations' }),
materialStyle: Material.getParam(),
celShaded: PD.Boolean(false, { description: 'Cel Shading light for stylized rendering of representations' }),
};
export const LodParams = {
lodLevels: Spheres.Params.lodLevels,
cellSize: Spheres.Params.cellSize,
batchSize: Spheres.Params.batchSize,
approximate: Spheres.Params.approximate,
};
export const SimpleClipParams = {
type: PD.Select('none', PD.objectToOptions(Clip.Type, t => stringToWords(t))),
invert: PD.Boolean(false),
position: PD.Group({
x: PD.Numeric(0, { min: -100, max: 100, step: 1 }, { immediateUpdate: true }),
y: PD.Numeric(0, { min: -100, max: 100, step: 1 }, { immediateUpdate: true }),
z: PD.Numeric(0, { min: -100, max: 100, step: 1 }, { immediateUpdate: true }),
}, { hideIf: g => g.type === 'none', isExpanded: true }),
rotation: PD.Group({
axis: PD.Vec3(Vec3.create(1, 0, 0)),
angle: PD.Numeric(0, { min: -180, max: 180, step: 1 }, { immediateUpdate: true }),
}, { hideIf: g => g.type === 'none', isExpanded: true }),
scale: PD.Group({
x: PD.Numeric(100, { min: 0, max: 100, step: 1 }, { immediateUpdate: true }),
y: PD.Numeric(100, { min: 0, max: 100, step: 1 }, { immediateUpdate: true }),
z: PD.Numeric(100, { min: 0, max: 100, step: 1 }, { immediateUpdate: true }),
}, { hideIf: g => ['none', 'plane'].includes(g.type), isExpanded: true }),
};
export function getClipObjects(values, boundingSphere) {
const { center, radius } = boundingSphere;
const position = Vec3.clone(center);
Vec3.add(position, position, Vec3.create(radius * values.position.x / 100, radius * values.position.y / 100, radius * values.position.z / 100));
const scale = Vec3.create(values.scale.x, values.scale.y, values.scale.z);
Vec3.scale(scale, scale, 2 * radius / 100);
return [{
type: values.type,
invert: values.invert,
position,
scale,
rotation: values.rotation,
transform: Mat4.identity(),
}];
}
export function createClipMapping(node) {
return ParamMapping({
params: SimpleClipParams,
target: (ctx) => {
return node.clipValue;
}
})({
values(props, ctx) {
if (!props || props.objects.length === 0) {
return {
type: 'none',
invert: false,
position: { x: 0, y: 0, z: 0 },
rotation: { axis: Vec3.create(1, 0, 0), angle: 0 },
scale: { x: 100, y: 100, z: 100 },
};
}
const { center, radius } = node.plugin.canvas3d.boundingSphere;
const { invert, position, scale, rotation, type } = props.objects[0];
const p = Vec3.clone(position);
Vec3.sub(p, p, center);
Vec3.scale(p, p, 100 / radius);
Vec3.round(p, p);
const s = Vec3.clone(scale);
Vec3.scale(s, s, 100 / radius / 2);
Vec3.round(s, s);
return {
type,
invert,
position: { x: p[0], y: p[1], z: p[2] },
rotation,
scale: { x: s[0], y: s[1], z: s[2] },
};
},
update: (s, props) => {
if (!props)
return;
const clipObjects = getClipObjects(s, node.plugin.canvas3d.boundingSphere);
props.objects = clipObjects;
},
apply: async (props, ctx) => {
if (props)
node.updateClip(props);
}
});
}
export const MesoscaleGroupParams = {
root: PD.Value(false, { isHidden: true }),
index: PD.Value(-1, { isHidden: true }),
tag: PD.Value('', { isHidden: true }),
label: PD.Value('', { isHidden: true }),
description: PD.Value('', { isHidden: true }),
hidden: PD.Boolean(false),
color: PD.Group(RootParams),
lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }),
alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }),
emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }),
lod: PD.Group(LodParams),
clip: PD.Group(SimpleClipParams),
};
export class MesoscaleGroupObject extends PSO.Create({ name: 'Mesoscale Group', typeClass: 'Object' }) {
}
export const MesoscaleGroup = PluginStateTransform.BuiltIn({
name: 'mesoscale-group',
display: { name: 'Mesoscale Group' },
from: [PSO.Root, MesoscaleGroupObject],
to: MesoscaleGroupObject,
params: MesoscaleGroupParams,
})({
apply({ a, params }, plugin) {
return Task.create('Apply Mesoscale Group', async () => {
return new MesoscaleGroupObject({}, { label: params.label, description: params.description });
});
},
});
export function getMesoscaleGroupParams(graphicsMode) {
const groupParams = PD.getDefaultValues(MesoscaleGroupParams);
if (graphicsMode === 'custom')
return groupParams;
return {
...groupParams,
lod: {
...groupParams.lod,
...getGraphicsModeProps(graphicsMode),
}
};
}
export function getLodLevels(graphicsMode) {
switch (graphicsMode) {
case 'performance':
return [
{ minDistance: 1, maxDistance: 300, overlap: 0, stride: 1, scaleBias: 1 },
{ minDistance: 300, maxDistance: 2000, overlap: 0, stride: 40, scaleBias: 3 },
{ minDistance: 2000, maxDistance: 6000, overlap: 0, stride: 150, scaleBias: 3 },
{ minDistance: 6000, maxDistance: 10000000, overlap: 0, stride: 300, scaleBias: 2.5 },
];
case 'balanced':
return [
{ minDistance: 1, maxDistance: 500, overlap: 0, stride: 1, scaleBias: 1 },
{ minDistance: 500, maxDistance: 2000, overlap: 0, stride: 15, scaleBias: 3 },
{ minDistance: 2000, maxDistance: 6000, overlap: 0, stride: 70, scaleBias: 2.7 },
{ minDistance: 6000, maxDistance: 10000000, overlap: 0, stride: 200, scaleBias: 2.5 },
];
case 'quality':
return [
{ minDistance: 1, maxDistance: 1000, overlap: 0, stride: 1, scaleBias: 1 },
{ minDistance: 1000, maxDistance: 4000, overlap: 0, stride: 10, scaleBias: 3 },
{ minDistance: 4000, maxDistance: 10000, overlap: 0, stride: 50, scaleBias: 2.7 },
{ minDistance: 10000, maxDistance: 10000000, overlap: 0, stride: 200, scaleBias: 2.3 },
];
case 'ultra':
return [
{ minDistance: 1, maxDistance: 5000, overlap: 0, stride: 1, scaleBias: 1 },
{ minDistance: 5000, maxDistance: 10000, overlap: 0, stride: 10, scaleBias: 3 },
{ minDistance: 10000, maxDistance: 30000, overlap: 0, stride: 50, scaleBias: 2.5 },
{ minDistance: 30000, maxDistance: 10000000, overlap: 0, stride: 200, scaleBias: 2 },
];
default:
assertUnreachable(graphicsMode);
}
}
export function getGraphicsModeProps(graphicsMode) {
return {
lodLevels: getLodLevels(graphicsMode),
approximate: graphicsMode !== 'quality' && graphicsMode !== 'ultra',
alphaThickness: graphicsMode === 'performance' ? 15 : 12,
};
}
export function setGraphicsCanvas3DProps(ctx, graphics) {
var _a, _b;
const pixelScale = graphics === 'balanced' ? 0.75
: graphics === 'performance' ? 0.5 : 1;
(_a = ctx.canvas3dContext) === null || _a === void 0 ? void 0 : _a.setProps({ pixelScale });
(_b = ctx.canvas3d) === null || _b === void 0 ? void 0 : _b.setProps({
postprocessing: {
sharpening: pixelScale < 1 ? {
name: 'on',
params: { sharpness: 0.5, denoise: true }
} : { name: 'off', params: {} }
}
});
}
//
export const MesoscaleStateParams = {
filter: PD.Value('', { isHidden: true }),
graphics: PD.Select('quality', PD.arrayToOptions(['ultra', 'quality', 'balanced', 'performance', 'custom'])),
description: PD.Value('', { isHidden: true }),
focusInfo: PD.Value('', { isHidden: true }),
link: PD.Value('', { isHidden: true }),
textSizeDescription: PD.Numeric(14, { min: 1, max: 100, step: 1 }, { isHidden: true }),
index: PD.Value(-1, { isHidden: true })
};
export class MesoscaleStateObject extends PSO.Create({ name: 'Mesoscale State', typeClass: 'Object' }) {
}
const MesoscaleStateTransform = PluginStateTransform.BuiltIn({
name: 'mesoscale-state',
display: { name: 'Mesoscale State' },
from: PSO.Root,
to: MesoscaleStateObject,
params: MesoscaleStateParams,
})({
apply({ a, params }, plugin) {
return Task.create('Apply Mesoscale State', async () => {
return new MesoscaleStateObject(params);
});
},
});
export { MesoscaleState };
const MesoscaleState = {
async init(ctx) {
const cell = ctx.state.data.selectQ(q => q.ofType(MesoscaleStateObject))[0];
if (cell)
throw new Error('MesoscaleState already initialized');
const customState = ctx.customState;
const state = await ctx.state.data.build().toRoot().apply(MesoscaleStateTransform, {
filter: '',
graphics: customState.graphicsMode,
}).commit();
customState.stateRef = state.ref;
},
get(ctx) {
const ref = this.ref(ctx);
return ctx.state.data.tryGetCellData(ref);
},
async set(ctx, props) {
const ref = this.ref(ctx);
await ctx.state.data.build().to(ref).update(MesoscaleStateTransform, old => Object.assign(old, props)).commit();
},
ref(ctx) {
const ref = ctx.customState.stateRef;
if (!ref)
throw new Error('MesoscaleState not initialized');
return ref;
},
has(ctx) {
const ref = ctx.customState.stateRef || '';
return ctx.state.data.cells.has(ref) ? true : false;
},
};
//
export function getRoots(plugin) {
const s = plugin.customState;
if (!s.stateCache.roots) {
s.stateCache.roots = plugin.state.data.select(StateSelection.Generators.rootsOfType(MesoscaleGroupObject));
}
return s.stateCache.roots;
}
export function getGroups(plugin, tag) {
const s = plugin.customState;
const k = `groups-${tag || ''}`;
if (!s.stateCache[k]) {
const selector = tag !== undefined
? StateSelection.Generators.ofTransformer(MesoscaleGroup).withTag(tag)
: StateSelection.Generators.ofTransformer(MesoscaleGroup);
s.stateCache[k] = plugin.state.data.select(selector);
}
return s.stateCache[k];
}
function _getAllGroups(plugin, tag, list) {
var _a;
const groups = getGroups(plugin, tag);
list.push(...groups);
for (const g of groups) {
_getAllGroups(plugin, (_a = g.params) === null || _a === void 0 ? void 0 : _a.values.tag, list);
}
return list;
}
export function getAllGroups(plugin, tag) {
return _getAllGroups(plugin, tag, []);
}
export function getAllLeafGroups(plugin, tag) {
const allGroups = getAllGroups(plugin, tag);
allGroups.sort((a, b) => { var _a, _b; return ((_a = a.params) === null || _a === void 0 ? void 0 : _a.values.index) - ((_b = b.params) === null || _b === void 0 ? void 0 : _b.values.index); });
return allGroups.filter(g => {
var _a;
return getEntities(plugin, (_a = g.params) === null || _a === void 0 ? void 0 : _a.values.tag).length > 0;
});
}
export function getEntities(plugin, tag) {
const s = plugin.customState;
const k = `entities-${tag || ''}`;
if (!s.stateCache[k]) {
const structureSelector = tag !== undefined
? StateSelection.Generators.ofTransformer(StructureRepresentation3D).withTag(tag)
: StateSelection.Generators.ofTransformer(StructureRepresentation3D);
const shapeSelector = tag !== undefined
? StateSelection.Generators.ofTransformer(ShapeRepresentation3D).withTag(tag)
: StateSelection.Generators.ofTransformer(ShapeRepresentation3D);
s.stateCache[k] = [
...plugin.state.data.select(structureSelector).filter(c => c.obj.data.sourceData.elementCount > 0),
...plugin.state.data.select(shapeSelector),
];
}
return s.stateCache[k];
}
function getFilterMatcher(filter) {
return filter.startsWith('"') && filter.endsWith('"')
? new RegExp(`^${escapeRegExp(filter.substring(1, filter.length - 1))}$`, 'g')
: new RegExp(escapeRegExp(filter), 'gi');
}
export function getFilteredEntities(plugin, tag, filter) {
if (!filter)
return getEntities(plugin, tag);
const matcher = getFilterMatcher(filter);
return getEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
}
function _getAllEntities(plugin, tag, list) {
var _a;
list.push(...getEntities(plugin, tag));
for (const g of getGroups(plugin, tag)) {
_getAllEntities(plugin, (_a = g.params) === null || _a === void 0 ? void 0 : _a.values.tag, list);
}
return list;
}
export function getAllEntities(plugin, tag) {
return _getAllEntities(plugin, tag, []);
}
export function getAllFilteredEntities(plugin, tag, filter) {
if (!filter)
return getAllEntities(plugin, tag);
const matcher = getFilterMatcher(filter);
return getAllEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
}
export function getEveryEntity(plugin, filter, tag) {
if (filter) {
const matcher = getFilterMatcher(filter);
return getAllEntities(plugin, tag).filter(c => getEntityLabel(plugin, c).match(matcher) !== null);
}
else {
return getAllEntities(plugin, tag);
}
}
export function getEntityLabel(plugin, cell) {
var _a, _b;
return ((_b = (_a = StateObjectRef.resolve(plugin.state.data, cell.transform.parent)) === null || _a === void 0 ? void 0 : _a.obj) === null || _b === void 0 ? void 0 : _b.label) || 'Entity';
}
export function getCellDescription(cell) {
var _a, _b;
// markdown style for description
return '**' + ((_a = cell === null || cell === void 0 ? void 0 : cell.obj) === null || _a === void 0 ? void 0 : _a.label) + '**\n\n' + ((_b = cell === null || cell === void 0 ? void 0 : cell.obj) === null || _b === void 0 ? void 0 : _b.description);
}
export function getEntityDescription(plugin, cell) {
const s = StateObjectRef.resolve(plugin.state.data, cell.transform.parent);
const d = getCellDescription(s);
return d;
}
export async function updateStyle(plugin, options) {
const update = plugin.state.data.build();
const { ignoreLight, material, celShaded, illustrative } = options;
const entities = getAllEntities(plugin);
for (let j = 0; j < entities.length; ++j) {
update.to(entities[j]).update(old => {
if (old.type) {
const value = old.colorTheme.name === 'illustrative'
? old.colorTheme.params.style.params.value
: old.colorTheme.params.value;
const lightness = old.colorTheme.name === 'illustrative'
? old.colorTheme.params.style.params.lightness
: old.colorTheme.params.lightness;
if (illustrative) {
old.colorTheme = { name: 'illustrative', params: { style: { name: 'uniform', params: { value, lightness } } } };
}
else {
old.colorTheme = { name: 'uniform', params: { value, lightness } };
}
old.type.params.ignoreLight = ignoreLight;
old.type.params.material = material;
old.type.params.celShaded = celShaded;
}
});
}
await update.commit();
}
;
export async function updateColors(plugin, values, tag, filter) {
var _a, _b;
const update = plugin.state.data.build();
const { type, illustrative, value, shift, lightness, alpha, emissive } = values;
if (type === 'group-generate' || type === 'group-uniform') {
const leafGroups = getAllLeafGroups(plugin, tag);
const rootLeafGroups = getRoots(plugin).filter(g => { var _a, _b; return ((_a = g.params) === null || _a === void 0 ? void 0 : _a.values.tag) === tag && getEntities(plugin, (_b = g.params) === null || _b === void 0 ? void 0 : _b.values.tag).length > 0; });
const groups = [...leafGroups, ...rootLeafGroups];
const baseColors = getDistinctBaseColors(groups.length, shift);
for (let i = 0; i < groups.length; ++i) {
const g = groups[i];
const entities = getFilteredEntities(plugin, (_a = g.params) === null || _a === void 0 ? void 0 : _a.values.tag, filter);
let groupColors = [];
if (type === 'group-generate') {
const c = (_b = g.params) === null || _b === void 0 ? void 0 : _b.values.color;
groupColors = getDistinctGroupColors(entities.length, baseColors[i], c.variability, c.shift);
}
for (let j = 0; j < entities.length; ++j) {
const c = type === 'group-generate' ? groupColors[j] : baseColors[i];
update.to(entities[j]).update(old => {
if (old.type) {
if (illustrative) {
old.colorTheme = { name: 'illustrative', params: { style: { name: 'uniform', params: { value: c, lightness: lightness } } } };
}
else {
old.colorTheme = { name: 'uniform', params: { value: c, lightness: lightness } };
}
old.type.params.alpha = alpha;
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
old.type.params.emissive = emissive;
}
else if (old.coloring) {
old.coloring.params.color = c;
old.coloring.params.lightness = lightness;
old.alpha = alpha;
old.xrayShaded = alpha < 1 ? true : false;
old.emissive = emissive;
}
});
}
update.to(g.transform.ref).update(old => {
old.color.type = type === 'group-generate' ? 'generate' : 'uniform';
old.color.illustrative = illustrative;
old.color.value = baseColors[i];
old.color.lightness = lightness;
old.color.alpha = alpha;
old.color.emissive = emissive;
});
}
}
else if (type === 'generate' || type === 'uniform') {
const entities = getAllFilteredEntities(plugin, tag, filter);
let groupColors = [];
if (type === 'generate') {
groupColors = getDistinctBaseColors(entities.length, shift);
}
for (let j = 0; j < entities.length; ++j) {
const c = type === 'generate' ? groupColors[j] : value;
update.to(entities[j]).update(old => {
if (old.type) {
if (illustrative) {
old.colorTheme = { name: 'illustrative', params: { style: { name: 'uniform', params: { value: c, lightness: lightness } } } };
}
else {
old.colorTheme = { name: 'uniform', params: { value: c, lightness: lightness } };
}
old.type.params.alpha = alpha;
old.type.params.xrayShaded = alpha < 1 ? 'inverted' : false;
old.type.params.emissive = emissive;
}
else if (old.coloring) {
old.coloring.params.color = c;
old.coloring.params.lightness = lightness;
old.alpha = alpha;
old.xrayShaded = alpha < 1 ? true : false;
old.emissive = emissive;
}
});
}
const others = getAllLeafGroups(plugin, tag);
for (const o of others) {
update.to(o).update(old => {
old.color.type = type === 'generate' ? 'custom' : 'uniform';
old.color.illustrative = illustrative;
old.color.value = value;
old.color.lightness = lightness;
old.color.alpha = alpha;
old.color.emissive = emissive;
});
}
}
await update.commit();
}
;
export function expandAllGroups(plugin) {
for (const g of getAllGroups(plugin)) {
if (g.state.isCollapsed) {
plugin.state.data.updateCellState(g.transform.ref, { isCollapsed: false });
}
}
}
;