فهرست منبع

added ShapeProvider and hooked it up in plugin

Alexander Rose 6 سال پیش
والد
کامیت
a5f24db8f3

+ 15 - 27
src/mol-io/reader/ply/parse_data/ply_parser.ts

@@ -4,23 +4,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-// import { Column } from 'mol-data/db'
 import { Tokens, TokenBuilder, Tokenizer } from '../../common/text/tokenizer'
 import * as Data from './data-model'
 
 import Result from '../../result'
-import {Task, RuntimeContext, chunkedSubtask, Progress,} from 'mol-task'
-import * as plyToShape from 'mol-model/shape/formarts/ply/plyData_to_shape'
-import {MyData} from 'mol-model/shape/formarts/ply/plyData_to_shape';
-import {Mesh} from '../../../../mol-geo/geometry/mesh/mesh';
-import {ParamDefinition} from '../../../../mol-util/param-definition';
-import Color = ParamDefinition.Color;
-import {ColorNames} from '../../../../mol-util/color/tables';
-import {ShapeRepresentation} from '../../../../mol-repr/shape/representation';
-//import {init2} from '../../../../tests/browser/render-shape';
-
-
-
+import {Task, RuntimeContext, chunkedSubtask } from 'mol-task'
 
 const enum PlyTokenType {
     Value = 0,
@@ -92,7 +80,7 @@ function State(data: string, runtimeCtx: RuntimeContext, opts: PlyOptions): Stat
         properties: [],
         vertices: [],
         colors: [],
-        normals:[],
+        normals: [],
         faces: [],
         propertyNames: [],
         check: [],
@@ -289,13 +277,13 @@ function init(state: State) { // only for first two lines to get the format and
     }
     addHeadEntry(state)
     if(state.initialHead[0] !== 'ply'){
-        console.log("ERROR: this is not a .ply file!")
-        throw new Error("this is not a .ply file!");
+        console.log('ERROR: this is not a .ply file!')
+        throw new Error('this is not a .ply file!');
         return 0;
     }
     if(state.initialHead[2] !== 'ascii'){
-        console.log("ERROR: only ASCII-DECODING is supported!");
-        throw new Error("only ASCII-DECODING is supported!");
+        console.log('ERROR: only ASCII-DECODING is supported!');
+        throw new Error('only ASCII-DECODING is supported!');
         return 0;
     }
     state.columnCount = state.initialHead.length
@@ -304,8 +292,8 @@ function init(state: State) { // only for first two lines to get the format and
 
 async function handleRecords(state: State): Promise<Data.ply_form> {
     if(!init(state)){
-        console.log("ERROR: parsing file (PLY) failed!")
-        throw new Error("parsing file (PLY) failed!");
+        console.log('ERROR: parsing file (PLY) failed!')
+        throw new Error('arsing file (PLY) failed!');
     }
     await readRecordsChunks(state)
 
@@ -320,13 +308,13 @@ async function parseInternal(data: string, ctx: RuntimeContext, opts: PlyOptions
     const result = Data.PlyFile(PLYdata)
     console.log(result);
 
-   // let Data_for_Shape = plyToShape.collectData_for_Shape(table, datas);
-    //console.log(plyToShape.getShape(state.runtimeCtx, table));
-    let shape  = plyToShape.init_ren(PLYdata);
-    console.log("shape"+shape);
-   // const script = document.createElement('script');
-   // script.src = "../../build/src/mol-model/shape/formarts/ply/plyData_to_shape.js";
-   // document.body.appendChild(script);
+    // let Data_for_Shape = plyToShape.collectData_for_Shape(table, datas);
+    // console.log(plyToShape.getShape(state.runtimeCtx, table));
+    // let shape  = plyToShape.init_ren(PLYdata);
+    // console.log("shape"+shape);
+    // const script = document.createElement('script');
+    // script.src = "../../build/src/mol-model/shape/formarts/ply/plyData_to_shape.js";
+    // document.body.appendChild(script);
 
     return Result.success(result);
 }

+ 40 - 101
src/mol-model/shape/formarts/ply/plyData_to_shape.ts

@@ -1,143 +1,82 @@
-import {ply_form} from '../../../../mol-io/reader/ply/parse_data/data-model';
-import {MyData} from '../../../../../build/src/mol-model/shape/formarts/ply/plyData_to_shape';
-import {Progress, RuntimeContext} from 'mol-task';
+import {ply_form, PlyFile} from '../../../../mol-io/reader/ply/parse_data/data-model';
+import {RuntimeContext, Task} from 'mol-task';
 import {Mesh} from '../../../../mol-geo/geometry/mesh/mesh';
 import {MeshBuilder} from '../../../../mol-geo/geometry/mesh/mesh-builder';
-import {Mat4, Vec3} from '../../../../mol-math/linear-algebra/3d';
-import {Sphere} from '../../../../mol-geo/primitive/sphere';
+import { addSphere } from 'mol-geo/geometry/mesh/builder/sphere';
+import {Vec3} from '../../../../mol-math/linear-algebra/3d';
 import {Shape} from '../../shape';
 import {Color} from '../../../../mol-util/color';
-import {Canvas3D} from '../../../../mol-canvas3d/canvas3d';
-import {labelFirst} from '../../../../mol-theme/label';
-import {ColorNames} from '../../../../mol-util/color/tables';
-import {ShapeRepresentation} from '../../../../mol-repr/shape/representation';
-
-
-const parent = document.getElementById('app')!
-parent.style.width = '100%'
-parent.style.height = '100%'
-
-const canvas = document.createElement('canvas')
-canvas.style.width = '100%'
-canvas.style.height = '100%'
-parent.appendChild(canvas)
-
-const info = document.createElement('div')
-info.style.position = 'absolute'
-info.style.fontFamily = 'sans-serif'
-info.style.fontSize = '24pt'
-info.style.bottom = '20px'
-info.style.right = '20px'
-info.style.color = 'white'
-parent.appendChild(info)
-
-const canvas3d = Canvas3D.create(canvas, parent)
-canvas3d.animate()
-canvas3d.input.move.subscribe(async ({x, y}) => {
-    const pickingId = await canvas3d.identify(x, y)
-    let label = ''
-    if (pickingId) {
-        const { loci } = canvas3d.getLoci(pickingId)
-        label = labelFirst(loci)
-    }
-    info.innerText = label
-})
-
-
-
+import { ShapeProvider } from 'mol-model/shape/provider';
 
 export interface MyData {
     centers: number[],
-    colors: string[],
+    colors: Color[],
     labels: string[],
     transforms: number[]
 }
 
-let data:MyData = {
-    centers: [],
-    colors: [],
-    labels: [],
-    transforms:[]
-}
-
-
-function componentToHex(c) {
-    let hex = c.toString(16);
-    return hex.length == 1 ? "0" + hex : hex;
-}
-
-function rgbToHex(r, g, b) {
-    return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
-}
-
- function collectData_for_Shape(parsedData: ply_form):{centers: number[], colors: string[], labels: string[], transforms: number[]}{
+function collectData_for_Shape(parsedData: ply_form): MyData {
     // parsedData.data.PLY_File. to access So.format.Ply
+    console.log('parsedData', parsedData)
+    const { vertices, colors } = parsedData
+    const data: MyData = {
+        centers: vertices,
+        colors: [],
+        labels: [],
+        transforms: []
+    }
 
-    data.centers = parsedData.vertices;
-    let hexColor;
-
-    for(let i=0; i<parsedData.vertexCount; i++){
-        hexColor = rgbToHex(parsedData.colors[i*3+0],parsedData.colors[i*3+1],parsedData.colors[i*3+2]);
-        data.colors[i] = hexColor;
+    for (let i = 0; i<parsedData.vertexCount; i++) {
+        data.colors[i] = Color.fromRgb(colors[i*3+0], colors[i*3+1], colors[i*3+2]);
         data.labels[i] = '';
         data.transforms[i] = 0;
     }
-    console.log(data);
+    console.log('data', data);
     return data;
 }
 
-
-
-
- async function getSphereMesh(ctx: RuntimeContext, centers: number[], mesh?: Mesh) {
+async function getSphereMesh(ctx: RuntimeContext, centers: number[], mesh?: Mesh) {
     const builderState = MeshBuilder.createState(centers.length * 128, centers.length * 128 / 2, mesh)
-    const t = Mat4.identity()
     const v = Vec3.zero()
-    const sphere = Sphere(4)
     builderState.currentGroup = 0
     for (let i = 0, il = centers.length / 3; i < il; ++i) {
-        // for production, calls to update should be guarded by `if (ctx.shouldUpdate)`
-        await ctx.update({ current: i, max: il, message: `adding sphere ${i}` })
+        if (i % 10000 === 0 && ctx.shouldUpdate) await ctx.update({ current: i, max: il, message: `adding sphere ${i}` })
         builderState.currentGroup = i
-        Mat4.setTranslation(t, Vec3.fromArray(v, centers, i * 3))
-        MeshBuilder.addPrimitive(builderState, t, sphere)
+        addSphere(builderState, Vec3.fromArray(v, centers, i * 3), 0.2, 1)
     }
     let a = MeshBuilder.getMesh(builderState);
-    //console.log(a);
+    // console.log(a);
     return a
 }
 
 
 export async function getShape(ctx: RuntimeContext, parsedData: ply_form, props: {}, shape?: Shape<Mesh>) {
-    let data:MyData;
-    data = collectData_for_Shape(parsedData)
+    const data = collectData_for_Shape(parsedData)
     await ctx.update('async creation of shape from  myData')
-    const { centers , colors, labels} = data
+    const { centers , colors, labels } = data
     const mesh = await getSphereMesh(ctx, centers, shape && shape.geometry)
     const groupCount = centers.length / 3
     return shape || Shape.create(
         'test', mesh,
-        (groupId: number) => Color(Number(colors[groupId])), // color: per group, same for instances
+        (groupId: number) => colors[groupId], // color: per group, same for instances
         () => 1, // size: constant
         (groupId: number, instanceId: number) => labels[instanceId * groupCount + groupId] // label: per group and instance
     )
 }
 
-const repr = ShapeRepresentation(getShape, Mesh.Utils)
-
-export async function init_ren(myData : ply_form) {
-    // Create shape from myData and add to canvas3d
-    await repr.createOrUpdate({}, myData).run((p: Progress) => console.log(Progress.format(p)))
-    canvas3d.add(repr)
-    canvas3d.resetCamera()
-
-    // Change color after 1s
-    setTimeout(async () => {
-        myData.colors[0] = ColorNames.darkmagenta
-        // Calling `createOrUpdate` with `data` will trigger color and transform update
-        await repr.createOrUpdate({}, myData).run()
-    }, 1000)
+export const PlyShapeParams = {
+    ...Mesh.Params
 }
-
-
-
+export type PlyShapeParams = typeof PlyShapeParams
+
+export function shapeFromPly(source: PlyFile, params?: {}) {
+    return Task.create<ShapeProvider<ply_form, Mesh, PlyShapeParams>>('Parse Shape Data', async ctx => {
+        console.log('source', source)
+        return {
+            label: 'Mesh',
+            data: source.PLY_File,
+            getShape,
+            geometryUtils: Mesh.Utils
+        }
+    })
+}

+ 15 - 0
src/mol-model/shape/provider.ts

@@ -0,0 +1,15 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ShapeGetter } from 'mol-repr/shape/representation';
+import { Geometry, GeometryUtils } from 'mol-geo/geometry/geometry';
+
+export interface ShapeProvider<D, G extends Geometry, P extends Geometry.Params<G>> {
+    label: string
+    data: D
+    getShape: ShapeGetter<D, G, P>
+    geometryUtils: GeometryUtils<G>
+}

+ 2 - 2
src/mol-plugin/index.ts

@@ -11,7 +11,7 @@ import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { PluginCommands } from './command';
 import { PluginSpec } from './spec';
-import {DownloadStructure, CreateComplexRepresentation, OpenStructure, PLYtest} from './state/actions/basic';
+import {DownloadStructure, CreateComplexRepresentation, OpenStructure, OpenPlyFile} from './state/actions/basic';
 import { StateTransforms } from './state/transforms';
 import { PluginBehaviors } from './behavior';
 
@@ -24,7 +24,7 @@ const DefaultSpec: PluginSpec = {
     actions: [
         PluginSpec.Action(DownloadStructure),
         PluginSpec.Action(OpenStructure),
-        PluginSpec.Action(PLYtest),
+        PluginSpec.Action(OpenPlyFile),
         PluginSpec.Action(CreateComplexRepresentation),
         PluginSpec.Action(StateTransforms.Data.Download),
         PluginSpec.Action(StateTransforms.Data.ParseCif),

+ 8 - 16
src/mol-plugin/state/actions/basic.ts

@@ -14,9 +14,6 @@ import { PluginStateObject } from '../objects';
 import { StateTransforms } from '../transforms';
 import { Download } from '../transforms/data';
 import { StructureRepresentation3DHelpers } from '../transforms/representation';
-import * as data_functions from 'mol-io/reader/ply/read_data/data'
-
-
 
 // TODO: "structure parser provider"
 
@@ -88,25 +85,20 @@ export const OpenStructure = StateAction.build({
     return state.update(createStructureTree(ctx, data, false));
 });
 
-
-export const PLYtest = StateAction.build({
-    display: { name: 'PLY Test', description: 'nothing ply' },
+export const OpenPlyFile = StateAction.build({
+    display: { name: 'Open PLY file', description: 'Load a PLY file' },
     from: PluginStateObject.Root,
     params: { file: PD.File({ accept: '.ply' }) }
 })(({ params, state }, ctx: PluginContext) => {
     const b = state.build();
-    const data = b.toRoot().apply(data_functions.ReadFile_ascii, { file: params.file, isBinary: false });
-    let tmp = state.update(getPLYdata(ctx, data));
-    return tmp ;
+    const data = b.toRoot()
+        .apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: false })
+        .apply(StateTransforms.Data.ParsePly)
+        .apply(StateTransforms.Model.ShapeFromPly)
+        .apply(StateTransforms.Representation.ShapeRepresentation3D)
+    return state.update(data.getTree());
 });
 
-function getPLYdata(ctx: PluginContext, b: StateTreeBuilder.To<PluginStateObject.Data.String>, ): StateTree {
-    let root = b.apply(data_functions.ParsePLY);
-
-    return root.getTree();
-}
-
-
 function createStructureTree(ctx: PluginContext, b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, supportProps: boolean): StateTree {
     let root = b
         .apply(StateTransforms.Data.ParseCif)

+ 8 - 0
src/mol-plugin/state/objects.ts

@@ -13,6 +13,9 @@ import { Representation } from 'mol-repr/representation';
 import { StructureRepresentation } from 'mol-repr/structure/representation';
 import { VolumeRepresentation } from 'mol-repr/volume/representation';
 import { StateObject, Transformer } from 'mol-state';
+import { ShapeRepresentation } from 'mol-repr/shape/representation';
+import { Shape as _Shape } from 'mol-model/shape';
+import { ShapeProvider } from 'mol-model/shape/provider';
 
 export type TypeClass = 'root' | 'data' | 'prop'
 
@@ -71,6 +74,11 @@ export namespace PluginStateObject {
         export class Data extends Create<VolumeData>({ name: 'Volume Data', typeClass: 'Object' }) { }
         export class Representation3D extends CreateRepresentation3D<VolumeRepresentation<any>>({ name: 'Volume 3D' }) { }
     }
+
+    export namespace Shape {
+        export class Provider extends Create<ShapeProvider<any, any, any>>({ name: 'Shape Provider', typeClass: 'Object' }) { }
+        export class Representation3D extends CreateRepresentation3D<ShapeRepresentation<any, any, any>>({ name: 'Shape 3D' }) { }
+    }
 }
 
 export namespace PluginStateTransform {

+ 18 - 0
src/mol-plugin/state/transforms/data.ts

@@ -12,6 +12,7 @@ import { PluginContext } from 'mol-plugin/context';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Transformer } from 'mol-state';
 import { readFromFile } from 'mol-util/data-source';
+import * as PLY from 'mol-io/reader/ply/parse_data/ply_parser'
 
 export { Download }
 type Download = typeof Download
@@ -91,3 +92,20 @@ const ParseCif = PluginStateTransform.BuiltIn({
         });
     }
 });
+
+export { ParsePly }
+type ParsePly = typeof ParsePly
+const ParsePly = PluginStateTransform.BuiltIn({
+    name: 'parse-ply',
+    display: { name: 'Parse PLY', description: 'Parse PLY from Binary data' },
+    from: [SO.Data.String],
+    to: SO.Format.Ply
+})({
+    apply({ a }) {
+        return Task.create('Parse PLY', async ctx => {
+            const parsed = await PLY.parse(a.data).runInContext(ctx);
+            if (parsed.isError) throw new Error(parsed.message);
+            return new SO.Format.Ply(parsed.result, { label: parsed.result.name || 'PLY Data' });
+        });
+    }
+});

+ 22 - 1
src/mol-plugin/state/transforms/model.ts

@@ -15,6 +15,7 @@ import { MolScriptBuilder } from 'mol-script/language/builder';
 import { StateObject } from 'mol-state';
 import { PluginContext } from 'mol-plugin/context';
 import { stringToWords } from 'mol-util/string';
+import { shapeFromPly } from 'mol-model/shape/formarts/ply/plyData_to_shape';
 
 export { TrajectoryFromMmCif }
 type TrajectoryFromMmCif = typeof TrajectoryFromMmCif
@@ -190,4 +191,24 @@ async function attachProps(model: Model, ctx: PluginContext, taskCtx: RuntimeCon
         const p = ctx.customModelProperties.get(name);
         await p.attach(model).runInContext(taskCtx);
     }
-}
+}
+
+export { ShapeFromPly }
+type ShapeFromPly = typeof ShapeFromPly
+const ShapeFromPly = PluginStateTransform.BuiltIn({
+    name: 'shape-from-ply',
+    display: { name: 'Shape from PLY', description: 'Create Shape from PLY data' },
+    from: SO.Format.Ply,
+    to: SO.Shape.Provider,
+    params(a) {
+        return { };
+    }
+})({
+    apply({ a, params }) {
+        return Task.create('Create shape from PLY', async ctx => {
+            const shape = await shapeFromPly(a.data, params).runInContext(ctx)
+            const props = { label: 'Shape' };
+            return new SO.Shape.Provider(shape, props);
+        });
+    }
+});

+ 23 - 0
src/mol-plugin/state/transforms/representation.ts

@@ -15,6 +15,7 @@ import { createTheme } from 'mol-theme/theme';
 import { BuiltInStructureRepresentationsName } from 'mol-repr/structure/registry';
 import { Structure } from 'mol-model/structure';
 import { StructureParams } from 'mol-repr/structure/representation';
+import { ShapeRepresentation } from 'mol-repr/shape/representation';
 
 export namespace StructureRepresentation3DHelpers {
     export function getDefaultParams(ctx: PluginContext, name: BuiltInStructureRepresentationsName, structure: Structure, structureParams?: Partial<PD.Values<StructureParams>>): Transformer.Params<StructureRepresentation3D> {
@@ -95,4 +96,26 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
             return Transformer.UpdateResult.Updated;
         });
     }
+});
+
+export { ShapeRepresentation3D }
+type ShapeRepresentation3D = typeof ShapeRepresentation3D
+const ShapeRepresentation3D = PluginStateTransform.BuiltIn({
+    name: 'shape-representation-3d',
+    display: '3D Representation',
+    from: SO.Shape.Provider,
+    to: SO.Shape.Representation3D,
+    params: (a, ctx: PluginContext) => {
+        return { }
+    }
+})({
+    apply({ a, params }, plugin: PluginContext) {
+        return Task.create('Shape Representation', async ctx => {
+            const props = { ...PD.getDefaultValues(a.data.geometryUtils.Params), params }
+            const repr = ShapeRepresentation(a.data.getShape, a.data.geometryUtils)
+            // TODO set initial state, repr.setState({})
+            await repr.createOrUpdate(props, a.data.data).runInContext(ctx);
+            return new SO.Shape.Representation3D(repr, { label: a.data.label });
+        });
+    }
 });

+ 207 - 0
src/mol-repr/measurements/representation.ts

@@ -0,0 +1,207 @@
+// /**
+//  * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+//  *
+//  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+//  */
+
+// import { ParamDefinition as PD } from 'mol-util/param-definition';
+// import { Loci as _Loci, EmptyLoci } from 'mol-model/loci';
+// import { Vec3 } from 'mol-math/linear-algebra';
+// import { Mesh } from 'mol-geo/geometry/mesh/mesh';
+// import { Representation, RepresentationContext } from 'mol-repr/representation';
+// import { Subject } from 'rxjs';
+// import { RenderObject, MeshRenderObject, createMeshRenderObject } from 'mol-gl/render-object';
+// import { createEmptyTheme } from 'mol-theme/theme';
+// import { LocationIterator } from 'mol-geo/util/location-iterator';
+// import { Task } from 'mol-task';
+// import { Geometry } from 'mol-geo/geometry/geometry';
+// import { createIdentityTransform } from 'mol-geo/geometry/transform-data';
+// import { PickingId } from 'mol-geo/geometry/picking';
+// import { MarkerAction } from 'mol-geo/geometry/marker-data';
+// import { Lines } from 'mol-geo/geometry/lines/lines';
+// import { Text } from 'mol-geo/geometry/text/text';
+// import { MeshBuilder } from 'mol-geo/geometry/mesh/mesh-builder';
+// import { LinesBuilder } from 'mol-geo/geometry/lines/lines-builder';
+// import { TextBuilder } from 'mol-geo/geometry/text/text-builder';
+
+
+
+// export interface Measurements {
+//     readonly inputs: Measurements.Inputs
+// }
+
+// export namespace Measurements {
+//     export interface Loci {
+//         readonly kind: 'measurements-loci',
+//         readonly data: any,
+//         readonly tag: string
+//         readonly indices: OrderedSet<number>
+//     }
+
+//     export interface DistanceInput {
+//         kind: 'distance-input'
+//         lociA: _Loci,
+//         lociB: _Loci
+//     }
+//     export function createDistanceInput(lociA: _Loci, lociB: _Loci): DistanceInput {
+//         return { kind: 'distance-input', lociA, lociB }
+//     }
+
+//     export interface AngleInput {
+//         kind: 'angle-input'
+//         lociA: _Loci,
+//         lociB: _Loci,
+//         lociC: _Loci
+//     }
+
+//     export interface DihedralInput {
+//         kind: 'dihedral-input'
+//         lociA: _Loci,
+//         lociB: _Loci,
+//         lociC: _Loci,
+//         lociD: _Loci
+//     }
+
+//     // export type MeasurementInput = [Vec3, Vec3] | [Vec3, Vec3, Vec3] | [Vec3, Vec3, Vec3, Vec3]
+//     export type Input = DistanceInput | AngleInput | DihedralInput
+//     export type Inputs = Input[]
+// }
+
+// // const measureVecA = Vec3.zero()
+// // const measureVecB = Vec3.zero()
+// // export function measure(input: MeasurementInput): number | null {
+// //     switch (input.length) {
+// //         case 2: return Vec3.distance(input[0], input[1])
+// //         case 3: return Vec3.angle(Vec3.sub(measureVecA, input[1], input[0]), Vec3.sub(measureVecB, input[2], input[1]))
+// //         default:
+// //             // TODO
+// //             return null
+// //     }
+// // }
+
+// //
+
+// export interface MeasurementsMeshProps {
+//     detail: number,
+//     sizeFactor: number
+// }
+
+// interface MeasurementsGeometries { mesh?: Mesh, lines?: Lines, text?: Text }
+
+// export function createMeasurementsGeometries(inputs: Measurements.Inputs, props: MeasurementsMeshProps, geometries: MeasurementsGeometries): MeasurementsGeometries {
+//     const { mesh, lines, text } = geometries
+//     const { detail, sizeFactor } = props
+
+//     const vertexCount = inputs.length * 10
+//     const meshBuilderState = MeshBuilder.createState(vertexCount, vertexCount / 2, mesh)
+//     const linesBuilder = LinesBuilder.create(vertexCount, vertexCount / 2, lines)
+//     const textBuilder = TextBuilder.create(props, vertexCount, vertexCount / 2, text)
+
+//     const vA = Vec3.zero()
+//     const vB = Vec3.zero()
+//     const vC = Vec3.zero()
+//     const vD = Vec3.zero()
+
+//     for (let i = 0, il = inputs.length; i < il; i++) {
+//         const input = inputs[i]
+//         if (input.lociA) Loci.getCenter(input.lociA, vA)
+
+//         meshBuilderState.currentGroup = i
+//         linesBuilder.add()
+//     }
+
+//     return {
+//         mesh: MeshBuilder.getMesh(meshBuilderState),
+//         lines: linesBuilder.getLines(),
+//         text: textBuilder.getText()
+//     }
+// }
+
+// //
+
+// export interface MeasurementsRepresentation<P extends MeasurementsParams> extends Representation<MeasurementInputs, P> { }
+
+// export const MeasurementsParams = {
+//     ...Mesh.Params,
+// }
+// export type MeasurementsParams = typeof MeasurementsParams
+
+// export function MeasuresRepresentation<P extends MeasurementsParams>(ctx: RepresentationContext): MeasurementsRepresentation<P> {
+//     let version = 0
+//     const updated = new Subject<number>()
+//     const _state = Representation.createState()
+//     const renderObjects: RenderObject[] = []
+//     let _renderObject: MeshRenderObject | undefined
+//     let _inputs: Measurements.Inputs
+
+//     let _mesh: Mesh
+//     let _lines: Lines
+//     let _text: Text
+
+//     let _theme = createEmptyTheme()
+//     let currentProps: PD.Values<P> = PD.getDefaultValues(MeasurementsParams) as PD.Values<P>
+//     let currentParams: P
+//     let locationIt: LocationIterator
+
+//     function createOrUpdate(props: Partial<PD.Values<P>> = {}, inputs?: MeasurementInputs) {
+//         currentProps = Object.assign({}, currentProps, props)
+//         if (inputs) _inputs = inputs
+
+//         return Task.create('MeasurementRepresentation.create', async runtime => {
+//             renderObjects.length = 0
+
+//             if (!_inputs) return
+
+//             const mesh = _shape.mesh
+//             locationIt = ShapeGroupIterator.fromShape(_shape)
+//             const transform = createIdentityTransform()
+
+//             const values = Mesh.createValues(mesh, transform, locationIt, _theme, currentProps)
+//             const state = Geometry.createRenderableState(currentProps)
+
+//             _renderObject = createMeshRenderObject(values, state)
+//             renderObjects.push(_renderObject)
+//             updated.next(version++)
+//         });
+//     }
+
+//     return {
+//         label: 'Measures mesh',
+//         get groupCount () { return locationIt ? locationIt.count : 0 },
+//         get renderObjects () { return renderObjects },
+//         get props () { return currentProps },
+//         get params () { return currentParams },
+//         get state() { return _state },
+//         get theme() { return _theme },
+//         updated,
+//         createOrUpdate,
+//         getLoci(pickingId: PickingId) { return EmptyLoci },
+//         mark(loci: Loci, action: MarkerAction) { return false },
+//         setState(state: Partial<Representation.State>) {
+//             if (state.visible !== undefined) renderObjects.forEach(ro => ro.state.visible = state.visible!)
+
+//             Representation.updateState(_state, state)
+//         },
+//         setTheme(theme: Theme) {
+//             _theme = theme
+//         },
+//         destroy() {
+//             // TODO
+//             renderObjects.length = 0
+//             _renderObject = undefined
+//         }
+//     }
+// }
+
+// export namespace ShapeGroupIterator {
+//     export function fromShape(shape: Shape): LocationIterator {
+//         const { groupCount } = shape
+//         const instanceCount = 1
+//         const location = Shape.Location(shape)
+//         const getLocation = (groupIndex: number) => {
+//             location.group = groupIndex
+//             return location
+//         }
+//         return LocationIterator(groupCount, instanceCount, getLocation)
+//     }
+// }