molstar
Version:
A comprehensive macromolecular library.
259 lines (258 loc) • 11.8 kB
JavaScript
/**
* Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.AssemblySymmetryProvider = exports.AssemblySymmetryParams = exports.AssemblySymmetryDataProvider = exports.AssemblySymmetryDataParams = exports.AssemblySymmetryData = void 0;
exports.isBiologicalAssembly = isBiologicalAssembly;
exports.getSymmetrySelectParam = getSymmetrySelectParam;
const param_definition_1 = require("../../mol-util/param-definition");
const structure_1 = require("../../mol-model/structure");
const db_1 = require("../../mol-data/db");
const graphql_client_1 = require("../../mol-util/graphql-client");
const custom_structure_property_1 = require("../../mol-model-props/common/custom-structure-property");
const mmcif_1 = require("../../mol-model-formats/structure/mmcif");
const set_1 = require("../../mol-util/set");
const builder_1 = require("../../mol-script/language/builder");
const compiler_1 = require("../../mol-script/runtime/query/compiler");
const custom_property_1 = require("../../mol-model/custom-property");
const assets_1 = require("../../mol-util/assets");
const rcsb_symmetry_gql = /* GraphQL */ `
query AssemblySymmetry($assembly_id: String!, $entry_id: String!) {
assembly(assembly_id: $assembly_id, entry_id: $entry_id) {
rcsb_struct_symmetry {
clusters {
avg_rmsd
members {
asym_id
pdbx_struct_oper_list_ids
}
}
kind
oligomeric_state
rotation_axes {
order
start
end
}
stoichiometry
symbol
type
}
}
}
`;
const BiologicalAssemblyNames = new Set([
'author_and_software_defined_assembly',
'author_defined_assembly',
'complete icosahedral assembly',
'complete point assembly',
'representative helical assembly',
'software_defined_assembly'
]);
function isBiologicalAssembly(structure) {
var _a;
if (!mmcif_1.MmcifFormat.is(structure.models[0].sourceData))
return false;
const mmcif = structure.models[0].sourceData.data.db;
if (!mmcif.pdbx_struct_assembly.details.isDefined)
return false;
const id = ((_a = structure.units[0].conformation.operator.assembly) === null || _a === void 0 ? void 0 : _a.id) || '';
if (id === '')
return true;
const indices = db_1.Column.indicesOf(mmcif.pdbx_struct_assembly.id, e => e === id);
if (indices.length !== 1)
return false;
const details = mmcif.pdbx_struct_assembly.details.value(indices[0]);
return BiologicalAssemblyNames.has(details);
}
var AssemblySymmetryData;
(function (AssemblySymmetryData) {
let Tag;
(function (Tag) {
Tag["Cluster"] = "assembly-symmetry-cluster";
Tag["Representation"] = "assembly-symmetry-3d";
})(Tag = AssemblySymmetryData.Tag || (AssemblySymmetryData.Tag = {}));
AssemblySymmetryData.DefaultServerUrl = 'https://data.rcsb.org/graphql'; // Alternative: 'https://www.ebi.ac.uk/pdbe/aggregated-api/pdb/symmetry' (if serverType is 'pdbe')
function isApplicable(structure) {
if (!structure || structure.models.length !== 1 || !structure_1.Model.hasPdbId(structure.models[0]))
return false;
return isBiologicalAssembly(structure) || structure_1.Model.isIntegrative(structure.models[0]);
}
AssemblySymmetryData.isApplicable = isApplicable;
async function fetch(ctx, structure, props) {
if (!isApplicable(structure))
return { value: [] };
if (props.serverType === 'pdbe')
return fetchPDBe(ctx, structure, props);
else
return fetchRCSB(ctx, structure, props);
}
AssemblySymmetryData.fetch = fetch;
async function fetchRCSB(ctx, structure, props) {
var _a, _b;
const client = new graphql_client_1.GraphQLClient(props.serverUrl, ctx.assetManager);
const variables = {
assembly_id: ((_a = structure.units[0].conformation.operator.assembly) === null || _a === void 0 ? void 0 : _a.id) || 'deposited', // data.rcsb.org guarantees assembly 'deposited' to be present for IHM
entry_id: structure.units[0].model.entryId
};
const result = await client.request(ctx.runtime, rcsb_symmetry_gql, variables);
let value = [];
if (!((_b = result.data.assembly) === null || _b === void 0 ? void 0 : _b.rcsb_struct_symmetry)) {
console.error('expected `rcsb_struct_symmetry` field');
}
else {
value = result.data.assembly.rcsb_struct_symmetry;
}
return { value, assets: [result] };
}
AssemblySymmetryData.fetchRCSB = fetchRCSB;
async function fetchPDBe(ctx, structure, props) {
var _a, _b;
const assembly_id = ((_a = structure.units[0].conformation.operator.assembly) === null || _a === void 0 ? void 0 : _a.id) || '-1'; // should use '' instead of '-1' but the API does not support non-number assembly_id
const entry_id = structure.units[0].model.entryId.toLowerCase();
const url = `${props.serverUrl}/${entry_id}?assembly_id=${assembly_id}`;
const asset = assets_1.Asset.getUrlAsset(ctx.assetManager, url);
let dataWrapper;
try {
dataWrapper = await ctx.assetManager.resolve(asset, 'json').runInContext(ctx.runtime);
}
catch (err) {
// PDBe API returns 404 when there are no symmetries -> treat as success with empty json in body
if (`${err}`.includes('404')) { // dirrrty
dataWrapper = assets_1.Asset.Wrapper({}, asset, ctx.assetManager);
}
else {
throw err;
}
}
const data = dataWrapper.data;
const value = ((_b = data[entry_id]) !== null && _b !== void 0 ? _b : []).map((v) => ({
kind: 'Global Symmetry',
oligomeric_state: v.oligomeric_state,
stoichiometry: [v.stoichiometry],
symbol: v.symbol,
type: v.type,
clusters: [],
rotation_axes: v.rotation_axes,
}));
return { value, assets: [dataWrapper] };
}
/** Returns the index of the first non C1 symmetry or -1 */
function firstNonC1(assemblySymmetryData) {
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
if (assemblySymmetryData[i].symbol !== 'C1')
return i;
}
return -1;
}
AssemblySymmetryData.firstNonC1 = firstNonC1;
function isRotationAxes(x) {
return !!x && x.length > 0;
}
AssemblySymmetryData.isRotationAxes = isRotationAxes;
function getAsymIds(assemblySymmetry) {
const asymIds = new Set();
for (const c of assemblySymmetry.clusters) {
if (!(c === null || c === void 0 ? void 0 : c.members))
continue;
for (const m of c.members) {
if (m === null || m === void 0 ? void 0 : m.asym_id)
asymIds.add(m.asym_id);
}
}
return set_1.SetUtils.toArray(asymIds);
}
AssemblySymmetryData.getAsymIds = getAsymIds;
function getAsymIdsStructure(structure, asymIds) {
const query = builder_1.MolScriptBuilder.struct.modifier.union([
builder_1.MolScriptBuilder.struct.generator.atomGroups({
'chain-test': builder_1.MolScriptBuilder.core.set.has([builder_1.MolScriptBuilder.set(...asymIds), builder_1.MolScriptBuilder.ammp('label_asym_id')])
})
]);
const compiled = (0, compiler_1.compile)(query);
const result = compiled(new structure_1.QueryContext(structure));
return structure_1.StructureSelection.unionStructure(result);
}
/** Returns structure limited to all cluster member chains */
function getStructure(structure, assemblySymmetry) {
const asymIds = AssemblySymmetryData.getAsymIds(assemblySymmetry);
return asymIds.length > 0 ? getAsymIdsStructure(structure, asymIds) : structure;
}
AssemblySymmetryData.getStructure = getStructure;
})(AssemblySymmetryData || (exports.AssemblySymmetryData = AssemblySymmetryData = {}));
function getSymmetrySelectParam(structure) {
const param = param_definition_1.ParamDefinition.Select(0, [[0, 'First Symmetry']]);
if (structure) {
const assemblySymmetryData = exports.AssemblySymmetryDataProvider.get(structure).value;
if (assemblySymmetryData) {
const options = [
[-1, 'Off']
];
for (let i = 0, il = assemblySymmetryData.length; i < il; ++i) {
const { symbol, kind } = assemblySymmetryData[i];
if (symbol !== 'C1') {
options.push([i, `${i + 1}: ${symbol} ${kind}`]);
}
}
if (options.length > 1) {
param.options = options;
param.defaultValue = options[1][0];
}
else {
options.length = 0;
}
}
}
return param;
}
//
exports.AssemblySymmetryDataParams = {
serverType: param_definition_1.ParamDefinition.Select('rcsb', [['rcsb', 'RCSB'], ['pdbe', 'PDBe']]),
serverUrl: param_definition_1.ParamDefinition.Text(AssemblySymmetryData.DefaultServerUrl, { description: 'GraphQL endpoint URL (if server type is RCSB) or PDBe API endpoint URL (if server type is PDBe)' })
};
exports.AssemblySymmetryDataProvider = custom_structure_property_1.CustomStructureProperty.createProvider({
label: 'Assembly Symmetry Data',
descriptor: (0, custom_property_1.CustomPropertyDescriptor)({
name: 'molstar_struct_symmetry_data',
// TODO `cifExport` and `symbol`
}),
type: 'root',
defaultParams: exports.AssemblySymmetryDataParams,
getParams: (data) => exports.AssemblySymmetryDataParams,
isApplicable: (data) => AssemblySymmetryData.isApplicable(data),
obtain: async (ctx, data, props) => {
const p = { ...param_definition_1.ParamDefinition.getDefaultValues(exports.AssemblySymmetryDataParams), ...props };
return await AssemblySymmetryData.fetch(ctx, data, p);
}
});
//
function getAssemblySymmetryParams(data) {
return {
...exports.AssemblySymmetryDataParams,
symmetryIndex: getSymmetrySelectParam(data)
};
}
exports.AssemblySymmetryParams = getAssemblySymmetryParams();
exports.AssemblySymmetryProvider = custom_structure_property_1.CustomStructureProperty.createProvider({
label: 'Assembly Symmetry',
descriptor: (0, custom_property_1.CustomPropertyDescriptor)({
name: 'molstar_struct_symmetry',
// TODO `cifExport` and `symbol`
}),
type: 'root',
defaultParams: exports.AssemblySymmetryParams,
getParams: getAssemblySymmetryParams,
isApplicable: (data) => AssemblySymmetryData.isApplicable(data),
obtain: async (ctx, data, props) => {
const p = { ...param_definition_1.ParamDefinition.getDefaultValues(getAssemblySymmetryParams(data)), ...props };
await exports.AssemblySymmetryDataProvider.attach(ctx, data, p);
const assemblySymmetryData = exports.AssemblySymmetryDataProvider.get(data).value;
const assemblySymmetry = assemblySymmetryData === null || assemblySymmetryData === void 0 ? void 0 : assemblySymmetryData[p.symmetryIndex];
if (!assemblySymmetry)
new Error(`No assembly symmetry found for index ${p.symmetryIndex}`);
return { value: assemblySymmetry };
}
});
;