mirror of
https://github.com/obsidian-desci/Obsidian-Desci.git
synced 2026-07-22 08:50:32 +00:00
view molecule
This commit is contained in:
parent
3c1d4fc996
commit
2033373495
8 changed files with 257 additions and 36 deletions
6
main.tsx
6
main.tsx
|
|
@ -13,6 +13,7 @@ import ExampleClient from './artifacts/ExampleClient.json'
|
|||
import { getMolecule} from './src/plex/getMolecule'
|
||||
import { getProtein} from './src/plex/getProtein'
|
||||
import { runEquibind } from './src/plex/runEquibind'
|
||||
import { viewMolecule } from 'src/plex/viewMolecule';
|
||||
import {
|
||||
ObsidianDesciSettings,
|
||||
ObsidianDesciSettingTab,
|
||||
|
|
@ -82,6 +83,11 @@ export default class ObsidianDesci extends Plugin {
|
|||
name: 'runEquibind - run equibind on a molecule and protein node',
|
||||
callback: runEquibind.bind(this)
|
||||
})
|
||||
this.addCommand({
|
||||
id: 'viewMolecule',
|
||||
name: 'viewMolecule - view a molecule',
|
||||
callback: viewMolecule.bind(this)
|
||||
})
|
||||
this.addSettingTab(new ObsidianDesciSettingTab(this.app, this));
|
||||
|
||||
|
||||
|
|
|
|||
47
package-lock.json
generated
47
package-lock.json
generated
|
|
@ -17,6 +17,7 @@
|
|||
"@ipld/dag-json": "^10.1.4",
|
||||
"@libp2p/delegated-content-routing": "^4.0.9",
|
||||
"@libp2p/delegated-peer-routing": "^4.0.12",
|
||||
"3dmol": "^2.0.6",
|
||||
"blockstore-core": "^4.3.4",
|
||||
"datastore-core": "^9.2.3",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
|
|
@ -3984,6 +3985,21 @@
|
|||
"resolved": "https://registry.npmjs.org/@vascosantos/moving-average/-/moving-average-1.1.0.tgz",
|
||||
"integrity": "sha512-MVEJ4vWAPNbrGLjz7ITnHYg+YXZ6ijAqtH5/cHwSoCpbvuJ98aLXwFfPKAUfZpJMQR5uXB58UJajbY130IRF/w=="
|
||||
},
|
||||
"node_modules/3dmol": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/3dmol/-/3dmol-2.0.6.tgz",
|
||||
"integrity": "sha512-KpfC7mR8fPDVG71Aarc5a7gOkARiW3xE1pJqh4DTmgRQRGT0yJxK1YxWPZOJ6B7IfPA2l5pbXbUo5gbl2TLfqg==",
|
||||
"dependencies": {
|
||||
"iobuffer": "^5.3.1",
|
||||
"netcdfjs": "^3.0.0",
|
||||
"pako": "^2.1.0",
|
||||
"upng-js": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.16.0",
|
||||
"npm": ">=8.11"
|
||||
}
|
||||
},
|
||||
"node_modules/abortable-iterator": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/abortable-iterator/-/abortable-iterator-5.0.1.tgz",
|
||||
|
|
@ -6391,6 +6407,11 @@
|
|||
"fp-ts": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iobuffer": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.3.2.tgz",
|
||||
"integrity": "sha512-kO3CjNfLZ9t+tHxAMd+Xk4v3D/31E91rMs1dHrm7ikEQrlZ8mLDbQ4z3tZfDM48zOkReas2jx8MWSAmN9+c8Fw=="
|
||||
},
|
||||
"node_modules/ip-regex": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-5.0.0.tgz",
|
||||
|
|
@ -8241,6 +8262,14 @@
|
|||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/netcdfjs": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/netcdfjs/-/netcdfjs-3.0.0.tgz",
|
||||
"integrity": "sha512-LOvT8KkC308qtpUkcBPiCMBtii7ZQCN6LxcVheWgyUeZ6DQWcpSRFV9dcVXLj/2eHZ/bre9tV5HTH4Sf93vrFw==",
|
||||
"dependencies": {
|
||||
"iobuffer": "^5.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/netmask": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
|
||||
|
|
@ -9141,6 +9170,11 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
|
|
@ -10231,6 +10265,19 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/upng-js": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/upng-js/-/upng-js-2.1.0.tgz",
|
||||
"integrity": "sha512-d3xzZzpMP64YkjP5pr8gNyvBt7dLk/uGI67EctzDuVp4lCZyVMo0aJO6l/VDlgbInJYDY6cnClLoBp29eKWI6g==",
|
||||
"dependencies": {
|
||||
"pako": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/upng-js/node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||
},
|
||||
"node_modules/uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@
|
|||
"@ipld/dag-json": "^10.1.4",
|
||||
"@libp2p/delegated-content-routing": "^4.0.9",
|
||||
"@libp2p/delegated-peer-routing": "^4.0.12",
|
||||
"3dmol": "^2.0.6",
|
||||
"blockstore-core": "^4.3.4",
|
||||
"datastore-core": "^9.2.3",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { requestUrl, Vault } from 'obsidian'
|
||||
import { CanvasNode } from 'src/utils/canvas-internal'
|
||||
import { requestUrl, Vault } from 'obsidian';
|
||||
import { CanvasNode } from 'src/utils/canvas-internal';
|
||||
|
||||
import * as fs from 'fs'
|
||||
import {
|
||||
createNode,
|
||||
|
|
@ -47,6 +48,18 @@ export const getProtein = async function () {
|
|||
chat_role: 'assistant'
|
||||
}
|
||||
)
|
||||
const displayNode = createNode(canvas, created,
|
||||
{
|
||||
text: "test",
|
||||
size: { height: placeholderNoteHeight*3 }
|
||||
},
|
||||
{
|
||||
color: assistantColor,
|
||||
chat_role: 'assistant'
|
||||
}
|
||||
)
|
||||
displayNode.setText("")
|
||||
|
||||
|
||||
try {
|
||||
const res = await requestUrl({
|
||||
|
|
@ -57,27 +70,45 @@ export const getProtein = async function () {
|
|||
}
|
||||
})
|
||||
console.log('res', res.arrayBuffer)
|
||||
const regex = /\/([^/]+)$/;
|
||||
const matches = nodeText.match(regex);
|
||||
const filename = matches ? matches[1] : "";
|
||||
const buffer = Buffer.from(res.arrayBuffer);
|
||||
fs.writeFile(`${this.app.vault.adapter.basePath}/${filename}`, buffer, 'base64', (error) => {
|
||||
if (error) {
|
||||
console.error('Error saving image:', error);
|
||||
} else {
|
||||
const file = this.app.vault.getAbstractFileByPath(`${filename}`)
|
||||
const cidNode = createNode(canvas, created,
|
||||
{
|
||||
file,
|
||||
size: { height: placeholderNoteHeight }
|
||||
},
|
||||
{
|
||||
color: assistantColor,
|
||||
chat_role: 'assistant'
|
||||
}
|
||||
)
|
||||
}
|
||||
});
|
||||
const regex = /\/([^/]+)$/;
|
||||
const matches = nodeText.match(regex);
|
||||
const filename = matches ? matches[1] : "";
|
||||
const buffer = Buffer.from(res.arrayBuffer);
|
||||
fs.writeFile(`${this.app.vault.adapter.basePath}/${filename}`, buffer, 'base64', (error) => {
|
||||
if (error) {
|
||||
console.error('Error saving image:', error);
|
||||
} else {
|
||||
const file = this.app.vault.getAbstractFileByPath(`${filename}`)
|
||||
import("3dmol/build/3Dmol.js").then(async ($3Dmol) => {
|
||||
|
||||
console.log('displayNode', displayNode)
|
||||
const el = displayNode.containerEl
|
||||
console.log($3Dmol);
|
||||
console.log('el', el)
|
||||
console.log('canvas', canvas)
|
||||
await sleep(200)
|
||||
let config = { backgroundColor: 'rgb(30,30,30)' };
|
||||
let viewer = $3Dmol.createViewer(el, config);
|
||||
let m = viewer.addModel()
|
||||
m.addMolData(fs.readFileSync(`${this.app.vault.adapter.basePath}/${filename}`, 'utf-8'), 'pdb')
|
||||
|
||||
viewer.zoomTo();
|
||||
viewer.render();
|
||||
console.log('viewer', viewer)
|
||||
viewer.zoom(0.8, 2000);
|
||||
});
|
||||
const cidNode = createNode(canvas, created,
|
||||
{
|
||||
file,
|
||||
size: { height: placeholderNoteHeight }
|
||||
},
|
||||
{
|
||||
color: assistantColor,
|
||||
chat_role: 'assistant'
|
||||
}
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import { requestUrl, Vault } from 'obsidian'
|
||||
import { CanvasNode } from 'src/utils/canvas-internal'
|
||||
import * as fs from 'fs'
|
||||
import * as $3Dmol from '3dmol/build/3Dmol.js';
|
||||
|
||||
import {
|
||||
createNode,
|
||||
addEdge,
|
||||
placeholderNoteHeight,
|
||||
assistantColor,
|
||||
getNodeText
|
||||
getNodeText,
|
||||
randomHexString
|
||||
} from '../utils/canvas-util'
|
||||
import { exec } from 'child_process'
|
||||
import { CID } from 'multiformats/cid'
|
||||
|
|
@ -23,7 +27,8 @@ export const runEquibind = async function () {
|
|||
const values: CanvasNode[] = Array.from(selection.values())
|
||||
const node: CanvasNode = values[0]
|
||||
const node2: CanvasNode = values[1]
|
||||
if (node) {
|
||||
console.log('values', values)
|
||||
if (node && node2) {
|
||||
await canvas.requestSave()
|
||||
await sleep(200)
|
||||
|
||||
|
|
@ -35,16 +40,17 @@ export const runEquibind = async function () {
|
|||
}
|
||||
let proteinPath: string | undefined;
|
||||
let moleculePath: string | undefined;
|
||||
|
||||
if (node.filePath?.endsWith('.pdb')) {
|
||||
let pdb = /pdb/
|
||||
let sdf = /sdf/
|
||||
if (pdb.test(node.filePath)) {
|
||||
proteinPath = node.filePath;
|
||||
} else if (node2.filePath?.endsWith('.pdb')) {
|
||||
} else if (pdb.test(node2.filePath)) {
|
||||
proteinPath = node2.filePath;
|
||||
}
|
||||
|
||||
if (node.filePath?.endsWith('.sdf')) {
|
||||
if (sdf.test(node.filePath)) {
|
||||
moleculePath = node.filePath;
|
||||
} else if (node2.filePath?.endsWith('.sdf')) {
|
||||
} else if (sdf.test(node2.filePath)) {
|
||||
moleculePath = node2.filePath;
|
||||
}
|
||||
console.log('moleculePath', moleculePath)
|
||||
|
|
@ -61,18 +67,31 @@ export const runEquibind = async function () {
|
|||
chat_role: 'assistant'
|
||||
}
|
||||
)
|
||||
addEdge(canvas, randomHexString(16), {
|
||||
fromOrTo: "from",
|
||||
side: "bottom",
|
||||
node: node2,
|
||||
}, {
|
||||
fromOrTo: "to",
|
||||
side: "top",
|
||||
node: created,
|
||||
})
|
||||
|
||||
try {
|
||||
const command = `${this.app.vault.adapter.basePath}/${this.app.vault.configDir}/plugins/obsidian-desci/src/plex/equibind.py`
|
||||
const equibind = exec(`python3 ${command} ${this.app.vault.adapter.basePath} ${proteinPath} ${moleculePath} ${this.settings.publicKey}`)
|
||||
equibind.stdout?.on('data', (data) => {
|
||||
console.log('stdout:data:', data)
|
||||
const regex = /Completed IO JSON CID: (\w+)/;
|
||||
const match = data.match(regex);
|
||||
const cid = match ? match[1] : "";
|
||||
console.log(cid); // Output: QmbijdVrr
|
||||
const outputDirRegex = /Created working directory: (.*)\n/;
|
||||
const outputFileRegex = /[^\/]+(?=\/$|$)/
|
||||
const outputDir = data.match(outputDirRegex)[1]
|
||||
const outputFile = outputDir.match(outputFileRegex)[0]
|
||||
console.log('outputfile', outputFile)
|
||||
|
||||
const cidRegex = /Completed IO JSON CID: (.*)\n/;
|
||||
const cid = data.match(cidRegex)[1]
|
||||
|
||||
const cideNodeError = createNode(canvas, created,
|
||||
const cideNode = createNode(canvas, created,
|
||||
{
|
||||
text: `${cid}`,
|
||||
size: { height: placeholderNoteHeight }
|
||||
|
|
@ -82,6 +101,18 @@ export const runEquibind = async function () {
|
|||
chat_role: 'assistant'
|
||||
}
|
||||
)
|
||||
const outputFileNode = createNode(canvas, created,
|
||||
{
|
||||
text: `Output folder at: ${outputFile}`,
|
||||
size: { height: placeholderNoteHeight }
|
||||
},
|
||||
{
|
||||
color: assistantColor,
|
||||
chat_role: 'assistant'
|
||||
|
||||
},
|
||||
{ x:-400, y:0}
|
||||
)
|
||||
|
||||
})
|
||||
equibind.stderr?.on('data', (data) => {
|
||||
|
|
|
|||
98
src/plex/viewMolecule.ts
Normal file
98
src/plex/viewMolecule.ts
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
|
||||
import { requestUrl, Vault } from 'obsidian';
|
||||
import { CanvasNode } from 'src/utils/canvas-internal';
|
||||
|
||||
import * as fs from 'fs'
|
||||
import {
|
||||
createNode,
|
||||
placeholderNoteHeight,
|
||||
assistantColor,
|
||||
getNodeText
|
||||
} from '../utils/canvas-util'
|
||||
|
||||
import { CID } from 'multiformats/cid'
|
||||
|
||||
export const viewMolecule = async function () {
|
||||
if (this.unloaded) return
|
||||
|
||||
const canvas = this.getActiveCanvas()
|
||||
if (!canvas) {
|
||||
this.logDebug('No active canvas')
|
||||
return
|
||||
}
|
||||
const selection = canvas.selection
|
||||
if (selection?.size !== 1) return
|
||||
const values: CanvasNode[] = Array.from(selection.values())
|
||||
const node: CanvasNode = values[0]
|
||||
if (node) {
|
||||
await canvas.requestSave()
|
||||
await sleep(200)
|
||||
|
||||
const settings = this.settings
|
||||
|
||||
const nodeData = node.getData()
|
||||
console.log('nodeData', nodeData)
|
||||
if (!node.file) {
|
||||
this.logDebug('no node Text found')
|
||||
return
|
||||
}
|
||||
|
||||
const displayNode = createNode(canvas, node,
|
||||
{
|
||||
text: "loading model",
|
||||
size: {
|
||||
width: 1000,
|
||||
height: 1000 }
|
||||
},
|
||||
{
|
||||
color: assistantColor,
|
||||
chat_role: 'assistant'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
try {
|
||||
import("3dmol/build/3Dmol.js").then(async ($3Dmol) => {
|
||||
|
||||
await sleep(200)
|
||||
console.log('displayNode', displayNode)
|
||||
|
||||
const path = `${this.app.vault.adapter.basePath}/${nodeData.file}`
|
||||
console.log('path', path)
|
||||
const el = displayNode.contentEl.createEl("div", {
|
||||
cls: "molecule-viewer",
|
||||
})
|
||||
let config = { backgroundColor: 'rgb(30,30,30)' };
|
||||
let viewer = $3Dmol.createViewer(el, config);
|
||||
let m = viewer.addModel()
|
||||
|
||||
const pdb = /pdb/
|
||||
const fileType = pdb.test(nodeData.file) ? 'pdb' : 'sdf'
|
||||
console.log('fileType', fileType)
|
||||
m.addMolData(
|
||||
fs.readFileSync(`${this.app.vault.adapter.basePath}/${nodeData.file}`, 'utf-8'),
|
||||
fileType
|
||||
)
|
||||
|
||||
viewer.zoomTo();
|
||||
viewer.render();
|
||||
console.log('viewer', viewer)
|
||||
viewer.zoom(0.8, 2000);
|
||||
displayNode.setText("")
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
const cideNodeError = createNode(canvas, node,
|
||||
{
|
||||
text: `error: ${e}`,
|
||||
size: { height: placeholderNoteHeight }
|
||||
},
|
||||
{
|
||||
color: assistantColor,
|
||||
chat_role: 'assistant'
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -98,7 +98,7 @@ export const emptyNoteHeight = 100
|
|||
*/
|
||||
export const placeholderNoteHeight = 60
|
||||
|
||||
const randomHexString = (len: number) => {
|
||||
export const randomHexString = (len: number) => {
|
||||
const t = []
|
||||
for (let n = 0; n < len; n++) {
|
||||
t.push((16 * Math.random() | 0).toString(16))
|
||||
|
|
|
|||
|
|
@ -14,4 +14,11 @@ display: flex;
|
|||
div[data-path*="sdf"]{
|
||||
color: #9999996e;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.molecule-viewer {
|
||||
display: flex;
|
||||
width: 100vh;
|
||||
height: 100vh;
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue