Open spencertr opened 1 year ago
Hi @spencertr, the OpenBabel module is compiled to an individual .wasm file (not in js format), so it usually can not be automatically wrapped togather with js bundle by your pack tool. To use this module, you may have to manually copy the essential files from kekule/dist/extra/
directory (including openbabel.*
and kekule.worker.obStructureGenerator.js
to your dest path, then manually set this path in Kekule.js:
Kekule.environment.setEnvVar('openbabel.path', 'Your/Custom/Path/To/OpenBabel/Files/'); // need only to call this once
Kekule.OpenBabel.enable(() => {
smilesString = this.props.smilesString;
mol = Kekule.IO.loadFormatData(smilesString, 'smi');
// ....
}
I tried what you suggested in two different scenarios, in an attempt to troubleshoot: 1) in create-react-app:
.
├── App.js
├── App.test.js
├── components
│ ├── ComposerViewer.js
├── index.js
├── kekule
│ ├── kekule.react.base.js
│ ├── kekule.react.components.js
│ ├── kekule.react.css
│ ├── kekule.react.js
│ ├── kekule.react.wrap.js
│ ├── kekule.worker.obStructureGenerator.js
│ ├── openbabel.data
│ ├── openbabel.js
│ └── openbabel.wasm
and ...
Kekule.environment.setEnvVar('openbabel.path', '../kekule/') // need only to call this once
Kekule.OpenBabel.enable(() => {
smilesString = this.props.smilesString;
mol = Kekule.IO.loadFormatData(smilesString, 'smi');
// ....
}
I tried setting the path with ./kekule
and with absolute path as well. I am currently getting this error: Uncaught SyntaxError: Unexpected token '<' (at openbabel.js:1:1)
Tried two additional things:
componentDidMount() {
const script = document.createElement('script')
script.src = './src/kekule/openbabel.js' // tried ./kekule and ../kekule
script.async = true
document.body.appendChild(script)
}
and
<script type="text/javascript" src="./src/kekule/openbabel.js"></script> // tried ./kekule and ../kekule
2) vanilla js:
.
├── package-lock.json
├── package.json
├── public
│ ├── bundle.js
│ ├── index.html
│ ├── index.js
│ ├── kekule.worker.obStructureGenerator.js
│ ├── openbabel.data
│ ├── openbabel.js
│ └── openbabel.wasm
└── webpack.config.js
and ...
Kekule.environment.setEnvVar('openbabel.path', './') // need only to call this once
Kekule.OpenBabel.enable(() => {
smilesString = this.props.smilesString;
mol = Kekule.IO.loadFormatData(smilesString, 'smi');
// ....
}
I don't get the error like above in the react app, however it doesn't run the block of code to load the smilesString
. I instead just wrote it like this:
Kekule.environment.setEnvVar('openbabel.path', './') // need only to call this once
Kekule.OpenBabel.enable()
console.log('test')
mol = Kekule.IO.loadFormatData(smiles, 'smi')
console.log(mol)
But it gives me the error:
Uncaught Kekule.Exception
name: undefined
"Can not read data of format: smi"
"Error
at eval (webpack:///./node_modules/kekule/dist/mins/common.min.js?:1:54842)
at ./node_modules/kekule/dist/mins/common.min.js (http://localhost:8080/bundle.js:114:1)
at __webpack_require__ (http://localhost:8080/bundle.js:2191:42)
at eval (webpack:///./node_modules/kekule/dist/kekule.esm.mjs?:14:77)
at ./node_modules/kekule/dist/kekule.esm.mjs (http://localhost:8080/bundle.js:2156:1)
at __webpack_require__ (http://localhost:8080/bundle.js:2191:42)
at eval (webpack:///./public/index.js?:2:64)
at ./public/index.js (http://localhost:8080/bundle.js:19:1)
at __webpack_require__ (http://localhost:8080/bundle.js:2191:42)
at http://localhost:8080/bundle.js:2301:37"
Not sure what else to try, appreciate some help. Thanks
I also tried this, which didn't work:
Kekule.Indigo.enable(() => {
let smiles = 'C1CCCCC1'
let mol = Kekule.IO.loadFormatData(smiles, 'smi')
let generator = new Kekule.Calculator.ObStructure2DGenerator()
generator.setSourceMol(mol)
console.log('test')
console.log(smiles)
generator.execute((err) => {
if (!err) {
let newMol = generator.getGeneratedMol()
composer.setChemObj(newMol)
}
})
})
but this seemed to work ...
const { Molecule } = require('openchemlib')
let smiles = 'C1CCCCC1'
const molfile = Molecule.fromSmiles(smiles).toMolfile()
let mol = Kekule.IO.loadFormatData(molfile, 'mol')
let generator = new Kekule.Calculator.ObStructure2DGenerator()
generator.setSourceMol(mol)
console.log(smiles)
generator.execute((err) => {
if (!err) {
let newMol = generator.getGeneratedMol()
composer.setChemObj(newMol)
} else {
console.error(err)
}
})
But I see an error in the console: FS.trackingDelegate error on read file: /dev/stdin - openbabel.js :8
Finally, got the react app to work, i moved the kekule files to be in public
. The only solution I found to work so far was:
// ...
this.composer.current.getWidget().setChemObj(newMol)
// this.setState({ chemObj: newMol }) // I couldn't get this to work
// ...
It would be nice to get the openbabel to work as originally planned, rather than install another similar package. Also, do you know why I can't setState the newMol object?
Hi @spencertr, thanks for the detailed feedback. With further investigation, I have found and solved some bugs preventing the proper loading of OpenBabel module in React app. Please check the latest dist files in Kekule.js repo (https://github.com/partridgejiang/Kekule.js/tree/master/dist) and use them to overwrite the ones in your /node_modules/kekule/dist
(installed from npm) .
A simple demo is also attached here. The demo utilize vite to create the React App. The openbabel related files locates in /public/openbabel/
directory, and will be published to /dist/openbabel/
in building. So in the components/composerAndViewer.jsx
, this path is manually setted:
Kekule.environment.setEnvVar('openbabel.path', '/openbabel/');
Kekule.OpenBabel.enable(error => {
if (!error)
{
this.composer.current.widget.loadFromData('c1ccccc1', null, null, Kekule.IO.DataFormat.SMILES);
// Almost equal to Kekule.IO.loadFormatData but do automatical coordinate generation in composer.
// You previous code should also be workable here:
// let smiles = 'C1CCCCC1';
// let mol = Kekule.IO.loadFormatData(smiles, 'smi');
// let generator = new Kekule.Calculator.ObStructure2DGenerator();
// ...
// this.setState({ chemObj: newMol });
}
});
By the way, the chemObj
prop should be set to <Composer>
, otherwise this.setState({ chemObj: newMol })
should not work.
render()
{
return (<Composer className="ComposerWidget" ref={this.composer} chemObj={this.state.chemObj} ></Composer>);
}
HI @partridgejiang , thanks for providing the information. I tried to run the attached kekule-react-vite.zip
app but I don't think it works either. I did not make any changes to any of the .jsx
files and ran npm run dev
opened the webpage and do see the Chem editor (no errors in console), however there is no console.log('OpenBabel loaded!');
. I adapted your code to my current application and it still gives me: Uncaught Kekule.Exception {message: 'Can not read data of format 'smi'
. I tried following your instructions by copying all the new dist
files into my node_modules/kekule/dist
too. Did I miss another step? Thanks for help.
Hi @spencertr, after running npm install for kekule-react-vite
demo, the node_modules/kekule/dist
should also be updated. Anyway, the demo with all node_module
files included is attached here. You may run the npm run dev
directly after decompression. Please have a try, :).
kekule-react-vite.zip
hi @partridgejiang , that .zip
file you attached does indeed work. What did you do to get it working? I would like to dockerise the app eventually and I would like to know the steps to get it consistently working. I tried to get the original kekule-react-vite.zip
you sent last week to work but have failed. I don't get any errors, but openbabel
does not get enabled. Likewise, when I tried what I believe are all the steps you mentioned for my app, the same, lack of enablement happens, the Kekule.OpenBabel.enable()
line does not seem to work. I don't see any differences in the two .zip
files you sent. I did indeed update the node_modules/kekule/dist
directory; I even copied from the newest .zip
file you sent and that OpenBabel.enable()
line still does not work. Appreciate your responses. Cheers.
Hi @spencertr, my approximate steps to create the kekule-react-vite
:
vite
to create a React appkekule-react
package/node_modules/kekule/dist
/public/openbabel
directoy, and copy OpenBabel related files from /node_modules/kekule/dist/extra
to /public/openbabel
composerAndViewer.jsx
)Kekule.environment.setEnvVar('openbabel.path', '/openbabel/');
and so on)npm run dev
If possible, could you please attach your unworkable demo here (better to include files in node_modules
directory) ? So that I may do a further investigation?
hi @partridgejiang , thanks for the step by step instructions. I did exactly that besides copying /node_modules/kekule/dist/extra
into /public/openbabel
, I did have 'those' files in the correct directory with the correct names, however I created those files by copying and pasting from this repo: https://github.com/partridgejiang/cheminfo-to-web. I guess those are not up-to-date? Anyways, I got the smiles string to load using openbabel.js
, however I am running into another error. Here is the Composer
code:
import React from "react";
import { Kekule } from "kekule";
import { KekuleReact, Components } from "kekule-react";
import "kekule/theme";
Kekule.environment.setEnvVar("openbabel.path", "/openbabel/");
let Composer = Components.Composer;
class Chemcomposer extends React.Component {
constructor(props) {
super(props);
this.state = {
composerPredefinedSetting: "fullFunc",
viewerPredefinedSetting: "basic",
chemObj: null,
selectedObjs: undefined,
};
this.composer = React.createRef();
this.onPredefineSettingChange = this.onPredefineSettingChange.bind(this);
this.onComposerUserModificationDone = this.onComposerUserModificationDone.bind(
this
);
this.onComposerSelectionChange = this.onComposerSelectionChange.bind(this);
}
componentDidMount() {
this.composer.current.widget.newDoc();
Kekule.OpenBabel.enable((error) => {
if (!error) {
//let mol = Kekule.IO.loadFormatData('c1ccccc1', Kekule.IO.DataFormat.SMILES);
//this.composer.current.widget.setChemObj(mol);
//this.setState({'chemObj': mol});
this.composer.current.widget.loadFromData(
"c1ccccc1",
null,
null,
Kekule.IO.DataFormat.SMILES
);
}
});
}
getComposerSelectedAtomsAndBonds(selection) {
let result = { atoms: [], bonds: [] };
(selection || []).forEach((obj) => {
if (obj instanceof Kekule.Atom) result.atoms.push(obj);
else if (obj instanceof Kekule.Bond) result.bonds.push(obj);
});
return result;
}
onPredefineSettingChange(e) {
this.setState({ composerPredefinedSetting: e.target.value });
}
onComposerUserModificationDone(e) {
this.setState({ chemObj: this.composer.current.getWidget().getChemObj() });
}
onComposerSelectionChange(e) {
this.setState({
selectedObjs: this.composer.current.getWidget().getSelection(),
});
}
render() {
let selectionInfoElem;
if (this.state.selectedObjs && this.state.selectedObjs.length) {
let selDetails = this.getComposerSelectedAtomsAndBonds(
this.state.selectedObjs
);
selectionInfoElem = (
<span>
You have selected {this.state.selectedObjs.length} object(s),
including {selDetails.atoms.length} atom(s) and{" "}
{selDetails.bonds.length} bond(s).
</span>
);
} else
selectionInfoElem = (
<span>
Please edit and select objects in the composer to see the changes.
</span>
);
return (
<div>
<div className="InfoPanel">
<label>{selectionInfoElem}</label>
</div>
<div>
<Composer
className="SubWidget"
ref={this.composer}
predefinedSetting={this.state.composerPredefinedSetting}
onUserModificationDone={this.onComposerUserModificationDone}
onSelectionChange={this.onComposerSelectionChange}
chemObj={this.state.chemObj}
></Composer>
</div>
</div>
);
}
}
export default Chemcomposer;
if I remove onSelectionChange={this.onComposerSelectionChange}
, it works. Otherwise the error is Uncaught TypeError: this.composer.current is null
. The code I am using for my app is almost verbatim copy from your Kekule-React
repo, and it has that method in it; I didn't touch it. It seems to be working as is, without the openbabel.js
.
The code in componentDidMount
will invoke a selectionChange
event (selection to empty), but this.composer.current
may not be initialized until the React component rendering process done. So an error is raised. It is quite easy to fix that, just add a condition check in the onComposerSelectionChange
method:
onComposerSelectionChange(e) {
if (this.composer.current)
this.setState({
selectedObjs: this.composer.current.getWidget().getSelection(),
});
}
hi Partridge,
How do I load a smiles string? I followed this git hub issue: link
I'm currently getting an error in the console:
UPDATE: i just realised I need to deploy openbabel.js link, i downloaded the three files and placed the
<script>
tag in myindex.html
as described in the website, but now I am getting:Why is it trying to import from
/extra
? I don't have this directory, do I need to make anextra
folder? The docs don't mention a specific directory name. I suppose I am missing another step.