view molecule

This commit is contained in:
Taylor Hulsmans 2023-11-12 15:54:32 -08:00
parent 3c1d4fc996
commit 2033373495
8 changed files with 257 additions and 36 deletions

View file

@ -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
View file

@ -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",

View file

@ -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",

View file

@ -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) {

View file

@ -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
View 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'
}
)
}
}
}

View file

@ -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))

View file

@ -14,4 +14,11 @@ display: flex;
div[data-path*="sdf"]{
color: #9999996e;
display: flex;
}
.molecule-viewer {
display: flex;
width: 100vh;
height: 100vh;
}