├── .gitignore ├── LICENSE ├── README.md ├── ci.sh ├── coveragerc ├── examples ├── dna.xyz ├── draw.py ├── export.py └── show.py ├── mogli.py ├── pylintrc ├── pytest.ini ├── setup.py └── tests ├── test_bonds.py ├── test_draw.py ├── test_export.py ├── test_read.py └── test_show.py /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | !.gitignore 3 | *.pyc 4 | 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | pip-wheel-metadata/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py,cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | local_settings.py 65 | db.sqlite3 66 | db.sqlite3-journal 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | #Others 136 | *~ 137 | .vscode -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Florian Rhiem 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mogli 2 | 3 | mogli is a python module for easily rendering molecules. 4 | 5 | The fastest way to get started is using mogli.py in the command line: 6 | ``` 7 | % python -m mogli 8 | ``` 9 | 10 | This will open a new window in which the molecule will be rendered. You can then rotate the molecule using your mouse. 11 | 12 | In python, you can achieve something similar with three lines of code: 13 | 14 | ``` 15 | >>> import mogli 16 | >>> molecules = mogli.read('examples/dna.xyz') 17 | >>> mogli.show(molecules[0]) 18 | ``` 19 | 20 | ![DNA rendered in a GR window](https://raw.githubusercontent.com/FlorianRhiem/mogli/doc-images/dna-cli.png) 21 | 22 | ## Atomic bonds 23 | You might notice that the atomic bonds between the atoms don't look right. You can hide the bonds by adding the `show_bonds=False` to your `show()` call, but of course there's a a better way to fix the missing bonds here. 24 | 25 | For the example above, adding `bonds_param=1.15` to `show()` will do the trick. mogli currently offers two ways of calculating atomic bonds. The first method compares the distance between every two atoms with the sum of their valence radii. If they are further apart, no bond is formed. To allow a bit of adjustment, this radii sum is multiplied with a factor that can be set using the `bonds_param` parameter to `show()` and `draw()`. By default, `1.0` is used. 26 | 27 | The second method uses a constant maximum distance instead. If you use this method by passing `bonds_method='constant_delta'` to `show()` or `draw()`, you can set the constant distance with the `bonds_param` parameter. 28 | 29 | ## Exporting to files 30 | Instead of viewing molecules interactively, you can export the molecule as well, for example as Portable Network Graphics into a `.png` file, or as HTML5 page with interactive WebGL code as `.html` file. To do so, simply call the `export()` function, like so: 31 | 32 | ``` 33 | >>> import mogli 34 | >>> molecules = mogli.read('examples/dna.xyz') 35 | >>> mogli.export(molecules[0], 'dna.html', width=1920, height=1080, 36 | bonds_param=1.15, camera=((40, 0, 0), 37 | (0, 0, 0), 38 | (0, 1, 0))) 39 | ``` 40 | 41 | This example code also shows setting the camera in code by passing the `camera` parameter. It expects three sequences of three floating point numbers: the camera position, the center of focus and a direction that should point upward. *Exporting to HTML5 with WebGL is a pretty near feature, give it a try!* 42 | 43 | ## Drawing with GR 44 | In case you use the GR framework, you can use mogli to draw molecules into your GR graphics. To do so, just call `draw()`. You can use the parameters `xmin`, `xmax`, `ymin`, `ymax`, `width` and `height` just like you would when using `gr.drawimage()`. 45 | 46 | ``` 47 | >>> import gr 48 | >>> import mogli 49 | >>> molecules = mogli.read('examples/dna.xyz') 50 | >>> mogli.draw(molecules[0]) 51 | >>> gr.updatews() 52 | ``` 53 | 54 | ![DNA rendered in a GR window](https://raw.githubusercontent.com/FlorianRhiem/mogli/doc-images/dna-gr.png) 55 | 56 | ## Dependencies 57 | mogli depends on GR3, which is included in the [GR framework](http://gr-framework.org/) ([PyPI]( https://pypi.python.org/pypi/gr), [GitHub](https://github.com/jheinen/gr)), on [glfw3](http://www.glfw.org/) ([GitHub](https://github.com/glfw/glfw)) and python bindings for glfw3 ([PyPI](https://pypi.python.org/pypi/glfw), [GitHub](https://github.com/FlorianRhiem/pyGLFW)). 58 | 59 | Additionally, [Pybel](http://openbabel.org/docs/dev/UseTheLibrary/Python_Pybel.html) should be installed; mogli will work without it, but only for xyz files. 60 | -------------------------------------------------------------------------------- /ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export PYTHONPATH=`pwd`:$PYTHONPATH 4 | 5 | # Set up virtual environment 6 | [ ! -e env ] || rm -rf env 7 | virtualenv --python=python2.7 env 8 | . env/bin/activate 9 | # Install testing packages 10 | pip install pep8 pylint pytest pytest-cov 11 | 12 | # Install requirements 13 | pip install numpy gr glfw PyOpenGL 14 | 15 | 16 | # PEP8 conformance check (will cause build failure if python files are not PEP8 conformant) 17 | pep8 $(find . -path ./env -prune -o -name "*.py" -print) | tee pep8.log 18 | if [[ ${PIPESTATUS[0]} -ne 0 ]]; then 19 | echo "Failed PEP8 conformance check!"; 20 | exit 1; 21 | fi 22 | 23 | 24 | # PyLint code style check (will NOT cause build failure as pylint tends to overreact) 25 | if [[ ! -f pylintrc ]]; then 26 | echo "pylintrc missing!"; 27 | exit 1; 28 | fi 29 | pylint --rcfile=pylintrc "--msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}" $(find . -path ./env -prune -o -path ./tests -prune -o -name "*.py" -print) | tee pylint.log 30 | 31 | 32 | # Run py.test (will cause build failure if a test fails) 33 | if [[ ! -f pytest.ini ]]; then 34 | echo "pytest.ini missing!"; 35 | exit 1; 36 | fi 37 | if [[ ! -f coveragerc ]]; then 38 | echo "coveragerc missing!"; 39 | exit 1; 40 | fi 41 | py.test --cov-config coveragerc --cov-report html --cov . | tee py.test.log 42 | if [[ ${PIPESTATUS[0]} -ne 0 ]]; then 43 | echo "Failed tests!"; 44 | exit 1; 45 | fi 46 | 47 | 48 | # Report build success 49 | exit 0 50 | -------------------------------------------------------------------------------- /coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = env/*, setup.py, examples/* 3 | branch = True 4 | -------------------------------------------------------------------------------- /examples/dna.xyz: -------------------------------------------------------------------------------- 1 | 486 2 | dna 3 | O 18.934999 34.195000 25.617001 4 | C 19.129999 33.921001 24.219000 5 | C 19.961000 32.667999 24.100000 6 | O 19.360001 31.583000 24.851999 7 | C 20.172001 32.122002 22.694000 8 | O 21.350000 31.325001 22.681000 9 | C 18.948000 31.223000 22.646999 10 | C 19.231001 30.482000 23.944000 11 | N 18.070000 29.660999 24.379999 12 | C 18.224001 28.454000 25.014999 13 | O 19.360001 28.014000 25.214001 14 | N 17.143000 27.761000 25.377001 15 | C 15.917000 28.226000 25.120001 16 | N 14.828000 27.476999 25.444000 17 | C 15.719000 29.441999 24.471001 18 | C 16.843000 30.171000 24.101000 19 | P 22.409000 31.285999 21.483000 20 | O 21.822001 31.459000 20.139000 21 | O 23.535999 32.157001 21.851000 22 | O 22.840000 29.750999 21.497999 23 | C 23.542999 29.174999 22.594000 24 | C 23.493999 27.709000 22.278999 25 | O 22.193001 27.252001 22.674000 26 | C 23.693001 27.325001 20.806999 27 | O 24.723000 26.320000 20.653000 28 | C 22.273001 26.885000 20.416000 29 | C 21.721001 26.304001 21.716000 30 | N 20.237000 26.469999 21.780001 31 | C 19.525999 27.584000 21.429001 32 | N 18.207001 27.455000 21.636000 33 | C 18.083000 26.212000 22.142000 34 | C 16.903999 25.525000 22.545000 35 | O 15.739000 25.916000 22.518000 36 | N 17.197001 24.278999 23.037001 37 | C 18.434000 23.716999 23.155001 38 | N 18.507999 22.455999 23.667999 39 | N 19.537001 24.360001 22.770000 40 | C 19.290001 25.594000 22.274000 41 | P 25.063999 25.621000 19.252001 42 | O 24.559000 26.412001 18.115000 43 | O 26.506001 25.316000 19.219999 44 | O 24.260000 24.246000 19.327000 45 | C 24.584000 23.285000 20.334999 46 | C 23.523001 22.233000 20.245001 47 | O 22.256001 22.844000 20.452999 48 | C 23.424000 21.556999 18.903000 49 | O 24.121000 20.309000 18.927999 50 | C 21.930000 21.406000 18.660999 51 | C 21.278000 21.966000 19.909000 52 | N 20.195999 22.889000 19.521000 53 | C 18.909000 22.584000 19.816000 54 | O 18.684999 21.511999 20.382000 55 | N 17.934999 23.447001 19.502001 56 | C 18.216999 24.603001 18.896999 57 | N 17.221001 25.499001 18.629000 58 | C 19.525999 24.945000 18.570999 59 | C 20.537001 24.048000 18.899000 60 | P 24.249001 19.412001 17.617001 61 | O 24.208000 20.296000 16.440001 62 | O 25.420000 18.535000 17.764999 63 | O 22.931000 18.537001 17.670000 64 | C 22.714001 17.625000 18.753000 65 | C 21.393000 16.959999 18.504999 66 | O 20.353001 17.952000 18.496000 67 | C 21.264000 16.229000 17.176001 68 | O 20.284000 15.214000 17.238001 69 | C 20.792999 17.368000 16.288000 70 | C 19.716000 17.900999 17.218000 71 | N 19.305000 19.281000 16.868999 72 | C 20.017000 20.263000 16.232000 73 | N 19.313000 21.393999 16.077000 74 | C 18.121000 21.100000 16.635000 75 | C 16.952000 21.903999 16.749001 76 | O 16.768999 23.056999 16.368000 77 | N 15.933000 21.214001 17.351999 78 | C 15.972000 19.930000 17.816000 79 | N 14.831000 19.416000 18.353001 80 | N 17.068001 19.179001 17.716999 81 | C 18.084000 19.825001 17.121000 82 | P 20.356001 13.969000 16.245001 83 | O 20.837000 14.423000 14.910000 84 | O 21.115999 12.891000 16.892000 85 | O 18.809999 13.581000 16.160999 86 | C 18.014999 13.569000 17.362000 87 | C 16.672001 14.088000 16.957001 88 | O 16.841999 15.447000 16.561001 89 | C 16.018999 13.393000 15.764000 90 | O 14.762000 12.796000 16.120001 91 | C 15.952000 14.498000 14.696000 92 | C 15.851000 15.732000 15.569000 93 | N 16.391001 16.916000 14.867000 94 | C 17.658001 17.103001 14.382000 95 | N 17.863001 18.346001 13.913000 96 | C 16.673000 18.952999 14.098000 97 | C 16.230000 20.278999 13.819000 98 | N 17.045000 21.222000 13.268000 99 | N 14.966000 20.577999 14.118000 100 | C 14.178000 19.652000 14.669000 101 | N 14.463000 18.392000 14.984000 102 | C 15.750000 18.110001 14.661000 103 | P 13.866000 12.006000 15.063000 104 | O 14.715000 11.499000 13.968000 105 | O 13.028000 11.039000 15.800000 106 | O 12.879000 13.111000 14.480000 107 | C 11.802000 13.597000 15.290000 108 | C 11.111000 14.603000 14.435000 109 | O 12.152000 15.460000 13.962000 110 | C 10.417000 14.070000 13.187000 111 | O 9.007000 14.369000 13.181000 112 | C 11.240000 14.692000 12.061000 113 | C 11.699000 15.974000 12.719000 114 | N 12.918000 16.525999 12.078000 115 | C 14.115000 15.899000 11.868000 116 | N 15.049000 16.714001 11.356000 117 | C 14.416000 17.900999 11.246000 118 | C 14.873000 19.187000 10.815000 119 | N 16.160999 19.417999 10.427000 120 | N 13.999000 20.191000 10.852000 121 | C 12.753000 19.962000 11.272000 122 | N 12.210000 18.823999 11.698000 123 | C 13.116000 17.823000 11.657000 124 | P 8.081000 14.050000 11.915000 125 | O 8.600000 12.894000 11.137000 126 | O 6.668000 13.960000 12.342000 127 | O 8.239000 15.387000 11.076000 128 | C 7.907000 16.635000 11.686000 129 | C 8.162000 17.628000 10.598000 130 | O 9.543000 17.580000 10.279000 131 | C 7.461000 17.284000 9.296000 132 | O 6.251000 18.034000 9.162000 133 | C 8.532000 17.527000 8.223000 134 | C 9.644000 18.209000 9.019000 135 | N 11.021000 17.903000 8.565000 136 | C 11.822000 18.923000 8.176000 137 | O 11.383000 20.077000 8.143000 138 | N 13.119000 18.641001 7.852000 139 | C 13.633000 17.372000 7.882000 140 | O 14.830000 17.222000 7.619000 141 | C 12.781000 16.325001 8.235000 142 | C 13.269000 14.902000 8.236000 143 | C 11.465000 16.615999 8.594000 144 | P 5.384000 17.990000 7.824000 145 | O 5.458000 16.667999 7.160000 146 | O 4.025000 18.444000 8.180000 147 | O 6.086000 19.118000 6.927000 148 | C 6.146000 20.478001 7.418000 149 | C 6.995000 21.229000 6.438000 150 | O 8.188000 20.458000 6.284000 151 | C 6.418000 21.332001 5.029000 152 | O 5.967000 22.667000 4.696000 153 | C 7.513000 20.718000 4.139000 154 | C 8.736000 20.855000 5.034000 155 | N 9.823000 19.875999 4.759000 156 | C 11.086000 20.316000 4.494000 157 | O 11.324000 21.516001 4.389000 158 | N 12.094000 19.403000 4.412000 159 | C 11.876000 18.059999 4.551000 160 | O 12.858000 17.316999 4.503000 161 | C 10.569000 17.611000 4.765000 162 | C 10.261000 16.139999 4.896000 163 | C 9.545000 18.548000 4.904000 164 | P 5.531000 23.070999 3.209000 165 | O 5.010000 21.905001 2.470000 166 | O 4.648000 24.243999 3.269000 167 | O 6.926000 23.547001 2.611000 168 | C 7.636000 24.627001 3.249000 169 | C 8.897000 24.853001 2.457000 170 | O 9.638000 23.627001 2.448000 171 | C 8.717000 25.240000 0.998000 172 | O 9.470000 26.414000 0.667000 173 | C 9.126000 23.965000 0.253000 174 | C 10.241000 23.483000 1.157000 175 | N 10.524000 22.021999 1.015000 176 | C 11.814000 21.603001 0.840000 177 | O 12.691000 22.447001 0.670000 178 | N 12.106000 20.297001 0.873000 179 | C 11.141000 19.395000 1.046000 180 | N 11.461000 18.075001 1.089000 181 | C 9.803000 19.775000 1.177000 182 | C 9.499000 21.132999 1.167000 183 | P 9.055000 27.333000 -0.581000 184 | O 7.632000 27.106001 -0.947000 185 | O 9.496000 28.716999 -0.258000 186 | O 9.954000 26.764999 -1.771000 187 | C 11.382000 26.940001 -1.720000 188 | C 11.972000 26.090000 -2.802000 189 | O 11.802000 24.724001 -2.404000 190 | C 11.327000 26.177999 -4.188000 191 | O 12.311000 26.096001 -5.214000 192 | C 10.414000 24.962000 -4.186000 193 | C 11.429000 24.028000 -3.587000 194 | N 10.890000 22.712999 -3.200000 195 | C 9.616000 22.315001 -2.910000 196 | N 9.541000 21.009001 -2.613000 197 | C 10.818000 20.587999 -2.718000 198 | C 11.376000 19.292000 -2.511000 199 | O 10.813000 18.252001 -2.179000 200 | N 12.729000 19.299000 -2.720000 201 | C 13.498000 20.365000 -3.082000 202 | N 14.834000 20.169001 -3.237000 203 | N 12.982000 21.573000 -3.267000 204 | C 11.656000 21.601000 -3.061000 205 | P 12.763000 27.421000 -5.980000 206 | O 11.886000 27.542000 -7.164000 207 | O 12.796000 28.572001 -5.049000 208 | O 14.272000 27.086000 -6.366000 209 | C 15.275000 27.108000 -5.318000 210 | C 16.222000 25.945999 -5.510000 211 | O 15.443000 24.754000 -5.397000 212 | C 16.941999 25.827000 -6.848000 213 | O 18.340000 25.511000 -6.701000 214 | C 16.118000 24.767000 -7.578000 215 | C 15.856000 23.836000 -6.414000 216 | N 14.672000 22.975000 -6.637000 217 | C 14.802000 21.628000 -6.529000 218 | O 15.924000 21.177999 -6.314000 219 | N 13.723000 20.841999 -6.627000 220 | C 12.515000 21.372999 -6.836000 221 | N 11.410000 20.573999 -6.872000 222 | C 12.348000 22.743999 -6.978000 223 | C 13.470000 23.558001 -6.869000 224 | P 19.330999 25.774000 -7.925000 225 | O 18.763000 26.851000 -8.758000 226 | O 20.704000 25.976000 -7.408000 227 | O 19.302000 24.412001 -8.763000 228 | C 20.108999 23.284000 -8.359000 229 | C 19.747999 22.167000 -9.299000 230 | O 18.350000 21.969000 -9.139000 231 | C 19.921000 22.403999 -10.815000 232 | O 20.985001 21.635000 -11.401000 233 | C 18.535000 22.062000 -11.381000 234 | C 17.965000 21.200001 -10.269000 235 | N 16.493000 21.219999 -10.265000 236 | C 15.663000 22.289000 -10.478000 237 | N 14.368000 21.958000 -10.390000 238 | C 14.388000 20.639999 -10.102000 239 | C 13.301000 19.742001 -9.856000 240 | O 12.091000 19.966999 -9.857000 241 | N 13.750000 18.466000 -9.625000 242 | C 15.042000 18.042999 -9.605000 243 | N 15.259000 16.716999 -9.406000 244 | N 16.061001 18.885000 -9.792000 245 | C 15.660000 20.156000 -10.027000 246 | O 7.458000 11.884000 -9.070000 247 | C 8.252000 10.968000 -9.854000 248 | C 9.714000 11.141000 -9.512000 249 | O 10.144000 12.455000 -9.908000 250 | C 10.103000 10.989000 -8.055000 251 | O 11.293000 10.221000 -7.904000 252 | C 10.254000 12.437000 -7.607000 253 | C 10.896000 13.044000 -8.837000 254 | N 10.575000 14.487000 -8.944000 255 | C 11.559000 15.430000 -9.006000 256 | O 12.725000 15.066000 -8.932000 257 | N 11.246000 16.714001 -9.193000 258 | C 9.980000 17.087999 -9.334000 259 | N 9.698000 18.395000 -9.589000 260 | C 8.939000 16.162001 -9.274000 261 | C 9.265000 14.824000 -9.080000 262 | P 11.602000 9.510000 -6.502000 263 | O 10.644000 10.010000 -5.494000 264 | O 11.666000 8.032000 -6.664000 265 | O 13.051000 10.094000 -6.177000 266 | C 14.100000 10.021000 -7.156000 267 | C 15.113000 10.992000 -6.657000 268 | O 14.556000 12.300000 -6.755000 269 | C 15.445000 10.806000 -5.189000 270 | O 16.836000 10.560000 -5.013000 271 | C 14.937000 12.100000 -4.529000 272 | C 15.058000 13.086000 -5.671000 273 | N 14.036000 14.140000 -5.536000 274 | C 12.710000 13.957000 -5.259000 275 | N 12.016000 15.103000 -5.269000 276 | C 12.937000 16.041000 -5.558000 277 | C 12.761000 17.451000 -5.710000 278 | O 11.723000 18.111000 -5.630000 279 | N 13.952000 18.079000 -5.973000 280 | C 15.171000 17.485001 -6.107000 281 | N 16.243999 18.292000 -6.325000 282 | N 15.329000 16.160999 -5.986000 283 | C 14.179000 15.499000 -5.721000 284 | P 17.478001 10.380000 -3.569000 285 | O 16.427000 9.940000 -2.633000 286 | O 18.665001 9.516000 -3.729000 287 | O 17.957001 11.865000 -3.208000 288 | C 18.962999 12.531000 -3.996000 289 | C 18.936001 13.958000 -3.536000 290 | O 17.591999 14.409000 -3.622000 291 | C 19.253000 14.139000 -2.066000 292 | O 20.659000 14.219000 -1.858000 293 | C 18.520000 15.417000 -1.728000 294 | C 17.545000 15.602000 -2.872000 295 | N 16.145000 15.696000 -2.428000 296 | C 15.507000 16.886000 -2.558000 297 | O 16.162001 17.846001 -2.957000 298 | N 14.209000 16.983000 -2.264000 299 | C 13.536000 15.919000 -1.825000 300 | N 12.205000 16.017000 -1.553000 301 | C 14.164000 14.689000 -1.652000 302 | C 15.509000 14.584000 -1.979000 303 | P 21.304001 14.529000 -0.436000 304 | O 20.488001 13.954000 0.650000 305 | O 22.695999 14.087000 -0.524000 306 | O 21.306000 16.117001 -0.363000 307 | C 22.177000 16.875999 -1.212000 308 | C 21.739000 18.292000 -1.021000 309 | O 20.305000 18.225000 -1.048000 310 | C 22.101000 18.959000 0.293000 311 | O 22.591999 20.292999 0.097000 312 | C 20.820000 18.829000 1.121000 313 | C 19.764999 18.985001 0.046000 314 | N 18.513000 18.299000 0.468000 315 | C 18.363001 17.062000 1.039000 316 | N 17.080000 16.743999 1.281000 317 | C 16.400000 17.832001 0.868000 318 | C 14.996000 18.090000 0.882000 319 | O 14.082000 17.378000 1.280000 320 | N 14.712000 19.349001 0.418000 321 | C 15.606000 20.268000 -0.027000 322 | N 15.134000 21.493000 -0.382000 323 | N 16.912001 20.017000 -0.072000 324 | C 17.236000 18.794001 0.384000 325 | P 22.903999 21.238001 1.339000 326 | O 23.104000 20.389999 2.538000 327 | O 23.993999 22.183001 1.025000 328 | O 21.577000 22.107000 1.390000 329 | C 21.216000 22.833000 0.200000 330 | C 20.101000 23.788000 0.484000 331 | O 18.913000 23.054001 0.816000 332 | C 20.347000 24.743000 1.633000 333 | O 19.732000 26.010000 1.411000 334 | C 19.752001 23.945000 2.791000 335 | C 18.497000 23.393000 2.145000 336 | N 18.079000 22.094999 2.758000 337 | C 18.847000 21.020000 3.133000 338 | N 18.114000 19.983999 3.584000 339 | C 16.841999 20.424000 3.488000 340 | C 15.577000 19.816999 3.786000 341 | N 15.448000 18.537001 4.242000 342 | N 14.482000 20.556999 3.593000 343 | C 14.597000 21.801001 3.118000 344 | N 15.700000 22.472000 2.783000 345 | C 16.791000 21.705999 3.002000 346 | P 19.802999 27.141001 2.526000 347 | O 20.952999 26.858000 3.426000 348 | O 19.796000 28.478001 1.888000 349 | O 18.396000 26.938999 3.241000 350 | C 17.202999 27.028000 2.452000 351 | C 16.035000 26.958000 3.388000 352 | O 15.856000 25.612000 3.850000 353 | C 16.101000 27.861000 4.615000 354 | O 14.890000 28.608000 4.757000 355 | C 16.368000 26.844000 5.724000 356 | C 15.561000 25.655001 5.243000 357 | N 16.104000 24.372999 5.755000 358 | C 17.410999 23.966999 5.830000 359 | N 17.539000 22.705999 6.276000 360 | C 16.266001 22.309000 6.480000 361 | C 15.715000 21.073000 6.933000 362 | N 16.483000 19.993999 7.243000 363 | N 14.389000 20.993999 7.036000 364 | C 13.636000 22.041000 6.708000 365 | N 14.019000 23.233999 6.265000 366 | C 15.367000 23.291000 6.174000 367 | P 14.604000 29.545000 6.020000 368 | O 15.852000 29.836000 6.749000 369 | O 13.792000 30.695999 5.582000 370 | O 13.633000 28.628000 6.885000 371 | C 12.398000 28.171000 6.303000 372 | C 11.809000 27.216999 7.302000 373 | O 12.767000 26.184000 7.534000 374 | C 11.515000 27.822001 8.669000 375 | O 10.103000 27.952000 8.891000 376 | C 12.267000 26.906000 9.630000 377 | C 12.426000 25.645000 8.799000 378 | N 13.609000 24.850000 9.205000 379 | C 13.442000 23.575001 9.656000 380 | O 12.311000 23.101000 9.802000 381 | N 14.551000 22.825001 9.913000 382 | C 15.815000 23.320999 9.777000 383 | O 16.754999 22.570000 10.029000 384 | C 15.972000 24.646999 9.362000 385 | C 17.344999 25.239000 9.234000 386 | C 14.844000 25.405001 9.048000 387 | P 9.513000 28.533001 10.260000 388 | O 10.455000 29.513000 10.841000 389 | O 8.145000 29.007000 9.998000 390 | O 9.395000 27.223000 11.153000 391 | C 8.576000 26.148001 10.664000 392 | C 8.655000 25.059999 11.678000 393 | O 10.003000 24.615000 11.764000 394 | C 8.272000 25.471001 13.087000 395 | O 7.199000 24.657000 13.553000 396 | C 9.586000 25.306999 13.860000 397 | C 10.190000 24.148001 13.089000 398 | N 11.660000 24.070000 13.205000 399 | C 12.257000 22.879999 13.486000 400 | O 11.583000 21.865999 13.691000 401 | N 13.620000 22.829000 13.497000 402 | C 14.402000 23.914000 13.225000 403 | O 15.625000 23.764000 13.252000 404 | C 13.774000 25.125999 12.933000 405 | C 14.563000 26.358000 12.612000 406 | C 12.385000 25.187000 12.926000 407 | P 6.594000 24.823000 15.016000 408 | O 6.870000 26.188999 15.511000 409 | O 5.169000 24.424000 14.987000 410 | O 7.409000 23.731001 15.839000 411 | C 7.331000 22.351999 15.433000 412 | C 8.100000 21.598000 16.461000 413 | O 9.478000 21.902000 16.263000 414 | C 7.766000 22.045000 17.879000 415 | O 7.036000 21.041000 18.611000 416 | C 9.123000 22.414000 18.469000 417 | C 10.107000 21.743000 17.523001 418 | N 11.328000 22.556000 17.330999 419 | C 12.534000 21.938999 17.329000 420 | O 12.560000 20.731001 17.579000 421 | N 13.639000 22.639000 17.035000 422 | C 13.560000 23.938000 16.739000 423 | N 14.685000 24.628000 16.403999 424 | C 12.338000 24.608999 16.736000 425 | C 11.193000 23.878000 17.035000 426 | P 6.509000 21.323999 20.099001 427 | O 6.235000 22.774000 20.306000 428 | O 5.387000 20.396999 20.396000 429 | O 7.767000 20.924000 20.993000 430 | C 8.216000 19.559000 21.073000 431 | C 9.422000 19.556999 21.976999 432 | O 10.493000 20.260000 21.319000 433 | C 9.267000 20.267000 23.325001 434 | O 10.088000 19.657000 24.292999 435 | C 9.751000 21.670000 22.990000 436 | C 10.988000 21.226000 22.256001 437 | N 11.599000 22.357000 21.542999 438 | C 11.037000 23.545000 21.159000 439 | N 11.921000 24.362000 20.566000 440 | C 13.072000 23.653000 20.580000 441 | C 14.370000 24.003000 20.101999 442 | O 14.747000 25.056999 19.584999 443 | N 15.268000 22.983000 20.308001 444 | C 15.023000 21.775999 20.891001 445 | N 16.066000 20.914000 21.038000 446 | N 13.815000 21.452000 21.350000 447 | C 12.902000 22.429001 21.150999 448 | P 9.477000 18.627001 25.340000 449 | O 8.670000 19.409000 26.312000 450 | O 8.767000 17.534000 24.627001 451 | O 10.807000 18.066999 26.034000 452 | C 11.688000 17.170000 25.309999 453 | C 13.115000 17.573000 25.593000 454 | O 13.284000 18.804001 24.893000 455 | C 13.441000 17.879000 27.059000 456 | O 14.341000 16.938000 27.677000 457 | C 13.928000 19.322001 27.025000 458 | C 14.312000 19.507999 25.568001 459 | N 14.144000 20.931999 25.170000 460 | C 15.199000 21.594999 24.629999 461 | O 16.257000 20.983999 24.504000 462 | N 15.067000 22.877001 24.257000 463 | C 13.898000 23.510000 24.403999 464 | N 13.771000 24.813000 24.018000 465 | C 12.795000 22.865999 24.966999 466 | C 12.935000 21.540001 25.358999 467 | P 14.658000 17.063999 29.247000 468 | O 13.633000 17.912001 29.920000 469 | O 14.863000 15.717000 29.825001 470 | O 16.033001 17.879999 29.284000 471 | C 17.243000 17.320000 28.742001 472 | C 18.208000 18.464001 28.757999 473 | O 17.716000 19.427999 27.829000 474 | C 18.230000 19.236000 30.058001 475 | O 18.978001 18.583000 31.084000 476 | C 18.885000 20.518999 29.577999 477 | C 18.275999 20.693001 28.188000 478 | N 17.164000 21.659000 28.139000 479 | C 15.874000 21.535999 28.580000 480 | N 15.129000 22.614000 28.308001 481 | C 15.990000 23.436001 27.673000 482 | C 15.765000 24.729000 27.117001 483 | O 14.719000 25.372999 27.066999 484 | N 16.926001 25.257000 26.604000 485 | C 18.157000 24.666000 26.579000 486 | N 19.208000 25.386000 26.096001 487 | N 18.350000 23.438000 27.052999 488 | C 17.231001 22.893000 27.570000 489 | -------------------------------------------------------------------------------- /examples/draw.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example for drawing a molecule using mogli and GR 3 | """ 4 | import gr 5 | import mogli 6 | 7 | molecules = mogli.read('examples/dna.xyz') 8 | gr.clearws() 9 | gr.setviewport(0, 1, 0, 1) 10 | mogli.draw(molecules[0], bonds_param=1.15, camera=((60, 0, 0), 11 | (0, 0, 0), 12 | (0, 1, 0))) 13 | gr.settextalign(gr.TEXT_HALIGN_CENTER, gr.TEXT_VALIGN_TOP) 14 | gr.text(0.5, 1.0, 'DNA example') 15 | gr.updatews() 16 | -------------------------------------------------------------------------------- /examples/export.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example for exporting a molecule to an HTML5 file using mogli 3 | """ 4 | import mogli 5 | 6 | molecules = mogli.read('examples/dna.xyz') 7 | mogli.BOND_RADIUS = 0.05 8 | mogli.export(molecules[0], 'dna.html', width=1920, height=1080, 9 | bonds_param=1.15, camera=((40, 0, 0), 10 | (0, 0, 0), 11 | (0, 1, 0))) 12 | -------------------------------------------------------------------------------- /examples/show.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example for interactively displaying a molecule using mogli 3 | """ 4 | import mogli 5 | 6 | molecules = mogli.read('examples/dna.xyz') 7 | for molecule in molecules: 8 | mogli.show(molecule, bonds_param=1.15) 9 | -------------------------------------------------------------------------------- /mogli.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | """ 4 | Simple visualization of molecules. 5 | """ 6 | 7 | from __future__ import absolute_import 8 | from __future__ import division 9 | from __future__ import print_function 10 | from __future__ import unicode_literals 11 | 12 | from typing import List, Tuple, Optional 13 | 14 | import argparse 15 | import itertools 16 | 17 | import numpy as np 18 | import numpy.linalg as la 19 | try: 20 | import pybel 21 | except ImportError: 22 | pybel = None 23 | import gr3 24 | import glfw 25 | from OpenGL.GL import glEnable, glDisable, glClear, glBindFramebuffer, glViewport,\ 26 | GL_FRAMEBUFFER, GL_DEPTH_TEST, GL_DEPTH_BUFFER_BIT, GL_COLOR_BUFFER_BIT,\ 27 | GL_MULTISAMPLE 28 | 29 | # Atom color rgb tuples (used for rendering, may be changed by users) 30 | ATOM_COLORS = np.array([(0, 0, 0), # Avoid atomic number to index conversion 31 | (255, 255, 255), (217, 255, 255), (204, 128, 255), 32 | (194, 255, 0), (255, 181, 181), (144, 144, 144), 33 | (48, 80, 248), (255, 13, 13), (144, 224, 80), 34 | (179, 227, 245), (171, 92, 242), (138, 255, 0), 35 | (191, 166, 166), (240, 200, 160), (255, 128, 0), 36 | (255, 255, 48), (31, 240, 31), (128, 209, 227), 37 | (143, 64, 212), (61, 225, 0), (230, 230, 230), 38 | (191, 194, 199), (166, 166, 171), (138, 153, 199), 39 | (156, 122, 199), (224, 102, 51), (240, 144, 160), 40 | (80, 208, 80), (200, 128, 51), (125, 128, 176), 41 | (194, 143, 143), (102, 143, 143), (189, 128, 227), 42 | (225, 161, 0), (166, 41, 41), (92, 184, 209), 43 | (112, 46, 176), (0, 255, 0), (148, 255, 255), 44 | (148, 224, 224), (115, 194, 201), (84, 181, 181), 45 | (59, 158, 158), (36, 143, 143), (10, 125, 140), 46 | (0, 105, 133), (192, 192, 192), (255, 217, 143), 47 | (166, 117, 115), (102, 128, 128), (158, 99, 181), 48 | (212, 122, 0), (148, 0, 148), (66, 158, 176), 49 | (87, 23, 143), (0, 201, 0), (112, 212, 255), 50 | (255, 255, 199), (217, 225, 199), (199, 225, 199), 51 | (163, 225, 199), (143, 225, 199), (97, 225, 199), 52 | (69, 225, 199), (48, 225, 199), (31, 225, 199), 53 | (0, 225, 156), (0, 230, 117), (0, 212, 82), 54 | (0, 191, 56), (0, 171, 36), (77, 194, 255), 55 | (77, 166, 255), (33, 148, 214), (38, 125, 171), 56 | (38, 102, 150), (23, 84, 135), (208, 208, 224), 57 | (255, 209, 35), (184, 184, 208), (166, 84, 77), 58 | (87, 89, 97), (158, 79, 181), (171, 92, 0), 59 | (117, 79, 69), (66, 130, 150), (66, 0, 102), 60 | (0, 125, 0), (112, 171, 250), (0, 186, 255), 61 | (0, 161, 255), (0, 143, 255), (0, 128, 255), 62 | (0, 107, 255), (84, 92, 242), (120, 92, 227), 63 | (138, 79, 227), (161, 54, 212), (179, 31, 212), 64 | (179, 31, 186), (179, 13, 166), (189, 13, 135), 65 | (199, 0, 102), (204, 0, 89), (209, 0, 79), 66 | (217, 0, 69), (224, 0, 56), (230, 0, 46), 67 | (235, 0, 38), (255, 0, 255), (255, 0, 255), 68 | (255, 0, 255), (255, 0, 255), (255, 0, 255), 69 | (255, 0, 255), (255, 0, 255), (255, 0, 255), 70 | (255, 0, 255)], dtype=np.float32)/255.0 71 | 72 | # Atom numbers mapped to their symbols 73 | ATOM_NUMBERS = {"H": 1, "HE": 2, "LI": 3, "BE": 4, "B": 5, "C": 6, "N": 7, 74 | "O": 8, "F": 9, "NE": 10, "NA": 11, "MG": 12, "AL": 13, 75 | "SI": 14, "P": 15, "S": 16, "CL": 17, "AR": 18, "K": 19, 76 | "CA": 20, "SC": 21, "TI": 22, "V": 23, "CR": 24, "MN": 25, 77 | "FE": 26, "CO": 27, "NI": 28, "CU": 29, "ZN": 30, "GA": 31, 78 | "GE": 32, "AS": 33, "SE": 34, "BR": 35, "KR": 36, "RB": 37, 79 | "SR": 38, "Y": 39, "ZR": 40, "NB": 41, "MO": 42, "TC": 43, 80 | "RU": 44, "RH": 45, "PD": 46, "AG": 47, "CD": 48, "IN": 49, 81 | "SN": 50, "SB": 51, "TE": 52, "I": 53, "XE": 54, "CS": 55, 82 | "BA": 56, "LA": 57, "CE": 58, "PR": 59, "ND": 60, "PM": 61, 83 | "SM": 62, "EU": 63, "GD": 64, "TB": 65, "DY": 66, "HO": 67, 84 | "ER": 68, "TM": 69, "YB": 70, "LU": 71, "HF": 72, "TA": 73, 85 | "W": 74, "RE": 75, "OS": 76, "IR": 77, "PT": 78, "AU": 79, 86 | "HG": 80, "TL": 81, "PB": 82, "BI": 83, "PO": 84, "AT": 85, 87 | "RN": 86, "FR": 87, "RA": 88, "AC": 89, "TH": 90, "PA": 91, 88 | "U": 92, "NP": 93, "PU": 94, "AM": 95, "CM": 96, "BK": 97, 89 | "CF": 98, "ES": 99, "FM": 100, "MD": 101, "NO": 102, 90 | "LR": 103, "RF": 104, "DB": 105, "SG": 106, "BH": 107, 91 | "HS": 108, "MT": 109, "DS": 110, "RG": 111, "CN": 112, 92 | "UUB": 112, "UUT": 113, "UUQ": 114, "UUP": 115, "UUH": 116, 93 | "UUS": 117, "UUO": 118} 94 | 95 | # Atom valence radii in Å (used for bond calculation) 96 | ATOM_VALENCE_RADII = np.array([0, # Avoid atomic number to index conversion 97 | 230, 930, 680, 350, 830, 680, 680, 680, 640, 98 | 1120, 970, 1100, 1350, 1200, 750, 1020, 990, 99 | 1570, 1330, 990, 1440, 1470, 1330, 1350, 1350, 100 | 1340, 1330, 1500, 1520, 1450, 1220, 1170, 1210, 101 | 1220, 1210, 1910, 1470, 1120, 1780, 1560, 1480, 102 | 1470, 1350, 1400, 1450, 1500, 1590, 1690, 1630, 103 | 1460, 1460, 1470, 1400, 1980, 1670, 1340, 1870, 104 | 1830, 1820, 1810, 1800, 1800, 1990, 1790, 1760, 105 | 1750, 1740, 1730, 1720, 1940, 1720, 1570, 1430, 106 | 1370, 1350, 1370, 1320, 1500, 1500, 1700, 1550, 107 | 1540, 1540, 1680, 1700, 2400, 2000, 1900, 1880, 108 | 1790, 1610, 1580, 1550, 1530, 1510, 1500, 1500, 109 | 1500, 1500, 1500, 1500, 1500, 1500, 1600, 1600, 110 | 1600, 1600, 1600, 1600, 1600, 1600, 1600, 1600, 111 | 1600, 1600, 1600, 1600, 1600, 1600], 112 | dtype=np.float32)/1000.0 113 | # Prevent unintentional changes 114 | ATOM_VALENCE_RADII.flags.writeable = False 115 | 116 | # Atom radii in Å (used for rendering, scaled down by factor 0.4, may be 117 | # changed by users) 118 | ATOM_RADII = np.array(ATOM_VALENCE_RADII, copy=True)*0.4 119 | 120 | # Bond radius in Å (used for rendering, may be changed by users) 121 | BOND_RADIUS = 0.1 122 | 123 | # Shade of gray used to render bond (from 0 black to 1 white) 124 | BOND_GRAY_SHADE = 0.3 125 | 126 | 127 | def _create_rotation_matrix(angle, x, y, z): 128 | """ Creates a 3x3 rotation matrix. """ 129 | if la.norm((x, y, z)) < 0.0001: 130 | return np.eye(3, dtype=np.float32) 131 | x, y, z = np.array((x, y, z))/la.norm((x, y, z)) 132 | matrix = np.zeros((3, 3), dtype=np.float32) 133 | cos = np.cos(angle) 134 | sin = np.sin(angle) 135 | matrix[0, 0] = x*x*(1-cos)+cos 136 | matrix[1, 0] = x*y*(1-cos)+sin*z 137 | matrix[0, 1] = x*y*(1-cos)-sin*z 138 | matrix[2, 0] = x*z*(1-cos)-sin*y 139 | matrix[0, 2] = x*z*(1-cos)+sin*y 140 | matrix[1, 1] = y*y*(1-cos)+cos 141 | matrix[1, 2] = y*z*(1-cos)-sin*x 142 | matrix[2, 1] = y*z*(1-cos)+sin*x 143 | matrix[2, 2] = z*z*(1-cos)+cos 144 | return matrix 145 | 146 | _mouse_dragging = False 147 | _previous_mouse_position = None 148 | _camera = None 149 | 150 | 151 | def _mouse_move_callback(window, x, y): 152 | """ Mouse move event handler for GLFW. """ 153 | global _previous_mouse_position, _camera 154 | if _mouse_dragging and _camera is not None: 155 | width, height = glfw.get_window_size(window) 156 | dx = (x-_previous_mouse_position[0])/width 157 | dy = (y-_previous_mouse_position[1])/height 158 | rotation_intensity = la.norm((dx, dy)) * 2 159 | eye, center, up = _camera 160 | camera_distance = la.norm(center-eye) 161 | forward = (center-eye)/camera_distance 162 | right = np.cross(forward, up) 163 | rotation_axis = (up*dx+right*dy) 164 | rotation_matrix = _create_rotation_matrix(-rotation_intensity, 165 | rotation_axis[0], 166 | rotation_axis[1], 167 | rotation_axis[2]) 168 | forward = np.dot(rotation_matrix, forward) 169 | up = np.dot(rotation_matrix, up) 170 | eye = center-forward*camera_distance 171 | _camera = eye, center, up 172 | _previous_mouse_position = (x, y) 173 | 174 | 175 | def _mouse_click_callback(window, button, status, modifiers): 176 | """ Mouse click event handler for GLFW. """ 177 | global _mouse_dragging, _previous_mouse_position 178 | if button == glfw.MOUSE_BUTTON_LEFT: 179 | if status == glfw.RELEASE: 180 | _mouse_dragging = False 181 | _previous_mouse_position = None 182 | elif status == glfw.PRESS: 183 | _mouse_dragging = True 184 | _previous_mouse_position = glfw.get_cursor_pos(window) 185 | 186 | 187 | class Molecule(object): 188 | """ 189 | A class for storing information about a Molecule. The atomic bonds need to 190 | be explicitly calculated using the calculate_bonds function. 191 | """ 192 | def __init__(self, atomic_numbers, positions): 193 | self.atomic_numbers = atomic_numbers 194 | self.positions = positions 195 | self.bonds = None 196 | self._atomic_radii = None 197 | self._atom_colors = None 198 | 199 | @property 200 | def atom_count(self) -> int: 201 | return len(self.atomic_numbers) 202 | 203 | @property 204 | def atom_colors(self) -> Optional[List[Tuple[float]]]: 205 | return self._atom_colors 206 | 207 | @atom_colors.setter 208 | def atom_colors(self, colors: Optional[List[Tuple[float]]]) -> None: 209 | if colors is None: 210 | self._atom_colors = None 211 | else: 212 | if len(colors) != len(self.atomic_numbers): 213 | raise RuntimeError("The number of colors must match the number of atoms in the Molecule") 214 | 215 | for color in colors: 216 | 217 | if len(color) != 3: 218 | raise RuntimeError(f"The value {color} is not a valid color tuple") 219 | 220 | for channel in color: 221 | if channel < 0 or channel > 1: 222 | raise RuntimeError("The value of each color channel must be between 0 and 1") 223 | 224 | self._atom_colors = colors 225 | 226 | @property 227 | def atomic_radii(self): 228 | if self._atomic_radii is None: 229 | self._atomic_radii = ATOM_RADII[self.atomic_numbers] 230 | return self._atomic_radii 231 | 232 | def calculate_bonds(self, method='radii', param=None): 233 | """ 234 | This function calculates which pair of atoms forms an atomic bond. 235 | Pairs of indices are returned, indicating that the two associated atoms 236 | form a bond. 237 | 238 | The method used for this is specified with the parameter 'method'. 239 | Currently two methods are supported: 240 | - using the valence radii of the atoms (optionally multiplied by 241 | param) 242 | - using a constant delta as maximum distance between to bonded atoms 243 | """ 244 | method = method.lower() 245 | if method not in ('radii', 'constant_delta'): 246 | raise ValueError("method should be either 'radii' or 'constant " 247 | "delta', not '%s'" % method) 248 | 249 | if self.bonds is not None: 250 | # Bonds were calculated for this molecule already. 251 | # Reuse those, if the method and optional parameter are like those 252 | # provided to this function. 253 | if self.bonds.method == method: 254 | if self.bonds.param == param: 255 | return 256 | elif param is None: 257 | # Allow reuse when default param is used. 258 | if method == 'radii' and self.bonds.param == 1.0: 259 | return 260 | 261 | if method == 'radii': 262 | radii = ATOM_VALENCE_RADII[self.atomic_numbers] 263 | if param is None: 264 | param = 1.0 265 | 266 | if method == 'constant_delta': 267 | if param is None: 268 | raise ValueError("method 'constant_delta' requires param to " 269 | "specify the maximum distance between two " 270 | "atoms to cause a bond between them.") 271 | 272 | index_pair_list = [] 273 | for index, position in enumerate(self.positions): 274 | distance_vectors = self.positions[index+1:] - position 275 | distances_squared = np.sum(np.power(distance_vectors, 2), axis=1) 276 | if method == 'radii': 277 | deltas = np.square((radii[index+1:]+radii[index])*param) 278 | elif method == 'constant_delta': 279 | deltas = param**2 280 | nonzero_indices = np.nonzero(distances_squared <= deltas) 281 | num_bonds = len(nonzero_indices[0]) 282 | if num_bonds > 0: 283 | index_pairs = np.hstack((np.ones(num_bonds, dtype=np.uint32) 284 | .reshape(num_bonds, 1)*index, 285 | index+1+nonzero_indices[0] 286 | .reshape(num_bonds, 1))) 287 | index_pair_list.append(index_pairs) 288 | if index_pair_list: 289 | index_pairs = np.vstack(index_pair_list) 290 | else: 291 | index_pairs = np.zeros((0, 2)) 292 | self.bonds = Bonds(method, param, index_pairs) 293 | 294 | 295 | class Bonds(object): 296 | """ 297 | A simple class for storing information about atomic bonds in a molecule. 298 | """ 299 | def __init__(self, method, param, index_pairs): 300 | self.method = method 301 | self.param = param 302 | self.index_pairs = index_pairs 303 | 304 | def __len__(self): 305 | return len(self.index_pairs) 306 | 307 | 308 | class UnknownFileFormatException(Exception): 309 | """ 310 | This exception will be raised when read() is called with file name that 311 | cannot be mapped to a known file format. 312 | """ 313 | pass 314 | 315 | 316 | def read(file_name, file_format=None): 317 | """ 318 | Reads molecules from a file. If available, pybel will be used to read the 319 | file, otherwise only xyz files can be read. 320 | If the file_format is unknown, a UnknownFileFormatException will be raised. 321 | """ 322 | if pybel: 323 | if file_format is None: 324 | file_extension = file_name.split('.')[-1] 325 | if file_extension in pybel.informats.keys(): 326 | file_format = file_extension 327 | else: 328 | message = ("The file format {format} is not supported by " 329 | "pybel. If the file extension does not match the " 330 | "actual format, please use the file_format " 331 | "parameter.".format(format=file_extension)) 332 | raise UnknownFileFormatException(message) 333 | elif file_format not in pybel.informats.keys(): 334 | message = ("The file format {format} is not supported by pybel, so" 335 | "mogli cannot understand it yet. Sorry!" 336 | .format(format=file_format)) 337 | raise UnknownFileFormatException(message) 338 | try: 339 | # Try reading the file with the 'b' option set. This speeds up 340 | # file reading dramatically for some formats (e.g. pdb). 341 | # If this fails for some reasons, use pybel.readfile(). 342 | molecules = [] 343 | conv = pybel.ob.OBConversion() 344 | conv.SetOptions("b".encode('ascii'), conv.INOPTIONS) 345 | success = conv.SetInFormat(file_format.encode('ascii')) 346 | if success: 347 | mol = pybel.ob.OBMol() 348 | has_molecule = conv.ReadFile(mol, file_name.encode('ascii')) 349 | while has_molecule: 350 | num_atoms = mol.NumAtoms() 351 | atomic_numbers = np.zeros(num_atoms, dtype=np.uint8) 352 | positions = np.zeros((num_atoms, 3), dtype=np.float32) 353 | for i, atom in enumerate(pybel.ob.OBMolAtomIter(mol)): 354 | atomic_numbers[i] = atom.GetAtomicNum() 355 | positions[i] = (atom.GetX(), atom.GetY(), atom.GetZ()) 356 | molecules.append(Molecule(atomic_numbers, positions)) 357 | has_molecule = conv.Read(mol) 358 | except: 359 | molecule_file = pybel.readfile(file_format.encode('ascii'), 360 | file_name.encode('ascii')) 361 | molecules = [] 362 | for molecule in molecule_file: 363 | atoms = molecule.atoms 364 | atomic_numbers_iterator = (atom.atomicnum for atom in atoms) 365 | atomic_numbers = np.fromiter(atomic_numbers_iterator, 366 | dtype=np.uint8, count=len(atoms)) 367 | positions_iterator = itertools.chain.from_iterable(atom.coords 368 | for atom 369 | in atoms) 370 | positions = np.fromiter(positions_iterator, 371 | dtype=np.float32, count=len(atoms)*3) 372 | positions.shape = (len(atoms), 3) 373 | molecules.append(Molecule(atomic_numbers, positions)) 374 | return molecules 375 | else: 376 | if (file_format == 'xyz' or 377 | (file_format is None and file_name.endswith('.xyz'))): 378 | with open(file_name, 'r') as input_file: 379 | file_content = input_file.readlines() 380 | molecules = [] 381 | while file_content: 382 | num_atoms = int(file_content[0]) 383 | atom_strings = file_content[2:2+num_atoms] 384 | file_content = file_content[2+num_atoms:] 385 | atomic_numbers = [ATOM_NUMBERS[atom_string.split()[0].upper()] 386 | for atom_string 387 | in atom_strings] 388 | positions = np.array([(float(atom_string.split()[1]), 389 | float(atom_string.split()[2]), 390 | float(atom_string.split()[3])) 391 | for atom_string 392 | in atom_strings]) 393 | molecules.append(Molecule(atomic_numbers, positions)) 394 | return molecules 395 | else: 396 | message = ("Failed to import pybel. Currently mogli only supports " 397 | "xyz files if pybel is not installed.") 398 | raise UnknownFileFormatException(message) 399 | 400 | 401 | def show(molecule, width=500, height=500, 402 | show_bonds=True, bonds_method='radii', bonds_param=None, 403 | camera=None, title='mogli'): 404 | """ 405 | Interactively show the given molecule with OpenGL. By default, bonds are 406 | drawn, if this is undesired the show_bonds parameter can be set to False. 407 | For information on the bond calculation, see Molecule.calculate_bonds. 408 | If you pass a tuple of camera position, center of view and an up vector to 409 | the camera parameter, the camera will be set accordingly. Otherwise the 410 | molecule will be viewed in the direction of the z axis, with the y axis 411 | pointing upward. 412 | """ 413 | global _camera 414 | molecule.positions -= np.mean(molecule.positions, axis=0) 415 | max_atom_distance = np.max(la.norm(molecule.positions, axis=1)) 416 | if show_bonds: 417 | molecule.calculate_bonds(bonds_method, bonds_param) 418 | 419 | # If GR3 was initialized earlier, it would use a different context, so 420 | # it will be terminated first. 421 | gr3.terminate() 422 | 423 | # Initialize GLFW and create an OpenGL context 424 | glfw.init() 425 | glfw.window_hint(glfw.SAMPLES, 16) 426 | window = glfw.create_window(width, height, title, None, None) 427 | glfw.make_context_current(window) 428 | glEnable(GL_MULTISAMPLE) 429 | 430 | # Set up the camera (it will be changed during mouse rotation) 431 | if camera is None: 432 | camera_distance = -max_atom_distance*2.5 433 | camera = ((0, 0, camera_distance), 434 | (0, 0, 0), 435 | (0, 1, 0)) 436 | camera = np.array(camera) 437 | _camera = camera 438 | 439 | # Create the GR3 scene 440 | gr3.setbackgroundcolor(255, 255, 255, 0) 441 | _create_gr3_scene(molecule, show_bonds) 442 | 443 | # Configure GLFW 444 | glfw.set_cursor_pos_callback(window, _mouse_move_callback) 445 | glfw.set_mouse_button_callback(window, _mouse_click_callback) 446 | glfw.swap_interval(1) 447 | # Start the GLFW main loop 448 | while not glfw.window_should_close(window): 449 | glfw.poll_events() 450 | width, height = glfw.get_window_size(window) 451 | glViewport(0, 0, width, height) 452 | _set_gr3_camera() 453 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 454 | gr3.drawimage(0, width, 0, height, 455 | width, height, gr3.GR3_Drawable.GR3_DRAWABLE_OPENGL) 456 | glfw.swap_buffers(window) 457 | glfw.terminate() 458 | gr3.terminate() 459 | 460 | 461 | def draw(molecule, 462 | xmin=0, xmax=1, ymin=0, ymax=1, width=500, height=500, 463 | show_bonds=True, bonds_method='radii', bonds_param=None, 464 | camera=None): 465 | """ 466 | Draw the given molecule with the GR framework. By default, bonds are drawn, 467 | if this is undesired the show_bonds parameter can be set to False. 468 | For information on the bond calculation, see Molecule.calculate_bonds. 469 | If you pass a tuple of camera position, center of view and an up vector to 470 | the camera parameter, the camera will be set accordingly. Otherwise the 471 | molecule will be viewed in the direction of the z axis, with the y axis 472 | pointing upward. 473 | """ 474 | global _camera 475 | molecule.positions -= np.mean(molecule.positions, axis=0) 476 | max_atom_distance = np.max(la.norm(molecule.positions, axis=1)) 477 | if show_bonds: 478 | molecule.calculate_bonds(bonds_method, bonds_param) 479 | 480 | if camera is None: 481 | camera_distance = -max_atom_distance*2.5 482 | camera = ((0, 0, camera_distance), 483 | (0, 0, 0), 484 | (0, 1, 0)) 485 | camera = np.array(camera) 486 | _camera = camera 487 | 488 | # Create the GR3 scene 489 | gr3.setbackgroundcolor(255, 255, 255, 0) 490 | _set_gr3_camera() 491 | _create_gr3_scene(molecule, show_bonds) 492 | glEnable(GL_DEPTH_TEST) 493 | gr3.setquality(gr3.GR3_Quality.GR3_QUALITY_OPENGL_2X_SSAA) 494 | gr3.drawimage(xmin, xmax, ymin, ymax, 495 | width, height, gr3.GR3_Drawable.GR3_DRAWABLE_GKS) 496 | glBindFramebuffer(GL_FRAMEBUFFER, 0) 497 | glDisable(GL_DEPTH_TEST) 498 | 499 | 500 | def export(molecule, file_name, width=500, height=500, 501 | show_bonds=True, bonds_method='radii', bonds_param=None, 502 | camera=None): 503 | """ 504 | Draw the given molecule into a given file. The file type is determined by 505 | the file extension, e.g. '.png' or '.html'. By default, bonds are drawn, 506 | if this is undesired the show_bonds parameter can be set to False. 507 | For information on the bond calculation, see Molecule.calculate_bonds. 508 | If you pass a tuple of camera position, center of view and an up vector to 509 | the camera parameter, the camera will be set accordingly. Otherwise the 510 | molecule will be viewed in the direction of the z axis, with the y axis 511 | pointing upward. 512 | """ 513 | global _camera 514 | molecule.positions -= np.mean(molecule.positions, axis=0) 515 | max_atom_distance = np.max(la.norm(molecule.positions, axis=1)) 516 | if show_bonds: 517 | molecule.calculate_bonds(bonds_method, bonds_param) 518 | 519 | if camera is None: 520 | if _camera is None: 521 | camera_distance = -max_atom_distance*2.5 522 | camera = ((0, 0, camera_distance), 523 | (0, 0, 0), 524 | (0, 1, 0)) 525 | else: 526 | camera = _camera 527 | camera = np.array(camera) 528 | _camera = camera 529 | 530 | # Create the GR3 scene 531 | gr3.setbackgroundcolor(255, 255, 255, 0) 532 | _set_gr3_camera() 533 | _create_gr3_scene(molecule, show_bonds) 534 | glEnable(GL_DEPTH_TEST) 535 | gr3.export(file_name, width, height) 536 | glBindFramebuffer(GL_FRAMEBUFFER, 0) 537 | glDisable(GL_DEPTH_TEST) 538 | 539 | 540 | def _set_gr3_camera(): 541 | """ Set the GR3 camera, using the global _camera variable. """ 542 | eye, center, up = _camera 543 | gr3.cameralookat(eye[0], eye[1], eye[2], 544 | center[0], center[1], center[2], 545 | up[0], up[1], up[2]) 546 | 547 | 548 | def _create_gr3_scene(molecule: Molecule, show_bonds=True): 549 | """ 550 | Create the GR3 scene from the provided molecule and - if show_bonds is 551 | True (default) - the atomic bonds in the molecule. 552 | """ 553 | gr3.clear() 554 | num_atoms = len(molecule.atomic_numbers) 555 | colors = molecule.atom_colors if molecule.atom_colors else ATOM_COLORS[molecule.atomic_numbers] 556 | 557 | gr3.drawspheremesh(num_atoms, 558 | molecule.positions, 559 | colors, 560 | molecule.atomic_radii) 561 | 562 | if show_bonds and len(molecule.bonds.index_pairs) > 0: 563 | index_pairs = molecule.bonds.index_pairs 564 | num_bonds = len(molecule.bonds) 565 | bond_positions = molecule.positions[index_pairs[:, 0]] 566 | bond_directions = molecule.positions[index_pairs[:, 1]]-bond_positions 567 | bond_lengths = la.norm(bond_directions, axis=1) 568 | bond_directions /= bond_lengths.reshape(num_bonds, 1) 569 | bond_radii = np.ones(num_bonds, dtype=np.float32)*BOND_RADIUS 570 | bond_colors = np.ones((num_bonds, 3), dtype=np.float32)*BOND_GRAY_SHADE 571 | gr3.drawcylindermesh(num_bonds, bond_positions, bond_directions, 572 | bond_colors, bond_radii, bond_lengths) 573 | 574 | 575 | def main(): 576 | """ 577 | Command line interface 578 | """ 579 | parser = argparse.ArgumentParser() 580 | 581 | show_bonds_group = parser.add_mutually_exclusive_group() 582 | show_bonds_group.add_argument('--show_bonds', 583 | action='store_true', default=True, 584 | help='show atomic bonds (default)') 585 | show_bonds_group.add_argument('--hide_bonds', 586 | action='store_false', dest='show_bonds', 587 | help='do not show atomic bonds') 588 | parser.add_argument('-m', '--bond_method', 589 | default='radii', choices=['radii', 'constant_delta']) 590 | parser.add_argument('-p', '--bond_param', default=1.0, type=float) 591 | parser.add_argument('-i', '--molecule', default=0, type=int, 592 | help='the molecule index for files with multiple' 593 | ' molecules') 594 | parser.add_argument('-f', '--format', type=str) 595 | parser.add_argument('file_name') 596 | args, unknown = parser.parse_known_args() 597 | if unknown: 598 | print('The following arguments were ignored:') 599 | print(' '.join(unknown)) 600 | molecules = read(args.file_name, args.format) 601 | show(molecules[args.molecule], 602 | show_bonds=args.show_bonds, 603 | bonds_method=args.bond_method, 604 | bonds_param=args.bond_param) 605 | 606 | 607 | if __name__ == '__main__': 608 | main() 609 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=CVS 16 | 17 | # Pickle collected data for later comparisons. 18 | #persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | # DEPRECATED 25 | #include-ids=no 26 | 27 | # DEPRECATED 28 | #symbols=no 29 | 30 | 31 | [MESSAGES CONTROL] 32 | 33 | # Enable the message, report, category or checker with the given id(s). You can 34 | # either give multiple identifier separated by comma (,) or put this option 35 | # multiple time. See also the "--disable" option for examples. 36 | #enable= 37 | 38 | # Disable the message, report, category or checker with the given id(s). You 39 | # can either give multiple identifiers separated by comma (,) or put this 40 | # option multiple times (only on the command line, not in the configuration 41 | # file where it should appear only once).You can also use "--disable=all" to 42 | # disable everything first and then reenable specific checks. For example, if 43 | # you want to run only the similarities checker, you can use "--disable=all 44 | # --enable=similarities". If you want to run only the classes checker, but have 45 | # no Warning level messages displayed, use"--disable=all --enable=classes 46 | # --disable=W" 47 | #disable= 48 | 49 | 50 | [REPORTS] 51 | 52 | # Set the output format. Available formats are text, parseable, colorized, msvs 53 | # (visual studio) and html. You can also give a reporter class, eg 54 | # mypackage.mymodule.MyReporterClass. 55 | output-format=text 56 | 57 | # Put messages in a separate file for each module / package specified on the 58 | # command line instead of printing them on stdout. Reports (if any) will be 59 | # written in a file name "pylint_global.[txt|html]". 60 | files-output=no 61 | 62 | # Tells whether to display a full report or only the messages 63 | reports=yes 64 | 65 | # Python expression which should return a note less than 10 (10 is the highest 66 | # note). You have access to the variables errors warning, statement which 67 | # respectively contain the number of errors / warnings messages and the total 68 | # number of statements analyzed. This is used by the global evaluation report 69 | # (RP0004). 70 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 71 | 72 | # Add a comment according to your evaluation note. This is used by the global 73 | # evaluation report (RP0004). 74 | comment=no 75 | 76 | # Template used to display messages. This is a python new-style format string 77 | # used to format the message information. See doc for all details 78 | #msg-template= 79 | 80 | 81 | [BASIC] 82 | 83 | # Required attributes for module, separated by a comma 84 | required-attributes= 85 | 86 | # List of builtins function names that should not be used, separated by a comma 87 | bad-functions=map,filter,apply,input,file 88 | 89 | # Good variable names which should always be accepted, separated by a comma 90 | good-names=i,j,k,x,y,z,ex,Run,_ 91 | 92 | # Bad variable names which should always be refused, separated by a comma 93 | bad-names=foo,bar,baz,toto,tutu,tata 94 | 95 | # Colon-delimited sets of names that determine each other's naming style when 96 | # the name regexes allow several styles. 97 | name-group= 98 | 99 | # Include a hint for the correct naming format with invalid-name 100 | include-naming-hint=no 101 | 102 | # Regular expression matching correct function names 103 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 104 | 105 | # Naming hint for function names 106 | function-name-hint=[a-z_][a-z0-9_]{2,30}$ 107 | 108 | # Regular expression matching correct variable names 109 | variable-rgx=[a-z_][a-z0-9_]{1,30}$ 110 | 111 | # Naming hint for variable names 112 | variable-name-hint=[a-z_][a-z0-9_]{1,30}$ 113 | 114 | # Regular expression matching correct constant names 115 | const-rgx=(([A-Za-z_][A-Za-z0-9_]*)|(__.*__))$ 116 | 117 | # Naming hint for constant names 118 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 119 | 120 | # Regular expression matching correct attribute names 121 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 122 | 123 | # Naming hint for attribute names 124 | attr-name-hint=[a-z_][a-z0-9_]{2,30}$ 125 | 126 | # Regular expression matching correct argument names 127 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 128 | 129 | # Naming hint for argument names 130 | argument-name-hint=[a-z_][a-z0-9_]{2,30}$ 131 | 132 | # Regular expression matching correct class attribute names 133 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 134 | 135 | # Naming hint for class attribute names 136 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 137 | 138 | # Regular expression matching correct inline iteration names 139 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 140 | 141 | # Naming hint for inline iteration names 142 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ 143 | 144 | # Regular expression matching correct class names 145 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 146 | 147 | # Naming hint for class names 148 | class-name-hint=[A-Z_][a-zA-Z0-9]+$ 149 | 150 | # Regular expression matching correct module names 151 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 152 | 153 | # Naming hint for module names 154 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 155 | 156 | # Regular expression matching correct method names 157 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 158 | 159 | # Naming hint for method names 160 | method-name-hint=[a-z_][a-z0-9_]{2,30}$ 161 | 162 | # Regular expression which should only match function or class names that do 163 | # not require a docstring. 164 | no-docstring-rgx=__.*__ 165 | 166 | # Minimum line length for functions/classes that require docstrings, shorter 167 | # ones are exempt. 168 | docstring-min-length=-1 169 | 170 | 171 | [FORMAT] 172 | 173 | # Maximum number of characters on a single line. 174 | max-line-length=80 175 | 176 | # Regexp for a line that is allowed to be longer than the limit. 177 | ignore-long-lines=^\s*(# )??$ 178 | 179 | # Allow the body of an if to be on the same line as the test if there is no 180 | # else. 181 | single-line-if-stmt=no 182 | 183 | # List of optional constructs for which whitespace checking is disabled 184 | no-space-check=trailing-comma,dict-separator 185 | 186 | # Maximum number of lines in a module 187 | max-module-lines=1000 188 | 189 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 190 | # tab). 191 | indent-string=' ' 192 | 193 | # Number of spaces of indent required inside a hanging or continued line. 194 | indent-after-paren=4 195 | 196 | 197 | [LOGGING] 198 | 199 | # Logging modules to check that the string format arguments are in logging 200 | # function parameter format 201 | logging-modules=logging 202 | 203 | 204 | [MISCELLANEOUS] 205 | 206 | # List of note tags to take in consideration, separated by a comma. 207 | notes=FIXME,XXX,TODO 208 | 209 | 210 | [SIMILARITIES] 211 | 212 | # Minimum lines number of a similarity. 213 | min-similarity-lines=4 214 | 215 | # Ignore comments when computing similarities. 216 | ignore-comments=yes 217 | 218 | # Ignore docstrings when computing similarities. 219 | ignore-docstrings=yes 220 | 221 | # Ignore imports when computing similarities. 222 | ignore-imports=no 223 | 224 | 225 | [TYPECHECK] 226 | 227 | # Tells whether missing members accessed in mixin class should be ignored. A 228 | # mixin class is detected if its name ends with "mixin" (case insensitive). 229 | ignore-mixin-members=yes 230 | 231 | # List of module names for which member attributes should not be checked 232 | # (useful for modules/projects where namespaces are manipulated during runtime 233 | # and thus existing member attributes cannot be deduced by static analysis 234 | ignored-modules= 235 | 236 | # List of classes names for which member attributes should not be checked 237 | # (useful for classes with attributes dynamically set). 238 | ignored-classes=SQLObject 239 | 240 | # When zope mode is activated, add a predefined set of Zope acquired attributes 241 | # to generated-members. 242 | zope=no 243 | 244 | # List of members which are set dynamically and missed by pylint inference 245 | # system, and so shouldn't trigger E0201 when accessed. Python regular 246 | # expressions are accepted. 247 | generated-members=REQUEST,acl_users,aq_parent 248 | 249 | 250 | [VARIABLES] 251 | 252 | # Tells whether we should check for unused import in __init__ files. 253 | init-import=no 254 | 255 | # A regular expression matching the name of dummy variables (i.e. expectedly 256 | # not used). 257 | dummy-variables-rgx=_$|dummy 258 | 259 | # List of additional names supposed to be defined in builtins. Remember that 260 | # you should avoid to define new builtins when possible. 261 | additional-builtins= 262 | 263 | 264 | [CLASSES] 265 | 266 | # List of interface methods to ignore, separated by a comma. This is used for 267 | # instance to not check methods defines in Zope's Interface base class. 268 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by 269 | 270 | # List of method names used to declare (i.e. assign) instance attributes. 271 | defining-attr-methods=__init__,__new__,setUp 272 | 273 | # List of valid names for the first argument in a class method. 274 | valid-classmethod-first-arg=cls 275 | 276 | # List of valid names for the first argument in a metaclass class method. 277 | valid-metaclass-classmethod-first-arg=mcs 278 | 279 | 280 | [DESIGN] 281 | 282 | # Maximum number of arguments for function / method 283 | max-args=5 284 | 285 | # Argument names that match this expression will be ignored. Default to name 286 | # with leading underscore 287 | ignored-argument-names=_.* 288 | 289 | # Maximum number of locals for function / method body 290 | max-locals=15 291 | 292 | # Maximum number of return / yield for function / method body 293 | max-returns=6 294 | 295 | # Maximum number of branch for function / method body 296 | max-branches=12 297 | 298 | # Maximum number of statements in function / method body 299 | max-statements=50 300 | 301 | # Maximum number of parents for a class (see R0901). 302 | max-parents=7 303 | 304 | # Maximum number of attributes for a class (see R0902). 305 | max-attributes=7 306 | 307 | # Minimum number of public methods for a class (see R0903). 308 | min-public-methods=2 309 | 310 | # Maximum number of public methods for a class (see R0904). 311 | max-public-methods=20 312 | 313 | 314 | [IMPORTS] 315 | 316 | # Deprecated modules which should not be used, separated by a comma 317 | deprecated-modules=regsub,TERMIOS,Bastion,rexec 318 | 319 | # Create a graph of every (i.e. internal and external) dependencies in the 320 | # given file (report RP0402 must not be disabled) 321 | import-graph= 322 | 323 | # Create a graph of external dependencies in the given file (report RP0402 must 324 | # not be disabled) 325 | ext-import-graph= 326 | 327 | # Create a graph of internal dependencies in the given file (report RP0402 must 328 | # not be disabled) 329 | int-import-graph= 330 | 331 | 332 | [EXCEPTIONS] 333 | 334 | # Exceptions that will emit a warning when being caught. Defaults to 335 | # "Exception" 336 | overgeneral-exceptions=Exception 337 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | norecursedirs = env 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | Setup for mogli: https://pypi.python.org/pypi/mogli 3 | """ 4 | from setuptools import setup 5 | import os.path 6 | import shutil 7 | import subprocess 8 | 9 | 10 | def create_required_files(): 11 | """ 12 | Creates the files required for building a package. 13 | """ 14 | # Manifest 15 | if not os.path.isfile('MANIFEST.in'): 16 | with open('MANIFEST.in', 'w') as manifest_in: 17 | manifest_in.write('include *.txt\n') 18 | manifest_in.write('include *.rst\n') 19 | 20 | # License 21 | if not os.path.isfile('LICENSE.txt'): 22 | shutil.copyfile('LICENSE', 'LICENSE.txt') 23 | 24 | # Readme in reST format 25 | if not os.path.isfile('README.rst'): 26 | subprocess.call(['pandoc', 'README.md', '-t', 'rst', '-o', 'README.rst']) 27 | 28 | create_required_files() 29 | # README.txt was created in the function above, so we can use its content for 30 | # the long description: 31 | with open('README.rst') as readme_file: 32 | long_description = readme_file.read() 33 | 34 | setup(name='mogli', 35 | version='0.3.0', 36 | description='Simple visualization of molecules in python', 37 | long_description=long_description, 38 | author='Florian Rhiem', 39 | author_email='florian.rhiem@gmail.com', 40 | url='https://github.com/FlorianRhiem/mogli', 41 | classifiers=[ 42 | 'Development Status :: 5 - Production/Stable', 43 | 'Intended Audience :: Science/Research', 44 | 'Intended Audience :: Developers', 45 | 'License :: OSI Approved :: MIT License', 46 | 'Programming Language :: Python', 47 | 'Topic :: Scientific/Engineering :: Visualization', 48 | 'Topic :: Scientific/Engineering :: Chemistry', 49 | 'Topic :: Scientific/Engineering :: Physics', 50 | ], 51 | py_modules=['mogli'], 52 | install_requires=['glfw', 'gr', 'numpy', 'PyOpenGL']) 53 | -------------------------------------------------------------------------------- /tests/test_bonds.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Tests for mogli.Molecule.calculate_bonds() 4 | """ 5 | 6 | from __future__ import absolute_import 7 | from __future__ import division 8 | from __future__ import print_function 9 | from __future__ import unicode_literals 10 | 11 | import mogli 12 | import pytest 13 | 14 | 15 | def test_calculate_bonds_with_default_method(): 16 | """ 17 | Calculate bonds with no parameters. 18 | """ 19 | molecule = mogli.read('examples/dna.xyz')[0] 20 | assert molecule.bonds is None 21 | molecule.calculate_bonds() 22 | assert molecule.bonds is not None 23 | assert molecule.bonds.method == 'radii' 24 | 25 | 26 | def test_calculate_bonds_with_wrong_method(): 27 | """ 28 | Calculate bonds with an invalid method name and check the expected failure. 29 | """ 30 | molecule = mogli.read('examples/dna.xyz')[0] 31 | with pytest.raises(ValueError): 32 | molecule.calculate_bonds(method='invalid_method_name') 33 | 34 | 35 | def test_calculate_bonds_with_radii_method(): 36 | """ 37 | Calculate bonds with the radii method. 38 | """ 39 | molecule = mogli.read('examples/dna.xyz')[0] 40 | molecule.calculate_bonds(method='radii') 41 | assert molecule.bonds is not None 42 | 43 | 44 | def test_calculate_bonds_with_constant_delta_method_with_default_param(): 45 | """ 46 | Calculate bonds with the constant_delta method without providing a value 47 | for param and check the expected failure. 48 | """ 49 | molecule = mogli.read('examples/dna.xyz')[0] 50 | with pytest.raises(ValueError): 51 | molecule.calculate_bonds(method='constant_delta') 52 | assert molecule.bonds is None 53 | 54 | 55 | def test_calculate_bonds_with_constant_delta_method_with_explicit_param(): 56 | """ 57 | Calculate bonds with the constant_delta method. 58 | """ 59 | molecule = mogli.read('examples/dna.xyz')[0] 60 | molecule.calculate_bonds(method='constant_delta', param=3.0) 61 | assert molecule.bonds is not None 62 | 63 | 64 | def test_calculate_bonds_empty(): 65 | """ 66 | Calculate bonds in a way that should not produce any bonds. 67 | """ 68 | molecule = mogli.read('examples/dna.xyz')[0] 69 | molecule.calculate_bonds(method='constant_delta', param=0.0) 70 | assert molecule.bonds is not None 71 | assert len(molecule.bonds) == 0 72 | 73 | 74 | def test_calculate_bonds_memoization_with_other_method_or_param(): 75 | """ 76 | Calculate bonds and check the function memoization with varying parameters. 77 | """ 78 | molecule = mogli.read('examples/dna.xyz')[0] 79 | assert molecule.bonds is None 80 | molecule.calculate_bonds(method='constant_delta', param=0.0) 81 | bonds = molecule.bonds 82 | molecule.calculate_bonds(method='radii', param=0.0) 83 | assert molecule.bonds is not bonds 84 | bonds = molecule.bonds 85 | molecule.calculate_bonds(method='radii') 86 | assert molecule.bonds is not bonds 87 | bonds = molecule.bonds 88 | molecule.calculate_bonds(method='radii', param=0.0) 89 | assert molecule.bonds is not bonds 90 | 91 | 92 | def test_calculate_bonds_memoization_with_default_param(): 93 | """ 94 | Calculate bonds and check the function memoization for default paremeters. 95 | """ 96 | molecule = mogli.read('examples/dna.xyz')[0] 97 | assert molecule.bonds is None 98 | molecule.calculate_bonds() 99 | bonds = molecule.bonds 100 | molecule.calculate_bonds() 101 | assert molecule.bonds is bonds 102 | 103 | 104 | def test_calculate_bonds_memoization_with_explicit_param(): 105 | """ 106 | Calculate bonds and check the function memoization for given paremeters. 107 | """ 108 | molecule = mogli.read('examples/dna.xyz')[0] 109 | assert molecule.bonds is None 110 | molecule.calculate_bonds(param=1.0) 111 | bonds = molecule.bonds 112 | molecule.calculate_bonds(param=1.0) 113 | assert molecule.bonds is bonds 114 | 115 | if __name__ == '__main__': 116 | test_calculate_bonds_with_default_method() 117 | test_calculate_bonds_with_wrong_method() 118 | test_calculate_bonds_with_radii_method() 119 | test_calculate_bonds_with_constant_delta_method_with_default_param() 120 | test_calculate_bonds_with_constant_delta_method_with_explicit_param() 121 | test_calculate_bonds_empty() 122 | test_calculate_bonds_memoization_with_other_method_or_param() 123 | test_calculate_bonds_memoization_with_default_param() 124 | test_calculate_bonds_memoization_with_explicit_param() 125 | -------------------------------------------------------------------------------- /tests/test_draw.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Tests for mogli.draw() 4 | 5 | These tests only check if the function does not fail, they do not check the 6 | results. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | from __future__ import division 11 | from __future__ import print_function 12 | from __future__ import unicode_literals 13 | 14 | import gr 15 | import mogli 16 | 17 | 18 | def test_draw(): 19 | """ 20 | Draw a molecule using GR 21 | """ 22 | molecule = mogli.read('examples/dna.xyz')[0] 23 | # Draw the molecule (with the current GR workstation type) 24 | mogli.draw(molecule) 25 | # Close GR again 26 | gr.emergencyclosegks() 27 | 28 | if __name__ == '__main__': 29 | test_draw() 30 | -------------------------------------------------------------------------------- /tests/test_export.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Tests for mogli.export() 4 | 5 | These tests only check if the function does not fail, they do not check the 6 | results. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | from __future__ import division 11 | from __future__ import print_function 12 | from __future__ import unicode_literals 13 | 14 | import os.path 15 | import mogli 16 | 17 | 18 | def test_export(): 19 | """ 20 | Export a molecule to an HTML file. 21 | """ 22 | molecule = mogli.read('examples/dna.xyz')[0] 23 | mogli.export(molecule, 'test.html') 24 | assert os.path.exists('test.html') 25 | 26 | if __name__ == '__main__': 27 | test_export() 28 | -------------------------------------------------------------------------------- /tests/test_read.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Tests for mogli.read() 4 | """ 5 | 6 | from __future__ import absolute_import 7 | from __future__ import division 8 | from __future__ import print_function 9 | from __future__ import unicode_literals 10 | 11 | import mogli 12 | import pytest 13 | 14 | 15 | def test_read_xyz_file(): 16 | """ 17 | Read an xyz file and check the number of atoms. 18 | """ 19 | molecules = mogli.read('examples/dna.xyz') 20 | assert len(molecules) == 1 21 | assert len(molecules[0].positions) == 486 22 | 23 | 24 | def test_read_xyz_file_and_get_atomic_radii(): 25 | """ 26 | Read an xyz file, check the number of atomic_radii and their memoization. 27 | """ 28 | molecule = mogli.read('examples/dna.xyz')[0] 29 | atomic_radii = molecule.atomic_radii 30 | assert len(atomic_radii) == 486 31 | assert atomic_radii is molecule.atomic_radii 32 | 33 | 34 | def test_read_unknown_file(): 35 | """ 36 | Read an unknown file and check the expected failure. 37 | """ 38 | with pytest.raises(mogli.UnknownFileFormatException): 39 | mogli.read('unknown')[0] 40 | 41 | if __name__ == '__main__': 42 | test_read_xyz_file() 43 | test_read_xyz_file_and_get_atomic_radii() 44 | test_read_unknown_file() 45 | -------------------------------------------------------------------------------- /tests/test_show.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Tests for mogli.show() 4 | 5 | These tests only check if the function does not fail, they do not check the 6 | results. 7 | """ 8 | 9 | from __future__ import absolute_import 10 | from __future__ import division 11 | from __future__ import print_function 12 | from __future__ import unicode_literals 13 | 14 | import signal 15 | import mogli 16 | import glfw 17 | 18 | 19 | def test_show(): 20 | """ 21 | Show a molecule and close the window after 1 second. 22 | """ 23 | 24 | def alarm_handler(signal_number, stack_frame): 25 | """ 26 | Close the current GLFW window when called due to a SIGALRM. 27 | """ 28 | assert signal_number == signal.SIGALRM 29 | window = glfw.get_current_context() 30 | assert window is not None 31 | glfw.set_window_should_close(window, True) 32 | 33 | molecule = mogli.read("examples/dna.xyz")[0] 34 | # Set the signal handler 35 | signal.signal(signal.SIGALRM, alarm_handler) 36 | # Set the alarm to 1 second (and check no previous alarm had been set) 37 | assert signal.alarm(1) == 0 38 | # Show the molecule (and open the GLFW window closed by the alarm handler) 39 | mogli.show(molecule) 40 | 41 | 42 | def test_show_custom_colors(): 43 | """ 44 | Show a molecule and close the window after 1 second. 45 | """ 46 | 47 | def alarm_handler(signal_number, stack_frame): 48 | """ 49 | Close the current GLFW window when called due to a SIGALRM. 50 | """ 51 | assert signal_number == signal.SIGALRM 52 | window = glfw.get_current_context() 53 | assert window is not None 54 | glfw.set_window_should_close(window, True) 55 | 56 | molecule: mogli.Molecule = mogli.read("examples/dna.xyz")[0] 57 | 58 | # Set a list of custom colors 59 | molecule.atom_colors = [ 60 | (i / (molecule.atom_count - 1), 0, 0) for i in range(molecule.atom_count) 61 | ] 62 | 63 | # Set the signal handler 64 | signal.signal(signal.SIGALRM, alarm_handler) 65 | # Set the alarm to 1 second (and check no previous alarm had been set) 66 | assert signal.alarm(1) == 0 67 | # Show the molecule (and open the GLFW window closed by the alarm handler) 68 | mogli.show(molecule) 69 | 70 | 71 | if __name__ == "__main__": 72 | test_show() 73 | --------------------------------------------------------------------------------