diff --git a/src/apps/canvas/app.ts b/src/apps/canvas/app.ts new file mode 100644 index 0000000000000000000000000000000000000000..b6c450a26893adb7faf2f05bc704954e54f3bed4 --- /dev/null +++ b/src/apps/canvas/app.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import Viewer from 'mol-view/viewer'; +import { getCifFromUrl, getModelsFromMmcif } from './util'; +import { StructureView } from './view'; +import { BehaviorSubject } from 'rxjs'; + +export class App { + viewer: Viewer + container: HTMLDivElement | null = null; + canvas: HTMLCanvasElement | null = null; + structureView: StructureView | null = null; + + pdbIdLoaded: BehaviorSubject<StructureView | null> = new BehaviorSubject<StructureView | null>(null) + + initViewer(_canvas: HTMLCanvasElement, _container: HTMLDivElement) { + this.canvas = _canvas + this.container = _container + + try { + this.viewer = Viewer.create(this.canvas, this.container) + this.viewer.animate() + return true + } catch (e) { + console.error(e) + return false + } + } + + async loadPdbId(id: string) { + if (this.structureView) this.structureView.destroy() + const cif = await getCifFromUrl(`https://files.rcsb.org/download/${id}.cif`) + const models = await getModelsFromMmcif(cif) + this.structureView = await StructureView(this.viewer, models, { assembly: '1' }) + this.pdbIdLoaded.next(this.structureView) + } +} \ No newline at end of file diff --git a/src/apps/canvas/assembly-symmetry.ts b/src/apps/canvas/assembly-symmetry.ts index ecf11d1ae6562642350f95eeb8c7b755109295fc..e5b916802337473d2a5c5b7860fb1056e2b5b37c 100644 --- a/src/apps/canvas/assembly-symmetry.ts +++ b/src/apps/canvas/assembly-symmetry.ts @@ -4,17 +4,14 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { AssemblySymmetry } from "mol-model-props/rcsb/symmetry"; -import { Table } from "mol-data/db"; -import { Color } from "mol-util/color"; -import { MeshBuilder } from "mol-geo/mesh/mesh-builder"; -import { Tensor } from "mol-math/linear-algebra"; -import { addSphere } from "mol-geo/mesh/builder/sphere"; -import { addCylinder } from "mol-geo/mesh/builder/cylinder"; -import { Shape } from "mol-model/shape"; -import { DefaultView } from "./view"; -import { Model } from "mol-model/structure"; -import { ShapeRepresentation, ShapeProps } from "mol-geo/representation/shape"; +import { AssemblySymmetry } from 'mol-model-props/rcsb/symmetry'; +import { Table } from 'mol-data/db'; +import { Color } from 'mol-util/color'; +import { MeshBuilder } from 'mol-geo/mesh/mesh-builder'; +import { Tensor } from 'mol-math/linear-algebra'; +import { addSphere } from 'mol-geo/mesh/builder/sphere'; +import { addCylinder } from 'mol-geo/mesh/builder/cylinder'; +import { Shape } from 'mol-model/shape'; export function getAxesShape(featureId: number, assemblySymmetry: AssemblySymmetry) { const f = assemblySymmetry.db.rcsb_assembly_symmetry_feature @@ -59,39 +56,4 @@ export function getClusterColorTheme(featureId: number, assemblySymmetry: Assemb for (let i = 0, il = clusters._rowCount; i < il; ++i) { console.log(clusters.members.value(i), clusters.avg_rmsd.value(i), feature.stoichiometry_value, feature.stoichiometry_description) } -} - -export interface SymmetryView extends DefaultView { - readonly axes: ShapeRepresentation<ShapeProps> // TODO -} - -export async function SymmetryView(model: Model, assembly: string): Promise<SymmetryView> { - const view = await DefaultView(model, assembly) - const axesRepr = ShapeRepresentation() - - await AssemblySymmetry.attachFromCifOrAPI(model) - const assemblySymmetry = AssemblySymmetry.get(model) - console.log(assemblySymmetry) - if (assemblySymmetry) { - const features = assemblySymmetry.getFeatures(assembly) - if (features._rowCount) { - const axesShape = getAxesShape(features.id.value(1), assemblySymmetry) - console.log(axesShape) - if (axesShape) { - - await axesRepr.create(axesShape, { - colorTheme: { name: 'shape-group' }, - // colorTheme: { name: 'uniform', value: Color(0xFFCC22) }, - useFog: false // TODO fog not working properly - }).run() - } - - getClusterColorTheme(features.id.value(0), assemblySymmetry) - getClusterColorTheme(features.id.value(1), assemblySymmetry) - } - } - - return Object.assign({}, view, { - axes: axesRepr - }) } \ No newline at end of file diff --git a/src/apps/canvas/component/app.tsx b/src/apps/canvas/component/app.tsx new file mode 100644 index 0000000000000000000000000000000000000000..73fcbdcec1f0b900ba1d288ad7ea076b6f9772a2 --- /dev/null +++ b/src/apps/canvas/component/app.tsx @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' +import { StructureView } from '../view'; +import { App } from '../app'; +import { Viewport } from './viewport'; +import { StructureComponent } from './structure'; + +// export function FileInput (props: { +// accept: string +// onChange: (v: FileList | null) => void, +// }) { +// return <input +// accept={props.accept || '*.*'} +// type='file' +// onChange={e => props.onChange.call(null, e.target.files)} +// /> +// } + +export interface AppProps { + app: App +} + +export interface AppState { + structureView: StructureView | null +} + +export class AppComponent extends React.Component<AppProps, AppState> { + state = { + structureView: this.props.app.structureView, + } + + componentDidMount() { + this.props.app.pdbIdLoaded.subscribe(() => this.setState({ + structureView: this.props.app.structureView + })) + } + + render() { + const { structureView } = this.state + + return <div style={{width: '100%', height: '100%'}}> + <div style={{float: 'left', width: '70%', height: '100%'}}> + <Viewport app={this.props.app} /> + </div> + + <div style={{float: 'right', width: '25%', height: '100%'}}> + {structureView ? <StructureComponent structureView={structureView} /> : ''} + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/apps/canvas/component/structure.tsx b/src/apps/canvas/component/structure.tsx new file mode 100644 index 0000000000000000000000000000000000000000..124ccc6847816f24dcac126185881bbd504a93a9 --- /dev/null +++ b/src/apps/canvas/component/structure.tsx @@ -0,0 +1,112 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' +import { StructureView } from '../view'; + +// export function FileInput (props: { +// accept: string +// onChange: (v: FileList | null) => void, +// }) { +// return <input +// accept={props.accept || '*.*'} +// type='file' +// onChange={e => props.onChange.call(null, e.target.files)} +// /> +// } + +export interface StructureComponentProps { + structureView: StructureView +} + +export interface StructureComponentState { + label: string + assemblyId: string + assemblyIds: { id: string, label: string }[] + symmetryFeatureId: number + symmetryFeatureIds: { id: number, label: string }[] +} + +export class StructureComponent extends React.Component<StructureComponentProps, StructureComponentState> { + state = { + label: this.props.structureView.label, + assemblyId: this.props.structureView.assemblyId, + assemblyIds: this.props.structureView.getAssemblyIds(), + symmetryFeatureId: this.props.structureView.symmetryFeatureId, + symmetryFeatureIds: this.props.structureView.getSymmetryFeatureIds() + } + + componentWillMount() { + const sv = this.props.structureView + + this.setState({ + ...this.state, + label: sv.label, + assemblyId: sv.assemblyId, + assemblyIds: sv.getAssemblyIds(), + symmetryFeatureId: sv.symmetryFeatureId, + symmetryFeatureIds: sv.getSymmetryFeatureIds() + }) + } + + async update(state: Partial<StructureComponentState>) { + const sv = this.props.structureView + + if (state.assemblyId !== undefined) await sv.setAssembly(state.assemblyId) + if (state.symmetryFeatureId !== undefined) await sv.setSymmetryFeature(state.symmetryFeatureId) + + const newState = { + ...this.state, + label: sv.label, + assemblyId: sv.assemblyId, + assemblyIds: sv.getAssemblyIds(), + symmetryFeatureId: sv.symmetryFeatureId, + symmetryFeatureIds: sv.getSymmetryFeatureIds() + } + this.setState(newState) + } + + render() { + const { label, assemblyIds, symmetryFeatureIds } = this.state + + const assemblyIdOptions = assemblyIds.map(a => { + return <option key={a.id} value={a.id}>{a.label}</option> + }) + const symmetryFeatureIdOptions = symmetryFeatureIds.map(f => { + return <option key={f.id} value={f.id}>{f.label}</option> + }) + + return <div> + <div> + <span>{label}</span> + </div> + <div> + <div> + <span>Assembly</span> + <select + value={this.state.assemblyId} + onChange={(e) => { + this.update({ assemblyId: e.target.value }) + }} + > + {assemblyIdOptions} + </select> + </div> + <div> + <span>Symmetry Feature</span> + <select + value={this.state.symmetryFeatureId} + onChange={(e) => { + this.update({ symmetryFeatureId: parseInt(e.target.value) }) + }} + > + {symmetryFeatureIdOptions} + </select> + </div> + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/apps/canvas/component/viewport.tsx b/src/apps/canvas/component/viewport.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f391125b28d0d2dbe1d4302dc650e27f57b656c5 --- /dev/null +++ b/src/apps/canvas/component/viewport.tsx @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' +import { App } from '../app'; +import { MarkerAction } from 'mol-geo/util/marker-data'; +import { EveryLoci } from 'mol-model/loci'; +import { labelFirst } from 'mol-view/label'; + +interface ViewportProps { + app: App +} + +interface ViewportState { + noWebGl: boolean, + info: string +} + +export class Viewport extends React.Component<ViewportProps, ViewportState> { + private container: HTMLDivElement | null = null; + private canvas: HTMLCanvasElement | null = null; + + state: ViewportState = { + noWebGl: false, + info: '' + }; + + handleResize() { + this.props.app.viewer.handleResize() + } + + componentDidMount() { + if (!this.canvas || !this.container || !this.props.app.initViewer(this.canvas, this.container)) { + this.setState({ noWebGl: true }); + } + this.handleResize() + + const viewer = this.props.app.viewer + + viewer.input.resize.subscribe(() => this.handleResize()) + + viewer.input.move.subscribe(({x, y, inside, buttons}) => { + if (!inside || buttons) return + const p = viewer.identify(x, y) + const loci = viewer.getLoci(p) + + viewer.mark(EveryLoci, MarkerAction.RemoveHighlight) + viewer.mark(loci, MarkerAction.Highlight) + + const label = labelFirst(loci) + const info = `${label}` + this.setState({ info }) + }) + } + + componentWillUnmount() { + if (super.componentWillUnmount) super.componentWillUnmount(); + // TODO viewer cleanup + } + + renderMissing() { + return <div> + <div> + <p><b>WebGL does not seem to be available.</b></p> + <p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps.</p> + <p>For a list of supported browsers, refer to <a href='http://caniuse.com/#feat=webgl' target='_blank'>http://caniuse.com/#feat=webgl</a>.</p> + </div> + </div> + } + + render() { + if (this.state.noWebGl) return this.renderMissing(); + + return <div style={{ backgroundColor: 'rgb(0, 0, 0)', width: '100%', height: '100%'}}> + <div ref={elm => this.container = elm} style={{width: '100%', height: '100%'}}> + <canvas ref={elm => this.canvas = elm}></canvas> + </div> + <div + style={{ + position: 'absolute', + top: 10, + left: 10, + padding: 10, + color: 'lightgrey', + background: 'rgba(0, 0, 0, 0.2)' + }} + > + {this.state.info} + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/apps/canvas/index.html b/src/apps/canvas/index.html index 7b2d2512a1552862b42bd98ebe9be164f15812dc..aa8b418e98a6b136e98934a53547f0f03cde5d3b 100644 --- a/src/apps/canvas/index.html +++ b/src/apps/canvas/index.html @@ -4,12 +4,20 @@ <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> <title>Mol* Canvas</title> + <style> + * { + margin: 0; + padding: 0; + } + html, body { + width: 100%; + height: 100%; + overflow: hidden; + } + </style> </head> <body> - <div id="container" style="width:1024px; height: 768px;"> - <canvas id="canvas"></canvas> - </div> - <span id="info"></span> + <div id="app" style="width: 100%; height: 100%"></div> <script type="text/javascript" src="./index.js"></script> </body> </html> \ No newline at end of file diff --git a/src/apps/canvas/index.ts b/src/apps/canvas/index.ts index 41b176944d4f0b3ce9fb76eaab86fb89873db1cb..49d4094c098e08a94eafdab889d7eaca7fdc5453 100644 --- a/src/apps/canvas/index.ts +++ b/src/apps/canvas/index.ts @@ -4,57 +4,18 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import './index.html' - -import Viewer from 'mol-view/viewer'; -import { EveryLoci } from 'mol-model/loci'; -import { MarkerAction } from 'mol-geo/util/marker-data'; -import { labelFirst } from 'mol-view/label'; -import { getCifFromUrl, getModelFromMmcif } from './util'; -import { SymmetryView } from './assembly-symmetry'; - -const container = document.getElementById('container') -if (!container) throw new Error('Can not find element with id "container".') - -const canvas = document.getElementById('canvas') as HTMLCanvasElement -if (!canvas) throw new Error('Can not find element with id "canvas".') - -const info = document.getElementById('info') -if (!info) throw new Error('Can not find element with id "info".') +import * as React from 'react' +import * as ReactDOM from 'react-dom' -const viewer = Viewer.create(canvas, container) -viewer.animate() - -viewer.input.resize.subscribe(() => { - // do whatever appropriate -}) - -viewer.input.move.subscribe(({x, y, inside, buttons}) => { - if (!inside || buttons) return - const p = viewer.identify(x, y) - const loci = viewer.getLoci(p) - - viewer.mark(EveryLoci, MarkerAction.RemoveHighlight) - viewer.mark(loci, MarkerAction.Highlight) - - const label = labelFirst(loci) - info.innerText = `${label}` -}) +import './index.html' -async function init() { - const assembly = '1' +import { App } from './app'; +import { AppComponent } from './component/app'; - const cif = await getCifFromUrl('https://files.rcsb.org/download/4hhb.cif') - const model = await getModelFromMmcif(cif) - - const view = await SymmetryView(model, assembly) - viewer.center(view.structure.boundary.sphere.center) - viewer.add(view.cartoon) - viewer.add(view.ballAndStick) - viewer.add(view.axes) +const elm = document.getElementById('app') as HTMLElement +if (!elm) throw new Error('Can not find element with id "app".') - // ensure the added representations get rendered, i.e. without mouse input - viewer.requestDraw() -} +const app = new App() +ReactDOM.render(React.createElement(AppComponent, { app }), elm); -init() \ No newline at end of file +app.loadPdbId('2ONK') \ No newline at end of file diff --git a/src/apps/canvas/util.ts b/src/apps/canvas/util.ts index 6057b09d7a185e9c8110d1821a1502adae303abe..f63797e1016804f9209bd39a2411750f314063a8 100644 --- a/src/apps/canvas/util.ts +++ b/src/apps/canvas/util.ts @@ -5,7 +5,7 @@ */ import CIF, { CifBlock } from 'mol-io/reader/cif' -import { readUrlAs } from "mol-util/read"; +import { readUrlAs } from 'mol-util/read'; import { Model, Format, StructureSymmetry, Structure } from 'mol-model/structure'; // import { parse as parseObj } from 'mol-io/reader/obj/parser' @@ -25,16 +25,15 @@ export async function getCifFromUrl(url: string) { return parsed.result.blocks[0] } -export async function getModelFromMmcif(cif: CifBlock) { - const models = await Model.create(Format.mmCIF(cif)).run() - return models[0] +export async function getModelsFromMmcif(cif: CifBlock) { + return await Model.create(Format.mmCIF(cif)).run() } -export async function getStructureFromModel(model: Model, assembly = '0') { +export async function getStructureFromModel(model: Model, assembly: string) { const assemblies = model.symmetry.assemblies - if (assemblies.length) { - return await StructureSymmetry.buildAssembly(Structure.ofModel(model), assembly).run() - } else { + if (assembly === '0') { return Structure.ofModel(model) + } else if (assemblies.find(a => a.id === assembly)) { + return await StructureSymmetry.buildAssembly(Structure.ofModel(model), assembly).run() } } \ No newline at end of file diff --git a/src/apps/canvas/view.ts b/src/apps/canvas/view.ts index c0dba012644e40f303f178a4b958a04cfa356191..ca30b1692b1474d4219d35695b078361c0bf4aee 100644 --- a/src/apps/canvas/view.ts +++ b/src/apps/canvas/view.ts @@ -4,54 +4,213 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Model, Structure } from "mol-model/structure"; +import { Model, Structure } from 'mol-model/structure'; import { CartoonRepresentation } from 'mol-geo/representation/structure/representation/cartoon'; -import { BallAndStickRepresentation } from 'mol-geo/representation/structure/representation/ball-and-stick'; -import { getStructureFromModel } from "./util"; +// import { BallAndStickRepresentation } from 'mol-geo/representation/structure/representation/ball-and-stick'; +import { getStructureFromModel } from './util'; +import { AssemblySymmetry } from 'mol-model-props/rcsb/symmetry'; +import { ShapeRepresentation, ShapeProps } from 'mol-geo/representation/shape'; +import { getAxesShape } from './assembly-symmetry'; +import Viewer from 'mol-view/viewer'; + +export interface StructureView { + readonly label: string + readonly models: ReadonlyArray<Model> + readonly structure: Structure | undefined + readonly assemblySymmetry: AssemblySymmetry | undefined -export interface DefaultView { - readonly model: Model - readonly structure: Structure readonly cartoon: CartoonRepresentation - readonly ballAndStick: BallAndStickRepresentation - - setAssembly(assembly: string): void + // readonly ballAndStick: BallAndStickRepresentation + readonly axes: ShapeRepresentation<ShapeProps> + + readonly modelId: number + readonly assemblyId: string + readonly symmetryFeatureId: number + + setAssembly(assembly: string): Promise<void> + getAssemblyIds(): { id: string, label: string }[] + setSymmetryFeature(symmetryFeature: number): Promise<void> + getSymmetryFeatureIds(): { id: number, label: string }[] + + destroy: () => void } -export async function DefaultView(model: Model, assembly: string): Promise<DefaultView> { +interface StructureViewProps { + assembly?: string + symmetryFeature?: number +} + +export async function StructureView(viewer: Viewer, models: ReadonlyArray<Model>, props: StructureViewProps): Promise<StructureView> { const cartoon = CartoonRepresentation() - const ballAndStick = BallAndStickRepresentation() - - let structure: Structure + // const ballAndStick = BallAndStickRepresentation() + const axes = ShapeRepresentation() + + let label: string + let model: Model | undefined + let assemblySymmetry: AssemblySymmetry | undefined + let structure: Structure | undefined + + let modelId: number + let assemblyId: string + let symmetryFeatureId: number - async function setAssembly(assembly: string) { - structure = await getStructureFromModel(model, assembly) - await createRepr() + async function setModel(newModelId: number, newAssemblyId?: string, newSymmetryFeatureId?: number) { + modelId = newModelId + + model = models[modelId] + await AssemblySymmetry.attachFromCifOrAPI(model) + assemblySymmetry = AssemblySymmetry.get(model) + + await setAssembly(newAssemblyId, newSymmetryFeatureId) + } + + async function setAssembly(newAssemblyId?: string, newSymmetryFeatureId?: number) { + if (newAssemblyId !== undefined) { + assemblyId = newAssemblyId + } else if (model && model.symmetry.assemblies.length) { + assemblyId = model.symmetry.assemblies[0].id + } else if (model) { + assemblyId = '0' + } else { + assemblyId = '-1' + } + await getStructure() + await createStructureRepr() + await setSymmetryFeature(newSymmetryFeatureId) } - async function createRepr() { - await cartoon.create(structure, { - colorTheme: { name: 'chain-id' }, - sizeTheme: { name: 'uniform', value: 0.2 }, - useFog: false // TODO fog not working properly - }).run() - - await ballAndStick.create(structure, { - colorTheme: { name: 'element-symbol' }, - sizeTheme: { name: 'uniform', value: 0.1 }, - useFog: false // TODO fog not working properly - }).run() + function getAssemblyIds() { + const assemblyIds: { id: string, label: string }[] = [ + { id: '0', label: '0: model' } + ] + if (model) model.symmetry.assemblies.forEach(a => { + assemblyIds.push({ id: a.id, label: `${a.id}: ${a.details}` }) + }) + return assemblyIds } - await setAssembly(assembly) + async function setSymmetryFeature(newSymmetryFeatureId?: number) { + if (newSymmetryFeatureId !== undefined) { + symmetryFeatureId = newSymmetryFeatureId + } else if (assemblySymmetry) { + const f = assemblySymmetry.getFeatures(assemblyId) + if (f._rowCount) { + symmetryFeatureId = f.id.value(0) + } else { + symmetryFeatureId = -1 + } + } else { + symmetryFeatureId = -1 + } + await createSymmetryRepr() + } + + function getSymmetryFeatureIds() { + const symmetryFeatureIds: { id: number, label: string }[] = [] + if (assemblySymmetry) { + const symmetryFeatures = assemblySymmetry.getFeatures(assemblyId) + for (let i = 0, il = symmetryFeatures._rowCount; i < il; ++i) { + const id = symmetryFeatures.id.value(i) + const symmetry = symmetryFeatures.symmetry_value.value(i) + const type = symmetryFeatures.type.value(i) + const stoichiometry = symmetryFeatures.stoichiometry_value.value(i) + const label = `${id}: ${symmetry} ${type} ${stoichiometry}` + symmetryFeatureIds.push({ id, label }) + } + } + return symmetryFeatureIds + } + + async function getStructure() { + if (model) structure = await getStructureFromModel(model, assemblyId) + if (model && structure) { + label = `${model.label} - Assembly ${assemblyId}` + } else { + label = '' + } + await createStructureRepr() + } + + async function createStructureRepr() { + if (structure) { + await cartoon.create(structure, { + colorTheme: { name: 'chain-id' }, + sizeTheme: { name: 'uniform', value: 0.2 }, + useFog: false // TODO fog not working properly + }).run() + + // await ballAndStick.create(structure, { + // colorTheme: { name: 'element-symbol' }, + // sizeTheme: { name: 'uniform', value: 0.1 }, + // useFog: false // TODO fog not working properly + // }).run() + + viewer.center(structure.boundary.sphere.center) + } else { + cartoon.destroy() + // ballAndStick.destroy() + } + + viewer.add(cartoon) + // viewer.add(ballAndStick) + } + + async function createSymmetryRepr() { + if (assemblySymmetry) { + const features = assemblySymmetry.getFeatures(assemblyId) + if (features._rowCount) { + const axesShape = getAxesShape(symmetryFeatureId, assemblySymmetry) + if (axesShape) { + // getClusterColorTheme(symmetryFeatureId, assemblySymmetry) + await axes.create(axesShape, { + colorTheme: { name: 'shape-group' }, + // colorTheme: { name: 'uniform', value: Color(0xFFCC22) }, + useFog: false // TODO fog not working properly + }).run() + } else { + axes.destroy() + } + } else { + axes.destroy() + } + } else { + axes.destroy() + } + viewer.add(axes) + viewer.requestDraw() + } + + await setModel(0, props.assembly, props.symmetryFeature) return { - model, + get label() { return label }, + models, get structure() { return structure }, + get assemblySymmetry() { return assemblySymmetry }, + cartoon, - ballAndStick, + // ballAndStick, + axes, + + get modelId() { return modelId }, + get assemblyId() { return assemblyId }, + get symmetryFeatureId() { return symmetryFeatureId }, + + setAssembly, + getAssemblyIds, + setSymmetryFeature, + getSymmetryFeatureIds, + + destroy: () => { + viewer.remove(cartoon) + // viewer.remove(ballAndStick) + viewer.remove(axes) + viewer.requestDraw() - setAssembly + cartoon.destroy() + // ballAndStick.destroy() + axes.destroy() + } } }