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.

This commit is contained in:
Russ Taylor
2025-02-10 14:51:38 -05:00
parent 8c2d3a577a
commit 8841f04af6
6 changed files with 82 additions and 720 deletions

View File

@@ -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);
});
});

View File

@@ -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,

View File

@@ -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<Result<Kinemage>> {
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<string, Column<number>>();
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<Result<KinFile>> {
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<Result<KinFile>>('Parse KIN', async ctx => {
return Task.create<Result<Kinemage>>('Parse KIN', async ctx => {
return await parseInternal(data, ctx);
});
}

View File

@@ -5,6 +5,7 @@
*/
export interface Kinemage {
readonly comments: ReadonlyArray<string>
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<string>
readonly elementNames: ReadonlyArray<string>
getElement(name: string): KinElement | undefined
}
export function KinFile(elements: KinElement[], elementNames: string[], comments: string[]): KinFile {
const elementMap = new Map<string, KinElement>();
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<string>
readonly propertyTypes: ReadonlyArray<KinType>
getProperty(name: string): Column<number> | undefined
}
export interface KinListValue {
readonly entries: ArrayLike<number>
readonly count: number
}
export interface KinList {
readonly kind: 'list'
readonly rowCount: number,
readonly name: string,
readonly type: KinType,
value: (row: number) => KinListValue
}

View File

@@ -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<number>) {
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<number>, 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<number>, map: ArrayLike<number>, label: string }
function getGrouping(vertex: KinTable, props: PD.Values<KinShapeParams>): 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<number>, green: Column<number>, blue: Column<number> }
function getColoring(vertex: KinTable, material: KinTable | undefined, props: PD.Values<KinShapeParams>): Coloring {
const { coloring } = props;
const { rowCount } = vertex;
let red: Column<number>, green: Column<number>, blue: Column<number>;
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<KinShapeParams> | undefined;
let _shape: Shape<Mesh>;
let _mesh: Mesh;
let _coloring: Coloring;
let _grouping: Grouping;
const getShape = async (ctx: RuntimeContext, kinData: KinData, props: PD.Values<KinShapeParams>, shape?: Shape<Mesh>) => {
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<ShapeProvider<KinData, Mesh, KinShapeParams>>('Shape Provider', async ctx => {
return {
label: 'Mesh',

View File

@@ -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<PrmtopFile>({ name: 'PRMTOP File', typeClass: 'Data' }) { }
export class Top extends Create<TopFile>({ name: 'TOP File', typeClass: 'Data' }) { }
export class Ply extends Create<PlyFile>({ name: 'PLY File', typeClass: 'Data' }) { }
export class Kin extends Create<KinFile>({ name: 'KIN File', typeClass: 'Data' }) { }
export class Kin extends Create<Kinemage>({ name: 'KIN File', typeClass: 'Data' }) { }
export class Ccp4 extends Create<Ccp4File>({ name: 'CCP4/MRC/MAP File', typeClass: 'Data' }) { }
export class Dsn6 extends Create<Dsn6File>({ name: 'DSN6/BRIX File', typeClass: 'Data' }) { }
export class Dx extends Create<DxFile>({ 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 }
)