Kinemage drag-and-drop handler now shows both lines and ribbons

This commit is contained in:
Russ Taylor
2026-02-17 13:15:50 -05:00
parent 4e331001ef
commit b2ce1fc6fa
2 changed files with 152 additions and 40 deletions

View File

@@ -23,7 +23,7 @@ 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';
import { StateTransforms } from '../../mol-plugin-state/transforms';
import { shapeLinesFromKin } from '../../mol-model-formats/shape/kin';
import { shapeLinesFromKin, shapeMeshFromKin } from '../../mol-model-formats/shape/kin';
import { Kinemage } from '../../mol-io/reader/kin/schema';
const Tag = KinemageData.Tag;
@@ -38,15 +38,15 @@ let g_kinemageInfo: KinemageData = {
const Transform = StateTransformer.builderFactory('sb-kinemage');
export const KinemageShapeProvider = Transform({
name: 'sb-kinemage-shape-provider',
display: { name: 'Kinemage Shape Provider' },
export const KinemageShapeLinesProvider = Transform({
name: 'sb-kinemage-shape-lines-provider',
display: { name: 'Kinemage Shape Lines Provider' },
from: PluginStateObject.Root,
to: PluginStateObject.Shape.Provider,
params: {
data: PD.Value<Kinemage>(undefined as any, { isHidden: true })
}
})({
})({
apply({ params }) {
return Task.create('Kinemage Lines Shape Provider', async ctx => {
// shapeFromKin returns a Task that resolves to a ShapeProvider-like object
@@ -59,6 +59,27 @@ export const KinemageShapeProvider = Transform({
}
});
export const KinemageShapeMeshProvider = Transform({
name: 'sb-kinemage-shape-mesh-provider',
display: { name: 'Kinemage Shape Mesh Provider' },
from: PluginStateObject.Root,
to: PluginStateObject.Shape.Provider,
params: {
data: PD.Value<Kinemage>(undefined as any, { isHidden: true })
}
})({
apply({ params }) {
return Task.create('Kinemage Mesh Shape Provider', async ctx => {
// shapeFromKin returns a Task that resolves to a ShapeProvider-like object
const provider = await shapeMeshFromKin(params.data).runInContext(ctx);
return new PluginStateObject.Shape.Provider(provider as any, {
label: params.data.captions?.[0] || 'Kinemage',
description: params.data.text || ''
});
});
}
});
export const KinemageExtension = PluginBehavior.create<{ autoAttach: boolean }>({
name: 'kinemage-data-prop',
category: 'custom-props',
@@ -235,17 +256,28 @@ const KinemageDragAndDropHandler: DragAndDropHandler = {
const task = Task.create('Load KIN file', async ctx => {
const kinInfo = await KinemageData.open(file);
// Create a state entry for each kinemage using the KinemageShapeProvider transform and add its geometry.
const update = plugin.state.data.build();
// Create a state lines entry for each kinemage using the KinemageShapeLinesProvider transform and add its geometry.
const updateLines = plugin.state.data.build();
for (const kinData of kinInfo.kinemages) {
await update
await updateLines
.toRoot()
.apply(KinemageShapeProvider, { data: kinData })
.apply(KinemageShapeLinesProvider, { data: kinData })
.apply(StateTransforms.Representation.ShapeRepresentation3D)
.commit();
applied = true;
}
// Create a state mesh entry for each kinemage using the KinemageShapeMeshProvider transform and add its geometry.
const updateMesh = plugin.state.data.build();
for (const kinData of kinInfo.kinemages) {
await updateMesh
.toRoot()
.apply(KinemageShapeMeshProvider, { data: kinData })
.apply(StateTransforms.Representation.ShapeRepresentation3D, { doubleSided: true })
.commit();
applied = true;
}
// keep legacy global info as well (optional)
/// @todo Remove this once we no longer need it.
for (const kinData of kinInfo.kinemages) {

View File

@@ -7,10 +7,12 @@
import { RuntimeContext, Task } from '../../mol-task';
import { ShapeProvider } from '../../mol-model/shape/provider';
import { Color } from '../../mol-util/color';
import { Kinemage, VectorList } from '../../mol-io/reader/kin/schema';
import { Kinemage, VectorList, RibbonObject } from '../../mol-io/reader/kin/schema';
import { Lines } from '../../mol-geo/geometry/lines/lines';
import { LinesBuilder } from '../../mol-geo/geometry/lines/lines-builder';
//import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { MeshBuilder } from '../../mol-geo/geometry/mesh/mesh-builder';
import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
import { Vec3 } from '../../mol-math/linear-algebra';
import { Shape } from '../../mol-model/shape';
import { ParamDefinition as PD } from '../../mol-util/param-definition';
//import { ValueCell } from '../../mol-util/value-cell';
@@ -29,9 +31,17 @@ function createKinShapeLinesParams(kinemage?: Kinemage) {
...Lines.Params,
};
}
export const KinShapeLinesParams = createKinShapeLinesParams();
export type KinShapeLinesParams = typeof KinShapeLinesParams
function createKinShapeMeshParams(kinemage?: Kinemage) {
return {
...Mesh.Params,
};
}
export const KinShapeMeshParams = createKinShapeMeshParams();
export type KinShapeMeshParams = typeof KinShapeMeshParams
async function getLines(ctx: RuntimeContext, vectorLists: VectorList[]) {
const builderState = LinesBuilder.create();
@@ -59,39 +69,70 @@ async function getLines(ctx: RuntimeContext, vectorLists: VectorList[]) {
return builderState.getLines();
}
async function getMesh(ctx: RuntimeContext, ribbonObjects: RibbonObject[]) {
const builderState = MeshBuilder.createState();
for (let ri = 0; ri < ribbonObjects.length; ri++) {
const ribbonObject = ribbonObjects[ri];
builderState.currentGroup = ri; /// @todo Base this on something in the file instead?
for (let i = 0; i < ribbonObject.masterArray.length; i++) {
const coords = ribbonObject.positionArray;
/// @todo Update in chunks of 100000 like the Ply files do rather than all at once like we do here.
// The positionArray contains 3x as many entries as there are vertices since it's a catenation of x, y, z for each vertex.
// The breakArray contains a boolean for each vertex indicating if there is a break there.
// We need to add a triangle for each new verticex after the first two, but we also need to check the break array to see
// if there is a break between any of the vertices in the triangle. If there is a break, we need to start over accumulating
// vertices until we get to three.
// Keep track of the parity and flip over every other triangle so their front faces match.
/// @todo Lighting is to be set up to make each pair of triangles look like a quad with the same normal.
const numVertices = coords.length / 3;
const vertexList: Vec3[] = [];
let flip = true; // Start with true so that the third flip will make it false.
for (let i = 0; i < numVertices; i++) {
// Get the next vertex
const v = Vec3.zero();
v[0] = coords[3 * i + 0];
v[1] = coords[3 * i + 1];
v[2] = coords[3 * i + 2];
vertexList.push(v);
flip = !flip;
// Once we have three vertices, make a triangle out of them and pop the oldest off the list.
if (vertexList.length == 3) {
if (flip) {
MeshBuilder.addTriangle(builderState, vertexList[2], vertexList[1], vertexList[0]);
} else {
MeshBuilder.addTriangle(builderState, vertexList[0], vertexList[1], vertexList[2]);
}
vertexList.shift();
}
// If there is a break, clear the vertex list so we start accumulating vertices for the next triangle from scratch.
if (ribbonObject.breakArray[i]) {
vertexList.length = 0;
flip = true;
}
if (ctx.shouldUpdate && (i % 10000 == 0)) {
await ctx.update({ message: 'adding kin ribbon vertices', current: i, max: numVertices });
}
}
}
}
return MeshBuilder.getMesh(builderState);
}
function makeLineShapeGetter() {
const getShape = async (ctx: RuntimeContext, kinData: KinData, props: PD.Values<KinShapeLinesParams>, shape?: Shape<Lines>) => {
console.log(`XXX Number of vector lists for lines: ${kinData.source.vectorLists.length}, ballLists: ${kinData.source.ballLists.length}, ribbonLists: ${kinData.source.ribbonLists.length}`);
/// @todo
/*
// Create an empty Mesh
const mesh = Mesh.createEmpty();
// 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
);
return emptyShape;
*/
console.log(`XXX Number of vector lists for lines: ${kinData.source.vectorLists.length}`);
// Get our lines, adding them from all of the entries in the vector lists
const _lines = await getLines(ctx, kinData.source.vectorLists);
/*
let _lines: Lines = {};
for (let i = 0; i < kinData.source.vectorLists.length; i++) {
_lines = getLines(ctx, kinData.source.vectorLists[i], [], _lines);
}
*/
let _shape: Shape<Lines>;
_shape = Shape.create<Lines>(
'kin-lines',
@@ -106,6 +147,33 @@ function makeLineShapeGetter() {
return getShape;
}
function makeMeshShapeGetter() {
const getShape = async (ctx: RuntimeContext, kinData: KinData, props: PD.Values<KinShapeMeshParams>, shape?: Shape<Mesh>) => {
console.log(`XXX Number of ribbon lists for mesh: ribbonLists: ${kinData.source.ribbonLists.length}`);
/// @todo
let _mesh = await getMesh(ctx, kinData.source.ribbonLists);
// Ensure that _mesh is not undifined before we pass it to Shape.create. If it is undefined, create an empty mesh instead.
if (!_mesh) {
console.warn('No mesh could be created from the KIN data. Creating an empty mesh instead.');
_mesh = Mesh.createEmpty();
}
let _shape: Shape<Mesh>;
_shape = Shape.create<Mesh>(
'kin-mesh',
kinData.source,
_mesh,
() => Color(0x7F7F7F), // @todo color function
() => 1, // size function
() => '' // @todo label function
);
return _shape;
};
return getShape;
}
export function shapeLinesFromKin(source: Kinemage, params?: { transforms?: Mat4[] }) {
return Task.create<ShapeProvider<KinData, Lines, KinShapeLinesParams>>('Kin Shape Lines Provider', async ctx => {
return {
@@ -116,4 +184,16 @@ export function shapeLinesFromKin(source: Kinemage, params?: { transforms?: Mat4
geometryUtils: Lines.Utils
};
});
}
}
export function shapeMeshFromKin(source: Kinemage, params?: { transforms?: Mat4[] }) {
return Task.create<ShapeProvider<KinData, Mesh, KinShapeMeshParams>>('Kin Shape Mesh Provider', async ctx => {
return {
label: 'Mesh',
data: { source, transforms: params?.transforms },
params: createKinShapeMeshParams(source),
getShape: makeMeshShapeGetter(),
geometryUtils: Mesh.Utils
};
});
}