From 8841f04af62f7fd5c23fdf6e4dfce5d200798377 Mon Sep 17 00:00:00 2001 From: Russ Taylor Date: Mon, 10 Feb 2025 14:51:38 -0500 Subject: [PATCH] Removing copied PLY parsing and geometry generation code from KIN file reader. Passing the Kinemage data structure from the parser to the geometry generator. Stubs now in place for KIN with no geometry currently being generated. Also updated the kin.spec.ts file to check a Kinemage file. --- src/mol-io/reader/_spec/kin.spec.ts | 166 ++++--------- src/mol-io/reader/kin/ngl-based-parser.ts | 3 + src/mol-io/reader/kin/parser.ts | 250 +------------------ src/mol-io/reader/kin/schema.ts | 97 +------- src/mol-model-formats/shape/kin.ts | 280 ++-------------------- src/mol-plugin-state/objects.ts | 6 +- 6 files changed, 82 insertions(+), 720 deletions(-) diff --git a/src/mol-io/reader/_spec/kin.spec.ts b/src/mol-io/reader/_spec/kin.spec.ts index 0ca091420..e37886d18 100644 --- a/src/mol-io/reader/_spec/kin.spec.ts +++ b/src/mol-io/reader/_spec/kin.spec.ts @@ -5,147 +5,65 @@ */ import { parseKin } from '../kin/parser'; -import { KinTable, KinList } from '../kin/schema'; -const kinString = `ply -format ascii 1.0 -comment file created by MegaMol -element vertex 6 -property float x -property float y -property float z -property uchar red -property uchar green -property uchar blue -property uchar alpha -property float nx -property float ny -property float nz -property int atomid -property uchar contactcount_r -property uchar contactcount_g -property uchar contactcount_b -property uchar contactsteps_r -property uchar contactsteps_g -property uchar contactsteps_b -property uchar hbonds_r -property uchar hbonds_g -property uchar hbonds_b -property uchar hbondsteps_r -property uchar hbondsteps_g -property uchar hbondsteps_b -property uchar molcount_r -property uchar molcount_g -property uchar molcount_b -property uchar spots_r -property uchar spots_g -property uchar spots_b -property uchar rmsf_r -property uchar rmsf_g -property uchar rmsf_b -element face 2 -property list uchar int vertex_index -end_header -130.901 160.016 163.033 90 159 210 255 -0.382 -0.895 -0.231 181 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 171 196 212 -131.372 159.778 162.83 90 159 210 255 -0.618 -0.776 -0.129 178 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 141 177 199 -131.682 159.385 163.089 90 159 210 255 -0.773 -0.579 -0.259 180 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 172 196 212 -131.233 160.386 162.11 90 159 210 255 -0.708 -0.383 -0.594 178 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 141 177 199 -130.782 160.539 162.415 90 159 210 255 -0.482 -0.459 -0.746 181 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 171 196 212 -131.482 160.483 161.621 90 159 210 255 -0.832 -0.431 -0.349 179 21 100 150 24 102 151 20 100 150 20 100 150 30 106 154 20 100 150 171 196 212 -3 0 2 1 -3 3 5 4 +const kinString = `@kinemage 1 +@caption probe.2.26.021123, run Tue Apr 23 14:49:17 2024 + command: C:\tmp\cctbx_phenix\build\probe\exe\probe.exe -kin -mc -het -once -wat2wat -onlybadout -stdbonds water all 1ssxFH.pdb +@group dominant {dots} +@subgroup dominant {once dots} +@master {bad overlap} +@pointmaster 'O' {Hets contacts} +@vectorlist {x} color=red master={bad overlap} +{ O HOH 319 A}hotpink P 'O' 31.146,32.100,-1.425 {"}hotpink 'O' 31.015,32.234,-1.324 +{"}hotpink P 'O' 31.607,32.750,-1.156 {"}hotpink 'O' 31.410,32.784,-1.097 +{"}hotpink P 'O' 31.263,32.074,-1.185 {"}hotpink 'O' 31.117,32.209,-1.122 +{ O BHOH 338 A}hotpink P 'O' 32.540,45.631,10.833 {"}hotpink 'O' 32.430,45.771,10.977 +{"}hotpink P 'O' 32.316,45.500,10.828 {"}hotpink 'O' 32.230,45.689,10.998 +{"}hotpink P 'O' 32.068,45.424,10.824 {"}hotpink 'O' 32.034,45.604,10.975 +{"}hotpink P 'O' 32.729,45.605,11.052 {"}hotpink 'O' 32.572,45.765,11.173 `; -const kinCubeString = `ply -format ascii 1.0 -comment test cube -element vertex 24 -property float32 x -property float32 y -property float32 z -property uint32 material_index -element face 6 -property list uint8 int32 vertex_indices -element material 6 -property uint8 red -property uint8 green -property uint8 blue -end_header --1 -1 -1 0 -1 -1 -1 0 -1 1 -1 0 --1 1 -1 0 -1 -1 1 1 --1 -1 1 1 --1 1 1 1 -1 1 1 1 -1 1 1 2 -1 1 -1 2 -1 -1 -1 2 -1 -1 1 2 --1 1 -1 3 --1 1 1 3 --1 -1 1 3 --1 -1 -1 3 --1 1 1 4 --1 1 -1 4 -1 1 -1 4 -1 1 1 4 -1 -1 1 5 -1 -1 -1 5 --1 -1 -1 5 --1 -1 1 5 -4 0 1 2 3 -4 4 5 6 7 -4 8 9 10 11 -4 12 13 14 15 -4 16 17 18 19 -4 20 21 22 23 -255 0 0 -0 255 0 -0 0 255 -255 255 0 -0 255 255 -255 0 255 +/// @todo Replace with more complex kinemage +const kinComplexString = `@kinemage 1 +@caption probe.2.26.021123, run Tue Apr 23 14:49:17 2024 + command: C:\tmp\cctbx_phenix\build\probe\exe\probe.exe -kin -mc -het -once -wat2wat -onlybadout -stdbonds water all 1ssxFH.pdb +@group dominant {dots} +@subgroup dominant {once dots} +@master {bad overlap} +@pointmaster 'O' {Hets contacts} +@vectorlist {x} color=red master={bad overlap} +{ O HOH 319 A}hotpink P 'O' 31.146,32.100,-1.425 {"}hotpink 'O' 31.015,32.234,-1.324 +{"}hotpink P 'O' 31.607,32.750,-1.156 {"}hotpink 'O' 31.410,32.784,-1.097 +{"}hotpink P 'O' 31.263,32.074,-1.185 {"}hotpink 'O' 31.117,32.209,-1.122 +{ O BHOH 338 A}hotpink P 'O' 32.540,45.631,10.833 {"}hotpink 'O' 32.430,45.771,10.977 +{"}hotpink P 'O' 32.316,45.500,10.828 {"}hotpink 'O' 32.230,45.689,10.998 +{"}hotpink P 'O' 32.068,45.424,10.824 {"}hotpink 'O' 32.034,45.604,10.975 +{"}hotpink P 'O' 32.729,45.605,11.052 {"}hotpink 'O' 32.572,45.765,11.173 `; describe('kin reader', () => { it('basic', async () => { const parsed = await parseKin(kinString).run(); if (parsed.isError) return; - const kinFile = parsed.result; + const kinemage = parsed.result; - const vertex = kinFile.getElement('vertex') as KinTable; - if (!vertex) return; - const x = vertex.getProperty('x'); - if (!x) return; - expect(x.value(0)).toEqual(130.901); + const vectors = kinemage.vectorLists; + expect(vectors.length).toEqual(1); - const face = kinFile.getElement('face') as KinList; - if (!face) return; - expect(face.value(0)).toEqual({ count: 3, entries: [0, 2, 1] }); - expect(face.value(1)).toEqual({ count: 3, entries: [3, 5, 4] }); + const element = vectors[0]; + expect(element.name).toEqual('x'); + expect(element.position1Array.length).toEqual(7); + + /// @todo Add more tests expect.assertions(3); }); - it('material', async () => { - const parsed = await parseKin(kinCubeString).run(); + it('complex', async () => { + const parsed = await parseKin(kinComplexString).run(); if (parsed.isError) return; - const kinFile = parsed.result; - const vertex = kinFile.getElement('vertex') as KinTable; - if (!vertex) return; - expect(vertex.rowCount).toBe(24); + /// @todo Add more complex tests - const face = kinFile.getElement('face') as KinList; - if (!face) return; - expect(face.rowCount).toBe(6); - - const material = kinFile.getElement('face') as KinTable; - if (!material) return; - expect(face.rowCount).toBe(6); - - expect.assertions(3); }); }); \ No newline at end of file diff --git a/src/mol-io/reader/kin/ngl-based-parser.ts b/src/mol-io/reader/kin/ngl-based-parser.ts index dce47ab20..58eba8694 100644 --- a/src/mol-io/reader/kin/ngl-based-parser.ts +++ b/src/mol-io/reader/kin/ngl-based-parser.ts @@ -15,6 +15,8 @@ // import { Vector3 } from 'three' // import Parser from './parser' +/// @todo Fill in commments + import { Kinemage, RibbonObject } from './schema'; function hsvToRgb (h: number, s: number, v: number) { @@ -315,6 +317,7 @@ class KinParser { // http://kinemage.biochem.duke.edu/software/king.php const kinemage: Kinemage = { + comments: [], kinemage: undefined, onewidth: undefined, '1viewid': undefined, diff --git a/src/mol-io/reader/kin/parser.ts b/src/mol-io/reader/kin/parser.ts index 7ed1cc289..bfd0f1f50 100644 --- a/src/mol-io/reader/kin/parser.ts +++ b/src/mol-io/reader/kin/parser.ts @@ -6,260 +6,18 @@ import { ReaderResult as Result } from '../result'; import { Task, RuntimeContext } from '../../../mol-task'; -import { KinFile, KinType, KinElement } from './schema'; +import { Kinemage } from './schema'; import KinParser from './ngl-based-parser'; -import { Tokenizer, TokenBuilder, Tokens } from '../common/text/tokenizer'; -import { Column } from '../../../mol-data/db'; -import { TokenColumn } from '../common/text/column/token'; -interface State { - data: string - tokenizer: Tokenizer - runtimeCtx: RuntimeContext - comments: string[] - elementSpecs: ElementSpec[] - elements: KinElement[] -} +async function parseInternal(data: string, ctx: RuntimeContext): Promise> { -function State(data: string, runtimeCtx: RuntimeContext): State { - const tokenizer = Tokenizer(data); - return { - data, - tokenizer, - runtimeCtx, - - comments: [], - elementSpecs: [], - elements: [] - }; -} - -type ColumnProperty = { kind: 'column', type: KinType, name: string } -type ListProperty = { kind: 'list', countType: KinType, dataType: KinType, name: string } -type Property = ColumnProperty | ListProperty - -type TableElementSpec = { kind: 'table', name: string, count: number, properties: ColumnProperty[] } -type ListElementSpec = { kind: 'list', name: string, count: number, property: ListProperty } -type ElementSpec = TableElementSpec | ListElementSpec - -function markHeader(tokenizer: Tokenizer) { - const endHeaderIndex = tokenizer.data.indexOf('end_header', tokenizer.position); - if (endHeaderIndex === -1) throw new Error(`no 'end_header' record found`); - // TODO set `tokenizer.lineNumber` correctly - tokenizer.tokenStart = tokenizer.position; - tokenizer.tokenEnd = endHeaderIndex; - tokenizer.position = endHeaderIndex; - Tokenizer.eatLine(tokenizer); -} - -function parseHeader(state: State) { - const { tokenizer, comments, elementSpecs } = state; - - markHeader(tokenizer); - const headerLines = Tokenizer.getTokenString(tokenizer).split(/\r?\n/); - - if (headerLines[0] !== 'ply') throw new Error(`data not starting with 'ply'`); - if (headerLines[1] !== 'format ascii 1.0') throw new Error(`format not 'ascii 1.0'`); - - let currentName: string | undefined; - let currentCount: number | undefined; - let currentProperties: Property[] | undefined; - - - function addCurrentElementSchema() { - if (currentName !== undefined && currentCount !== undefined && currentProperties !== undefined) { - let isList = false; - for (let i = 0, il = currentProperties.length; i < il; ++i) { - const p = currentProperties[i]; - if (p.kind === 'list') { - isList = true; - break; - } - } - if (isList && currentProperties.length !== 1) { - // TODO handle lists with appended properties - // currently only the list part will be accessible - } - if (isList) { - elementSpecs.push({ - kind: 'list', - name: currentName, - count: currentCount, - property: currentProperties[0] as ListProperty - }); - } else { - elementSpecs.push({ - kind: 'table', - name: currentName, - count: currentCount, - properties: currentProperties as ColumnProperty[] - }); - } - } - } - - for (let i = 2, il = headerLines.length; i < il; ++i) { - const l = headerLines[i]; - const ls = l.split(' '); - if (l.startsWith('comment')) { - comments.push(l.substr(8)); - } else if (l.startsWith('element')) { - addCurrentElementSchema(); - currentProperties = []; - currentName = ls[1]; - currentCount = parseInt(ls[2]); - } else if (l.startsWith('property')) { - if (currentProperties === undefined) throw new Error(`properties outside of element`); - if (ls[1] === 'list') { - currentProperties.push({ - kind: 'list', - countType: KinType(ls[2]), - dataType: KinType(ls[3]), - name: ls[4] - }); - } else { - currentProperties.push({ - kind: 'column', - type: KinType(ls[1]), - name: ls[2] - }); - } - } else if (l.startsWith('end_header')) { - addCurrentElementSchema(); - } else { - console.warn('unknown header line'); - } - } -} - -function parseElements(state: State) { - const { elementSpecs } = state; - for (let i = 0, il = elementSpecs.length; i < il; ++i) { - const spec = elementSpecs[i]; - if (spec.kind === 'table') parseTableElement(state, spec); - else if (spec.kind === 'list') parseListElement(state, spec); - } -} - -function getColumnSchema(type: KinType): Column.Schema { - switch (type) { - case 'char': case 'uchar': case 'int8': case 'uint8': - case 'short': case 'ushort': case 'int16': case 'uint16': - case 'int': case 'uint': case 'int32': case 'uint32': - return Column.Schema.int; - case 'float': case 'double': case 'float32': case 'float64': - return Column.Schema.float; - } -} - -function parseTableElement(state: State, spec: TableElementSpec) { - const { elements, tokenizer } = state; - const { count, properties } = spec; - const propertyCount = properties.length; - const propertyNames: string[] = []; - const propertyTypes: KinType[] = []; - const propertyTokens: Tokens[] = []; - const propertyColumns = new Map>(); - - for (let i = 0, il = propertyCount; i < il; ++i) { - const tokens = TokenBuilder.create(tokenizer.data, count * 2); - propertyTokens.push(tokens); - } - - for (let i = 0, il = count; i < il; ++i) { - for (let j = 0, jl = propertyCount; j < jl; ++j) { - Tokenizer.skipWhitespace(tokenizer); - Tokenizer.markStart(tokenizer); - Tokenizer.eatValue(tokenizer); - TokenBuilder.addUnchecked(propertyTokens[j], tokenizer.tokenStart, tokenizer.tokenEnd); - } - } - - for (let i = 0, il = propertyCount; i < il; ++i) { - const { type, name } = properties[i]; - const column = TokenColumn(propertyTokens[i], getColumnSchema(type)); - propertyNames.push(name); - propertyTypes.push(type); - propertyColumns.set(name, column); - } - - elements.push({ - kind: 'table', - rowCount: count, - propertyNames, - propertyTypes, - getProperty: (name: string) => propertyColumns.get(name) - }); -} - -function parseListElement(state: State, spec: ListElementSpec) { - const { elements, tokenizer } = state; - const { count, property } = spec; - - // initial tokens size assumes triangle index data - const tokens = TokenBuilder.create(tokenizer.data, count * 2 * 3); - - const offsets = new Uint32Array(count + 1); - let entryCount = 0; - - for (let i = 0, il = count; i < il; ++i) { - Tokenizer.skipWhitespace(tokenizer); - Tokenizer.markStart(tokenizer); - while (Tokenizer.skipWhitespace(tokenizer) !== 10) { - ++entryCount; - Tokenizer.markStart(tokenizer); - Tokenizer.eatValue(tokenizer); - TokenBuilder.addToken(tokens, tokenizer); - } - offsets[i + 1] = entryCount; - } - - /** holds row value entries transiently */ - const listValue = { - entries: [] as number[], - count: 0 - }; - - const column = TokenColumn(tokens, getColumnSchema(property.dataType)); - - elements.push({ - kind: 'list', - rowCount: count, - name: property.name, - type: property.dataType, - value: (row: number) => { - const offset = offsets[row] + 1; - const count = column.value(offset - 1); - for (let i = offset, il = offset + count; i < il; ++i) { - listValue.entries[i - offset] = column.value(i); - } - listValue.count = count; - return listValue; - } - }); -} - -async function parseInternal(data: string, ctx: RuntimeContext): Promise> { - - console.log('XXX Constructing KinParser'); const NGLParser = new KinParser(data); - console.log('XXX Getting data from KinParser'); const kinData = NGLParser.kinemage; - console.log(`XXX Number of vector lists: ${kinData.vectorLists.length}, ballLists: ${kinData.ballLists.length}, ribbonLists: ${kinData.ribbonLists.length}`); - - /// @todo Replace this with code that pulls from the NGL-based data and constructs a KinFile - const state = State(data, ctx); - ctx.update({ message: 'Parsing...', current: 0, max: data.length }); - parseHeader(state); - parseElements(state); - const { elements, elementSpecs, comments } = state; - const elementNames = elementSpecs.map(s => s.name); - const result = KinFile(elements, elementNames, comments); - return Result.success(result); + return Result.success(kinData); } export function parseKin(data: string) { - return Task.create>('Parse KIN', async ctx => { + return Task.create>('Parse KIN', async ctx => { return await parseInternal(data, ctx); }); } diff --git a/src/mol-io/reader/kin/schema.ts b/src/mol-io/reader/kin/schema.ts index 9c566f442..411b2246f 100644 --- a/src/mol-io/reader/kin/schema.ts +++ b/src/mol-io/reader/kin/schema.ts @@ -5,6 +5,7 @@ */ export interface Kinemage { + readonly comments: ReadonlyArray kinemage?: number, onewidth?: any, '1viewid'?: string, @@ -31,18 +32,6 @@ export interface DotList { colorArray: number[] ///< Catenation of r, g, b for each element, 3x as many as elements } -export interface VectorList { - name?: string, ///< Optional name of the whole List - masterArray: any[], ///< Array of master names per List, not per element - label1Array: string[], ///< Array of labels per List, not per element - label2Array: string[], ///< Array of labels per List, not per element - position1Array: number[], ///< Catenation of x, y, z for each element, 3x as many as elements - position2Array: number[], ///< Catenation of x, y, z for each element, 3x as many as elements - color1Array: number[], ///< Catenation of r, g, b for each element, 3x as many as elements - color2Array: number[], ///< Catenation of r, g, b for each element, 3x as many as elements - width: number[] ///< A single width per element -} - export interface BallList { name?: string, ///< Optional name of the whole List masterArray: any[], ///< Array of master names per List, not per element @@ -61,78 +50,14 @@ export interface RibbonObject { breakArray: boolean[] ///< A single boolean per element indicating if there is a break there } - -import { Column } from '../../../mol-data/db'; - -// @todo Point to format description -// http://paulbourke.net/dataformats/ply/ -// https://en.wikipedia.org/wiki/PLY_(file_format) - -export const KinTypeByteLength = { - 'char': 1, - 'uchar': 1, - 'short': 2, - 'ushort': 2, - 'int': 4, - 'uint': 4, - 'float': 4, - 'double': 8, - - 'int8': 1, - 'uint8': 1, - 'int16': 2, - 'uint16': 2, - 'int32': 4, - 'uint32': 4, - 'float32': 4, - 'float64': 8 -}; -export type KinType = keyof typeof KinTypeByteLength -export const KinTypes = new Set(Object.keys(KinTypeByteLength)); -export function KinType(str: string) { - if (!KinTypes.has(str)) throw new Error(`unknown ply type '${str}'`); - return str as KinType; +export interface VectorList { + name?: string, ///< Optional name of the whole List + masterArray: any[], ///< Array of master names per List, not per element + label1Array: string[], ///< Array of labels per List, not per element + label2Array: string[], ///< Array of labels per List, not per element + position1Array: number[], ///< Catenation of x, y, z for each element, 3x as many as elements + position2Array: number[], ///< Catenation of x, y, z for each element, 3x as many as elements + color1Array: number[], ///< Catenation of r, g, b for each element, 3x as many as elements + color2Array: number[], ///< Catenation of r, g, b for each element, 3x as many as elements + width: number[] ///< A single width per element } - -export interface KinFile { - readonly comments: ReadonlyArray - readonly elementNames: ReadonlyArray - getElement(name: string): KinElement | undefined -} - -export function KinFile(elements: KinElement[], elementNames: string[], comments: string[]): KinFile { - const elementMap = new Map(); - for (let i = 0, il = elementNames.length; i < il; ++i) { - elementMap.set(elementNames[i], elements[i]); - } - return { - comments, - elementNames, - getElement: (name: string) => { - return elementMap.get(name); - } - }; -} - -export type KinElement = KinTable | KinList - -export interface KinTable { - readonly kind: 'table' - readonly rowCount: number - readonly propertyNames: ReadonlyArray - readonly propertyTypes: ReadonlyArray - getProperty(name: string): Column | undefined -} - -export interface KinListValue { - readonly entries: ArrayLike - readonly count: number -} - -export interface KinList { - readonly kind: 'list' - readonly rowCount: number, - readonly name: string, - readonly type: KinType, - value: (row: number) => KinListValue -} \ No newline at end of file diff --git a/src/mol-model-formats/shape/kin.ts b/src/mol-model-formats/shape/kin.ts index e617dc850..fbe095913 100644 --- a/src/mol-model-formats/shape/kin.ts +++ b/src/mol-model-formats/shape/kin.ts @@ -7,295 +7,53 @@ import { RuntimeContext, Task } from '../../mol-task'; import { ShapeProvider } from '../../mol-model/shape/provider'; import { Color } from '../../mol-util/color'; -import { KinFile, KinTable, KinList } from '../../mol-io/reader/kin/schema'; -import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder'; +import { Kinemage } from '../../mol-io/reader/kin/schema'; import { Mesh } from '../../mol-geo/geometry/mesh/mesh'; import { Shape } from '../../mol-model/shape'; -import { ChunkedArray } from '../../mol-data/util'; -import { arrayMax, fillSerial } from '../../mol-util/array'; -import { Column } from '../../mol-data/db'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; -import { ColorNames } from '../../mol-util/color/names'; -import { deepClone } from '../../mol-util/object'; -import { stringToWords } from '../../mol-util/string'; -import { ValueCell } from '../../mol-util/value-cell'; import { Mat4 } from '../../mol-math/linear-algebra/3d/mat4'; -// TODO support 'edge' element, see https://www.mathworks.com/help/vision/ug/the-ply-format.html -// TODO support missing face element +/// @todo Fill in geometry and coloring information export type KinData = { - source: KinFile, + source: Kinemage, transforms?: Mat4[], } -function createKinShapeParams(kinFile?: KinFile) { - const vertex = kinFile && kinFile.getElement('vertex') as KinTable; - const material = kinFile && kinFile.getElement('material') as KinTable; - - const defaultValues = { group: '', vRed: '', vGreen: '', vBlue: '', mRed: '', mGreen: '', mBlue: '' }; - - const groupOptions: [string, string][] = [['', '']]; - const colorOptions: [string, string][] = [['', '']]; - if (vertex) { - for (let i = 0, il = vertex.propertyNames.length; i < il; ++i) { - const name = vertex.propertyNames[i]; - const type = vertex.propertyTypes[i]; - if ( - type === 'uchar' || type === 'uint8' || - type === 'ushort' || type === 'uint16' || - type === 'uint' || type === 'uint32' || - type === 'int' - ) groupOptions.push([name, name]); - if (type === 'uchar' || type === 'uint8') colorOptions.push([name, name]); - } - - // TODO hardcoded as convenience for data provided by MegaMol - if (vertex.propertyNames.includes('atomid')) defaultValues.group = 'atomid'; - else if (vertex.propertyNames.includes('material_index')) defaultValues.group = 'material_index'; - - if (vertex.propertyNames.includes('red')) defaultValues.vRed = 'red'; - if (vertex.propertyNames.includes('green')) defaultValues.vGreen = 'green'; - if (vertex.propertyNames.includes('blue')) defaultValues.vBlue = 'blue'; - } - - const materialOptions: [string, string][] = [['', '']]; - if (material) { - for (let i = 0, il = material.propertyNames.length; i < il; ++i) { - const name = material.propertyNames[i]; - const type = material.propertyTypes[i]; - if (type === 'uchar' || type === 'uint8') materialOptions.push([name, name]); - } - - if (material.propertyNames.includes('red')) defaultValues.mRed = 'red'; - if (material.propertyNames.includes('green')) defaultValues.mGreen = 'green'; - if (material.propertyNames.includes('blue')) defaultValues.mBlue = 'blue'; - } - - const defaultColoring = defaultValues.vRed && defaultValues.vGreen && defaultValues.vBlue ? 'vertex' : - defaultValues.mRed && defaultValues.mGreen && defaultValues.mBlue ? 'material' : 'uniform'; +function createKinShapeParams(kinemage?: Kinemage) { return { ...Mesh.Params, - - coloring: PD.MappedStatic(defaultColoring, { - vertex: PD.Group({ - red: PD.Select(defaultValues.vRed, colorOptions, { label: 'Red Property' }), - green: PD.Select(defaultValues.vGreen, colorOptions, { label: 'Green Property' }), - blue: PD.Select(defaultValues.vBlue, colorOptions, { label: 'Blue Property' }), - }, { isFlat: true }), - material: PD.Group({ - red: PD.Select(defaultValues.mRed, materialOptions, { label: 'Red Property' }), - green: PD.Select(defaultValues.mGreen, materialOptions, { label: 'Green Property' }), - blue: PD.Select(defaultValues.mBlue, materialOptions, { label: 'Blue Property' }), - }, { isFlat: true }), - uniform: PD.Group({ - color: PD.Color(ColorNames.grey), - saturation: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }), - lightness: PD.Numeric(0, { min: -6, max: 6, step: 0.1 }), - }, { isFlat: true }) - }), - grouping: PD.MappedStatic(defaultValues.group ? 'vertex' : 'none', { - vertex: PD.Group({ - group: PD.Select(defaultValues.group, groupOptions, { label: 'Group Property' }), - }, { isFlat: true }), - none: PD.Group({ }) - }), }; } export const KinShapeParams = createKinShapeParams(); export type KinShapeParams = typeof KinShapeParams -function addVerticesRange(begI: number, endI: number, state: MeshBuilder.State, vertex: KinTable, groupIds: ArrayLike) { - const { vertices, normals, groups } = state; - - const x = vertex.getProperty('x'); - const y = vertex.getProperty('y'); - const z = vertex.getProperty('z'); - if (!x || !y || !z) throw new Error('missing coordinate properties'); - - const nx = vertex.getProperty('nx'); - const ny = vertex.getProperty('ny'); - const nz = vertex.getProperty('nz'); - - const hasNormals = !!nx && !!ny && !!nz; - - for (let i = begI; i < endI; ++i) { - ChunkedArray.add3(vertices, x.value(i), y.value(i), z.value(i)); - if (hasNormals) ChunkedArray.add3(normals, nx!.value(i), ny!.value(i), nz!.value(i)); - ChunkedArray.add(groups, groupIds[i]); - } -} - -function addFacesRange(begI: number, endI: number, state: MeshBuilder.State, face: KinList) { - const { indices } = state; - - for (let i = begI; i < endI; ++i) { - const { entries, count } = face.value(i); - if (count === 3) { - // triangle - ChunkedArray.add3(indices, entries[0], entries[1], entries[2]); - } else if (count === 4) { - // quadrilateral - ChunkedArray.add3(indices, entries[2], entries[1], entries[0]); - ChunkedArray.add3(indices, entries[2], entries[0], entries[3]); - } - } -} - -async function getMesh(ctx: RuntimeContext, vertex: KinTable, face: KinList, groupIds: ArrayLike, mesh?: Mesh) { - const builderState = MeshBuilder.createState(vertex.rowCount, vertex.rowCount / 4, mesh); - - const x = vertex.getProperty('x'); - const y = vertex.getProperty('y'); - const z = vertex.getProperty('z'); - if (!x || !y || !z) throw new Error('missing coordinate properties'); - - const nx = vertex.getProperty('nx'); - const ny = vertex.getProperty('ny'); - const nz = vertex.getProperty('nz'); - - const hasNormals = !!nx && !!ny && !!nz; - const updateChunk = 100000; - - for (let i = 0, il = vertex.rowCount; i < il; i += updateChunk) { - addVerticesRange(i, Math.min(i + updateChunk, il), builderState, vertex, groupIds); - - if (ctx.shouldUpdate) { - await ctx.update({ message: 'adding kin mesh vertices', current: i, max: il }); - } - } - - for (let i = 0, il = face.rowCount; i < il; i += updateChunk) { - addFacesRange(i, Math.min(i + updateChunk, il), builderState, face); - - if (ctx.shouldUpdate) { - await ctx.update({ message: 'adding kin mesh faces', current: i, max: il }); - } - } - - const m = MeshBuilder.getMesh(builderState); - if (!hasNormals) Mesh.computeNormals(m); - - // TODO: check if needed - ValueCell.updateIfChanged(m.varyingGroup, true); - - return m; -} - -const int = Column.Schema.int; - -type Grouping = { ids: ArrayLike, map: ArrayLike, label: string } -function getGrouping(vertex: KinTable, props: PD.Values): Grouping { - const { grouping } = props; - const { rowCount } = vertex; - const column = grouping.name === 'vertex' ? vertex.getProperty(grouping.params.group) : undefined; - const label = grouping.name === 'vertex' ? stringToWords(grouping.params.group) : 'Vertex'; - - const ids = column ? column.toArray({ array: Uint32Array }) : fillSerial(new Uint32Array(rowCount)); - const maxId = column ? arrayMax(ids) : rowCount - 1; // assumes uint ids - const map = new Uint32Array(maxId + 1); - for (let i = 0, il = ids.length; i < il; ++i) map[ids[i]] = i; - return { ids, map, label }; -} - -type Coloring = { kind: 'vertex' | 'material' | 'uniform', red: Column, green: Column, blue: Column } -function getColoring(vertex: KinTable, material: KinTable | undefined, props: PD.Values): Coloring { - const { coloring } = props; - const { rowCount } = vertex; - - let red: Column, green: Column, blue: Column; - if (coloring.name === 'vertex') { - red = vertex.getProperty(coloring.params.red) || Column.ofConst(127, rowCount, int); - green = vertex.getProperty(coloring.params.green) || Column.ofConst(127, rowCount, int); - blue = vertex.getProperty(coloring.params.blue) || Column.ofConst(127, rowCount, int); - } else if (coloring.name === 'material') { - red = (material && material.getProperty(coloring.params.red)) || Column.ofConst(127, rowCount, int); - green = (material && material.getProperty(coloring.params.green)) || Column.ofConst(127, rowCount, int); - blue = (material && material.getProperty(coloring.params.blue)) || Column.ofConst(127, rowCount, int); - } else { - let color = coloring.params.color; - color = Color.saturate(color, coloring.params.saturation); - color = Color.lighten(color, coloring.params.lightness); - const [r, g, b] = Color.toRgb(color); - red = Column.ofConst(r, rowCount, int); - green = Column.ofConst(g, rowCount, int); - blue = Column.ofConst(b, rowCount, int); - } - return { kind: coloring.name, red, green, blue }; -} - -function createShape(kinData: KinData, mesh: Mesh, coloring: Coloring, grouping: Grouping) { - const { kind, red, green, blue } = coloring; - const { ids, map, label } = grouping; - const { source, transforms } = kinData; - return Shape.create( - 'kin-mesh', source, mesh, - (groupId: number) => { - const idx = kind === 'material' ? groupId : map[groupId]; - return Color.fromRgb(red.value(idx), green.value(idx), blue.value(idx)); - }, - () => 1, // size: constant - (groupId: number) => { - return `${label} ${ids[groupId]}`; - }, - transforms - ); -} - function makeShapeGetter() { - let _kinData: KinData | undefined; - let _props: PD.Values | undefined; - - let _shape: Shape; - let _mesh: Mesh; - let _coloring: Coloring; - let _grouping: Grouping; const getShape = async (ctx: RuntimeContext, kinData: KinData, props: PD.Values, shape?: Shape) => { - const vertex = kinData.source.getElement('vertex') as KinTable; - if (!vertex) throw new Error('missing vertex element'); + console.log(`XXX Number of vector lists: ${kinData.source.vectorLists.length}, ballLists: ${kinData.source.ballLists.length}, ribbonLists: ${kinData.source.ribbonLists.length}`); + /// @todo + // Create an empty Mesh + const mesh = Mesh.createEmpty(); - const face = kinData.source.getElement('face') as KinList; - if (!face) throw new Error('missing face element'); + // Create an empty Shape with the empty Mesh + const emptyShape = Shape.create( + 'Empty Shape', // id + kinData, // source data + mesh, // geometry + () => Color(0xFFFFFF), // color function + () => 1, // size function + () => '' // label function + ); - const material = kinData.source.getElement('material') as KinTable; - - let newMesh = false; - let newColor = false; - - if (!_kinData || _kinData !== _kinData) { - newMesh = true; - } - - if (!_props || !PD.isParamEqual(KinShapeParams.grouping, _props.grouping, props.grouping)) { - newMesh = true; - } - - if (!_props || !PD.isParamEqual(KinShapeParams.coloring, _props.coloring, props.coloring)) { - newColor = true; - } - - if (newMesh) { - _coloring = getColoring(vertex, material, props); - _grouping = getGrouping(vertex, props); - _mesh = await getMesh(ctx, vertex, face, _grouping.ids, shape && shape.geometry); - _shape = createShape(kinData, _mesh, _coloring, _grouping); - } else if (newColor) { - _coloring = getColoring(vertex, material, props); - _shape = createShape(kinData, _mesh, _coloring, _grouping); - } - - _kinData = kinData; - _props = deepClone(props); - - return _shape; + return emptyShape; }; return getShape; } -export function shapeFromKin(source: KinFile, params?: { transforms?: Mat4[] }) { +export function shapeFromKin(source: Kinemage, params?: { transforms?: Mat4[] }) { return Task.create>('Shape Provider', async ctx => { return { label: 'Mesh', diff --git a/src/mol-plugin-state/objects.ts b/src/mol-plugin-state/objects.ts index 102cd7c4e..6a909a3af 100644 --- a/src/mol-plugin-state/objects.ts +++ b/src/mol-plugin-state/objects.ts @@ -10,7 +10,7 @@ import { CifFile } from '../mol-io/reader/cif'; import { DcdFile } from '../mol-io/reader/dcd/parser'; import { Dsn6File } from '../mol-io/reader/dsn6/schema'; import { PlyFile } from '../mol-io/reader/ply/schema'; -import { KinFile } from '../mol-io/reader/kin/schema'; +import { Kinemage } from '../mol-io/reader/kin/schema'; import { PsfFile } from '../mol-io/reader/psf/parser'; import { ShapeProvider } from '../mol-model/shape/provider'; import { Coordinates as _Coordinates, Model as _Model, Structure as _Structure, Trajectory as _Trajectory, StructureElement, Topology as _Topology } from '../mol-model/structure'; @@ -80,7 +80,7 @@ export namespace PluginStateObject { export class Prmtop extends Create({ name: 'PRMTOP File', typeClass: 'Data' }) { } export class Top extends Create({ name: 'TOP File', typeClass: 'Data' }) { } export class Ply extends Create({ name: 'PLY File', typeClass: 'Data' }) { } - export class Kin extends Create({ name: 'KIN File', typeClass: 'Data' }) { } + export class Kin extends Create({ name: 'KIN File', typeClass: 'Data' }) { } export class Ccp4 extends Create({ name: 'CCP4/MRC/MAP File', typeClass: 'Data' }) { } export class Dsn6 extends Create({ name: 'DSN6/BRIX File', typeClass: 'Data' }) { } export class Dx extends Create({ name: 'DX File', typeClass: 'Data' }) { } @@ -97,7 +97,7 @@ export namespace PluginStateObject { { kind: 'dsn6', data: Dsn6File } | { kind: 'dx', data: DxFile } | { kind: 'ply', data: PlyFile } | - { kind: 'kin', data: KinFile } | + { kind: 'kin', data: Kinemage } | // For non-built-in extensions { kind: 'custom', data: unknown, tag: string } )