diff --git a/app/app.view.css.ts b/app/app.view.css.ts index db197e6..fe2da60 100644 --- a/app/app.view.css.ts +++ b/app/app.view.css.ts @@ -34,6 +34,9 @@ namespace $.$$ { Body_content: { gap: $mol_gap.block, maxWidth: '25rem', + flex: { + direction: 'row', + }, }, Head: { justify: { @@ -42,6 +45,13 @@ namespace $.$$ { }, }, + Body: { + flex: { + direction: 'column', + grow: 1, + }, + }, + Player: { flex: { grow: 1, diff --git a/app/app.view.tree b/app/app.view.tree index 67d7207..d990968 100644 --- a/app/app.view.tree +++ b/app/app.view.tree @@ -13,10 +13,11 @@ $mpds_cifplayer_app $mol_drop uri \https://github.com/tilde-lab/cifplayer title \ <= Lights $mol_lights_toggle - body <= menu_body / + body / <= Body $mol_view sub / <= Upload $mol_button_open Icon => Upload_icon Native => Upload_native + files? <=> files_read? null sub / <= Upload_icon <= Upload_native diff --git a/lib/three/view/view.view.web.ts b/lib/three/view/view.view.web.ts index 9866125..d1fb465 100644 --- a/lib/three/view/view.view.web.ts +++ b/lib/three/view/view.view.web.ts @@ -29,18 +29,21 @@ namespace $.$$ { /** Remove an existing object and create a new */ new_object< T extends InstanceType< THREE["Object3D"] > >( name: string, make: ()=> T ): T { const old = this.scene()?.getObjectByName( name ) - if( old ) { - $mpds_cifplayer_lib_three_view_dispose_deep( old ) - this.scene()?.remove( old ) - } + if( old ) this.remove_object( old ) const obj = make() obj.name = name this.scene().add( obj ) + obj.destructor = ()=> this.remove_object( obj ) return obj } + remove_object( obj: any ) { + $mpds_cifplayer_lib_three_view_dispose_deep( obj ) + this.scene()?.remove( obj ) + } + @ $mol_mem scene() { const scene = new THREE.Scene() diff --git a/matinfio/cell/cell.ts b/matinfio/cell/cell.ts index b36b2bb..cd60c10 100644 --- a/matinfio/cell/cell.ts +++ b/matinfio/cell/cell.ts @@ -6,8 +6,17 @@ namespace $ { return math.divide( vec, math.norm( vec ) ) } + export type $mpds_cifplayer_matinfio_cell = { + a: number, + b: number, + c: number, + alpha: number, + beta: number, + gamma: number, + } + /** Crystalline cell parameters to 3x3 matrix */ - export function $mpds_cifplayer_matinfio_cell_to_matrix( this : $, cell: NonNullable< $mpds_cifplayer_matinfio_internal_obj['cell'] > ): number[][] { + export function $mpds_cifplayer_matinfio_cell_to_matrix( this : $, cell: $mpds_cifplayer_matinfio_cell ): number[][] { const { a, b, c, alpha, beta, gamma } = cell if( !a || !b || !c || !alpha || !beta || !gamma ) { return this.$mol_fail( new $mol_data_error('Error: invalid cell definition') ) diff --git a/matinfio/matinfio.web.ts b/matinfio/matinfio.web.ts index 16bed20..5fd7140 100644 --- a/matinfio/matinfio.web.ts +++ b/matinfio/matinfio.web.ts @@ -34,6 +34,8 @@ namespace $ { x: number, y: number, z: number, + c: string, //color + r: number, //radius symbol: string, label: string, overlays: Record< string, string | number >, @@ -41,14 +43,7 @@ namespace $ { export type $mpds_cifplayer_matinfio_internal_obj = { cell_matrix?: number[][], - cell?: { - a: number, - b: number, - c: number, - alpha: number, - beta: number, - gamma: number, - }, + cell?: $mpds_cifplayer_matinfio_cell, atoms: $mpds_cifplayer_matinfio_internal_obj_atom[], sg_name: string, ng_name: string, @@ -58,6 +53,33 @@ namespace $ { mpds_data: boolean, } + export type $mpds_cifplayer_matinfio_player_obj = { + cell_matrix?: number[][], + cell?: $mpds_cifplayer_matinfio_cell, + descr: $mpds_cifplayer_matinfio_cell & { + symlabel?: string, + }, + overlayed: Record< string, string >, + atoms: { + fract: { + x: number, + y: number, + z: number, + } | null, + x: number, + y: number, + z: number, + c: string, //color + r: number, //radius + overlays: Record< string, string | number >, + }[], + sg_name: string, + ng_name: number, + info: string, + mpds_demo: boolean, + mpds_data: boolean, + } + export class $mpds_cifplayer_matinfio extends $mol_object2 { static log = this.$.$mpds_cifplayer_matinfio_log diff --git a/matinfio/player/player.web.ts b/matinfio/player/player.web.ts index f22487d..f963743 100644 --- a/matinfio/player/player.web.ts +++ b/matinfio/player/player.web.ts @@ -4,32 +4,28 @@ namespace $ { /** Prepare internal repr for visualization in three.js */ export function $mpds_cifplayer_matinfio_player_from_obj( this: $, crystal: $mpds_cifplayer_matinfio_internal_obj ) { - let cell_matrix + let cell_matrix: number[][] | undefined let descr: any = false if( crystal.cell && Object.keys( crystal.cell ).length == 6 ) { // for CIF + cell_matrix = this.$mpds_cifplayer_matinfio_cell_to_matrix( crystal.cell ) descr = crystal.cell var symlabel = ( crystal.sg_name || crystal.ng_name ) ? ( ( crystal.sg_name ? crystal.sg_name : "" ) + ( crystal.ng_name ? ( " (" + crystal.ng_name + ")" ) : "" ) ) : false if( symlabel ) descr.symlabel = symlabel } else { + cell_matrix = crystal.cell_matrix // for POSCAR and OPTIMADE - - const params = $mpds_cifplayer_matinfio_cell_params_from_matrix( cell_matrix! ) - descr = { - 'a': params[0], - 'b': params[1], - 'c': params[2], - 'alpha': params[3], - 'beta': params[4], - 'gamma': params[5], + if( cell_matrix ) { + const [ a, b, c, alpha, beta, gamma ] = $mpds_cifplayer_matinfio_cell_params_from_matrix( cell_matrix ) + descr = { a, b, c, alpha, beta, gamma } } } if( !crystal.atoms.length ) this.$mpds_cifplayer_matinfio_log.warning( "Note: no atomic coordinates supplied" ) - const render = { + const render: $mpds_cifplayer_matinfio_player_obj = { atoms: [] as any[], cell_matrix: cell_matrix, cell: descr, diff --git a/player/player.view.css.ts b/player/player.view.css.ts index b3dfe77..1a9e5c3 100644 --- a/player/player.view.css.ts +++ b/player/player.view.css.ts @@ -6,6 +6,8 @@ namespace $.$$ { color: $mol_theme.back, }, + position: 'relative', + '@': { fullscreen: { 'true': { @@ -31,6 +33,28 @@ namespace $.$$ { color: $mol_style_func.vary('--color_c') }, + Spread_label_a: { + color: $mol_style_func.vary('--color_a'), + padding: $mol_gap.text, + }, + + Spread_label_b: { + color: $mol_style_func.vary('--color_b'), + padding: $mol_gap.text, + }, + + Spread_label_c: { + color: $mol_style_func.vary('--color_c'), + padding: $mol_gap.text, + }, + + Spread_cells: { + Bubble: { + display: 'grid', + gridTemplateColumns: 'auto auto', + }, + }, + Left_panel: { position: 'absolute', zIndex: 1, diff --git a/player/player.view.tree b/player/player.view.tree index ec85111..eca8614 100644 --- a/player/player.view.tree +++ b/player/player.view.tree @@ -5,14 +5,23 @@ $mpds_cifplayer_player $mol_view zoom_scale_step 0.3 vibrate? null unvibrate null + spread_cells / + <= spread_a? 1 + <= spread_b? 1 + <= spread_c? 1 + spread_cells_limit 50 + - auto / <= dir_light null <= ambient_light null - <= atom_box null - <= overlay_box null + ^ atom_boxes / + ^ overlay_boxes / <= cell_box null <= axes_box null <= overlay_changed null + atom_box* null + overlay_box* null + - sub / <= Three $mpds_cifplayer_lib_three_view scene => scene @@ -36,7 +45,7 @@ $mpds_cifplayer_player $mol_view title <= descr_beta \β= <= Descr_gamma $mol_paragraph title <= descr_gamma \γ= - <= Symlabel $mol_pick + ^ symlabel_visible / <= Symlabel $mol_pick trigger_content / <= Sym_icon $mol_icon_eye_check <= symlabel \SG @@ -51,6 +60,31 @@ $mpds_cifplayer_player $mol_view <= Sym_check*0 $mol_check_box title <= sym_name* \ checked? <=> symmetry_visible*? false + <= Spread_cells $mol_pick + trigger_content / + <= spread_cell_label \1×1×1 + bubble_content / + <= Spread_label_a $mol_paragraph + title \a + <= Spread_a $mol_number + value? <=> spread_a? + hint \1 + value_min 1 + value_max <= spread_limit_a 1 + <= Spread_label_b $mol_paragraph + title \b + <= Spread_b $mol_number + value? <=> spread_b? + hint \1 + value_min 1 + value_max <= spread_limit_b 1 + <= Spread_label_c $mol_paragraph + title \c + <= Spread_c $mol_number + value? <=> spread_c? + hint \1 + value_min 1 + value_max <= spread_limit_c 1 <= Center $mol_check_icon checked? <=> centered? true Icon <= Center_icon $mol_icon_image_filter_center_focus diff --git a/player/player.view.web.ts b/player/player.view.web.ts index 749dfea..28b167e 100644 --- a/player/player.view.web.ts +++ b/player/player.view.web.ts @@ -30,41 +30,40 @@ namespace $.$$ { @ $mol_mem symlabel(): string { - return this.structure_3d_data().mpds_data - ? '' - : (this.structure_3d_data().descr.symlabel) - ? 'SG ' + this.structure_3d_data().descr.symlabel - : '' + const data = this.structure_3d_data() + if( data.mpds_data ) return '' + + return data.descr.symlabel ? `SG ${data.descr.symlabel}` : '' } @ $mol_mem descr_a(): string { - return `a=${ parseFloat( this.structure_3d_data().descr.a ).toFixed( 3 ) }Å` + return `a=${ this.structure_3d_data().descr.a.toFixed( 3 ) }Å` } @ $mol_mem descr_b(): string { - return `b=${ parseFloat( this.structure_3d_data().descr.b ).toFixed( 3 ) }Å` + return `b=${ this.structure_3d_data().descr.b.toFixed( 3 ) }Å` } @ $mol_mem descr_c(): string { - return `c=${ parseFloat( this.structure_3d_data().descr.c ).toFixed( 3 ) }Å` + return `c=${ this.structure_3d_data().descr.c.toFixed( 3 ) }Å` } @ $mol_mem descr_alpha(): string { - return `α=${ parseFloat( this.structure_3d_data().descr.alpha ).toFixed( 3 ) }°` + return `α=${ this.structure_3d_data().descr.alpha.toFixed( 3 ) }°` } @ $mol_mem descr_beta(): string { - return `β=${ parseFloat( this.structure_3d_data().descr.beta ).toFixed( 3 ) }°` + return `β=${ this.structure_3d_data().descr.beta.toFixed( 3 ) }°` } @ $mol_mem descr_gamma(): string { - return `γ=${ parseFloat( this.structure_3d_data().descr.gamma ).toFixed( 3 ) }°` + return `γ=${ this.structure_3d_data().descr.gamma.toFixed( 3 ) }°` } @ $mol_mem @@ -180,7 +179,7 @@ namespace $.$$ { @ $mol_mem sym_checks() { - return this.spacegroup().symmetry_list().map( name => this.Sym_check( name ) ) + return this.symmetry_list().map( name => this.Sym_check( name ) ) } sym_name( id: any ): string { @@ -229,6 +228,11 @@ namespace $.$$ { ) } + @ $mol_mem + symmetry_list() { + return this.spacegroup().symmetry_list() + } + @ $mol_mem visible_atoms(){ const structure = this.structure_3d_data() @@ -237,13 +241,13 @@ namespace $.$$ { const atoms: $mpds_cifplayer_matinfio_internal_obj_atom[] = [] - const symmetries_enabled = this.spacegroup().symmetry_list().filter( name => this.symmetry_visible( name ) ) + const symmetries_enabled = this.symmetry_list().filter( name => this.symmetry_visible( name ) ) symmetries_enabled.forEach( symmetry => { const next_symmetries = symmetries_enabled.slice( 0, symmetries_enabled.indexOf( symmetry ) ) - this.symmetry_atoms( symmetry )!.forEach( ( data: any ) => { + this.symmetry_atoms( symmetry )!.forEach( data => { for (const name of next_symmetries) { @@ -261,46 +265,90 @@ namespace $.$$ { return atoms } - @ $mol_mem - atom_box() { - const atom_box = this.Three().new_object( `atom_box`, ()=> new THREE.Object3D() ) + @ $mol_mem_key + visible_atoms_translated( fract_translate: [ number, number, number ] ){ + const cell = this.structure_3d_data().cell + const cart_translate = cell ? [ + cell.a * fract_translate[0], + cell.b * fract_translate[1], + cell.c * fract_translate[2], + ] : [ 0, 0, 0 ] + + return this.visible_atoms().map( data => { + return { + ... data, + x: data.x + cart_translate[0], + y: data.y + cart_translate[1], + z: data.z + cart_translate[2], + } + } ) + } + + @ $mol_mem_key + atom_box( fract_translate: [ number, number, number ] ) { + const atom_box = this.Three().new_object( `atom_box` + fract_translate.toString(), ()=> new THREE.Object3D() ) - this.visible_atoms().forEach( ( data: any ) => { + this.visible_atoms_translated( fract_translate ).forEach( data => { const atom = new THREE.Mesh( new THREE.SphereGeometry( data.r * this.atom_radius_scale(), 10, 8 ), new THREE.MeshLambertMaterial( { color: data.c } ) ) atom.position.set( data.x, data.y, data.z ) - atom_box.add( atom ) + } ) return atom_box } + @ $mol_mem + cell_translations() { + const translations: [ number, number, number ][] = [] + + const [ spread_a, spread_b, spread_c ] = this.spread_cells() + + for( let a = 0; a < spread_a; a++ ) { + for( let b = 0; b < spread_b; b++ ) { + for( let c = 0; c < spread_c; c++ ) { + translations.push( [ a, b, c ] ) + } + } + } + + return translations + } + + atom_boxes() { + return this.cell_translations().map( t => this.atom_box( t ) ) + } + @ $mol_mem overlay_changed() { const overlay = this.overlay() const atom_datas = this.visible_atoms() - this.overlay_box().children.forEach( ( label: InstanceType< THREE["Object3D"] >, i: number ) => { + this.overlay_boxes().forEach( box => { - label.children.forEach( ( sprite: InstanceType< THREE["Object3D"] > ) => label.remove( sprite ) ) + box.children.forEach( ( label: InstanceType< THREE["Object3D"] >, i: number ) => { + + label.children.forEach( ( sprite: InstanceType< THREE["Object3D"] > ) => label.remove( sprite ) ) + + if( overlay ) { + const sprite = this.create_sprite( String( atom_datas[ i ].overlays[ overlay ] ) ) + label.add( sprite ) + } + } ) - if( overlay ) { - const sprite = this.create_sprite( String( atom_datas[ i ].overlays[ overlay ] ) ) - label.add( sprite ) - } - } ) + }) } - @ $mol_mem - overlay_box() { + @ $mol_mem_key + overlay_box( fract_translate: [ number, number, number ] ) { const overlay_box = this.Three().new_object( `overlay_box`, ()=> new THREE.Object3D() ) - this.visible_atoms().forEach( ( data ) => { + this.visible_atoms_translated( fract_translate ).forEach( ( data ) => { const label = new THREE.Object3D() label.position.set( data.x, data.y, data.z ) @@ -309,6 +357,12 @@ namespace $.$$ { return overlay_box } + + @ $mol_mem + overlay_boxes() { + return [ this.overlay_box( [0,0,0] ) ] + // return this.cell_translations().map( t => this.overlay_box( t ) ) + } @ $mol_mem dir_light(): InstanceType< THREE["DirectionalLight"] > { @@ -422,8 +476,8 @@ namespace $.$$ { vibrate( phonon: number[][] ) { $mol_wire_sync( this ).unvibrate() - const atoms = this.atom_box().children - const labels = this.overlay_box().children + const atoms = this.atom_box([0,0,0]).children + const labels = this.overlay_box([0,0,0]).children if( phonon.length !== atoms.length) { this.$.$mol_fail( new $mol_data_error(`Phonon length does not match number of atoms`) ) @@ -449,8 +503,8 @@ namespace $.$$ { this.tweens.removeAll() const atom_datas = this.visible_atoms() - const atoms = this.atom_box().children - const labels = this.overlay_box().children + const atoms = this.atom_box([0,0,0]).children + const labels = this.overlay_box([0,0,0]).children atoms.forEach( ( atom: InstanceType< THREE["Object3D"] >, i: number ) => { this.tweens.add( new TWEEN.Tween( atom.position ).to( atom_datas[ i ], 250 ).start() ) @@ -475,7 +529,48 @@ namespace $.$$ { return [] } - return this.structure_3d_data().cell_matrix ? super.left_panel() : [] + if( !this.structure_3d_data().cell_matrix ) return [] + + return super.left_panel() + } + + @ $mol_mem + symlabel_visible() { + return ( this.symmetry_list().length > 1 ) || this.symlabel() + ? super.symlabel_visible() + : [] + } + + @ $mol_mem + spread_cells() { + return [ + this.spread_a() || 1, + this.spread_b() || 1, + this.spread_c() || 1, + ] + } + + @ $mol_mem + spread_cell_label() { + return this.spread_cells().join('×') + } + + @ $mol_mem + spread_limit_a() { + const [ a, b, c ] = this.spread_cells() + return Math.floor( this.spread_cells_limit() / ( b * c ) ) + } + + @ $mol_mem + spread_limit_b() { + const [ a, b, c ] = this.spread_cells() + return Math.floor( this.spread_cells_limit() / ( a * c ) ) + } + + @ $mol_mem + spread_limit_c() { + const [ a, b, c ] = this.spread_cells() + return Math.floor( this.spread_cells_limit() / ( a * b ) ) } }