diff --git a/README.md b/README.md index 5b5ead33d2cf3cd0239daa67e14d2cc5833bc64d..4da71bf2ef54c6129134af1d0474ba794ba7be3d 100644 --- a/README.md +++ b/README.md @@ -114,9 +114,9 @@ and navigate to `build/viewer` **Convert any CIF to BinaryCIF** - node build/model-server/preprocess -i file.cif -ob file.bcif - -To see all available commands, use ``node build/model-server/preprocess -h``. + node lib/servers/model/preprocess -i file.cif -ob file.bcif + +To see all available commands, use ``node lib/servers/model/preprocess -h``. Or diff --git a/src/apps/viewer/extensions/cellpack/curve.ts b/src/apps/viewer/extensions/cellpack/curve.ts index 011d2dd15037fde20679e0b55ccf71fa0a03513e..4b789f7a5cb1755033270839266762a1686da4eb 100644 --- a/src/apps/viewer/extensions/cellpack/curve.ts +++ b/src/apps/viewer/extensions/cellpack/curve.ts @@ -191,19 +191,14 @@ function GetMiniFrame(points: Vec3[], normals: Vec3[]) { const rpTmpVec1 = Vec3() -export function getMatFromResamplePoints(points: NumberArray) { - const segmentLength = 3.4 - const new_points = ResampleControlPoints(points, 3.4) +export function getMatFromResamplePoints(points: NumberArray, segmentLength: number) { + const new_points = ResampleControlPoints(points, segmentLength) const npoints = new_points.length const new_normal = GetSmoothNormals(new_points) const frames = GetMiniFrame(new_points, new_normal) const limit = npoints const transforms: Mat4[] = [] const pti = Vec3.copy(rpTmpVec1, new_points[0]); - // console.log(new_points.length) - // console.log(points.length/3) - // console.log(limit) - // console.log(segmentLength) for (let i = 0; i<npoints-2; ++i) { const pti1: Vec3 = new_points[i+1] // Vec3.create(points[(i+1)*3],points[(i+1)*3+1],points[(i+1)*3+2]); const d = Vec3.distance(pti, pti1) diff --git a/src/apps/viewer/extensions/cellpack/data.ts b/src/apps/viewer/extensions/cellpack/data.ts index 183dfab4f62ac37bc8d87d65773c14fd4b7fbf05..ff393fac6d2e8526608b6f50af7d9aa763b37de9 100644 --- a/src/apps/viewer/extensions/cellpack/data.ts +++ b/src/apps/viewer/extensions/cellpack/data.ts @@ -27,8 +27,7 @@ export interface Cell { export interface Recipe { setupfile: string - /** First entry is name, secound is path: [name: string, path: string][] */ - paths: [string, string][] + paths: [string, string][] // [name: string, path: string][] version: string name: string } @@ -42,21 +41,40 @@ export interface Packing { ingredients: { [key: string]: Ingredient } } -export interface Ingredient { - source: IngredientSource - results: [Vec3, Quat][] - name: string - positions?: [Vec3[]] // why wrapped in an extra array? - radii?: [number[]] // why wrapped in an extra array? +export interface Positions { + coords?: Vec3[]; +} +export interface Radii { + radii?: number[]; +} + +export interface Ingredient { + source: IngredientSource; + results: [Vec3, Quat][]; + name: string; + /** Vec3[]];CoarseGraind Beads coordinates LOD */ + positions?: [Positions]; + /** number[]];CoarseGraind Beads radii LOD */ + radii?: [Radii]; /** Number of `curveX` properties in the object where `X` is a 0-indexed number */ - nbCurve?: number + nbCurve?: number; /** Curve properties are Vec3[] but that is not expressable in TypeScript */ - [curveX: string]: unknown + [curveX: string]: unknown; + /** the orientation in the membrane */ + principalAxis?: Vec3; + /** offset along membrane */ + offset?: Vec3; } export interface IngredientSource { - pdb: string - transform: { center: boolean, translate?: Vec3 } - biomt?: boolean -} + pdb: string; + bu?: string; /** biological unit e.g AU,BU1,etc.. */ + selection?: string; /** NGL selection or :A or :B etc.. */ + model?: string; /** model number e.g 0,1,2... */ + transform: { + center: boolean; + translate?: Vec3; + }; + biomt?: boolean; +} \ No newline at end of file diff --git a/src/apps/viewer/extensions/cellpack/model.ts b/src/apps/viewer/extensions/cellpack/model.ts index e43e7e5144c8472d01c49cef7f6ef8b7d86d647a..5ef48f02eff4e741a53a8b8f59350e9f5c413b96 100644 --- a/src/apps/viewer/extensions/cellpack/model.ts +++ b/src/apps/viewer/extensions/cellpack/model.ts @@ -8,7 +8,7 @@ import { StateAction, StateBuilder, StateTransformer, State } from '../../../../ import { PluginContext } from '../../../../mol-plugin/context'; import { PluginStateObject as PSO } from '../../../../mol-plugin-state/objects'; import { ParamDefinition as PD } from '../../../../mol-util/param-definition'; -import { Ingredient, CellPacking } from './data'; +import { Ingredient, IngredientSource, CellPacking } from './data'; import { getFromPdb, getFromCellPackDB, IngredientFiles, parseCif, parsePDBfile } from './util'; import { Model, Structure, StructureSymmetry, StructureSelection, QueryContext, Unit } from '../../../../mol-model/structure'; import { trajectoryFromMmCIF, MmcifFormat } from '../../../../mol-model-formats/structure/mmcif'; @@ -34,44 +34,55 @@ function getCellPackModelUrl(fileName: string, baseUrl: string) { return `${baseUrl}/results/${fileName}` } -async function getModel(id: string, baseUrl: string, file?: File) { +async function getModel(id: string, model_id: number, baseUrl: string, file?: File) { let model: Model; if (file) { const text = await file.text() if (file.name.endsWith('.cif')) { const cif = (await parseCif(text)).blocks[0] - model = (await trajectoryFromMmCIF(cif).run())[0] + model = (await trajectoryFromMmCIF(cif).run())[model_id] } else if (file.name.endsWith('.pdb')) { const pdb = await parsePDBfile(text, id) - model = (await trajectoryFromPDB(pdb).run())[0] + model = (await trajectoryFromPDB(pdb).run())[model_id] } else { throw new Error(`unsupported file type '${file.name}'`) } } else if (id.match(/^[1-9][a-zA-Z0-9]{3,3}$/i)) { // return const cif = await getFromPdb(id) - model = (await trajectoryFromMmCIF(cif).run())[0] + model = (await trajectoryFromMmCIF(cif).run())[model_id] } else { const pdb = await getFromCellPackDB(id, baseUrl) - model = (await trajectoryFromPDB(pdb).run())[0] + model = (await trajectoryFromPDB(pdb).run())[model_id] } return model } -async function getStructure(model: Model, props: { assembly?: string } = {}) { +async function getStructure(model: Model, source: IngredientSource, props: { assembly?: string } = {}) { let structure = Structure.ofModel(model) const { assembly } = props if (assembly) { structure = await StructureSymmetry.buildAssembly(structure, assembly).run() } + let query; + if (source.selection){ + const asymIds: string[] = source.selection.replace(' :','').split(' or') + query = MS.struct.modifier.union([ + MS.struct.generator.atomGroups({ + 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']), + 'chain-test': MS.core.set.has([MS.set(...asymIds), MS.ammp('auth_asym_id')]) + }) + ]) + } else { + query = MS.struct.modifier.union([ + MS.struct.generator.atomGroups({ + 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']) + }) + ]) + } - const query = MS.struct.modifier.union([ - MS.struct.generator.atomGroups({ - 'entity-test': MS.core.rel.eq([MS.ammp('entityType'), 'polymer']) - }) - ]) const compiled = compile<StructureSelection>(query) const result = compiled(new QueryContext(structure)) structure = StructureSelection.unionStructure(result) @@ -79,7 +90,7 @@ async function getStructure(model: Model, props: { assembly?: string } = {}) { return structure } -function getTransform(trans: Vec3, rot: Quat) { +function getTransformLegacy(trans: Vec3, rot: Quat) { const q: Quat = Quat.create(-rot[3], rot[0], rot[1], rot[2]) const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q) Mat4.transpose(m, m) @@ -88,14 +99,24 @@ function getTransform(trans: Vec3, rot: Quat) { return m } -function getResultTransforms(results: Ingredient['results']) { - return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1])) +function getTransform(trans: Vec3, rot: Quat) { + const q: Quat = Quat.create(rot[0], rot[1], rot[2], rot[3]) + const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q) + const p: Vec3 = Vec3.create(trans[0],trans[1],trans[2]) + Mat4.setTranslation(m, p) + return m +} + + +function getResultTransforms(results: Ingredient['results'], legacy: boolean) { + if (legacy) return results.map((r: Ingredient['results'][0]) => getTransformLegacy(r[0], r[1])) + else return results.map((r: Ingredient['results'][0]) => getTransform(r[0], r[1])) } function getCurveTransforms(ingredient: Ingredient) { const n = ingredient.nbCurve || 0 const instances: Mat4[] = [] - + const segmentLength = (ingredient.radii)? ((ingredient.radii[0].radii)?ingredient.radii[0].radii[0]*2.0:3.4):3.4; for (let i = 0; i < n; ++i) { const cname = `curve${i}` if (!(cname in ingredient)) { @@ -109,7 +130,7 @@ function getCurveTransforms(ingredient: Ingredient) { } const points = new Float32Array(_points.length * 3) for (let i = 0, il = _points.length; i < il; ++i) Vec3.toArray(_points[i], points, i * 3) - const newInstances = getMatFromResamplePoints(points) + const newInstances = getMatFromResamplePoints(points,segmentLength) instances.push(...newInstances) } @@ -224,7 +245,7 @@ function getCifCurve(name: string, transforms: Mat4[], model: Model) { }; } -async function getCurve(name: string, transforms: Mat4[], model: Model) { +async function getCurve(name: string, ingredient: Ingredient, transforms: Mat4[], model: Model) { const cif = getCifCurve(name, transforms, model) const curveModelTask = Task.create('Curve Model', async ctx => { @@ -234,7 +255,7 @@ async function getCurve(name: string, transforms: Mat4[], model: Model) { }) const curveModel = await curveModelTask.run() - return getStructure(curveModel) + return getStructure(curveModel, ingredient.source) } async function getIngredientStructure(ingredient: Ingredient, baseUrl: string, ingredientFiles: IngredientFiles) { @@ -251,14 +272,51 @@ async function getIngredientStructure(ingredient: Ingredient, baseUrl: string, i if (name === 'lypoglycane') return } - const model = await getModel(source.pdb || name, baseUrl, file) + // model id in case structure is NMR + const model_id = (ingredient.source.model)? parseInt(ingredient.source.model) : 0; + const model = await getModel(source.pdb || name,model_id, baseUrl, file) if (!model) return if (nbCurve) { - return getCurve(name, getCurveTransforms(ingredient), model) + return getCurve(name, ingredient, getCurveTransforms(ingredient), model) } else { - const structure = await getStructure(model, { assembly: source.biomt ? '1' : undefined }) - return getAssembly(getResultTransforms(results), structure) + let bu: string|undefined = source.bu ? source.bu : undefined; + if (bu){ + if (bu === 'AU') { + bu = undefined; + } else { + bu = bu.slice(2) + } + } + let structure = await getStructure(model,source, { assembly: bu }) + // transform with offset and pcp + let legacy: boolean = true + if (ingredient.offset || ingredient.principalAxis){ + // center the structure + legacy=false + const boundary = structure.boundary + let structureCenter: Vec3 = Vec3.zero() + Vec3.negate(structureCenter,boundary.sphere.center) + const m1: Mat4 = Mat4.identity() + Mat4.setTranslation(m1, structureCenter) + structure = Structure.transform(structure,m1) + if (ingredient.offset){ + if (!Vec3.exactEquals(ingredient.offset,Vec3.zero())){ + const m: Mat4 = Mat4.identity(); + Mat4.setTranslation(m, ingredient.offset) + structure = Structure.transform(structure,m); + } + } + if (ingredient.principalAxis){ + if (!Vec3.exactEquals(ingredient.principalAxis,Vec3.unitZ)){ + const q: Quat = Quat.identity(); + Quat.rotationTo(q,ingredient.principalAxis,Vec3.unitZ) + const m: Mat4 = Mat4.fromQuat(Mat4.zero(), q) + structure = Structure.transform(structure,m); + } + } + } + return getAssembly(getResultTransforms(results,legacy), structure) } } @@ -319,6 +377,44 @@ async function handleHivRna(ctx: { runtime: RuntimeContext, fetch: AjaxTask }, p } } +async function loadMembrane(name: string, plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) { + const fname: string = `${name}.bcif` + let ingredientFiles: IngredientFiles = {} + if (params.ingredients.files !== null) { + for (let i = 0, il = params.ingredients.files.length; i < il; ++i) { + const file = params.ingredients.files.item(i) + if (file) ingredientFiles[file.name] = file + } + } + const url = `${params.baseUrl}/membranes/${name}.bcif` + // + const file = (ingredientFiles)?ingredientFiles[fname]:null; + // can we check if url exist + let membrane + if (!file) { + membrane = await state.build().toRoot() + .apply(StateTransforms.Data.Download, { label: name, url, isBinary: true }, { state: { isGhost: true } }) + .apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } }) + .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } }) + .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } }) + .apply(StateTransforms.Model.StructureFromModel) + .commit() + } else { + membrane = await state.build().toRoot() + .apply(StateTransforms.Data.ReadFile, { file, isBinary: true, label: file.name }, { state: { isGhost: true } }) + .apply(StateTransforms.Data.ParseCif, undefined, { state: { isGhost: true } }) + .apply(StateTransforms.Model.TrajectoryFromMmCif, undefined, { state: { isGhost: true } }) + .apply(StateTransforms.Model.ModelFromTrajectory, undefined, { state: { isGhost: true } }) + .apply(StateTransforms.Model.StructureFromModel) + .commit() + } + + const membraneParams = { + representation: params.preset.representation, + } + await CellpackMembranePreset.apply(membrane, membraneParams, plugin) +} + async function loadHivMembrane(plugin: PluginContext, runtime: RuntimeContext, state: State, params: LoadCellPackModelParams) { const url = `${params.baseUrl}/membranes/hiv_lipids.bcif` const membrane = await state.build().toRoot() @@ -381,6 +477,9 @@ async function loadPackings(plugin: PluginContext, runtime: RuntimeContext, stat representation: params.preset.representation, } await CellpackPackingPreset.apply(packing, packingParams, plugin) + if ( packings[i].location === 'surface' ){ + await loadMembrane(packings[i].name, plugin, runtime, state, params) + } } } @@ -392,6 +491,7 @@ const LoadCellPackModelParams = { ['HIV-1_0.1.6-8_mixed_radii_pdb.cpr', 'HIV-1_0.1.6-8_mixed_radii_pdb'], ['hiv_lipids.bcif', 'hiv_lipids'], ['influenza_model1.json', 'influenza_model1'], + ['ExosomeModel.json', 'ExosomeModel'], ['Mycoplasma1.5_mixed_pdb_fixed.cpr', 'Mycoplasma1.5_mixed_pdb_fixed'], ] as const), 'file': PD.File({ accept: 'id' }),