@@ -35,7 +35,7 @@ import {
3535import {
3636 createGlyGenSelectionExpressions ,
3737 createSelectionExpressions ,
38- normalizeTargets ,
38+ normalizeTarget ,
3939 SelectionExpression ,
4040 Target ,
4141 targetToLoci ,
@@ -48,6 +48,7 @@ import {
4848} from 'molstar/lib/extensions/assembly-symmetry/prop' ;
4949import { Task } from 'molstar/lib/mol-task' ;
5050import { QualityAssessment } from 'molstar/lib/extensions/model-archive/quality-assessment/prop' ;
51+ import { getAssemblyIdsFromModel , firstMatchingAssemblyId } from './viewer' ;
5152
5253type BaseProps = {
5354 assemblyId ?: string
@@ -126,8 +127,12 @@ export type GlyGenProps = {
126127 glycosylation : Target [ ] ,
127128} & BaseProps
128129
130+ export type DefaultAssembly = {
131+ kind : 'default-assembly'
132+ } & BaseProps ;
133+
129134export type PresetProps = ValidationProps | StandardProps | SymmetryProps | FeatureProps | DensityProps | AlignmentProps |
130- MembraneProps | FeatureDensityProps | MotifProps | NakbProps | GlyGenProps | EmptyProps ;
135+ MembraneProps | FeatureDensityProps | MotifProps | NakbProps | GlyGenProps | EmptyProps | DefaultAssembly ;
131136
132137const RcsbParams = ( ) => ( {
133138 preset : PD . Value < PresetProps > ( { kind : 'standard' , assemblyId : '' } , { isHidden : true } )
@@ -150,17 +155,27 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
150155 // jump through some hoops to determine the unknown assemblyId of query selections
151156 if ( p . kind === 'motif' ) determineAssemblyId ( trajectory , p ) ;
152157
153- const structureParams : RootStructureDefinition . Params = { name : 'model' , params : { } } ;
154- if ( p . assemblyId && p . assemblyId !== '' && p . assemblyId !== '0' ) {
155- Object . assign ( structureParams , {
156- name : 'assembly' ,
157- params : { id : p . assemblyId }
158- } as RootStructureDefinition . Params ) ;
159- }
160-
161158 const model = await builder . createModel ( trajectory , modelParams ) ;
162159 const modelProperties = await builder . insertModelProperties ( model ) ;
163160
161+ const structureParams : RootStructureDefinition . Params = { name : 'model' , params : { } } ;
162+ if ( p . kind === 'default-assembly' ) {
163+ const assemblyIds = getAssemblyIdsFromModel ( model . cell ?. obj ?. data ) ;
164+ if ( assemblyIds . length > 0 ) {
165+ Object . assign ( structureParams , {
166+ name : 'assembly' ,
167+ params : { id : assemblyIds [ 0 ] }
168+ } as RootStructureDefinition . Params ) ;
169+ }
170+ } else {
171+ if ( p . assemblyId && p . assemblyId !== '' && p . assemblyId !== '0' ) {
172+ Object . assign ( structureParams , {
173+ name : 'assembly' ,
174+ params : { id : p . assemblyId }
175+ } as RootStructureDefinition . Params ) ;
176+ }
177+ }
178+
164179 let structure : StructureObject | undefined = undefined ;
165180 let structureProperties : StructureObject | undefined = undefined ;
166181 let unitcell : StateObjectSelector | undefined = undefined ;
@@ -232,7 +247,7 @@ export const RcsbPreset = TrajectoryHierarchyPresetProvider({
232247 } else if ( p . kind === 'motif' && structure ?. obj ) {
233248 // let's force ASM_1 for motifs (as we use this contract in the rest of the stack)
234249 // TODO should ASM_1 be the default, seems like we'd run into problems when selecting ligands that are e.g. ambiguous with asym_id & seq_id alone?
235- const targets = normalizeTargets ( p . targets , structure ! . obj . data ) ;
250+ const targets = p . targets . map ( t => normalizeTarget ( t , structure ! . obj ! . data ) ) ;
236251 let selectionExpressions = createSelectionExpressions ( p . label || model . data ! . entryId , targets ) ;
237252 const globalExpressions = createSelectionExpressions ( p . label || model . data ! . entryId ) ;
238253 selectionExpressions = selectionExpressions . concat ( globalExpressions . map ( e => { return { ...e , alpha : 0.21 } ; } ) ) ;
@@ -359,62 +374,15 @@ function determineAssemblyId(traj: any, p: MotifProps) {
359374 // nothing to do if assembly is known
360375 if ( p . assemblyId && p . assemblyId !== '' && p . assemblyId !== '0' ) return ;
361376
362- function equals ( expr : string , val : string ) : boolean {
363- const list = parseOperatorList ( expr ) ;
364- const split = val . split ( 'x' ) ;
365- let matches = 0 ;
366- for ( let i = 0 , il = Math . min ( list . length , split . length ) ; i < il ; i ++ ) {
367- if ( list [ i ] . indexOf ( split [ i ] ) !== - 1 ) matches ++ ;
368- }
369- return matches === split . length ;
370- }
371-
372- function parseOperatorList ( value : string ) : string [ ] [ ] {
373- // '(X0)(1-5)' becomes [['X0'], ['1', '2', '3', '4', '5']]
374- // kudos to Glen van Ginkel.
375-
376- const oeRegex = / \( ? ( [ ^ ( ) ] + ) \) ? ] * / g, groups : string [ ] = [ ] , ret : string [ ] [ ] = [ ] ;
377-
378- let g : any ;
379- while ( g = oeRegex . exec ( value ) ) groups [ groups . length ] = g [ 1 ] ;
380-
381- groups . forEach ( g => {
382- const group : string [ ] = [ ] ;
383- g . split ( ',' ) . forEach ( e => {
384- const dashIndex = e . indexOf ( '-' ) ;
385- if ( dashIndex > 0 ) {
386- const from = parseInt ( e . substring ( 0 , dashIndex ) ) , to = parseInt ( e . substring ( dashIndex + 1 ) ) ;
387- for ( let i = from ; i <= to ; i ++ ) group [ group . length ] = i . toString ( ) ;
388- } else {
389- group [ group . length ] = e . trim ( ) ;
390- }
391- } ) ;
392- ret [ ret . length ] = group ;
393- } ) ;
394-
395- return ret ;
396- }
397-
398377 // set of provided [structOperId, labelAsymId] combinations
399378 const ids = p . targets . map ( t => [ t . structOperId || '1' , t . labelAsymId ! ] ) . filter ( ( x , i , a ) => a . indexOf ( x ) === i ) ;
400-
401379 try {
402- // find first assembly that contains all requested structOperIds - if multiple, the first will be returned
403380 const pdbx_struct_assembly_gen = traj . obj . data . representative . sourceData . data . frame . categories . pdbx_struct_assembly_gen ;
404- if ( pdbx_struct_assembly_gen ) {
405- const assembly_id = pdbx_struct_assembly_gen . getField ( 'assembly_id' ) ;
406- const oper_expression = pdbx_struct_assembly_gen . getField ( 'oper_expression' ) ;
407- const asym_id_list = pdbx_struct_assembly_gen . getField ( 'asym_id_list' ) ;
408-
409- for ( let i = 0 , il = pdbx_struct_assembly_gen . rowCount ; i < il ; i ++ ) {
410- if ( ids . some ( val => ! equals ( oper_expression . str ( i ) , val [ 0 ] ) || asym_id_list . str ( i ) . indexOf ( val [ 1 ] ) === - 1 ) ) continue ;
411-
412- Object . assign ( p , { assemblyId : assembly_id . str ( i ) } ) ;
413- return ;
414- }
415- } else {
416- // this happens e.g. for AlphaFold structures
417- console . warn ( `Source file is missing 'pdbx_struct_assembly_gen' category` ) ;
381+ // find first assembly that contains all requested structOperIds - if multiple, the first will be returned
382+ const assemblyId = firstMatchingAssemblyId ( pdbx_struct_assembly_gen , ids ) ;
383+ if ( assemblyId ) {
384+ Object . assign ( p , { assemblyId : assemblyId } ) ;
385+ return ;
418386 }
419387 } catch ( error ) {
420388 console . warn ( error ) ;
@@ -466,3 +434,4 @@ async function initVolumeStreaming(plugin: PluginContext, structure: StructureOb
466434 volume : false
467435 } ) ;
468436}
437+
0 commit comments