RenderMolecules 0.0.1
A way to render molecules in Blender
Requires Python: <3.12,>=3.8
Keywords: molecules, chemistry, blender, rendering, quantum

# RenderMolecules
Easily import molecules from various filetypes, and render them in Blender.

## Supported filetypes:
 - XYZ (using XYZfile class)
 - CUBE (using CUBEfile class)
 - ...

## Periodic Boundary Conditions:
TODO, NOT IMPLEMENTED

## Rendering volumetric data:
RenderMolecules also allows for rendering volumetric data, such as the electron 
density of an orbital. For this, there are two options, 
- Without writing and reading from a file, in memory:
    1. Read the volumetric data using CUBEfile.readVolumetricData 
    2. Calculate the isosurface using CUBEfile.calculateIsosurface
    3. Create the isosurface in the Blender scene using utils.createIsosurface
- With writing and reading from a file:
    1. Read the volumetric data using CUBEfile.readVolumetricData 
    2. Calculate the isosurface, and write it to a .ply file using CUBEfile.writePLY
    3. Read the .ply file using utils.loadPLY
         
    > Note: With this method you have to be careful that you do the same displacements and rotations as you do to the structure in your scene. Otherwise, the location of the isosurface might be incorrect.

Almost always the first method will be easier. Using the second method straight from Blender (i.e. calculated the isosurface using PyTessel), I had some troubles that on my Windows machine. Blender would crash and exit. It works if I write the .ply file from the commandline directly. On my Linux machine, both work fine.

## Dependencies:
 - PyTessel
 - scikit-image
 - numpy
 - bpy

## How it works:
### Creating atoms:
The atoms generated by Structure.createAtoms() created using [Vertex Instancing](https://docs.blender.org/manual/en/latest/scene_layout/object/properties/instancing/verts.html). This means that all atoms of a single element (e.g. all hydrogen atoms) are part of a mesh, and only a single hydrogen atom sphere has to be created (positioned at the origin, visible in the viewport but not in the render). This significantly speeds up the creation of the structure, and general working with the structure if you're for example moving around in the scene.

### Creating bonds:
The Structure class has a method Structure.findBondsBasedOnDistance that will find all bonds in the structure, based on the distances between atoms. The allowed bond lengths are specified in the ElementData.bondLengths dictionary, where all possible bonds should have an key and value. If, for example, your system consists of H, N, C and O atoms, then your dictionary would have at least HH, HN, HC, HO, NN,... pairs. It is essential that the keys are in alphabetical order. If you do not want to create bonds between two hydrogens, you can for example set the value of HH to 0.0, as then no two H atoms will be close enough to eachother to create a bond. 

> If you change anything in one of the files (like a dictionary key-value pair), Blender has to be restarted.

The above method returns a list of Bond instances, and sets the Structure._bonds to the found bonds.

> This could also be done using Instancing, although because bonds have a direction, it is a bit more tricky and I have not had time/the need to figure it out yet (also how to generate it automatically from python makes it a bit harder).

#### Creating bonds with a higher bond order:
Sometimes you might want to create a molecule with double (e.g. in benzene) or triple bonds (e.g. in CO). This can be done using Structure.generateBondOrderBond. Using some linear algebra, it takes a vector, and calculates a displacement vector that is both perpendicular to the bond you want to change, and the vector you input. 

> Any time you use Structure.generateBondOrderBond, the original bond you input into it is removed from the Structure._bond list, and the new bonds are added at the end. This means that the order of bonds changes, and it might take some trial and error to generate the correct higher bondorder bonds.

## Manipulating structures:

> This is currently not a perfect implementation. We need to store the manipulations (both translations and rotations), their type (i.e. is it a rotation or a translation) and their order. We currently only store the translations. Thus, if a structure is rotated, it is likely that the isosurface will be incorrectly placed, as its vertices are not rotated with the rest of the structure. The way I'm thinking of doing this is by creating a three-tuple for every manipulation, storing:
> - Type (Rotation or translation)
> - Axis (R3 vector. For a rotation this is the axis, for a translation it is the translation vector itself)
> - Angle (not necessary in the case of a translation)
> 
> New manipulations are appended to the end of the list. In that way, when we're calculating the vertices of an isosurface, we loop through items in the manipulations list, and do the same manipulations to the created vertices (and maybe normals in case of rotation????).

### Translation
Structures can be translated using Structure.setCOMto, where the center of mass of the structure can be set to a certain position.

### Rotation
Structures can be rotated using a couple of different methods:
 - Structure.rotateAroundX/Y/Z: rotate around the X/Y/Z axis with a certain angle
 - Structure.rotateAroundAxis: rotate around an arbitrary axis

Rotation angle given in degrees, counterclockwise.

> It is important that any manipulation happens before Structure.createAtoms or Structure.createBonds, as otherwise the atoms or bonds might not be in the correct positions.

## Materials: