diff --git a/src/apps/canvas/assembly-symmetry.ts b/src/apps/canvas/assembly-symmetry.ts new file mode 100644 index 0000000000000000000000000000000000000000..ecf11d1ae6562642350f95eeb8c7b755109295fc --- /dev/null +++ b/src/apps/canvas/assembly-symmetry.ts @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @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"; + +export function getAxesShape(featureId: number, assemblySymmetry: AssemblySymmetry) { + const f = assemblySymmetry.db.rcsb_assembly_symmetry_feature + const feature = Table.pickRow(f, i => f.id.value(i) === featureId) + if (!feature) return + + const axes = assemblySymmetry.getAxes(featureId) + if (!axes._rowCount) return + + const vectorSpace = AssemblySymmetry.Schema.rcsb_assembly_symmetry_axis.start.space; + + const colors: Color[] = [] + const labels: string[] = [] + + const radius = 0.4 + const cylinderProps = { radiusTop: radius, radiusBottom: radius } + const meshBuilder = MeshBuilder.create(256, 128) + + for (let i = 0, il = axes._rowCount; i < il; ++i) { + const start = Tensor.toVec3(vectorSpace, axes.start.value(i)) + const end = Tensor.toVec3(vectorSpace, axes.end.value(i)) + meshBuilder.setGroup(i) + addSphere(meshBuilder, start, radius, 2) + addSphere(meshBuilder, end, radius, 2) + addCylinder(meshBuilder, start, end, 1, cylinderProps) + colors.push(Color(0xCCEE11)) + labels.push(`Axis ${i + 1} for ${feature.symmetry_value} ${feature.type.toLowerCase()} symmetry`) + } + const mesh = meshBuilder.getMesh() + const shape = Shape.create('Axes', mesh, colors, labels) + return shape +} + +export function getClusterColorTheme(featureId: number, assemblySymmetry: AssemblySymmetry) { + const f = assemblySymmetry.db.rcsb_assembly_symmetry_feature + const feature = Table.pickRow(f, i => f.id.value(i) === featureId) + if (!feature) return + + const clusters = assemblySymmetry.getClusters(featureId) + if (!clusters._rowCount) return + + 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/index.ts b/src/apps/canvas/index.ts index 52dcf24d33bc99fc19811e4d48617a2bf940b3fd..41b176944d4f0b3ce9fb76eaab86fb89873db1cb 100644 --- a/src/apps/canvas/index.ts +++ b/src/apps/canvas/index.ts @@ -7,26 +7,11 @@ import './index.html' import Viewer from 'mol-view/viewer'; -import CIF, { CifBlock } from 'mol-io/reader/cif' -// import { parse as parseObj } from 'mol-io/reader/obj/parser' -import { readUrlAs } from 'mol-util/read' -import { Model, Format, Structure, StructureSymmetry } 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 { EveryLoci } from 'mol-model/loci'; import { MarkerAction } from 'mol-geo/util/marker-data'; import { labelFirst } from 'mol-view/label'; -// import { Queries as Q, StructureProperties as SP, StructureSelection, StructureQuery } from 'mol-model/structure'; -import { MeshBuilder } from 'mol-geo/mesh/mesh-builder'; -import { ShapeRepresentation } from 'mol-geo/representation/shape'; -import { /*Vec3, Mat4,*/ Tensor } from 'mol-math/linear-algebra'; -import { Shape } from 'mol-model/shape'; -import { Color } from 'mol-util/color'; -import { addSphere } from 'mol-geo/mesh/builder/sphere'; -// import { Box } from 'mol-geo/primitive/box'; -import { AssemblySymmetry } from 'mol-model-props/rcsb/symmetry'; -import { addCylinder } from 'mol-geo/mesh/builder/cylinder'; -import { Table } from 'mol-data/db'; +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".') @@ -34,7 +19,7 @@ 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') as HTMLCanvasElement +const info = document.getElementById('info') if (!info) throw new Error('Can not find element with id "info".') const viewer = Viewer.create(canvas, container) @@ -56,178 +41,17 @@ viewer.input.move.subscribe(({x, y, inside, buttons}) => { info.innerText = `${label}` }) - -// async function getObjFromUrl(url: string) { -// const data = await readUrlAs(url, false) as string -// const comp = parseObj(data) -// const parsed = await comp.run() -// if (parsed.isError) throw parsed -// return parsed.result -// } - -async function getCifFromUrl(url: string) { - const data = await readUrlAs(url, false) - const comp = CIF.parse(data) - const parsed = await comp.run() - if (parsed.isError) throw parsed - return parsed.result.blocks[0] -} - -async function getModelFromMmcif(cif: CifBlock) { - const models = await Model.create(Format.mmCIF(cif)).run() - return models[0] -} - -async function getStructureFromModel(model: Model, assembly = '1') { - const assemblies = model.symmetry.assemblies - if (assemblies.length) { - return await StructureSymmetry.buildAssembly(Structure.ofModel(model), assembly).run() - } else { - return Structure.ofModel(model) - } -} - -function getAxesShape(featureId: number, assemblySymmetry: AssemblySymmetry) { - const f = assemblySymmetry.db.rcsb_assembly_symmetry_feature - const feature = Table.pickRow(f, i => f.id.value(i) === featureId) - if (!feature) return - - const axes = assemblySymmetry.getAxes(featureId) - if (!axes._rowCount) return - - const vectorSpace = AssemblySymmetry.Schema.rcsb_assembly_symmetry_axis.start.space; - - const colors: Color[] = [] - const labels: string[] = [] - - const radius = 0.4 - const cylinderProps = { radiusTop: radius, radiusBottom: radius } - const meshBuilder = MeshBuilder.create(256, 128) - - for (let i = 0, il = axes._rowCount; i < il; ++i) { - const start = Tensor.toVec3(vectorSpace, axes.start.value(i)) - const end = Tensor.toVec3(vectorSpace, axes.end.value(i)) - meshBuilder.setGroup(i) - addSphere(meshBuilder, start, radius, 2) - addSphere(meshBuilder, end, radius, 2) - addCylinder(meshBuilder, start, end, 1, cylinderProps) - colors.push(Color(0xCCEE11)) - labels.push(`Axis ${i + 1} for ${feature.symmetry_value} ${feature.type.toLowerCase()} symmetry`) - } - const mesh = meshBuilder.getMesh() - const shape = Shape.create('Axes', mesh, colors, labels) - return shape -} - -function getClusterColorTheme(featureId: number, assemblySymmetry: AssemblySymmetry) { - const f = assemblySymmetry.db.rcsb_assembly_symmetry_feature - const feature = Table.pickRow(f, i => f.id.value(i) === featureId) - if (!feature) return - - const clusters = assemblySymmetry.getClusters(featureId) - if (!clusters._rowCount) return - - 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) - } -} - async function init() { const assembly = '1' const cif = await getCifFromUrl('https://files.rcsb.org/download/4hhb.cif') const model = await getModelFromMmcif(cif) - const structure = await getStructureFromModel(model, assembly) - viewer.center(structure.boundary.sphere.center) - - // cartoon for whole structure - const cartoonRepr = CartoonRepresentation() - await cartoonRepr.create(structure, { - colorTheme: { name: 'chain-id' }, - sizeTheme: { name: 'uniform', value: 0.2 }, - useFog: false // TODO fog not working properly - }).run() - viewer.add(cartoonRepr) - - // ball+stick for whole structure - const ballStickRepr = BallAndStickRepresentation() - await ballStickRepr.create(structure, { - colorTheme: { name: 'element-symbol' }, - sizeTheme: { name: 'uniform', value: 0.1 }, - useFog: false // TODO fog not working properly - }).run() - viewer.add(ballStickRepr) - - // // create new structure via query - // const q1 = Q.generators.atoms({ - // residueTest: qtx => SP.residue.label_seq_id(qtx.element) < 7 - // }); - // const newStructure = StructureSelection.unionStructure(await StructureQuery.run(q1, structure)); - - // // ball+stick for new structure - // const newBallStickRepr = BallAndStickRepresentation() - // await newBallStickRepr.create(newStructure, { - // colorTheme: { name: 'element-symbol' }, - // sizeTheme: { name: 'uniform', value: 0.1 }, - // useFog: false // TODO fog not working properly - // }).run() - // viewer.add(newBallStickRepr) - - // // create a mesh - // const meshBuilder = MeshBuilder.create(256, 128) - // const colors: Color[] = [] - // const labels: string[] = [] - // // red sphere - // meshBuilder.setGroup(0) - // colors[0] = Color(0xFF2233) - // labels[0] = 'red sphere' - // addSphere(meshBuilder, Vec3.create(0, 0, 0), 4, 2) - // // green cube - // meshBuilder.setGroup(1) - // colors[1] = Color(0x2233FF) - // labels[1] = 'blue cube' - // const t = Mat4.identity() - // Mat4.fromTranslation(t, Vec3.create(10, 0, 0)) - // Mat4.scale(t, t, Vec3.create(3, 3, 3)) - // meshBuilder.add(t, Box()) - // const mesh = meshBuilder.getMesh() - // const mesh = getObjFromUrl('mesh.obj') - - // // create shape from mesh - // const shape = Shape.create('myShape', mesh, colors, labels) - - // // add representation from shape - // const customRepr = ShapeRepresentation() - // await customRepr.create(shape, { - // colorTheme: { name: 'shape-group' }, - // // colorTheme: { name: 'uniform', value: Color(0xFFCC22) }, - // useFog: false // TODO fog not working properly - // }).run() - // viewer.add(customRepr) - - - 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) { - const customRepr = ShapeRepresentation() - await customRepr.create(axesShape, { - colorTheme: { name: 'shape-group' }, - // colorTheme: { name: 'uniform', value: Color(0xFFCC22) }, - useFog: false // TODO fog not working properly - }).run() - viewer.add(customRepr) - } - - getClusterColorTheme(features.id.value(0), assemblySymmetry) - getClusterColorTheme(features.id.value(1), assemblySymmetry) - } - } + + 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) // ensure the added representations get rendered, i.e. without mouse input viewer.requestDraw() diff --git a/src/apps/canvas/util.ts b/src/apps/canvas/util.ts new file mode 100644 index 0000000000000000000000000000000000000000..6057b09d7a185e9c8110d1821a1502adae303abe --- /dev/null +++ b/src/apps/canvas/util.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import CIF, { CifBlock } from 'mol-io/reader/cif' +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' + +// export async function getObjFromUrl(url: string) { +// const data = await readUrlAs(url, false) as string +// const comp = parseObj(data) +// const parsed = await comp.run() +// if (parsed.isError) throw parsed +// return parsed.result +// } + +export async function getCifFromUrl(url: string) { + const data = await readUrlAs(url, false) + const comp = CIF.parse(data) + const parsed = await comp.run() + if (parsed.isError) throw parsed + 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 getStructureFromModel(model: Model, assembly = '0') { + const assemblies = model.symmetry.assemblies + if (assemblies.length) { + return await StructureSymmetry.buildAssembly(Structure.ofModel(model), assembly).run() + } else { + return Structure.ofModel(model) + } +} \ No newline at end of file diff --git a/src/apps/canvas/view.ts b/src/apps/canvas/view.ts new file mode 100644 index 0000000000000000000000000000000000000000..c0dba012644e40f303f178a4b958a04cfa356191 --- /dev/null +++ b/src/apps/canvas/view.ts @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +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"; + +export interface DefaultView { + readonly model: Model + readonly structure: Structure + readonly cartoon: CartoonRepresentation + readonly ballAndStick: BallAndStickRepresentation + + setAssembly(assembly: string): void +} + +export async function DefaultView(model: Model, assembly: string): Promise<DefaultView> { + const cartoon = CartoonRepresentation() + const ballAndStick = BallAndStickRepresentation() + + let structure: Structure + + async function setAssembly(assembly: string) { + structure = await getStructureFromModel(model, assembly) + await createRepr() + } + + 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() + } + + await setAssembly(assembly) + + return { + model, + get structure() { return structure }, + cartoon, + ballAndStick, + + setAssembly + } +} + +// // create new structure via query +// const q1 = Q.generators.atoms({ +// residueTest: qtx => SP.residue.label_seq_id(qtx.element) < 7 +// }); +// const newStructure = StructureSelection.unionStructure(await StructureQuery.run(q1, structure)); + +// // ball+stick for new structure +// const newBallStickRepr = BallAndStickRepresentation() +// await newBallStickRepr.create(newStructure, { +// colorTheme: { name: 'element-symbol' }, +// sizeTheme: { name: 'uniform', value: 0.1 }, +// useFog: false // TODO fog not working properly +// }).run() +// viewer.add(newBallStickRepr) + +// // create a mesh +// const meshBuilder = MeshBuilder.create(256, 128) +// const colors: Color[] = [] +// const labels: string[] = [] +// // red sphere +// meshBuilder.setGroup(0) +// colors[0] = Color(0xFF2233) +// labels[0] = 'red sphere' +// addSphere(meshBuilder, Vec3.create(0, 0, 0), 4, 2) +// // green cube +// meshBuilder.setGroup(1) +// colors[1] = Color(0x2233FF) +// labels[1] = 'blue cube' +// const t = Mat4.identity() +// Mat4.fromTranslation(t, Vec3.create(10, 0, 0)) +// Mat4.scale(t, t, Vec3.create(3, 3, 3)) +// meshBuilder.add(t, Box()) +// const mesh = meshBuilder.getMesh() +// const mesh = getObjFromUrl('mesh.obj') + +// // create shape from mesh +// const shape = Shape.create('myShape', mesh, colors, labels) + +// // add representation from shape +// const customRepr = ShapeRepresentation() +// await customRepr.create(shape, { +// colorTheme: { name: 'shape-group' }, +// // colorTheme: { name: 'uniform', value: Color(0xFFCC22) }, +// useFog: false // TODO fog not working properly +// }).run() +// viewer.add(customRepr) \ No newline at end of file diff --git a/src/mol-geo/representation/shape/index.ts b/src/mol-geo/representation/shape/index.ts index c9272af8df70789e128b6dc0be8d095f46504462..14fcd8e32935371921c5269ca9a34415e9dacc94 100644 --- a/src/mol-geo/representation/shape/index.ts +++ b/src/mol-geo/representation/shape/index.ts @@ -28,6 +28,9 @@ export const DefaultShapeProps = { } export type ShapeProps = typeof DefaultShapeProps +// TODO +// export type ShapeRepresentation = ShapeRepresentation<ShapeProps> + export function ShapeRepresentation<P extends ShapeProps>(): ShapeRepresentation<P> { const renderObjects: RenderObject[] = [] let _renderObject: MeshRenderObject diff --git a/src/mol-geo/representation/structure/representation/backbone.ts b/src/mol-geo/representation/structure/representation/backbone.ts index 2afb0c8cfde3121f807a4ba2e83c998f5af7bbcc..9d80d5ecebb9aa58a32de4062545bec199bacdc4 100644 --- a/src/mol-geo/representation/structure/representation/backbone.ts +++ b/src/mol-geo/representation/structure/representation/backbone.ts @@ -17,7 +17,9 @@ export const DefaultBackboneProps = { } export type BackboneProps = typeof DefaultBackboneProps -export function BackboneRepresentation(): StructureRepresentation<BackboneProps> { +export type BackboneRepresentation = StructureRepresentation<BackboneProps> + +export function BackboneRepresentation(): BackboneRepresentation { const traceRepr = UnitsRepresentation(PolymerBackboneVisual) let currentProps: BackboneProps diff --git a/src/mol-geo/representation/structure/representation/ball-and-stick.ts b/src/mol-geo/representation/structure/representation/ball-and-stick.ts index d26ab096839de45dba420f8cd18c14da374f3c1f..ee5675b3c9f7b20c7832261763bda1a185909e7e 100644 --- a/src/mol-geo/representation/structure/representation/ball-and-stick.ts +++ b/src/mol-geo/representation/structure/representation/ball-and-stick.ts @@ -24,7 +24,9 @@ export const DefaultBallAndStickProps = { } export type BallAndStickProps = typeof DefaultBallAndStickProps -export function BallAndStickRepresentation(): StructureRepresentation<BallAndStickProps> { +export type BallAndStickRepresentation = StructureRepresentation<BallAndStickProps> + +export function BallAndStickRepresentation(): BallAndStickRepresentation { const elmementRepr = UnitsRepresentation(ElementSphereVisual) const intraLinkRepr = UnitsRepresentation(IntraUnitLinkVisual) const interLinkRepr = ComplexRepresentation(InterUnitLinkVisual) diff --git a/src/mol-geo/representation/structure/representation/carbohydrate.ts b/src/mol-geo/representation/structure/representation/carbohydrate.ts index bc877da0f8223269ce86771643282d18dda58a0e..9f547b1b2dfde5625b6b4a558c11ba5e5fed1712 100644 --- a/src/mol-geo/representation/structure/representation/carbohydrate.ts +++ b/src/mol-geo/representation/structure/representation/carbohydrate.ts @@ -19,7 +19,9 @@ export const DefaultCartoonProps = { } export type CarbohydrateProps = typeof DefaultCartoonProps -export function CarbohydrateRepresentation(): StructureRepresentation<CarbohydrateProps> { +export type CarbohydrateRepresentation = StructureRepresentation<CarbohydrateProps> + +export function CarbohydrateRepresentation(): CarbohydrateRepresentation { const carbohydrateSymbolRepr = ComplexRepresentation(CarbohydrateSymbolVisual) const carbohydrateLinkRepr = ComplexRepresentation(CarbohydrateLinkVisual) diff --git a/src/mol-geo/representation/structure/representation/cartoon.ts b/src/mol-geo/representation/structure/representation/cartoon.ts index bc9b43e09a2c19f225b88a7ca122b4cbf362d807..62b1a2ef2b92bdd64686cb1e8f6d406f639247ea 100644 --- a/src/mol-geo/representation/structure/representation/cartoon.ts +++ b/src/mol-geo/representation/structure/representation/cartoon.ts @@ -23,7 +23,9 @@ export const DefaultCartoonProps = { } export type CartoonProps = typeof DefaultCartoonProps -export function CartoonRepresentation(): StructureRepresentation<CartoonProps> { +export type CartoonRepresentation = StructureRepresentation<CartoonProps> + +export function CartoonRepresentation(): CartoonRepresentation { const traceRepr = UnitsRepresentation(PolymerTraceVisual) const gapRepr = UnitsRepresentation(PolymerGapVisual) const blockRepr = UnitsRepresentation(NucleotideBlockVisual) diff --git a/src/mol-geo/representation/structure/representation/distance-restraint.ts b/src/mol-geo/representation/structure/representation/distance-restraint.ts index bf16518eb73e61c16ad4a8b1395a02eca2c4ac38..83f4f23d6b3cc85525cd6301d418914e4a02cd47 100644 --- a/src/mol-geo/representation/structure/representation/distance-restraint.ts +++ b/src/mol-geo/representation/structure/representation/distance-restraint.ts @@ -19,7 +19,9 @@ export const DefaultDistanceRestraintProps = { } export type DistanceRestraintProps = typeof DefaultDistanceRestraintProps -export function DistanceRestraintRepresentation(): StructureRepresentation<DistanceRestraintProps> { +export type DistanceRestraintRepresentation = StructureRepresentation<DistanceRestraintProps> + +export function DistanceRestraintRepresentation(): DistanceRestraintRepresentation { const crossLinkRepr = ComplexRepresentation(CrossLinkRestraintVisual) let currentProps: DistanceRestraintProps diff --git a/src/mol-geo/representation/structure/representation/spacefill.ts b/src/mol-geo/representation/structure/representation/spacefill.ts index d2062b2975e6e3efe568520b26c439447dec7870..fb18a901a88137ad88fc526bfa03b01325125a17 100644 --- a/src/mol-geo/representation/structure/representation/spacefill.ts +++ b/src/mol-geo/representation/structure/representation/spacefill.ts @@ -6,12 +6,15 @@ import { UnitsRepresentation } from '..'; import { ElementSphereVisual, DefaultElementSphereProps } from '../visual/element-sphere'; +import { StructureRepresentation } from '../units-representation'; export const DefaultSpacefillProps = { ...DefaultElementSphereProps } export type SpacefillProps = typeof DefaultSpacefillProps -export function SpacefillRepresentation() { +export type SpacefillRepresentation = StructureRepresentation<SpacefillProps> + +export function SpacefillRepresentation(): SpacefillRepresentation { return UnitsRepresentation(ElementSphereVisual) } \ No newline at end of file