model.ts 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. /**
  2. * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author Ludovic Autin <ludovic.autin@gmail.com>
  6. */
  7. import { StateAction, StateBuilder, StateTransformer, State } from '../../mol-state';
  8. import { PluginContext } from '../../mol-plugin/context';
  9. import { PluginStateObject as PSO } from '../../mol-plugin-state/objects';
  10. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  11. import { Ingredient, CellPacking, CompartmentPrimitives } from './data';
  12. import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile, getStructureMean, getFromOPM } from './util';
  13. import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit, Trajectory } from '../../mol-model/structure';
  14. import { trajectoryFromMmCIF, MmcifFormat } from '../../mol-model-formats/structure/mmcif';
  15. import { trajectoryFromPDB } from '../../mol-model-formats/structure/pdb';
  16. import { Mat4, Vec3, Quat } from '../../mol-math/linear-algebra';
  17. import { SymmetryOperator } from '../../mol-math/geometry';
  18. import { Task, RuntimeContext } from '../../mol-task';
  19. import { StateTransforms } from '../../mol-plugin-state/transforms';
  20. import { ParseCellPack, StructureFromCellpack, DefaultCellPackBaseUrl, StructureFromAssemblies, CreateCompartmentSphere } from './state';
  21. import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
  22. import { getMatFromResamplePoints } from './curve';
  23. import { compile } from '../../mol-script/runtime/query/compiler';
  24. import { CifCategory, CifField } from '../../mol-io/reader/cif';
  25. import { mmCIF_Schema } from '../../mol-io/reader/cif/schema/mmcif';
  26. import { Column } from '../../mol-data/db';
  27. import { createModels } from '../../mol-model-formats/structure/basic/parser';
  28. import { CellpackPackingPreset, CellpackMembranePreset } from './preset';
  29. import { Asset } from '../../mol-util/assets';
  30. import { Color } from '../../mol-util/color';
  31. import { objectForEach } from '../../mol-util/object';
  32. import { readFromFile } from '../../mol-util/data-source';
  33. import { ColorNames } from '../../mol-util/color/names';
  34. function getCellPackModelUrl(fileName: string, baseUrl: string) {
  35. return `${baseUrl}/results/${fileName}`;
  36. }
  37. class TrajectoryCache {
  38. private map = new Map<string, Trajectory>();
  39. set(id: string, trajectory: Trajectory) { this.map.set(id, trajectory); }
  40. get(id: string) { return this.map.get(id); }
  41. }
  42. async function getModel(plugin: PluginContext, id: string, ingredient: Ingredient,
  43. baseUrl: string, trajCache: TrajectoryCache, location: string,
  44. file?: Asset.File
  45. ) {
  46. const assetManager = plugin.managers.asset;
  47. const modelIndex = (ingredient.source.model) ? parseInt(ingredient.source.model) : 0;
  48. let surface = (ingredient.ingtype) ? (ingredient.ingtype === 'transmembrane') : false;
  49. if (location === 'surface') surface = true;
  50. let trajectory = trajCache.get(id);
  51. const assets: Asset.Wrapper[] = [];
  52. if (!trajectory) {
  53. if (file) {
  54. if (file.name.endsWith('.cif')) {
  55. const text = await plugin.runTask(assetManager.resolve(file, 'string'));
  56. assets.push(text);
  57. const cif = (await parseCif(plugin, text.data)).blocks[0];
  58. trajectory = await plugin.runTask(trajectoryFromMmCIF(cif));
  59. } else if (file.name.endsWith('.bcif')) {
  60. const binary = await plugin.runTask(assetManager.resolve(file, 'binary'));
  61. assets.push(binary);
  62. const cif = (await parseCif(plugin, binary.data)).blocks[0];
  63. trajectory = await plugin.runTask(trajectoryFromMmCIF(cif));
  64. } else if (file.name.endsWith('.pdb')) {
  65. const text = await plugin.runTask(assetManager.resolve(file, 'string'));
  66. assets.push(text);
  67. const pdb = await parsePDBfile(plugin, text.data, id);
  68. trajectory = await plugin.runTask(trajectoryFromPDB(pdb));
  69. } else {
  70. throw new Error(`unsupported file type '${file.name}'`);
  71. }
  72. } else if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) {
  73. if (surface) {
  74. try {
  75. const data = await getFromOPM(plugin, id, assetManager);
  76. assets.push(data.asset);
  77. data.pdb.id! = id.toUpperCase();
  78. trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
  79. } catch (e) {
  80. // fallback to getFromPdb
  81. // console.error(e);
  82. const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
  83. assets.push(asset);
  84. trajectory = await plugin.runTask(trajectoryFromMmCIF(mmcif));
  85. }
  86. } else {
  87. const { mmcif, asset } = await getFromPdb(plugin, id, assetManager);
  88. assets.push(asset);
  89. trajectory = await plugin.runTask(trajectoryFromMmCIF(mmcif));
  90. }
  91. } else {
  92. const data = await getFromCellPackDB(plugin, id, baseUrl, assetManager);
  93. assets.push(data.asset);
  94. if ('pdb' in data) {
  95. trajectory = await plugin.runTask(trajectoryFromPDB(data.pdb));
  96. } else {
  97. trajectory = await plugin.runTask(trajectoryFromMmCIF(data.mmcif));
  98. }
  99. }
  100. trajCache.set(id, trajectory!);
  101. }
  102. const model = await plugin.resolveTask(trajectory?.getFrameAtIndex(modelIndex)!);
  103. return { model, assets };
  104. }
  105. async function getStructure(plugin: PluginContext, model: Model, source: Ingredient, props: { assembly?: string } = {}) {
  106. let structure = Structure.ofModel(model);
  107. const { assembly } = props;
  108. if (assembly) {
  109. structure = await plugin.runTask(StructureSymmetry.buildAssembly(structure, assembly));
  110. }
  111. let query;
  112. if (source.source.selection) {
  113. const sel = source.source.selection;
  114. // selection can have the model ID as well. remove it
  115. const asymIds: string[] = sel.replace(/ /g, '').replace(/:/g, '').split('or').slice(1);
  116. query = MS.struct.modifier.union([
  117. MS.struct.generator.atomGroups({
  118. 'chain-test': MS.core.set.has([MS.set(...asymIds), MS.ammp('auth_asym_id')])
  119. })
  120. ]);
  121. } else {
  122. query = MS.struct.modifier.union([
  123. MS.struct.generator.atomGroups({
  124. 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer'])
  125. })
  126. ]);
  127. }
  128. const compiled = compile<StructureSelection>(query);
  129. const result = compiled(new QueryContext(structure));
  130. structure = StructureSelection.unionStructure(result);
  131. // change here if possible the label ?
  132. // structure.label = source.name;
  133. return structure;
  134. }
  135. function getTransformLegacy(trans: Vec3, rot: Quat) {
  136. const q: Quat = Quat.create(-rot[3], rot[0], rot[1], rot[2]);
  137. const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
  138. Mat4.transpose(m, m);
  139. Mat4.scale(m, m, Vec3.create(-1.0, 1.0, -1.0));
  140. Mat4.setTranslation(m, trans);
  141. return m;
  142. }
  143. function getTransform(trans: Vec3, rot: Quat) {
  144. const q: Quat = Quat.create(-rot[0], rot[1], rot[2], -rot[3]);
  145. const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
  146. const p: Vec3 = Vec3.create(-trans[0], trans[1], trans[2]);
  147. Mat4.setTranslation(m, p);
  148. return m;
  149. }
  150. function getResultTransforms(results: Ingredient['results'], legacy: boolean) {
  151. if (legacy) return results.map((r: Ingredient['results'][0]) => getTransformLegacy(r[0], r[1]));
  152. else return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1]));
  153. }
  154. function getCurveTransforms(ingredient: Ingredient) {
  155. const n = ingredient.nbCurve || 0;
  156. const instances: Mat4[] = [];
  157. let segmentLength = 3.4;
  158. if (ingredient.uLength) {
  159. segmentLength = ingredient.uLength;
  160. } else if (ingredient.radii) {
  161. segmentLength = ingredient.radii[0].radii
  162. ? ingredient.radii[0].radii[0] * 2.0
  163. : 3.4;
  164. }
  165. let resampling: boolean = false;
  166. for (let i = 0; i < n; ++i) {
  167. const cname = `curve${i}`;
  168. if (!(cname in ingredient)) {
  169. console.warn(`Expected '${cname}' in ingredient`);
  170. continue;
  171. }
  172. const _points = ingredient[cname] as Vec3[];
  173. if (_points.length <= 2) {
  174. // TODO handle curve with 2 or less points
  175. continue;
  176. }
  177. // test for resampling
  178. const distance: number = Vec3.distance(_points[0], _points[1]);
  179. if (distance >= segmentLength + 2.0) {
  180. // console.info(distance);
  181. resampling = true;
  182. }
  183. const points = new Float32Array(_points.length * 3);
  184. for (let i = 0, il = _points.length; i < il; ++i) Vec3.toArray(_points[i], points, i * 3);
  185. const newInstances = getMatFromResamplePoints(points, segmentLength, resampling);
  186. instances.push(...newInstances);
  187. }
  188. return instances;
  189. }
  190. function getAssembly(name: string, transforms: Mat4[], structure: Structure) {
  191. const builder = Structure.Builder({ label: name });
  192. const { units } = structure;
  193. for (let i = 0, il = transforms.length; i < il; ++i) {
  194. const id = `${i + 1}`;
  195. const op = SymmetryOperator.create(id, transforms[i], { assembly: { id, operId: i, operList: [id] } });
  196. for (const unit of units) {
  197. builder.addWithOperator(unit, op);
  198. }
  199. }
  200. return builder.getStructure();
  201. }
  202. function getCifCurve(name: string, transforms: Mat4[], model: Model) {
  203. if (!MmcifFormat.is(model.sourceData)) throw new Error('mmcif source data needed');
  204. const { db } = model.sourceData.data;
  205. const d = db.atom_site;
  206. const n = d._rowCount;
  207. const rowCount = n * transforms.length;
  208. const { offsets, count } = model.atomicHierarchy.chainAtomSegments;
  209. const x = d.Cartn_x.toArray();
  210. const y = d.Cartn_y.toArray();
  211. const z = d.Cartn_z.toArray();
  212. const Cartn_x = new Float32Array(rowCount);
  213. const Cartn_y = new Float32Array(rowCount);
  214. const Cartn_z = new Float32Array(rowCount);
  215. const map = new Uint32Array(rowCount);
  216. const seq = new Int32Array(rowCount);
  217. let offset = 0;
  218. for (let c = 0; c < count; ++c) {
  219. const cStart = offsets[c];
  220. const cEnd = offsets[c + 1];
  221. const cLength = cEnd - cStart;
  222. for (let t = 0, tl = transforms.length; t < tl; ++t) {
  223. const m = transforms[t];
  224. for (let j = cStart; j < cEnd; ++j) {
  225. const i = offset + j - cStart;
  226. const xj = x[j], yj = y[j], zj = z[j];
  227. Cartn_x[i] = m[0] * xj + m[4] * yj + m[8] * zj + m[12];
  228. Cartn_y[i] = m[1] * xj + m[5] * yj + m[9] * zj + m[13];
  229. Cartn_z[i] = m[2] * xj + m[6] * yj + m[10] * zj + m[14];
  230. map[i] = j;
  231. seq[i] = t + 1;
  232. }
  233. offset += cLength;
  234. }
  235. }
  236. function multColumn<T>(column: Column<T>) {
  237. const array = column.toArray();
  238. return Column.ofLambda({
  239. value: row => array[map[row]],
  240. areValuesEqual: (rowA, rowB) => map[rowA] === map[rowB] || array[map[rowA]] === array[map[rowB]],
  241. rowCount, schema: column.schema
  242. });
  243. }
  244. const _atom_site: CifCategory.SomeFields<mmCIF_Schema['atom_site']> = {
  245. auth_asym_id: CifField.ofColumn(multColumn(d.auth_asym_id)),
  246. auth_atom_id: CifField.ofColumn(multColumn(d.auth_atom_id)),
  247. auth_comp_id: CifField.ofColumn(multColumn(d.auth_comp_id)),
  248. auth_seq_id: CifField.ofNumbers(seq),
  249. B_iso_or_equiv: CifField.ofColumn(Column.ofConst(0, rowCount, Column.Schema.float)),
  250. Cartn_x: CifField.ofNumbers(Cartn_x),
  251. Cartn_y: CifField.ofNumbers(Cartn_y),
  252. Cartn_z: CifField.ofNumbers(Cartn_z),
  253. group_PDB: CifField.ofColumn(Column.ofConst('ATOM', rowCount, Column.Schema.str)),
  254. id: CifField.ofColumn(Column.ofLambda({
  255. value: row => row,
  256. areValuesEqual: (rowA, rowB) => rowA === rowB,
  257. rowCount, schema: d.id.schema,
  258. })),
  259. label_alt_id: CifField.ofColumn(multColumn(d.label_alt_id)),
  260. label_asym_id: CifField.ofColumn(multColumn(d.label_asym_id)),
  261. label_atom_id: CifField.ofColumn(multColumn(d.label_atom_id)),
  262. label_comp_id: CifField.ofColumn(multColumn(d.label_comp_id)),
  263. label_seq_id: CifField.ofNumbers(seq),
  264. label_entity_id: CifField.ofColumn(Column.ofConst('1', rowCount, Column.Schema.str)),
  265. occupancy: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.float)),
  266. type_symbol: CifField.ofColumn(multColumn(d.type_symbol)),
  267. pdbx_PDB_ins_code: CifField.ofColumn(Column.ofConst('', rowCount, Column.Schema.str)),
  268. pdbx_PDB_model_num: CifField.ofColumn(Column.ofConst(1, rowCount, Column.Schema.int)),
  269. };
  270. const categories = {
  271. entity: CifCategory.ofTable('entity', db.entity),
  272. chem_comp: CifCategory.ofTable('chem_comp', db.chem_comp),
  273. atom_site: CifCategory.ofFields('atom_site', _atom_site)
  274. };
  275. return {
  276. header: name,
  277. categoryNames: Object.keys(categories),
  278. categories
  279. };
  280. }
  281. async function getCurve(plugin: PluginContext, name: string, ingredient: Ingredient, transforms: Mat4[], model: Model) {
  282. const cif = getCifCurve(name, transforms, model);
  283. const curveModelTask = Task.create('Curve Model', async ctx => {
  284. const format = MmcifFormat.fromFrame(cif);
  285. const models = await createModels(format.data.db, format, ctx);
  286. return models.representative;
  287. });
  288. const curveModel = await plugin.runTask(curveModelTask);
  289. // ingredient.source.selection = undefined;
  290. return getStructure(plugin, curveModel, ingredient);
  291. }
  292. async function getIngredientStructure(plugin: PluginContext, ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles, trajCache: TrajectoryCache, location: 'surface' | 'interior' | 'cytoplasme') {
  293. const { name, source, results, nbCurve } = ingredient;
  294. if (source.pdb === 'None') return;
  295. const file = ingredientFiles[source.pdb];
  296. if (!file) {
  297. // TODO can these be added to the library?
  298. if (name === 'HIV1_CAhex_0_1_0') return; // 1VU4CtoH_hex.pdb
  299. if (name === 'HIV1_CAhexCyclophilA_0_1_0') return; // 1AK4fitTo1VU4hex.pdb
  300. if (name === 'iLDL') return; // EMD-5239
  301. if (name === 'peptides') return; // peptide.pdb
  302. if (name === 'lypoglycane') return;
  303. }
  304. // model id in case structure is NMR
  305. const { model, assets } = await getModel(plugin, source.pdb || name, ingredient, baseUrl, trajCache, location, file);
  306. if (!model) return;
  307. let structure: Structure;
  308. if (nbCurve) {
  309. structure = await getCurve(plugin, name, ingredient, getCurveTransforms(ingredient), model);
  310. } else {
  311. if ((!results || results.length === 0)) return;
  312. let bu: string|undefined = source.bu ? source.bu : undefined;
  313. if (bu) {
  314. if (bu === 'AU') {
  315. bu = undefined;
  316. } else {
  317. bu = bu.slice(2);
  318. }
  319. }
  320. structure = await getStructure(plugin, model, ingredient, { assembly: bu });
  321. // transform with offset and pcp
  322. let legacy: boolean = true;
  323. const pcp = ingredient.principalVector ? ingredient.principalVector : ingredient.principalAxis;
  324. if (pcp) {
  325. legacy = false;
  326. const structureMean = getStructureMean(structure);
  327. Vec3.negate(structureMean, structureMean);
  328. const m1: Mat4 = Mat4.identity();
  329. Mat4.setTranslation(m1, structureMean);
  330. structure = Structure.transform(structure, m1);
  331. if (ingredient.offset) {
  332. const o: Vec3 = Vec3.create(ingredient.offset[0], ingredient.offset[1], ingredient.offset[2]);
  333. if (!Vec3.exactEquals(o, Vec3.zero())) { // -1, 1, 4e-16 ??
  334. if (location !== 'surface') {
  335. Vec3.negate(o, o);
  336. }
  337. const m: Mat4 = Mat4.identity();
  338. Mat4.setTranslation(m, o);
  339. structure = Structure.transform(structure, m);
  340. }
  341. }
  342. if (pcp) {
  343. const p: Vec3 = Vec3.create(pcp[0], pcp[1], pcp[2]);
  344. if (!Vec3.exactEquals(p, Vec3.unitZ)) {
  345. const q: Quat = Quat.identity();
  346. Quat.rotationTo(q, p, Vec3.unitZ);
  347. const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q);
  348. structure = Structure.transform(structure, m);
  349. }
  350. }
  351. }
  352. structure = getAssembly(name, getResultTransforms(results, legacy), structure);
  353. }
  354. return { structure, assets };
  355. }
  356. export function createStructureFromCellPack(plugin: PluginContext, packing: CellPacking, baseUrl: string, ingredientFiles: IngredientFiles) {
  357. return Task.create('Create Packing Structure', async ctx => {
  358. const { ingredients, location, name } = packing;
  359. const assets: Asset.Wrapper[] = [];
  360. const trajCache = new TrajectoryCache();
  361. const structures: Structure[] = [];
  362. const colors: Color[] = [];
  363. for (const iName in ingredients) {
  364. if (ctx.shouldUpdate) await ctx.update(iName);
  365. const ingredientStructure = await getIngredientStructure(plugin, ingredients[iName], baseUrl, ingredientFiles, trajCache, location);
  366. if (ingredientStructure) {
  367. structures.push(ingredientStructure.structure);
  368. assets.push(...ingredientStructure.assets);
  369. const c = ingredients[iName].color;
  370. if (c) {
  371. colors.push(Color.fromNormalizedRgb(c[0], c[1], c[2]));
  372. } else {
  373. colors.push(Color.fromNormalizedRgb(1, 0, 0));
  374. }
  375. }
  376. }
  377. if (ctx.shouldUpdate) await ctx.update(`${name} - units`);
  378. const units: Unit[] = [];
  379. let offsetInvariantId = 0;
  380. let offsetChainGroupId = 0;
  381. for (const s of structures) {
  382. if (ctx.shouldUpdate) await ctx.update(`${s.label}`);
  383. let maxInvariantId = 0;
  384. const maxChainGroupId = 0;
  385. for (const u of s.units) {
  386. const invariantId = u.invariantId + offsetInvariantId;
  387. const chainGroupId = u.chainGroupId + offsetChainGroupId;
  388. if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId;
  389. units.push(Unit.create(units.length, invariantId, chainGroupId, u.traits, u.kind, u.model, u.conformation.operator, u.elements, u.props));
  390. }
  391. offsetInvariantId += maxInvariantId + 1;
  392. offsetChainGroupId += maxChainGroupId + 1;
  393. }
  394. if (ctx.shouldUpdate) await ctx.update(`${name} - structure`);
  395. const structure = Structure.create(units, { label: name + '.' + location });
  396. for (let i = 0, il = structure.models.length; i < il; ++i) {
  397. Model.TrajectoryInfo.set(structure.models[i], { size: il, index: i });
  398. }
  399. return { structure, assets, colors: colors };
  400. });
  401. }
  402. async function handleHivRna(plugin: PluginContext, packings: CellPacking[], baseUrl: string) {
  403. for (let i = 0, il = packings.length; i < il; ++i) {
  404. if (packings[i].name === 'HIV1_capsid_3j3q_PackInner_0_1_0' || packings[i].name === 'HIV_capsid') {
  405. const url = Asset.getUrlAsset(plugin.managers.asset, `${baseUrl}/extras/rna_allpoints.json`);
  406. const json = await plugin.runTask(plugin.managers.asset.resolve(url, 'json', false));
  407. const points = json.data.points as number[];
  408. const curve0: Vec3[] = [];
  409. for (let j = 0, jl = points.length; j < jl; j += 3) {
  410. curve0.push(Vec3.fromArray(Vec3(), points, j));
  411. }
  412. packings[i].ingredients['RNA'] = {
  413. source: { pdb: 'RNA_U_Base.pdb', transform: { center: false } },
  414. results: [],
  415. name: 'RNA',
  416. nbCurve: 1,
  417. curve0
  418. };
  419. }
  420. }
  421. }
  422. async function loadMembrane(plugin: PluginContext, name: string, state: State, params: LoadCellPackModelParams) {
  423. let file: Asset.File | undefined = undefined;
  424. if (params.ingredients !== null) {
  425. const fileName = `${name}.bcif`;
  426. for (const f of params.ingredients) {
  427. if (fileName === f.name) {
  428. file = f;
  429. break;
  430. }
  431. }
  432. if (!file) {
  433. // check for cif directly
  434. const cifileName = `${name}.cif`;
  435. for (const f of params.ingredients) {
  436. if (cifileName === f.name) {
  437. file = f;
  438. break;
  439. }
  440. }
  441. }
  442. }
  443. let legacy_membrane: boolean = false; // temporary variable until all membrane are converted to the new correct cif format
  444. let geometry_membrane: boolean = false; // membrane can be a mesh geometry
  445. let b = state.build().toRoot();
  446. if (file) {
  447. if (file.name.endsWith('.cif')) {
  448. b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: false, label: file.name }, { state: { isGhost: true } });
  449. } else if (file.name.endsWith('.bcif')) {
  450. b = b.apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } });
  451. }
  452. } else {
  453. if (name.toLowerCase().endsWith('.bcif')) {
  454. const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
  455. b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
  456. } else if (name.toLowerCase().endsWith('.cif')) {
  457. const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}`);
  458. b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
  459. } else if (name.toLowerCase().endsWith('.ply')) {
  460. const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/geometries/${name}`);
  461. b = b.apply(StateTransforms.Data.Download, { url, isBinary: false, label: name }, { state: { isGhost: true } });
  462. geometry_membrane = true;
  463. } else {
  464. const url = Asset.getUrlAsset(plugin.managers.asset, `${params.baseUrl}/membranes/${name}.bcif`);
  465. b = b.apply(StateTransforms.Data.Download, { url, isBinary: true, label: name }, { state: { isGhost: true } });
  466. legacy_membrane = true;
  467. }
  468. }
  469. const props = {
  470. type: {
  471. name: 'assembly' as const,
  472. params: { id: '1' }
  473. }
  474. };
  475. if (legacy_membrane) {
  476. // old membrane
  477. const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
  478. .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
  479. .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
  480. .apply(StructureFromAssemblies, undefined, { state: { isGhost: true } })
  481. .commit({ revertOnError: true });
  482. const membraneParams = {
  483. representation: params.preset.representation,
  484. };
  485. await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
  486. } else if (geometry_membrane) {
  487. await b.apply(StateTransforms.Data.ParsePly, undefined, { state: { isGhost: true } })
  488. .apply(StateTransforms.Model.ShapeFromPly)
  489. .apply(StateTransforms.Representation.ShapeRepresentation3D, { xrayShaded: true,
  490. doubleSided: true, coloring: { name: 'uniform', params: { color: ColorNames.orange } } })
  491. .commit({ revertOnError: true });
  492. } else {
  493. const membrane = await b.apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } })
  494. .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } })
  495. .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } })
  496. .apply(StateTransforms.Model.StructureFromModel, props, { state: { isGhost: true } })
  497. .commit({ revertOnError: true });
  498. const membraneParams = {
  499. representation: params.preset.representation,
  500. };
  501. await CellpackMembranePreset.apply(membrane, membraneParams, plugin);
  502. }
  503. }
  504. async function handleMembraneSpheres(state: State, primitives: CompartmentPrimitives) {
  505. const nSpheres = primitives.positions!.length / 3;
  506. // console.log('ok mb ', nSpheres);
  507. // TODO : take in account the type of the primitives.
  508. for (let j = 0; j < nSpheres; j++) {
  509. await state.build()
  510. .toRoot()
  511. .apply(CreateCompartmentSphere, {
  512. center: Vec3.create(
  513. primitives.positions![j * 3 + 0],
  514. primitives.positions![j * 3 + 1],
  515. primitives.positions![j * 3 + 2]
  516. ),
  517. radius: primitives!.radii![j]
  518. })
  519. .commit();
  520. }
  521. }
  522. async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) {
  523. const ingredientFiles = params.ingredients || [];
  524. let cellPackJson: StateBuilder.To<PSO.Format.Json, StateTransformer<PSO.Data.String, PSO.Format.Json>>;
  525. let resultsFile: Asset.File | null = params.results;
  526. if (params.source.name === 'id') {
  527. const url = Asset.getUrlAsset(plugin.managers.asset, getCellPackModelUrl(params.source.params, params.baseUrl));
  528. cellPackJson = state.build().toRoot()
  529. .apply(StateTransforms.Data.Download, { url, isBinary: false, label: params.source.params }, { state: { isGhost: true } });
  530. } else {
  531. const file = params.source.params;
  532. if (!file?.file) {
  533. plugin.log.error('No file selected');
  534. return;
  535. }
  536. let modelFile: Asset.File;
  537. if (file.name.toLowerCase().endsWith('.zip')) {
  538. const data = await readFromFile(file.file, 'zip').runInContext(runtime);
  539. if (data['model.json']) {
  540. modelFile = Asset.File(new File([data['model.json']], 'model.json'));
  541. } else {
  542. throw new Error('model.json missing from zip file');
  543. }
  544. if (data['results.bin']) {
  545. resultsFile = Asset.File(new File([data['results.bin']], 'results.bin'));
  546. }
  547. objectForEach(data, (v, k) => {
  548. if (k === 'model.json') return;
  549. if (k === 'results.bin') return;
  550. ingredientFiles.push(Asset.File(new File([v], k)));
  551. });
  552. } else {
  553. modelFile = file;
  554. }
  555. cellPackJson = state.build().toRoot()
  556. .apply(StateTransforms.Data.ReadFile, { file: modelFile, isBinary: false, label: modelFile.name }, { state: { isGhost: true } });
  557. }
  558. const cellPackBuilder = cellPackJson
  559. .apply(StateTransforms.Data.ParseJson, undefined, { state: { isGhost: true } })
  560. .apply(ParseCellPack, { resultsFile, baseUrl: params.baseUrl });
  561. const cellPackObject = await state.updateTree(cellPackBuilder).runInContext(runtime);
  562. const { packings } = cellPackObject.obj!.data;
  563. await handleHivRna(plugin, packings, params.baseUrl);
  564. for (let i = 0, il = packings.length; i < il; ++i) {
  565. const p = { packing: i, baseUrl: params.baseUrl, ingredientFiles };
  566. const packing = await state.build()
  567. .to(cellPackBuilder.ref)
  568. .apply(StructureFromCellpack, p)
  569. .commit({ revertOnError: true });
  570. const packingParams = {
  571. traceOnly: params.preset.traceOnly,
  572. representation: params.preset.representation,
  573. };
  574. await CellpackPackingPreset.apply(packing, packingParams, plugin);
  575. if (packings[i].compartment) {
  576. if (params.membrane === 'lipids') {
  577. if (packings[i].compartment!.geom_type) {
  578. if (packings[i].compartment!.geom_type === 'file') {
  579. // TODO: load mesh files or vertex,faces data
  580. await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
  581. } else if (packings[i].compartment!.compartment_primitives) {
  582. await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
  583. }
  584. } else {
  585. // try loading membrane from repo as a bcif file or from the given list of files.
  586. if (params.membrane === 'lipids') {
  587. await loadMembrane(plugin, packings[i].name, state, params);
  588. }
  589. }
  590. } else if (params.membrane === 'geometry') {
  591. if (packings[i].compartment!.compartment_primitives) {
  592. await handleMembraneSpheres(state, packings[i].compartment!.compartment_primitives!);
  593. } else if (packings[i].compartment!.geom_type === 'file') {
  594. if (packings[i].compartment!.filename!.toLowerCase().endsWith('.ply')) {
  595. await loadMembrane(plugin, packings[i].compartment!.filename!, state, params);
  596. }
  597. }
  598. }
  599. }
  600. }
  601. }
  602. const LoadCellPackModelParams = {
  603. source: PD.MappedStatic('id', {
  604. 'id': PD.Select('InfluenzaModel2.json', [
  605. ['blood_hiv_immature_inside.json', 'Blood HIV immature'],
  606. ['HIV_immature_model.json', 'HIV immature'],
  607. ['Blood_HIV.json', 'Blood HIV'],
  608. ['HIV-1_0.1.6-8_mixed_radii_pdb.json', 'HIV'],
  609. ['influenza_model1.json', 'Influenza envelope'],
  610. ['InfluenzaModel2.json', 'Influenza complete'],
  611. ['ExosomeModel.json', 'Exosome Model'],
  612. ['MycoplasmaGenitalium.json', 'Mycoplasma Genitalium curated model'],
  613. ] as const, { description: 'Download the model definition with `id` from the server at `baseUrl.`' }),
  614. 'file': PD.File({ accept: '.json,.cpr,.zip', description: 'Open model definition from .json/.cpr file or open .zip file containing model definition plus ingredients.', label: 'Recipe file' }),
  615. }, { options: [['id', 'Id'], ['file', 'File']] }),
  616. baseUrl: PD.Text(DefaultCellPackBaseUrl),
  617. results: PD.File({ accept: '.bin', description: 'open results file in binary format from cellpackgpu for the specified recipe', label: 'Results file' }),
  618. membrane: PD.Select('lipids', PD.arrayToOptions(['lipids', 'geometry', 'none'])),
  619. ingredients: PD.FileList({ accept: '.cif,.bcif,.pdb', label: 'Ingredient files' }),
  620. preset: PD.Group({
  621. traceOnly: PD.Boolean(false),
  622. representation: PD.Select('gaussian-surface', PD.arrayToOptions(['spacefill', 'gaussian-surface', 'point', 'orientation']))
  623. }, { isExpanded: true })
  624. };
  625. type LoadCellPackModelParams = PD.Values<typeof LoadCellPackModelParams>
  626. export const LoadCellPackModel = StateAction.build({
  627. display: { name: 'Load CellPack', description: 'Open or download a model' },
  628. params: LoadCellPackModelParams,
  629. from: PSO.Root
  630. })(({ state, params }, ctx: PluginContext) => Task.create('CellPack Loader', async taskCtx => {
  631. await loadPackings(ctx, taskCtx, state, params);
  632. }));