mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 13:30:24 +08:00
Adding GUI elements to select Views when they are present in the Kinemage file.
This commit is contained in:
@@ -9,6 +9,7 @@ It currently supports the following features:
|
||||
- Display of @ball, @sphere, @vector, @dot, @ribbon, and @triangle lists
|
||||
- Coloring of objects by vertex color, or by a single color for the entire list
|
||||
- Hovering over objects to see their labels (if present)
|
||||
- When there are views defined, JSON Data entries are added to the State Tree; when selected, they shift the view
|
||||
- State Tree names are based on the @pdbfile or @caption in the Kinemage file if there is one
|
||||
|
||||
Currently unsupported features include:
|
||||
@@ -22,4 +23,6 @@ Currently unsupported features include:
|
||||
Current limitations include:
|
||||
- Lines and triangles are a single color, not colored by vertex (Mol* does not support per-vertex coloring for these primitives)
|
||||
- Line segments in Mol* do not support end-caps for wide lines, so there are artifacts in highly-curved lines
|
||||
- The default perspective view and white background for Mol* differs from that of Kinemage
|
||||
- The default perspective view and white background for Mol* differs from that of Kinemage (though selecting a view from the
|
||||
State Tree will switch it to orthographic)
|
||||
- The name of the view is buried down inside of the JSON Data object and you must click the Update pull-down to see it
|
||||
@@ -21,11 +21,39 @@ import { shapePointsFromKin, shapeLinesFromKin, shapeMeshFromKin, shapeSpheresFr
|
||||
import { Kinemage } from '../../mol-io/reader/kin/schema';
|
||||
import { DataFormatProvider } from '../../mol-plugin-state/formats/provider';
|
||||
import { Camera } from '../../mol-canvas3d/camera';
|
||||
import { PluginCommands } from '../../mol-plugin/commands';
|
||||
import { StateObjectRef } from '../../mol-state';
|
||||
import { getPluginBoundingSphere } from '../../mol-plugin-state/manager/focus-camera/focus-object';
|
||||
|
||||
const Tag = KinemageData.Tag;
|
||||
|
||||
const Transform = StateTransformer.builderFactory('sb-kinemage');
|
||||
|
||||
/**
|
||||
* Apply a saved snapshot object (from a view state node) to the plugin camera.
|
||||
* Use PluginCommands.Camera.SetSnapshot so transitions and canvas props are handled properly.
|
||||
*/
|
||||
/// @todo Hook this in
|
||||
export async function applyViewSnapshot(plugin: PluginContext, snapshot: Partial<Camera.Snapshot>) {
|
||||
if (!snapshot) return;
|
||||
// If the snapshot provides a target, adjust the canvas `sceneRadiusFactor` so the scene isn't clipped
|
||||
// when we switch camera.
|
||||
if (snapshot.target) {
|
||||
try {
|
||||
const boundingSphere = getPluginBoundingSphere(plugin);
|
||||
if (boundingSphere && boundingSphere.radius > 0) {
|
||||
const offset = Vec3.distance(snapshot.target as Vec3, boundingSphere.center);
|
||||
const sceneRadiusFactor = (boundingSphere.radius + offset) / boundingSphere.radius;
|
||||
plugin.canvas3d?.setProps({ sceneRadiusFactor });
|
||||
}
|
||||
} catch (e) {
|
||||
// fallback: ignore errors and continue to set the camera snapshot
|
||||
console.warn('Failed to adjust sceneRadiusFactor for view snapshot', e);
|
||||
}
|
||||
}
|
||||
await PluginCommands.Camera.SetSnapshot(plugin, { snapshot });
|
||||
}
|
||||
|
||||
export const KinemageShapePointsProvider = Transform({
|
||||
name: 'sb-kinemage-shape-points-provider',
|
||||
display: { name: 'Kinemage Shape Points Provider' },
|
||||
@@ -110,6 +138,26 @@ export const KinemageShapeSpheresProvider = Transform({
|
||||
}
|
||||
});
|
||||
|
||||
export const KinemageViewProvider = Transform({
|
||||
name: 'sb-kinemage-view-provider',
|
||||
display: { name: 'Kinemage View Provider' },
|
||||
from: PluginStateObject.Root,
|
||||
to: PluginStateObject.Format.Json, // store view metadata as JSON data node
|
||||
params: {
|
||||
name: PD.Text(''),
|
||||
snapshot: PD.Value<Partial<Camera.Snapshot>>(undefined as any, { isHidden: true })
|
||||
}
|
||||
})({
|
||||
apply({ params }) {
|
||||
return Task.create('Kinemage View Provider', async ctx => {
|
||||
// PluginStateObject.Format.Json holds arbitrary JSON-like data; create instance with the payload
|
||||
return new PluginStateObject.Format.Json({
|
||||
name: params.name || 'Kinemage View',
|
||||
snapshot: params.snapshot
|
||||
} as any);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const KinemageExtension = PluginBehavior.create<{ autoAttach: boolean }>({
|
||||
name: 'kinemage-data-prop',
|
||||
@@ -120,6 +168,8 @@ export const KinemageExtension = PluginBehavior.create<{ autoAttach: boolean }>(
|
||||
},
|
||||
ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean }> {
|
||||
private provider = KinemageDataProvider;
|
||||
private applyViewAction?: any;
|
||||
private sub?: any;
|
||||
|
||||
register(): void {
|
||||
DefaultQueryRuntimeTable.addCustomProp(this.provider.descriptor);
|
||||
@@ -128,8 +178,39 @@ export const KinemageExtension = PluginBehavior.create<{ autoAttach: boolean }>(
|
||||
|
||||
this.ctx.managers.dragAndDrop.addHandler(KinemageDragAndDropHandler.name, KinemageDragAndDropHandler.handle);
|
||||
|
||||
// Register .kin file handler so opening/dropping .kin is supported via the data formats system
|
||||
this.ctx.dataFormats.add('KIN', KINFormatProvider);
|
||||
// Register .kin file handler so opening/dropping .kin is supported via the data formats system
|
||||
this.ctx.dataFormats.add('KIN', KINFormatProvider);
|
||||
|
||||
const applyViewAction = {
|
||||
// keep fields minimal and compatible with the actions system
|
||||
// adjust names if your repo expects different keys (use MVS LoadMvsData as example)
|
||||
label: 'Apply View',
|
||||
isApplicable: (a: any) => {
|
||||
const d = a?.data;
|
||||
return !!(d && d.data && (d.data as any).snapshot);
|
||||
},
|
||||
apply: async (a: any) => {
|
||||
const node = a?.data;
|
||||
const snapshot = node?.data?.snapshot as Partial<Camera.Snapshot> | undefined;
|
||||
if (snapshot) await applyViewSnapshot(this.ctx, snapshot);
|
||||
}
|
||||
};
|
||||
|
||||
// When this object is selected in the GUI, update the camera to its snapshot.
|
||||
this.sub = this.ctx.state.data.behaviors.currentObject.subscribe((e: any) => {
|
||||
const ref = e.ref;
|
||||
// state.select returns an array of cells; the first is the matching cell
|
||||
const cell = this.ctx.state.data.select(ref)[0];
|
||||
const obj = cell?.obj;
|
||||
const nodeData = obj?.data;
|
||||
if (nodeData && nodeData.snapshot) {
|
||||
applyViewSnapshot(this.ctx, nodeData.snapshot);
|
||||
}
|
||||
});
|
||||
|
||||
// store and register the exact object reference
|
||||
this.applyViewAction = applyViewAction;
|
||||
this.ctx.state.data.actions.add(this.applyViewAction);
|
||||
}
|
||||
|
||||
update(p: { autoAttach: boolean }) {
|
||||
@@ -150,6 +231,18 @@ export const KinemageExtension = PluginBehavior.create<{ autoAttach: boolean }>(
|
||||
|
||||
// Unregister the .kin data format provider
|
||||
this.ctx.dataFormats.remove('KIN');
|
||||
|
||||
// Remove the state action if we registered one
|
||||
if (this.applyViewAction) {
|
||||
this.ctx.state.data.actions.remove(this.applyViewAction);
|
||||
this.applyViewAction = undefined;
|
||||
}
|
||||
|
||||
// Remove the action subscription if we created one
|
||||
if (this.sub) {
|
||||
this.sub.unsubscribe();
|
||||
this.sub = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
params: () => ({
|
||||
@@ -192,8 +285,9 @@ async function applyKinemageInfoToState(plugin: PluginContext, kinInfo: Kinemage
|
||||
.apply(StateTransforms.Representation.ShapeRepresentation3D);
|
||||
}
|
||||
// Iterate over all entries in the view dictionary.
|
||||
for (const [_, viewObj] of Object.entries(kinData.viewDict)) {
|
||||
console.log('XXX view', viewObj);
|
||||
const createdViewRefs: StateObjectRef<PluginStateObject.Format.Json>[] = [];
|
||||
for (const [viewKey, viewObj] of Object.entries(kinData.viewDict)) {
|
||||
const viewName = viewObj.name || `View ${viewKey}`;
|
||||
|
||||
// If center is specified, then we will use that as the camera target. Otherwise, we will use the origin.
|
||||
const center = Vec3.create(0, 0, 0);
|
||||
@@ -216,11 +310,11 @@ async function applyKinemageInfoToState(plugin: PluginContext, kinInfo: Kinemage
|
||||
const yAxis = Vec3.create(0, 1, 0);
|
||||
Vec3.transformMat3(yAxis, yAxis, orientation);
|
||||
|
||||
// If span is specified, then we go half that distance along Z to find the camera position (90 degree FOV).
|
||||
// If span is specified, then we go that distance along Z to find the camera position (90 degree FOV).
|
||||
// Otherwise, we go a default distance of 100 along Z.
|
||||
let distance = 100;
|
||||
if (viewObj.span) {
|
||||
distance = viewObj.span / 2;
|
||||
distance = viewObj.span;
|
||||
} else if (viewObj.zoom) {
|
||||
/// @todo If zoom is specified, then we need to do more computations based on the bounds on the geometry.
|
||||
}
|
||||
@@ -229,12 +323,18 @@ async function applyKinemageInfoToState(plugin: PluginContext, kinInfo: Kinemage
|
||||
Vec3.add(position, center, zAxis);
|
||||
|
||||
// If the zslab is specified, then we set the radius to it; otherwise, we use a default of 100.
|
||||
const radius = viewObj.zslab || 100;
|
||||
// When the zslab value is 200, it should match the same as the span (half is percent of half span).
|
||||
let radius = 100;
|
||||
if (viewObj.zslab) {
|
||||
const scale = viewObj.zslab / 200;
|
||||
// Scale by 0.5 here to match the behavior of KiNG
|
||||
radius = 0.5 * distance * scale;
|
||||
}
|
||||
|
||||
// Fill in the camera shapshot
|
||||
let snap: Camera.Snapshot;
|
||||
snap = {
|
||||
mode: 'perspective', ///< @todo Make this orthographic by default, and set reasonable parameters
|
||||
mode: 'orthographic', ///< Make this orthographic by default, to match Kinemage defaults
|
||||
fov: Math.PI / 4, ///< 90-degree view by default for Molstar
|
||||
|
||||
position: position,
|
||||
@@ -248,8 +348,16 @@ async function applyKinemageInfoToState(plugin: PluginContext, kinInfo: Kinemage
|
||||
minNear: 1,
|
||||
minFar: 1
|
||||
};
|
||||
console.log('XXX snap', snap);
|
||||
|
||||
// Create a state object for the view (visible in State Tree)
|
||||
const viewNode = update
|
||||
.toRoot()
|
||||
.apply(KinemageViewProvider, { name: viewName, snapshot: snap });
|
||||
|
||||
// Store the selector for UI wiring
|
||||
createdViewRefs.push(viewNode.selector as StateObjectRef<PluginStateObject.Format.Json>);
|
||||
}
|
||||
|
||||
}
|
||||
update.commit();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user