From db446ef4638cbb0a41c2cacd53ca3436f24b9e19 Mon Sep 17 00:00:00 2001 From: KOLANICH Date: Thu, 25 Jan 2018 20:10:21 +0300 Subject: [PATCH] Initial commit --- .editorconfig | 12 ++++ .github/.templateMarker | 1 + .github/dependabot.yml | 8 +++ .github/workflows/CI.yml | 15 +++++ .gitignore | 10 ++++ .gitlab-ci.yml | 49 +++++++++++++++ Code_Of_Conduct.md | 1 + MANIFEST.in | 4 ++ ReadMe.md | 19 ++++++ UNLICENSE | 24 ++++++++ mol2scad.py | 125 +++++++++++++++++++++++++++++++++++++++ pyproject.toml | 40 +++++++++++++ util.scad | 61 +++++++++++++++++++ 13 files changed, 369 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/.templateMarker create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/CI.yml create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 Code_Of_Conduct.md create mode 100644 MANIFEST.in create mode 100644 ReadMe.md create mode 100644 UNLICENSE create mode 100644 mol2scad.py create mode 100644 pyproject.toml create mode 100644 util.scad diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c9162b9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +indent_style = tab +indent_size = 4 +insert_final_newline = true +end_of_line = lf + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 diff --git a/.github/.templateMarker b/.github/.templateMarker new file mode 100644 index 0000000..5e3a3e0 --- /dev/null +++ b/.github/.templateMarker @@ -0,0 +1 @@ +KOLANICH/python_project_boilerplate.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..89ff339 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "daily" + allow: + - dependency-type: "all" diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 0000000..7fe33b3 --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,15 @@ +name: CI +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-22.04 + steps: + - name: typical python workflow + uses: KOLANICH-GHActions/typical-python-workflow@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f73c5ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +__pycache__ +*.pyc +*.pyo +./mol2scad.py.egg-info +./build +./dist +./.eggs +*.mol +*.sdf +*.scad diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..1b9c3d4 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,49 @@ +image: registry.gitlab.com/kolanich-subgroups/docker-images/fixed_python:latest +#image: registry.gitlab.com/kolanich-subgroups/docker-images/python_graal_docker:latest + +variables: + DOCKER_DRIVER: overlay2 + SAST_ANALYZER_IMAGE_TAG: latest + SAST_DISABLE_DIND: "true" + SAST_CONFIDENCE_LEVEL: 5 + CODECLIMATE_VERSION: latest + +include: + - template: SAST.gitlab-ci.yml + - template: Code-Quality.gitlab-ci.yml + +build: + tags: + - shared + stage: build + variables: + GIT_DEPTH: "1" + PYTHONUSERBASE: ${CI_PROJECT_DIR}/python_user_packages + + cache: + paths: + - $PYTHONUSERBASE + - ./molFiles + + before_script: + - export PATH="$PATH:$PYTHONUSERBASE/bin" # don't move into `variables` + - mkdir ./molFiles || true; + - wget -nc -O ./molFiles/CuPc.sdf https://webbook.nist.gov/cgi/cbook.cgi?Str3File=C147148 || true + - wget -nc -O ./molFiles/DCM.sdf https://webbook.nist.gov/cgi/cbook.cgi?Str3File=C75092 || true + - mkdir ./scad + script: + - python3 setup.py bdist_wheel + - pip3 install --upgrade --user --pre ./dist/*.whl + - coverage run -a --source=mol2scad mol2scad.py ./molFiles/CuPc.sdf >./scad/CuPc.scad + - coverage run -a --source=mol2scad mol2scad.py ./molFiles/DCM.sdf > ./scad/DCM.scad + - cp ./util.scad ./scad/ + - coverage report -m + - coverage xml + coverage: /^TOTAL(?:\s*\d+){4}\s(\d+%).+/ + artifacts: + paths: + - dist + - scad + reports: + #junit: ./rspec.xml + cobertura: ./coverage.xml diff --git a/Code_Of_Conduct.md b/Code_Of_Conduct.md new file mode 100644 index 0000000..2b781c7 --- /dev/null +++ b/Code_Of_Conduct.md @@ -0,0 +1 @@ +No codes of conduct! diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..20f0fa8 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include UNLICENSE +include *.md +include tests +include .editorconfig diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..dcb50b3 --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,19 @@ +mol2scad.py [![Unlicensed work](https://raw.githubusercontent.com/unlicense/unlicense.org/master/static/favicon.png)](https://unlicense.org/) +=============== +~~[![GitLab Build Status](https://gitlab.com/KOLANICH/mol2scad.py/badges/master/pipeline.svg)](https://gitlab.com/KOLANICH/mol2scad.py/pipelines/master/latest)~~ +~~![GitLab Coverage](https://gitlab.com/KOLANICH/mol2scad.py/badges/master/coverage.svg)~~ +[![Libraries.io Status](https://img.shields.io/librariesio/github/KOLANICH/mol2scad.py.svg)](https://libraries.io/github/KOLANICH/mol2scad.py) +[![Code style: antiflash](https://img.shields.io/badge/code%20style-antiflash-FFF.svg)](https://codeberg.org/KOLANICH-tools/antiflash.py) + +This tool converts `mol` and `sdf` files into OpenSCAD files showing a 3D model of a molecule. Either run it in this folder or copy `util.scad` to the folder with generated files. Also you will need [scad-utils](https://github.com/OskarLinde/scad-utils.git) either installed or put in the same dir. + +Requirements +------------ +* [`mollusk`](https://github.com/georglind/mollusk) ![License](https://img.shields.io/github/license/georglind/mollusk.svg) - `mol` (and `sdf`) file parser +* [`webcolors`](https://github.com/ubernostrum/webcolors) [![PyPi Status](https://img.shields.io/pypi/v/webcolors.svg)](https://pypi.python.org/pypi/webcolors) [![CI Build Status](https://github.com/ubernostrum/webcolors/workflows/CI/badge.svg)](https://github.com/ubernostrum/webcolors/actions?query=workflow%3ACI) ![License](https://img.shields.io/github/license/ubernostrum/webcolors.svg) - colors +* [`SolidPython`](https://github.com/SolidCode/SolidPython) [![PyPi Status](https://img.shields.io/pypi/v/plumbum.svg)](https://pypi.python.org/pypi/solid) [![CircleCI Build Status](https://circleci.com/gh/SolidCode/SolidPython.svg?style=shield)](https://circleci.com/gh/SolidCode/SolidPython) ![License](https://img.shields.io/github/license/SolidCode/SolidPython.svg) - OpenSCAD AST +* `mendeleev` [![PyPi Status](https://img.shields.io/pypi/v/mendeleev.svg)](https://pypi.org/pypi/mendeleev) - chemical elements database +* [`plumbum`](https://github.com/tomerfiliba/plumbum) [![PyPi Status](https://img.shields.io/pypi/v/plumbum.svg)](https://pypi.org/pypi/plumbum) + [![CI Build Status](https://github.com/tomerfiliba/plumbum/workflows/CI/badge.svg)](https://github.com/tomerfiliba/plumbum/actions) + [![Coveralls Coverage](https://coveralls.io/repos/tomerfiliba/plumbum/badge.svg?branch=master&service=github)](https://coveralls.io/github/tomerfiliba/plumbum?branch=master) + ![License](https://img.shields.io/github/license/tomerfiliba/plumbum.svg) - for command line interface diff --git a/UNLICENSE b/UNLICENSE new file mode 100644 index 0000000..efb9808 --- /dev/null +++ b/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/mol2scad.py b/mol2scad.py new file mode 100644 index 0000000..29ea707 --- /dev/null +++ b/mol2scad.py @@ -0,0 +1,125 @@ +from mol.parser import parse as parseMol +import solid as sld +import mendeleev +import webcolors + +from plumbum import cli + +class rot2Vec(sld.OpenSCADObject): + def __init__(self, v2, v1=[0, 0, 1]): + sld.OpenSCADObject.__init__(self, 'rot2Vec', {"v2":v2, "v1":v1}) + +def rotByVec(v): + return rot2Vec(v) + +def convertWebColorToOpenScad(cstr): + try: + res = webcolors.hex_to_name(cstr) + except: + res = [c/255 for c in webcolors.hex_to_rgb(cstr)] + #res.append(0.5) + return res + +class singleBond(sld.OpenSCADObject): + def __init__(self, l1, l2, centr, length, color1, color2): + sld.OpenSCADObject.__init__(self, 'singleBond', { + "l1":l1, + "l2":l2, + "color1": color1, + "color2": color2, + }) + +def makeSingleBond(l1, l2, center, length, color1, color2): + return singleBond(l1, l2, center, length, convertWebColorToOpenScad(color1), convertWebColorToOpenScad(color2)) + +class bond(sld.OpenSCADObject): + def __init__(self, a1v, a2v, color1, color2, multiplicity=1): + sld.OpenSCADObject.__init__(self, 'bond', { + "a1v":a1v, + "a2v":a2v, + "color1": color1, + "color2": color2, + "multiplicity": multiplicity + }) + +def makeBond(a1v, a2v, color1, color2, multiplicity=1): + return bond(a1v, a2v, convertWebColorToOpenScad(color1), convertWebColorToOpenScad(color2), multiplicity=multiplicity) + +class module(sld.OpenSCADObject): + def __init__(self, **kwargs): + sld.OpenSCADObject.__init__(self, 'module', kwargs) + +def useOpenScadVariable(obj, vars={}): + s=sld.scad_render(obj) + for placeholder, replacement in vars.items(): + s=s.replace('"'+placeholder+'"', replacement) + return s + +def genScadSource(name, vectors, spieces, bonds, lFact=1, r=1, bondR=None, bondSpacingFactor=3): + if not bondR: + bondR=0.05*r + + spiecesDescr = {el:mendeleev.element(el) for el in spieces} + maxRad=max( (sp.covalent_radius for sp in spiecesDescr.values()) ) + + def createElementCall(name, el): + class element(sld.OpenSCADObject): + def __init__(self, pos): + sld.OpenSCADObject.__init__(self, el.name, {"pos":pos}) + element.__name__=el.name + return element + + + elToScadRemap={elN:createElementCall(elN, sp) for elN, sp in spiecesDescr.items()} + + spiecesChem = [spiecesDescr[el] for el in spieces] + spiecesScad = [elToScadRemap[el] for el in spieces] + + def createElementModule(name, sp): + elModSrc="module "+sp.name+"(pos=[0,0,0]){"+useOpenScadVariable( + sld.translate("%%%positionExprPlaceholder%%%")( + sld.color(convertWebColorToOpenScad(sp.cpk_color))( + sld.sphere(r*sp.covalent_radius/maxRad) + ) + ), + {"%%%positionExprPlaceholder%%%": "pos*lFact"} + )+"\n}" + return elModSrc + + src=[ + "$fs=0.1;", + "r="+str(r)+";", + "bondR="+str(bondR)+";", + "lFact="+str(lFact)+";", + "bondSpacingFactor="+str(bondSpacingFactor)+";", + "include <./util.scad>", + ] + src.extend((createElementModule(elN, sp) for elN, sp in spiecesDescr.items())) + + for i, v in enumerate(vectors): + src.append( + sld.scad_render( + spiecesScad[i](v) + ) + ) + + for i, (a1, a2, mult) in enumerate(bonds): + src.append( + sld.scad_render( + makeBond(vectors[a1], vectors[a2], spiecesChem[a1].cpk_color, spiecesChem[a2].cpk_color, mult) + ) + ) + return src + +class mol2scadApp(cli.Application): + """Converts .mol and .sdf files into OpenSCAD .scad files, which can be rendered into 3D models.""" + + r=cli.SwitchAttr(["radius"], float, default=1., help="A radius of the biggest atom.") + bondRFactor=cli.SwitchAttr(["bond-radius-factor"], float, default=0.05, help="A radius of a single bond is this number multiplied by radius of the largest atom.") + bondSpacingFactor=cli.SwitchAttr(["bond-spacing-factor"], float, default=3., help="When a bond is multiple its parts are placed on the circumference of the bond radius multiplied by this number.") + lFact=cli.SwitchAttr(["l-factor"], float, default=1., help="A factor bond length are multiplied. Useful when atoms overlap and you want to see bonds clearly.") + + def main(self, fileName:cli.ExistingFile): + print("\n".join(genScadSource(*parseMol(fileName), lFact=self.lFact, r=self.r, bondR=self.bondRFactor*self.r, bondSpacingFactor=3))) + +mol2scadApp.run() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f944369 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,40 @@ +[build-system] +requires = ["setuptools>=61.2.0", "wheel", "setuptools_scm[toml]>=3.4.3"] +build-backend = "setuptools.build_meta" + +[project] +name = "mol2scad" +authors = [{name = "KOLANICH"}] +description = "A tool to render mol and sdf files to OpenSCAD" +readme = "ReadMe.md" +keywords = ["mol", "structure data file", "OpenSCAD", "molecule"] +license = {text = "Unlicense"} +classifiers = [ + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Development Status :: 4 - Beta", + "Environment :: Other Environment", + "Intended Audience :: End Users/Desktop", + "License :: Public Domain", + "Operating System :: OS Independent", + "Topic :: Software Development :: Libraries :: Python Modules", + "Intended Audience :: Science/Research", + "Intended Audience :: Education", +] +urls = {Homepage = "https://codeberg.org/KOLANICH-physics/mol2scad.py"} +requires-python = ">=3.4" +dependencies = [ + "webcolors", # @ git+https://github.com/ubernostrum/webcolors.git" + "mollusk", # @ git+https://github.com/georglind/mollusk.git" + "solid", # @ git+https://github.com/SolidCode/SolidPython.git" + "mendeleev", + "plumbum # @ git+https://github.com/tomerfiliba/plumbum.git", +] +dynamic = ["version"] + +[tool.setuptools] +zip-safe = true +py-modules = ["mol2scad"] +include-package-data = false + +[tool.setuptools_scm] diff --git a/util.scad b/util.scad new file mode 100644 index 0000000..8643a47 --- /dev/null +++ b/util.scad @@ -0,0 +1,61 @@ +include + +function normalize(vec) = vec / norm(vec); + +function identity3()=[[1,0,0],[0,1,0],[0,0,1]]; + +module rot2Vec(v2, v1=[0, 0, 1]){ + v1=v1; + v2=normalize(v2); + + ax=cross(v1, v2); + c=v1 * v2; + ssc = [ + [0, -ax[2], ax[1]], + [ax[2], 0, -ax[0]], + [-ax[1], ax[0], 0] + ]; + + m=identity3() + ssc + (ssc*ssc)*(1-c)/(ax*ax); + //echo(m); + multmatrix(m) + children(); +}; + +module singleBond(l1, l2, color1, color2){ + union(){ + color(color2) + cylinder(h=l2, r=bondR); + translate([0, 0, -l1]) + color(color1) + cylinder(h=l1, r=bondR); + } +}; + +module bond(a1v, a2v, color1, color2, multiplicity=1){ + a1v=a1v*lFact; + a2v=a2v*lFact; + centr=(a2v+a1v)/2; + bondVec=normalize(a2v-a1v); + //echo(bondVec); + l1=norm(centr-a1v); + l2=norm(centr-a2v); + mOfs=multiplicity%2; + bondSpace=bondSpacingFactor*bondR; + translate(centr){ + union(){ + rot2Vec(bondVec){ + if (multiplicity>1){ + for (i = [0: multiplicity-1]){ + rotate(360/multiplicity*i, [0,0, 1]) + translate([0, bondSpace, 0]) + singleBond(l1, l2, color1, color2); + }; + }else{ + singleBond(l1, l2, color1, color2); + }; + }; + }; + }; +}; +