mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 13:30:24 +08:00
Initial Kinemage commit that copies the PLY files and references to make it possible to load a PLY-formate file from a file with a KIN extension.
Overwriting package-lock.json
This commit is contained in:
151
src/mol-io/reader/_spec/kin.spec.ts
Normal file
151
src/mol-io/reader/_spec/kin.spec.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author ReliaSolve <russ@reliasolve.com>
|
||||
*/
|
||||
|
||||
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 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
|
||||
`;
|
||||
|
||||
describe('kin reader', () => {
|
||||
it('basic', async () => {
|
||||
const parsed = await parseKin(kinString).run();
|
||||
if (parsed.isError) return;
|
||||
const kinFile = 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 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] });
|
||||
|
||||
expect.assertions(3);
|
||||
});
|
||||
|
||||
it('material', async () => {
|
||||
const parsed = await parseKin(kinCubeString).run();
|
||||
if (parsed.isError) return;
|
||||
const kinFile = parsed.result;
|
||||
|
||||
const vertex = kinFile.getElement('vertex') as KinTable;
|
||||
if (!vertex) return;
|
||||
expect(vertex.rowCount).toBe(24);
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
256
src/mol-io/reader/kin/parser.ts
Normal file
256
src/mol-io/reader/kin/parser.ts
Normal file
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author ReliaSolve <russ@reliasolve.com>
|
||||
*/
|
||||
|
||||
import { ReaderResult as Result } from '../result';
|
||||
import { Task, RuntimeContext } from '../../../mol-task';
|
||||
import { KinFile, KinType, KinElement } from './schema';
|
||||
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[]
|
||||
}
|
||||
|
||||
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>> {
|
||||
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);
|
||||
}
|
||||
|
||||
export function parseKin(data: string) {
|
||||
return Task.create<Result<KinFile>>('Parse KIN', async ctx => {
|
||||
return await parseInternal(data, ctx);
|
||||
});
|
||||
}
|
||||
80
src/mol-io/reader/kin/schema.ts
Normal file
80
src/mol-io/reader/kin/schema.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author ReliaSolve <russ@reliasolve.com>
|
||||
*/
|
||||
|
||||
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 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
|
||||
}
|
||||
308
src/mol-model-formats/shape/kin.ts
Normal file
308
src/mol-model-formats/shape/kin.ts
Normal file
@@ -0,0 +1,308 @@
|
||||
/**
|
||||
* Copyright (c) 2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author ReliaSolve <russ@reliasolve.com>
|
||||
*/
|
||||
|
||||
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 { 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
|
||||
|
||||
export type KinData = {
|
||||
source: KinFile,
|
||||
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';
|
||||
|
||||
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');
|
||||
|
||||
const face = kinData.source.getElement('face') as KinList;
|
||||
if (!face) throw new Error('missing face element');
|
||||
|
||||
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 getShape;
|
||||
}
|
||||
|
||||
export function shapeFromKin(source: KinFile, params?: { transforms?: Mat4[] }) {
|
||||
return Task.create<ShapeProvider<KinData, Mesh, KinShapeParams>>('Shape Provider', async ctx => {
|
||||
return {
|
||||
label: 'Mesh',
|
||||
data: { source, transforms: params?.transforms },
|
||||
params: createKinShapeParams(source),
|
||||
getShape: makeShapeGetter(),
|
||||
geometryUtils: Mesh.Utils
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -37,8 +37,33 @@ export const PlyProvider = DataFormatProvider({
|
||||
}
|
||||
});
|
||||
|
||||
export const KinProvider = DataFormatProvider({
|
||||
label: 'KIN',
|
||||
description: 'KIN',
|
||||
category: ShapeFormatCategory,
|
||||
stringExtensions: ['kin'],
|
||||
parse: async (plugin, data) => {
|
||||
const format = plugin.state.data.build()
|
||||
.to(data)
|
||||
.apply(StateTransforms.Data.ParseKin, {}, { state: { isGhost: true } });
|
||||
|
||||
const shape = format.apply(StateTransforms.Model.ShapeFromKin);
|
||||
|
||||
await format.commit();
|
||||
|
||||
return { format: format.selector, shape: shape.selector };
|
||||
},
|
||||
visuals(plugin: PluginContext, data: { shape: StateObjectRef<PluginStateObject.Shape.Provider> }) {
|
||||
const repr = plugin.state.data.build()
|
||||
.to(data.shape)
|
||||
.apply(StateTransforms.Representation.ShapeRepresentation3D);
|
||||
return repr.commit();
|
||||
}
|
||||
});
|
||||
|
||||
export const BuiltInShapeFormats = [
|
||||
['ply', PlyProvider] as const,
|
||||
['ply', PlyProvider] as const,
|
||||
['kin', KinProvider] as const,
|
||||
] as const;
|
||||
|
||||
export type BuildInShapeFormat = (typeof BuiltInShapeFormats)[number][0]
|
||||
export type BuiltInShapeFormat = (typeof BuiltInShapeFormats)[number][0]
|
||||
@@ -10,6 +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 { 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';
|
||||
@@ -79,6 +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 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' }) { }
|
||||
@@ -95,7 +97,8 @@ export namespace PluginStateObject {
|
||||
{ kind: 'dsn6', data: Dsn6File } |
|
||||
{ kind: 'dx', data: DxFile } |
|
||||
{ kind: 'ply', data: PlyFile } |
|
||||
// For non-build in extensions
|
||||
{ kind: 'kin', data: KinFile } |
|
||||
// For non-built-in extensions
|
||||
{ kind: 'custom', data: unknown, tag: string }
|
||||
)
|
||||
export type BlobData = BlobEntry[]
|
||||
|
||||
@@ -10,6 +10,7 @@ import * as CCP4 from '../../mol-io/reader/ccp4/parser';
|
||||
import { CIF } from '../../mol-io/reader/cif';
|
||||
import * as DSN6 from '../../mol-io/reader/dsn6/parser';
|
||||
import * as PLY from '../../mol-io/reader/ply/parser';
|
||||
import * as KIN from '../../mol-io/reader/kin/parser';
|
||||
import { parsePsf } from '../../mol-io/reader/psf/parser';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateObject, StateTransformer } from '../../mol-state';
|
||||
@@ -41,6 +42,7 @@ export { ParsePsf };
|
||||
export { ParsePrmtop };
|
||||
export { ParseTop };
|
||||
export { ParsePly };
|
||||
export { ParseKin }
|
||||
export { ParseCcp4 };
|
||||
export { ParseDsn6 };
|
||||
export { ParseDx };
|
||||
@@ -403,6 +405,22 @@ const ParsePly = PluginStateTransform.BuiltIn({
|
||||
}
|
||||
});
|
||||
|
||||
type ParseKin = typeof ParseKin
|
||||
const ParseKin = PluginStateTransform.BuiltIn({
|
||||
name: 'parse-kin',
|
||||
display: { name: 'Parse KIN', description: 'Parse KIN from String data' },
|
||||
from: [SO.Data.String],
|
||||
to: SO.Format.Kin
|
||||
})({
|
||||
apply({ a }) {
|
||||
return Task.create('Parse KIN', async ctx => {
|
||||
const parsed = await KIN.parseKin(a.data).runInContext(ctx);
|
||||
if (parsed.isError) throw new Error(parsed.message);
|
||||
return new SO.Format.Kin(parsed.result, { label: parsed.result.comments[0] || 'KIN Data' });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
type ParseCcp4 = typeof ParseCcp4
|
||||
const ParseCcp4 = PluginStateTransform.BuiltIn({
|
||||
name: 'parse-ccp4',
|
||||
|
||||
@@ -12,6 +12,7 @@ import { parseGRO } from '../../mol-io/reader/gro/parser';
|
||||
import { parsePDB } from '../../mol-io/reader/pdb/parser';
|
||||
import { Mat4, Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { shapeFromPly } from '../../mol-model-formats/shape/ply';
|
||||
import { shapeFromKin } from '../../mol-model-formats/shape/kin';
|
||||
import { coordinatesFromDcd } from '../../mol-model-formats/structure/dcd';
|
||||
import { trajectoryFromGRO } from '../../mol-model-formats/structure/gro';
|
||||
import { trajectoryFromCCD, trajectoryFromMmCIF } from '../../mol-model-formats/structure/mmcif';
|
||||
@@ -94,6 +95,7 @@ export { StructureComponent };
|
||||
export { CustomModelProperties };
|
||||
export { CustomStructureProperties };
|
||||
export { ShapeFromPly };
|
||||
export { ShapeFromKin };
|
||||
|
||||
type CoordinatesFromDcd = typeof CoordinatesFromDcd
|
||||
const CoordinatesFromDcd = PluginStateTransform.BuiltIn({
|
||||
@@ -1317,3 +1319,25 @@ const ShapeFromPly = PluginStateTransform.BuiltIn({
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
type ShapeFromKin = typeof ShapeFromKin
|
||||
const ShapeFromKin = PluginStateTransform.BuiltIn({
|
||||
name: 'shape-from-kin',
|
||||
display: { name: 'Shape from KIN', description: 'Create Shape from KIN data' },
|
||||
from: SO.Format.Kin,
|
||||
to: SO.Shape.Provider,
|
||||
params(a) {
|
||||
return {
|
||||
transforms: PD.Optional(PD.Value<Mat4[]>([], { isHidden: true })),
|
||||
label: PD.Optional(PD.Text('', { isHidden: true }))
|
||||
};
|
||||
}
|
||||
})({
|
||||
apply({ a, params }) {
|
||||
return Task.create('Create shape from KIN', async ctx => {
|
||||
const shape = await shapeFromKin(a.data, params).runInContext(ctx);
|
||||
const props = { label: params.label || 'Shape' };
|
||||
return new SO.Shape.Provider(shape, props);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user