Skip to content
Snippets Groups Projects
Unverified Commit 6db96001 authored by Dominik Tichy's avatar Dominik Tichy Committed by GitHub
Browse files

Partial atomic charges extension (#808)

* feat: partial charges extension

* chore: pullrequest actions

* feat: example

* fix: review changes

* fix: updated example structure categories

* fix(changelog): shorter description

* fix: code review changes

* fix: cosmetic changes
parent 557bf63b
No related branches found
No related tags found
No related merge requests found
...@@ -9,6 +9,8 @@ Note that since we don't clearly distinguish between a public and private interf ...@@ -9,6 +9,8 @@ Note that since we don't clearly distinguish between a public and private interf
- Add a uniform color theme for NtC tube that still paints residue and segment dividers in a different color - Add a uniform color theme for NtC tube that still paints residue and segment dividers in a different color
- Fix bond assignments `struct_conn` records referencing waters - Fix bond assignments `struct_conn` records referencing waters
- Fix `PluginState.setSnapshot` triggering unnecessary state updates - Fix `PluginState.setSnapshot` triggering unnecessary state updates
- Add `SbNcbrPartialCharges` extension for coloring and labeling atoms and residues by partial atomic charges
- uses custom mmcif categories `_sb_ncbr_partial_atomic_charges_meta` and `_sb_ncbr_partial_atomic_charges` (more info in [README.md](./src/extensions/sb-ncbr/README.md))
## [v3.34.0] - 2023-04-16 ## [v3.34.0] - 2023-04-16
......
Source diff could not be displayed: it is too large. Options to address this: view the blob.
...@@ -97,7 +97,8 @@ ...@@ -97,7 +97,8 @@
"Jason Pattle <jpattle@exscientia.co.uk>", "Jason Pattle <jpattle@exscientia.co.uk>",
"David Williams <dwilliams@nobiastx.com>", "David Williams <dwilliams@nobiastx.com>",
"Zhenyu Zhang <jump2cn@gmail.com>", "Zhenyu Zhang <jump2cn@gmail.com>",
"Russell Parker <russell@benchling.com>" "Russell Parker <russell@benchling.com>",
"Dominik Tichy <tichydominik451@gmail.com>"
], ],
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
......
...@@ -48,6 +48,7 @@ import '../../mol-util/polyfill'; ...@@ -48,6 +48,7 @@ import '../../mol-util/polyfill';
import { ObjectKeys } from '../../mol-util/type-helpers'; import { ObjectKeys } from '../../mol-util/type-helpers';
import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants'; import { SaccharideCompIdMapType } from '../../mol-model/structure/structure/carbohydrates/constants';
import { Backgrounds } from '../../extensions/backgrounds'; import { Backgrounds } from '../../extensions/backgrounds';
import { SbNcbrPartialCharges, SbNcbrPartialChargesPreset, SbNcbrPartialChargesPropertyProvider } from '../../extensions/sb-ncbr';
export { PLUGIN_VERSION as version } from '../../mol-plugin/version'; export { PLUGIN_VERSION as version } from '../../mol-plugin/version';
export { setDebugMode, setProductionMode, setTimingMode, consoleStats } from '../../mol-util/debug'; export { setDebugMode, setProductionMode, setTimingMode, consoleStats } from '../../mol-util/debug';
...@@ -71,6 +72,7 @@ const Extensions = { ...@@ -71,6 +72,7 @@ const Extensions = {
'geo-export': PluginSpec.Behavior(GeometryExport), 'geo-export': PluginSpec.Behavior(GeometryExport),
'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment), 'ma-quality-assessment': PluginSpec.Behavior(MAQualityAssessment),
'zenodo-import': PluginSpec.Behavior(ZenodoImport), 'zenodo-import': PluginSpec.Behavior(ZenodoImport),
'sb-ncbr-partial-charges': PluginSpec.Behavior(SbNcbrPartialCharges),
}; };
const DefaultViewerOptions = { const DefaultViewerOptions = {
...@@ -503,6 +505,8 @@ export const ViewerAutoPreset = StructureRepresentationPresetProvider({ ...@@ -503,6 +505,8 @@ export const ViewerAutoPreset = StructureRepresentationPresetProvider({
return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin); return await QualityAssessmentPLDDTPreset.apply(ref, params, plugin);
} else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) { } else if (!!structure.models.some(m => QualityAssessment.isApplicable(m, 'qmean'))) {
return await QualityAssessmentQmeanPreset.apply(ref, params, plugin); return await QualityAssessmentQmeanPreset.apply(ref, params, plugin);
} else if (!!structure.models.some(m => SbNcbrPartialChargesPropertyProvider.isApplicable(m))) {
return await SbNcbrPartialChargesPreset.apply(ref, params, plugin);
} else { } else {
return await PresetStructureRepresentations.auto.apply(ref, params, plugin); return await PresetStructureRepresentations.auto.apply(ref, params, plugin);
} }
......
# SB NCBR extensions
## Partial charges
This extension visualizes partial atomic charges for atoms and residues. The extension reads charge data of a structure from a mmcif file and displays them as a color gradient on the atoms/residues. The coloring uses two gradients: one for positive charges (white-to-blue) and one for negative charges (red-to-white). The color is interpolated between the appropriate gradient based on the charge value. The extension also displays the charge values in the description label when an atom/residue is selected.
### How to use
To visualize partial charges, you need to provide a mmcif file with the structure and its charges. The charges are stored under the following categories:
```
_sb_ncbr_partial_atomic_charges_meta.id # id of the charges (e.g. 1)
_sb_ncbr_partial_atomic_charges_meta.type # type of the charges (optional, e.g. 'empirical')
_sb_ncbr_partial_atomic_charges_meta.method # calculation method name (e.g. 'QEq', 'SQE+qp/Schindler 2021 (PUB_pept)')
_sb_ncbr_partial_atomic_charges.type_id # id of the charges (pointer to _sb_ncbr_partial_atomic_charges_meta.id)
_sb_ncbr_partial_atomic_charges.atom_id # atom id (pointer to _atom_site.id)
_sb_ncbr_partial_atomic_charges.charge # partial atomic charge
```
> Note that the mmcif item `_partial_atomic_charges_meta.method` is used as a description of the charge set in the UI (described in *Controls*).
The extension will automatically read the charges from the mmcif file and color the structure accordingly.
### Controls
The extension provides controls for setting the color gradient range and for selecting charge type (atom charges or residue charges).
These controls are available in Color Theme settings for 3D Representation cells in the State Tree UI.
There is also a dropdown menu for switching between charge sets.
These controls are available in Custom Model Properties settings for Model cell in the State Tree UI.
export { SbNcbrPartialCharges } from './partial-charges/behavior';
export { SbNcbrPartialChargesPreset } from './partial-charges/preset';
export { SbNcbrPartialChargesPropertyProvider } from './partial-charges/property';
\ No newline at end of file
import { LociLabelProvider } from '../../../mol-plugin-state/manager/loci-label';
import { PluginBehavior } from '../../../mol-plugin/behavior';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { SbNcbrPartialChargesColorThemeProvider } from './color';
import { SbNcbrPartialChargesPropertyProvider } from './property';
import { SbNcbrPartialChargesLociLabelProvider } from './labels';
import { SbNcbrPartialChargesPreset } from './preset';
export const SbNcbrPartialCharges = PluginBehavior.create<{ autoAttach: boolean; showToolTip: boolean }>({
name: 'sb-ncbr-partial-charges',
category: 'misc',
display: {
name: 'SB NCBR Partial Charges',
},
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean; showToolTip: boolean }> {
private SbNcbrPartialChargesLociLabelProvider: LociLabelProvider = SbNcbrPartialChargesLociLabelProvider(
this.ctx
);
register(): void {
this.ctx.customModelProperties.register(SbNcbrPartialChargesPropertyProvider, this.params.autoAttach);
this.ctx.representation.structure.themes.colorThemeRegistry.add(SbNcbrPartialChargesColorThemeProvider);
this.ctx.managers.lociLabels.addProvider(this.SbNcbrPartialChargesLociLabelProvider);
this.ctx.builders.structure.representation.registerPreset(SbNcbrPartialChargesPreset);
}
unregister() {
this.ctx.customModelProperties.unregister(SbNcbrPartialChargesPropertyProvider.descriptor.name);
this.ctx.representation.structure.themes.colorThemeRegistry.remove(SbNcbrPartialChargesColorThemeProvider);
this.ctx.managers.lociLabels.removeProvider(this.SbNcbrPartialChargesLociLabelProvider);
this.ctx.builders.structure.representation.unregisterPreset(SbNcbrPartialChargesPreset);
}
},
params: () => ({
autoAttach: PD.Boolean(true),
showToolTip: PD.Boolean(true),
}),
});
import { Bond, StructureElement, StructureProperties, Unit } from '../../../mol-model/structure';
import { ColorTheme, LocationColor } from '../../../mol-theme/color';
import { ThemeDataContext } from '../../../mol-theme/theme';
import { Color } from '../../../mol-util/color';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Location } from '../../../mol-model/location';
import { SbNcbrPartialChargesPropertyProvider } from './property';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
const Colors = {
Bond: Color(0xffffff),
Error: Color(0x00ff00),
MissingCharge: Color(0xffffff),
Negative: Color(0xff0000),
Zero: Color(0xffffff),
Positive: Color(0x0000ff),
getColor: (charge: number, maxCharge: number): Color => {
if (charge === 0) return Colors.Zero;
if (charge <= -maxCharge) return Colors.Negative;
if (charge >= maxCharge) return Colors.Positive;
const t = maxCharge !== 0 ? Math.abs(charge) / maxCharge : 1;
const endColor = charge < 0 ? Colors.Negative : Colors.Positive;
return Color.interpolate(Colors.Zero, endColor, t);
},
};
export const PartialChargesThemeParams = {
maxAbsoluteCharge: PD.Numeric(
0,
{ min: 0 },
{
label: 'Charge Range',
}
),
absolute: PD.Boolean(false, { isHidden: false, label: 'Use Range' }),
chargeType: PD.Select(
'residue',
[
['atom', 'Atom charges'],
['residue', 'Residue charges'],
],
{ isHidden: false }
),
};
export type PartialChargesThemeParams = typeof PartialChargesThemeParams;
export function getPartialChargesThemeParams() {
return PD.clone(PartialChargesThemeParams);
}
export function PartialChargesColorTheme(
ctx: ThemeDataContext,
props: PD.Values<PartialChargesThemeParams>
): ColorTheme<PartialChargesThemeParams> {
const model = ctx.structure?.models[0];
if (!model) {
throw new Error('No model found');
}
const data = SbNcbrPartialChargesPropertyProvider.get(model).value;
if (!data) {
throw new Error('No partial charges data found');
}
const { absolute, chargeType } = props;
const { typeIdToAtomIdToCharge, typeIdToResidueToCharge, maxAbsoluteAtomCharges, maxAbsoluteResidueCharges } = data;
const typeId = SbNcbrPartialChargesPropertyProvider.props(model).typeId;
const atomToCharge = typeIdToAtomIdToCharge.get(typeId);
const residueToCharge = typeIdToResidueToCharge.get(typeId);
let maxCharge = 0;
if (absolute) {
maxCharge = props.maxAbsoluteCharge < 0 ? 0 : props.maxAbsoluteCharge;
} else if (chargeType === 'atom') {
maxCharge = maxAbsoluteAtomCharges.get(typeId) || 0;
} else {
maxCharge = maxAbsoluteResidueCharges.get(typeId) || 0;
}
// forces coloring updates
const contextHash = SbNcbrPartialChargesPropertyProvider.get(model)?.version;
const chargeMap = chargeType === 'atom' ? atomToCharge : residueToCharge;
let color: LocationColor;
if (!chargeMap) {
color = (_: Location): Color => Colors.MissingCharge;
} else {
color = (location: Location): Color => {
let id = -1;
if (StructureElement.Location.is(location)) {
if (Unit.isAtomic(location.unit)) {
id = StructureProperties.atom.id(location);
}
} else if (Bond.isLocation(location)) {
if (Unit.isAtomic(location.aUnit)) {
const l = StructureElement.Location.create(ctx.structure?.root);
l.unit = location.aUnit;
l.element = location.aUnit.elements[location.aIndex];
id = StructureProperties.atom.id(l);
}
}
const charge = chargeMap.get(id);
if (charge === undefined) {
console.warn('No charge found for id', id);
return Colors.MissingCharge;
}
return Colors.getColor(charge, maxCharge);
};
}
return {
factory: PartialChargesColorTheme,
granularity: 'group',
color,
props,
description: 'Color atoms and residues based on their partial charge.',
preferSmoothing: false,
contextHash,
};
}
export const SbNcbrPartialChargesColorThemeProvider: ColorTheme.Provider<
PartialChargesThemeParams,
'sb-ncbr-partial-charges'
> = {
label: 'SB NCBR Partial Charges',
name: 'sb-ncbr-partial-charges',
category: ColorTheme.Category.Atom,
factory: PartialChargesColorTheme,
getParams: getPartialChargesThemeParams,
defaultValues: PD.getDefaultValues(PartialChargesThemeParams),
isApplicable: (ctx: ThemeDataContext) =>
!!ctx.structure &&
ctx.structure.models.some((model) => SbNcbrPartialChargesPropertyProvider.isApplicable(model)),
ensureCustomProperties: {
attach: (ctx: CustomProperty.Context, data: ThemeDataContext) =>
data.structure
? SbNcbrPartialChargesPropertyProvider.attach(ctx, data.structure.models[0], void 0, true)
: Promise.resolve(),
detach: (data) => data.structure && SbNcbrPartialChargesPropertyProvider.ref(data.structure.models[0], false),
},
};
import { StructureElement, StructureProperties } from '../../../mol-model/structure';
import { LociLabel } from '../../../mol-plugin-state/manager/loci-label';
import { SbNcbrPartialChargesPropertyProvider } from './property';
import { Loci } from '../../../mol-model/loci';
import { PluginContext } from '../../../mol-plugin/context';
import { LociLabelProvider } from '../../../mol-plugin-state/manager/loci-label';
export function SbNcbrPartialChargesLociLabelProvider(ctx: PluginContext): LociLabelProvider {
return {
label: (loci: Loci) => {
if (!StructureElement.Loci.is(loci)) return;
const model = loci.structure.model;
const data = SbNcbrPartialChargesPropertyProvider.get(model).value;
if (!data) return;
const loc = StructureElement.Loci.getFirstLocation(loci);
if (!loc) return;
const granularity = ctx.managers.interactivity.props.granularity;
if (granularity !== 'element' && granularity !== 'residue') {
return;
}
const atomId = StructureProperties.atom.id(loc);
const { typeIdToAtomIdToCharge, typeIdToResidueToCharge } = data;
const typeId = SbNcbrPartialChargesPropertyProvider.props(model).typeId;
const showResidueCharge = granularity === 'residue';
const charge = showResidueCharge
? typeIdToResidueToCharge.get(typeId)?.get(atomId)
: typeIdToAtomIdToCharge.get(typeId)?.get(atomId);
const label = granularity === 'residue' ? 'Residue charge' : 'Atom charge';
return `<strong>${label}: ${charge?.toFixed(4) || 'undefined'}</strong>`;
},
group: (label: LociLabel): string => (label as string).toString().replace(/Model [0-9]+/g, 'Models'),
};
}
import {
PresetStructureRepresentations,
StructureRepresentationPresetProvider,
} from '../../../mol-plugin-state/builder/structure/representation-preset';
import { StateObjectRef } from '../../../mol-state';
import { SbNcbrPartialChargesPropertyProvider } from './property';
import { SbNcbrPartialChargesColorThemeProvider } from './color';
export const SbNcbrPartialChargesPreset = StructureRepresentationPresetProvider({
id: 'sb-ncbr-partial-charges-preset',
display: {
name: 'SB NCBR Partial Charges',
group: 'Annotation',
description: 'Color atoms and residues based on their partial charge.',
},
isApplicable(a) {
return !!a.data.models.some((m) => SbNcbrPartialChargesPropertyProvider.isApplicable(m));
},
params: () => StructureRepresentationPresetProvider.CommonParams,
async apply(ref, params, plugin) {
const structureCell = StateObjectRef.resolveAndCheck(plugin.state.data, ref);
const structure = structureCell?.obj?.data;
if (!structureCell || !structure) return {};
const colorTheme = SbNcbrPartialChargesColorThemeProvider.name as any;
return PresetStructureRepresentations.auto.apply(
ref,
{ ...params, theme: { globalName: colorTheme, focus: { name: colorTheme, params: { chargeType: 'atom' } } } },
plugin
);
},
});
import { Model } from '../../../mol-model/structure';
import { CustomProperty } from '../../../mol-model-props/common/custom-property';
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
import { CustomPropertyDescriptor } from '../../../mol-model/custom-property';
import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property';
import { arrayMinMax } from '../../../mol-util/array';
type TypeId = number;
type IdToCharge = Map<number, number>;
export interface SBNcbrPartialChargeData {
typeIdToMethod: Map<TypeId, string>;
typeIdToAtomIdToCharge: Map<TypeId, IdToCharge>;
typeIdToResidueToCharge: Map<TypeId, IdToCharge>;
maxAbsoluteAtomCharges: IdToCharge;
maxAbsoluteResidueCharges: IdToCharge;
maxAbsoluteAtomChargeAll: number;
}
const PartialChargesPropertyParams = {
typeId: PD.Select<number>(0, [[0, '0']]),
};
type PartialChargesPropertyParams = typeof PartialChargesPropertyParams;
const defaultPartialChargesPropertyParams = PD.clone(PartialChargesPropertyParams);
function getParams(model: Model) {
const typeIdToMethod = getTypeIdToMethod(model);
const options = Array.from(typeIdToMethod.entries()).map(
([typeId, method]) => [typeId, method] as [number, string]
);
return {
typeId: PD.Select<number>(1, options),
};
}
async function getData(model: Model): Promise<CustomProperty.Data<SBNcbrPartialChargeData | undefined>> {
if (!SbNcbrPartialChargesPropertyProvider.isApplicable(model)) return { value: undefined };
const typeIdToMethod = getTypeIdToMethod(model);
const typeIdToAtomIdToCharge = getTypeIdToAtomIdToCharge(model);
const typeIdToResidueToCharge = getTypeIdToResidueIdToCharge(model, typeIdToAtomIdToCharge);
const maxAbsoluteAtomCharges = getMaxAbsoluteCharges(typeIdToAtomIdToCharge);
const maxAbsoluteResidueCharges = getMaxAbsoluteCharges(typeIdToResidueToCharge);
const maxAbsoluteAtomChargeAll = getMaxAbsoluteAtomChargeAll(maxAbsoluteAtomCharges, maxAbsoluteResidueCharges);
return {
value: {
typeIdToMethod,
typeIdToAtomIdToCharge,
typeIdToResidueToCharge,
maxAbsoluteAtomCharges,
maxAbsoluteResidueCharges,
maxAbsoluteAtomChargeAll,
},
};
}
function getTypeIdToMethod(model: Model) {
const typeIdToMethod: SBNcbrPartialChargeData['typeIdToMethod'] = new Map();
const sourceData = model.sourceData as MmcifFormat;
const rowCount = sourceData.data.frame.categories.sb_ncbr_partial_atomic_charges_meta.rowCount;
const typeIds = sourceData.data.frame.categories.sb_ncbr_partial_atomic_charges_meta.getField('id');
const methods = sourceData.data.frame.categories.sb_ncbr_partial_atomic_charges_meta.getField('method');
if (!typeIds || !methods) {
return typeIdToMethod;
}
for (let i = 0; i < rowCount; ++i) {
const typeId = typeIds.int(i);
const method = methods.str(i);
typeIdToMethod.set(typeId, method);
}
return typeIdToMethod;
}
function getTypeIdToAtomIdToCharge(model: Model): SBNcbrPartialChargeData['typeIdToAtomIdToCharge'] {
const atomIdToCharge: SBNcbrPartialChargeData['typeIdToAtomIdToCharge'] = new Map();
const sourceData = model.sourceData as MmcifFormat;
const rowCount = sourceData.data.frame.categories.sb_ncbr_partial_atomic_charges.rowCount;
const typeIds = sourceData.data.frame.categories.sb_ncbr_partial_atomic_charges.getField('type_id');
const atomIds = sourceData.data.frame.categories.sb_ncbr_partial_atomic_charges.getField('atom_id');
const charges = sourceData.data.frame.categories.sb_ncbr_partial_atomic_charges.getField('charge');
if (!typeIds || !atomIds || !charges) return atomIdToCharge;
for (let i = 0; i < rowCount; ++i) {
const typeId = typeIds.int(i);
const atomId = atomIds.int(i);
const charge = charges.float(i);
if (!atomIdToCharge.has(typeId)) atomIdToCharge.set(typeId, new Map());
atomIdToCharge.get(typeId)?.set(atomId, charge);
}
return atomIdToCharge;
}
function getTypeIdToResidueIdToCharge(
model: Model,
typeIdToAtomIdToCharge: SBNcbrPartialChargeData['typeIdToAtomIdToCharge']
) {
const { offsets, count } = model.atomicHierarchy.residueAtomSegments;
const { atomId: atomIds } = model.atomicConformation;
const residueToCharge: SBNcbrPartialChargeData['typeIdToResidueToCharge'] = new Map();
typeIdToAtomIdToCharge.forEach((atomIdToCharge, typeId: number) => {
if (!residueToCharge.has(typeId)) residueToCharge.set(typeId, new Map());
const residueCharges = residueToCharge.get(typeId)!;
for (let rI = 0; rI < count; rI++) {
let charge = 0;
for (let aI = offsets[rI], _aI = offsets[rI + 1]; aI < _aI; aI++) {
const atom_id = atomIds.value(aI);
charge += atomIdToCharge.get(atom_id) || 0;
}
for (let aI = offsets[rI], _aI = offsets[rI + 1]; aI < _aI; aI++) {
const atom_id = atomIds.value(aI);
residueCharges.set(atom_id, charge);
}
}
});
return residueToCharge;
}
function getMaxAbsoluteCharges(
typeIdToCharge: SBNcbrPartialChargeData['typeIdToAtomIdToCharge']
): SBNcbrPartialChargeData['maxAbsoluteAtomCharges'];
function getMaxAbsoluteCharges(
typeIdToCharge: SBNcbrPartialChargeData['typeIdToResidueToCharge']
): SBNcbrPartialChargeData['maxAbsoluteResidueCharges'] {
const maxAbsoluteCharges: Map<number, number> = new Map();
typeIdToCharge.forEach((idToCharge, typeId) => {
const charges = Array.from(idToCharge.values());
const [min, max] = arrayMinMax(charges);
const bound = Math.max(Math.abs(min), max);
maxAbsoluteCharges.set(typeId, bound);
});
return maxAbsoluteCharges;
}
function getMaxAbsoluteAtomChargeAll(
maxAbsoluteAtomCharges: SBNcbrPartialChargeData['maxAbsoluteAtomCharges'],
maxAbsoluteResidueCharges: SBNcbrPartialChargeData['maxAbsoluteResidueCharges']
): number {
let maxAbsoluteCharge = 0;
maxAbsoluteAtomCharges.forEach((_, typeId) => {
const maxCharge = maxAbsoluteAtomCharges.get(typeId) || 0;
if (maxCharge > maxAbsoluteCharge) maxAbsoluteCharge = maxCharge;
});
maxAbsoluteResidueCharges.forEach((_, typeId) => {
const maxCharge = maxAbsoluteResidueCharges.get(typeId) || 0;
if (maxCharge > maxAbsoluteCharge) maxAbsoluteCharge = maxCharge;
});
return maxAbsoluteCharge;
}
export function hasPartialChargesCategories(model: Model): boolean {
if (!model || !MmcifFormat.is(model.sourceData)) return false;
const names = model.sourceData.data.frame.categoryNames;
return (
names.includes('atom_site') &&
names.includes('sb_ncbr_partial_atomic_charges') &&
names.includes('sb_ncbr_partial_atomic_charges_meta')
);
}
export const SbNcbrPartialChargesPropertyProvider: CustomModelProperty.Provider<
PartialChargesPropertyParams,
SBNcbrPartialChargeData | undefined
> = CustomModelProperty.createProvider({
label: 'SB NCBR Partial Charges Property Provider',
descriptor: CustomPropertyDescriptor({
name: 'sb-ncbr-property-provider',
}),
type: 'static',
defaultParams: defaultPartialChargesPropertyParams,
getParams: (data: Model) => getParams(data),
isApplicable: (model: Model) => hasPartialChargesCategories(model),
obtain: (_ctx: CustomProperty.Context, model: Model) => getData(model),
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment