mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 13:30:24 +08:00
Modifying the kinemage parser to be able to read more than one kinemage entry from the same file. Hooking in a drag and drop handler on .kin files that for now just stores things into a global variable. Adjusting the parsing of kinemage through the original plugin path to handle the new parsing.
This commit is contained in:
@@ -16,6 +16,7 @@ import { MoleculeType } from '../../mol-model/structure/model/types';
|
||||
import { AccessibleSurfaceArea } from '../../mol-model-props/computed/accessible-surface-area/shrake-rupley';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { KinemageData } from './prop';
|
||||
import { Kinemage } from '../../mol-io/reader/kin/schema';
|
||||
|
||||
const LARGE_CA_THRESHOLD = 5000;
|
||||
const DEFAULT_UPDATE_INTERVAL = 10;
|
||||
@@ -194,12 +195,15 @@ export async function calculate(runtime: RuntimeContext, structure: Structure, p
|
||||
v3scale(center, center, 0.5);
|
||||
const extent = adjustExtent(ctx, membrane, center);
|
||||
|
||||
const kinemages: Kinemage[] = [];
|
||||
const activeKinemage = -1
|
||||
return {
|
||||
planePoint1: membrane.planePoint1,
|
||||
planePoint2: membrane.planePoint2,
|
||||
normalVector,
|
||||
centroid: center,
|
||||
radius: extent
|
||||
radius: extent,
|
||||
kinemages, activeKinemage
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { KinemageDataProvider, KinemageData } from './prop';
|
||||
import { StateObjectRef, StateTransformer, StateTransform } from '../../mol-state';
|
||||
import { Task } from '../../mol-task';
|
||||
import { PluginBehavior } from '../../mol-plugin/behavior';
|
||||
import { PluginDragAndDropHandler } from '../../mol-plugin-state/manager/drag-and-drop';
|
||||
import { KinemageDataRepresentationProvider, KinemageDataParams, KinemageDataRepresentation } from './representation';
|
||||
import { HydrophobicityColorThemeProvider } from '../../mol-theme/color/hydrophobicity';
|
||||
import { PluginStateObject, PluginStateTransform } from '../../mol-plugin-state/objects';
|
||||
@@ -20,9 +21,18 @@ import { DefaultQueryRuntimeTable } from '../../mol-script/runtime/query/compile
|
||||
import { StructureSelectionQuery, StructureSelectionCategory } from '../../mol-plugin-state/helpers/structure-selection-query';
|
||||
import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
|
||||
import { GenericRepresentationRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
|
||||
import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
|
||||
const Tag = KinemageData.Tag;
|
||||
|
||||
/// @todo Remove once we store into the proper location in the drag and drop handler
|
||||
/** Global KinemageInfo that is used to display */
|
||||
let g_kinemageInfo: KinemageData = {
|
||||
kinemages: [], activeKinemage: -1
|
||||
/// @todo Remove these when we no longer need them.
|
||||
, planePoint1: Vec3(), planePoint2: Vec3(), normalVector: Vec3(), radius: 0, centroid: Vec3()
|
||||
};
|
||||
|
||||
export const KinemageExtension = PluginBehavior.create<{ autoAttach: boolean }>({
|
||||
name: 'kinemage-data-prop',
|
||||
category: 'custom-props',
|
||||
@@ -50,6 +60,9 @@ export const KinemageExtension = PluginBehavior.create<{ autoAttach: boolean }>(
|
||||
return [refs, 'Membrane Orientation'];
|
||||
});
|
||||
this.ctx.builders.structure.representation.registerPreset(KinemageDataPreset);
|
||||
|
||||
console.log('XXX KinemageExtension register - adding drag and drop handler for ', KINDragAndDropHandler.name);
|
||||
this.ctx.managers.dragAndDrop.addHandler(KINDragAndDropHandler.name, KINDragAndDropHandler.handle);
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean }) {
|
||||
@@ -69,6 +82,8 @@ export const KinemageExtension = PluginBehavior.create<{ autoAttach: boolean }>(
|
||||
|
||||
this.ctx.genericRepresentationControls.delete(Tag.Representation);
|
||||
this.ctx.builders.structure.representation.unregisterPreset(KinemageDataPreset);
|
||||
|
||||
this.ctx.managers.dragAndDrop.removeHandler(KINDragAndDropHandler.name);
|
||||
}
|
||||
},
|
||||
params: () => ({
|
||||
@@ -171,6 +186,43 @@ export const KinemageDataPreset = StructureRepresentationPresetProvider({
|
||||
export function tryCreateKinemageData(plugin: PluginContext, structure: StateObjectRef<PluginStateObject.Molecule.Structure>, params?: StateTransformer.Params<KinemageData3D>, initialState?: Partial<StateTransform.State>) {
|
||||
const state = plugin.state.data;
|
||||
const KinemageData = state.build().to(structure)
|
||||
.applyOrUpdateTagged('membrane-orientation-3d', KinemageData3D, params, { state: initialState });
|
||||
.applyOrUpdateTagged('kinemage-3d', KinemageData3D, params, { state: initialState });
|
||||
return KinemageData.commit({ revertOnError: true });
|
||||
}
|
||||
|
||||
/** Registerable method for handling dragged-and-dropped files */
|
||||
interface DragAndDropHandler {
|
||||
name: string,
|
||||
handle: PluginDragAndDropHandler,
|
||||
}
|
||||
|
||||
/** DragAndDropHandler handler for `.kin` files */
|
||||
const KINDragAndDropHandler: DragAndDropHandler = {
|
||||
name: 'kin',
|
||||
/** Load .kin files. Append to previous plugin state.
|
||||
* If multiple files are provided, append them all.
|
||||
* Select the last-loaded one from the list.
|
||||
* Return `true` if at least one file has been loaded. */
|
||||
async handle(files: File[], plugin: PluginContext): Promise<boolean> {
|
||||
let applied = false;
|
||||
for (const file of files) {
|
||||
if (file.name.toLowerCase().endsWith('.kin')) {
|
||||
const task = Task.create('Load KIN file', async ctx => {
|
||||
console.log('XXX loading KIN file ', file.name); /// @todo Remove when done debugging
|
||||
const kinInfo = await KinemageData.open(file);
|
||||
for (const kinData of kinInfo.kinemages) {
|
||||
g_kinemageInfo.kinemages.push(kinData);
|
||||
g_kinemageInfo.activeKinemage = g_kinemageInfo.kinemages.length - 1;
|
||||
}
|
||||
console.log('XXX accumulated Kinemages size ', g_kinemageInfo.kinemages.length, ', active is ', g_kinemageInfo.activeKinemage);
|
||||
/// @todo See what loadMVS() ... LoadMolstarTree() ... MolstarLoadingActions().primitives() ... applyPrimitiveVisuals() does
|
||||
});
|
||||
console.log('XXX plugin.runTask');
|
||||
await plugin.runTask(task);
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
console.log('XXX KINDragAndDropHandler applied=', applied);
|
||||
return applied;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,6 +16,11 @@ import { Vec3 } from '../../mol-math/linear-algebra';
|
||||
import { QuerySymbolRuntime } from '../../mol-script/runtime/query/base';
|
||||
import { CustomPropSymbol } from '../../mol-script/language/symbol';
|
||||
import { Type } from '../../mol-script/language/type';
|
||||
import { Task } from '../../mol-task';
|
||||
|
||||
import { Kinemage } from '../../mol-io/reader/kin/schema';
|
||||
import { parseKin } from '../../mol-io/reader/kin/parser';
|
||||
|
||||
|
||||
export const KinemageDataParams = {
|
||||
...KinemageParams
|
||||
@@ -26,6 +31,7 @@ export type KinemageDataProps = PD.Values<KinemageDataParams>
|
||||
export { KinemageData };
|
||||
|
||||
interface KinemageData {
|
||||
/// @todo Remove these
|
||||
// point in membrane boundary
|
||||
readonly planePoint1: Vec3,
|
||||
// point in opposite side of membrane boundary
|
||||
@@ -34,17 +40,32 @@ interface KinemageData {
|
||||
readonly normalVector: Vec3,
|
||||
// the radius of the membrane layer
|
||||
readonly radius: number,
|
||||
readonly centroid: Vec3
|
||||
readonly centroid: Vec3,
|
||||
|
||||
/**
|
||||
* List of Kinemages read from one or more files.
|
||||
*/
|
||||
readonly kinemages: Kinemage[],
|
||||
|
||||
/**
|
||||
* Index of the active KinemageData
|
||||
*/
|
||||
activeKinemage: number
|
||||
}
|
||||
|
||||
const FileSourceParams = {
|
||||
input: PD.File({ accept: '.kin', multiple: false })
|
||||
};
|
||||
type FileSourceProps = PD.Values<typeof FileSourceParams>
|
||||
|
||||
namespace KinemageData {
|
||||
export enum Tag {
|
||||
Representation = 'membrane-orientation-3d'
|
||||
Representation = 'kinemage-3d'
|
||||
}
|
||||
|
||||
const pos = Vec3();
|
||||
export const symbols = {
|
||||
isTransmembrane: QuerySymbolRuntime.Dynamic(CustomPropSymbol('computed', 'membrane-orientation.is-transmembrane', Type.Bool),
|
||||
isTransmembrane: QuerySymbolRuntime.Dynamic(CustomPropSymbol('computed', 'kinemage.is-kinemage', Type.Bool),
|
||||
ctx => {
|
||||
const { unit, structure } = ctx.element;
|
||||
const { x, y, z } = StructureProperties.atom;
|
||||
@@ -56,6 +77,44 @@ namespace KinemageData {
|
||||
return isInMembranePlane(pos, normalVector, planePoint1, planePoint2);
|
||||
})
|
||||
};
|
||||
|
||||
async function loadKinemageData(data: string): Promise<Kinemage[]> {
|
||||
const task = parseKin(data);
|
||||
const result = await task.run();
|
||||
if (result.isError) {
|
||||
throw new Error('Failed to parse KIN data');
|
||||
}
|
||||
return result.result;
|
||||
}
|
||||
|
||||
export async function open(file: FileSourceProps | File): Promise<KinemageData> {
|
||||
|
||||
let fileToRead: File;
|
||||
|
||||
if (file instanceof File) {
|
||||
fileToRead = file;
|
||||
} else if (file && file.input && file.input.file) {
|
||||
fileToRead = file.input.file;
|
||||
} else {
|
||||
throw new Error('No file given');
|
||||
}
|
||||
|
||||
const task = Task.create('Load KIN file', async ctx => {
|
||||
const data = await fileToRead.text();
|
||||
const kinemages = await loadKinemageData(data);
|
||||
return kinemages;
|
||||
});
|
||||
|
||||
const kinemages = await task.run();
|
||||
const activeKinemage = kinemages.length - 1;
|
||||
/// @todo Remove these once we no longer need them
|
||||
const planePoint1 = Vec3.create(0, 0, 0);
|
||||
const planePoint2 = Vec3.create(0, 0, 0);
|
||||
const normalVector = Vec3.create(0, 0, 1);
|
||||
const radius = 0;
|
||||
const centroid = Vec3.create(0, 0, 0);
|
||||
return { kinemages, activeKinemage, planePoint1, planePoint2, normalVector, radius, centroid };
|
||||
}
|
||||
}
|
||||
|
||||
export const KinemageDataProvider: CustomStructureProperty.Provider<KinemageDataParams, KinemageData> = CustomStructureProperty.createProvider({
|
||||
|
||||
@@ -74,9 +74,9 @@ export function KinemageDataRepresentation(ctx: RepresentationContext, getParams
|
||||
}
|
||||
|
||||
export const KinemageDataRepresentationProvider = StructureRepresentationProvider({
|
||||
name: 'membrane-orientation',
|
||||
label: 'Membrane Orientation',
|
||||
description: 'Displays a grid of points representing membrane layers.',
|
||||
name: 'kinemage',
|
||||
label: 'Kinemage',
|
||||
description: 'Displays data from an Kinemage.',
|
||||
factory: KinemageDataRepresentation,
|
||||
getParams: getKinemageDataParams,
|
||||
defaultValues: PD.getDefaultValues(KinemageDataParams),
|
||||
|
||||
@@ -45,7 +45,8 @@ describe('kin reader', () => {
|
||||
it('basic', async () => {
|
||||
const parsed = await parseKin(kinString).run();
|
||||
if (parsed.isError) return;
|
||||
const kinemage = parsed.result;
|
||||
if (parsed.result.length != 1) return;
|
||||
const kinemage = parsed.result[0];
|
||||
|
||||
const vectors = kinemage.vectorLists;
|
||||
expect(vectors.length).toEqual(1);
|
||||
|
||||
@@ -9,15 +9,33 @@ import { Task, RuntimeContext } from '../../../mol-task';
|
||||
import { Kinemage } from './schema';
|
||||
import KinParser from './ngl-based-parser';
|
||||
|
||||
async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<Kinemage>> {
|
||||
async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<Kinemage[]>> {
|
||||
const kinemages: Kinemage[] = [];
|
||||
// Split the data into sections based on the '@kinemage' keyword, which indicates one or more kinemages in the file.
|
||||
// Handle the case where there is no '@kinemage' keyword by parsing the entire file.
|
||||
const kinemageSections = data.split(/@kinemage\s+\d+/); // Split based on '@kinemage' keyword followed by a number
|
||||
|
||||
const NGLParser = new KinParser(data);
|
||||
const kinData = NGLParser.kinemage;
|
||||
return Result.success(kinData);
|
||||
// If there are one or more @kinemage sections, ignore the portion before the first one.
|
||||
// This will either be an empty string (if the first section starts at the beginning of the file)
|
||||
// or header data that is not part of a particular kinemage. This has the effect of removing
|
||||
// the header data even in the case where there is a single @kinemage keyword.
|
||||
if (kinemageSections.length > 1) {
|
||||
kinemageSections.shift();
|
||||
}
|
||||
|
||||
for (const section of kinemageSections) {
|
||||
if (section.trim()) { // Ignore empty sections
|
||||
const NGLParser = new KinParser(section.trim());
|
||||
const kinData = NGLParser.kinemage;
|
||||
kinemages.push(kinData);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success(kinemages);
|
||||
}
|
||||
|
||||
export function parseKin(data: string) {
|
||||
return Task.create<Result<Kinemage>>('Parse KIN', async ctx => {
|
||||
return Task.create<Result<Kinemage[]>>('Parse KIN', async ctx => {
|
||||
return await parseInternal(data, ctx);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ export const KinProvider = DataFormatProvider({
|
||||
category: ShapeFormatCategory,
|
||||
stringExtensions: ['kin'],
|
||||
parse: async (plugin, data) => {
|
||||
// This returns the last kinemage in the file if there are multiple
|
||||
const format = plugin.state.data.build()
|
||||
.to(data)
|
||||
.apply(StateTransforms.Data.ParseKin, {}, { state: { isGhost: true } });
|
||||
@@ -63,7 +64,7 @@ export const KinProvider = DataFormatProvider({
|
||||
|
||||
export const BuiltInShapeFormats = [
|
||||
['ply', PlyProvider] as const,
|
||||
['kin', KinProvider] as const,
|
||||
/// @todo Replaced the plugin loader with an extension ['kin', KinProvider] as const,
|
||||
] as const;
|
||||
|
||||
export type BuiltInShapeFormat = (typeof BuiltInShapeFormats)[number][0]
|
||||
@@ -11,6 +11,7 @@ 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 { Kinemage } from '../../mol-io/reader/kin/schema';
|
||||
import { parsePsf } from '../../mol-io/reader/psf/parser';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { StateObject, StateTransformer } from '../../mol-state';
|
||||
@@ -414,9 +415,13 @@ const ParseKin = PluginStateTransform.BuiltIn({
|
||||
})({
|
||||
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' });
|
||||
const parsedList = await KIN.parseKin(a.data).runInContext(ctx);
|
||||
if (parsedList.isError) throw new Error(parsedList.message);
|
||||
// Read the last entry, handling the case where it is an error
|
||||
const kinemages: Kinemage[] = parsedList.result;
|
||||
const parsed = kinemages.length ? parsedList.result[kinemages.length - 1] : undefined;
|
||||
if (!parsed) return StateObject.Null;
|
||||
return new SO.Format.Kin(parsed, { label: parsed.comments[0] || 'KIN Data' });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user