More Rotations / Transformations

It would be useful to have some support for more complex rotations / transformations.

@Thomas previously linked to GitHub - smparker/orient-molecule: Python script for command-line manipulation of molecules

@Dhruv_J - you were asking for more plugin script ideas? I can think of a few based on that repository (e.g., see the README):

  • Rotate around the best-fit of a selection of atoms
  • Reflect around the best-fit plane of a set of atoms
  • Best fit plane: given more than three atoms
    • translate so centroid of selected atoms is at origin
    • rotate so atoms are in the xy plane and atoms 1 and 2 define x direction

I can imagine, for example, wanting to rotate a set of atoms around the normal to the best fit plane of the selection (e.g., some metal ligands)

Hello,
Happy Deepavali to all,

Can you please confirm how will be the input from the user, Will it be using an xyz file and then we could create geometryXYZ object and then apply the operations as they have done in the repository that you had mentioned.
OR by just passing the mol as done earlier?

Also how will we define the indices of the atoms that needs to be included in the best-fit planes, Can we do it like we had done for replace plugin ie something similar to

    indices = []

    for i, num in enumerate(atomic_numbers):
            if mol['atoms']['selected'][i]:
                indices.append(i)

Thanks

:diya_lamp:

Either? It’s certainly easy to ask Avogadro for the xyz input format to the script. I guess whatever seems easier.

Yes, this was my thought - the user selects some atoms and these define the best-fit plane. (In which case using cjson for the input to the script is probably better, since the XYZ format won’t include the selected atoms.

Hello,
Sorry for replying late,

I tried to code this but I am not understanding whether my output is correct or wrong.
Kindly have a look at the code:

def getOptions():
    userOptions = {}
    userOptions['angle'] = {}
    userOptions['angle']['label'] = 'Angle of Rotation'
    userOptions['angle']['type'] = 'float'
    userOptions['angle']['default'] = 0
    opts = {'userOptions': userOptions}
    return opts

def Rotate(opts, mol):
    angle = float(opts['angle'])
    atomic_numbers = mol['atoms']['elements']['number']
    selected_atoms = []

    for i in range(len(atomic_numbers)):
        if mol['atoms']['selected'][i]:
            selected_atoms.append(i)

    atom_names = []
    operation_list = OperationList()
    coordinates_array = np.array(mol['atoms']['coords']['3d']).reshape(-1, 3)
    selected_geometry = GeometryXYZ(
        names=atom_names,
        coordinates=coordinates_array,
        comment="Best-Fit Selection"
    )

    # Step 1: Translate so the centroid of selected atoms is at the origin
    translate_to_origin = CentroidTranslate(selected_atoms, fac=1.0)
    operation_list.append(translate_to_origin)

    # Step 2: Rotate so that atoms are in the xy plane and atoms 1 and 2 define the x direction
    rotate_to_xy_plane = NormalRotate(selected_atoms, angle)
    operation_list.append(rotate_to_xy_plane)

    #print(selected_geometry.coordinates)
    
    # Apply the sequence of operations to the molecule
    for operation in operation_list:
        operation(selected_geometry.coordinates)

    # Print the modified molecule
    print(selected_geometry.coordinates)

Input:

echo '{"angle":30,
 "cjson": {"chemicalJson": 1,
          "atoms": {"coords": {"3d": [0.617532, -0.027246, 0.481009, 0.156663, 0.031752, -0.362419, -0.774185, -0.004516, -0.11859,0.617532, -0.027246, 0.481009]}, "selected":[1,1,0,1],
           "elements": {"number": [1, 8, 1,4]}},
          "bonds": {"connections": {"index": [1, 2, 1, 0]},
                     "order": [1, 1]}}
}' | python rotation_script.py --run-command

Output:

[[ 0.59586659 -0.07929468 1.12826153]
[ 0.6193981 -0.00228632 0.16869508]
[-0.30969076 0.00113274 -0.08434275]
[ 0.59586659 -0.07929468 1.12826153]]

Can you please tell how can I know if this is correct or not?
I’ll be writing code for reflection very soon
Thanks

It seems like you’re defining a set of atoms and then rotating the whole molecule.

Beyond that, I think you’d want different scripts depending on what the user wants.

Like sometimes I want to rotate so that atoms 1 and 2 are in specific positions. @arugula_jones was asking about this for automating some analysis. So that would be one script.

But sometimes I want to take a set of selected atoms and rotate them around the centroid:

ethane

ethane.cjson (2.5 KB)

Another example would be:

Does that make sense? I want to rotate those selected atoms some angle around their centroid and normal. (e.g., 30°)

Hello,
Sorry for not being present for so many days (exam season :grimacing: :sweat:).
So basically at present we need two scripts:

“rotate around axis defined by pair of atoms”

rotate around normal of plane defined by list of atoms

Am I right? Or do we need scripts for all these options mentioned here

Thanks

I tried doing this but am not understanding whether its correct
Code:

def Rotate(opts,mol):
    angle=float(opts['angle'])
    atomic_numbers = mol['atoms']['elements']['number']
    selected_atoms  = []
    selected_coordinates=[]
    atom_names=[]
    
    for i in range(len(atomic_numbers)):
        if mol['atoms']['selected'][i]:
            selected_atoms.append(i)
            atom_names.append(name.get(atomic_numbers[i]))

    coords = mol['atoms']['coords']['3d']
    j=0
    for i in range(0, len(coords), 3):
        if j in selected_atoms:
            selected_coordinates.append(coords[i])
            selected_coordinates.append(coords[i+1])
            selected_coordinates.append(coords[i+2])
        j = j+1

    operation_list = OperationList()
    coordinates_array = np.array(selected_coordinates).reshape(-1, 3)
    selected_geometry = GeometryXYZ(
    names=atom_names,
    coordinates=coordinates_array,
    comment="Best-Fit Selection"
)
    selected=[]
    for k in range(len(selected_atoms)):
        selected.append(k)

    translate_to_origin = CentroidTranslate(selected, fac=-1.0)
    operation_list.append(translate_to_origin)

    rotate_to_xy_plane = NormalRotate(selected,angle)
    operation_list.append(rotate_to_xy_plane)
    
    rotate = ShiftedOperation(translate_to_origin, rotate_to_xy_plane)
    operation_list.append(rotate)

    for operation in operation_list:
        operation(selected_geometry.coordinates)


    for i, idx in enumerate(selected_atoms):
        start_idx = idx * 3
        end_idx = start_idx + 3
        mol['atoms']['coords']['3d'][start_idx:end_idx] = selected_geometry.coordinates[i]

    return mol
echo '{"angle":30,
>>  "cjson": {"chemicalJson": 1,
>>           "atoms": {"coords": {"3d": [0.617532, -0.027246, 0.481009, 0.156663, 0.031752, -0.362419, -0.774185, -0.004516, -0.11859,0.617532, -0.027246, 0.481009]}, "selected":[1,1,0,1],
>>            "elements": {"number": [1, 8, 1,4]}},
>>           "bonds": {"connections": {"index": [1, 2, 1, 0]},
>>                      "order": [1, 1]}}
>> }' | python rotation_script.py --run-command

Output:

{"cjson": {"chemicalJson": 1, "atoms": {"coords": {"3d": [0.11930055667962669, -0.015581411770221364, 0.29757784441826723, -0.23860111335925333, 0.031162823540442783, -0.5951556888365344, -0.774185, -0.004516, -0.11859, 0.11930055667962669, -0.015581411770221364, 0.29757784441826723]}, "selected": [1, 1, 0, 1], "elements": {"number": [1, 8, 1, 4]}}, "bonds": {"connections": {"index": [1, 2, 1, 0]}, 
"order": [1, 1]}}}

So I have applied Rotation only to the selected atoms.
Also can you please tell me how can I visualise the script on avogadro because when I try to drag and drop…The script gets installed but I dont know from where can I access it

Thank You

Welcome back – hope exams went well!

That depends on what the script declares as the name and menu path. For example in your previous script:

This means it shows up in the Build menu as “Replace Elements”.

I would guess you’re using the Build menu for this command? If not, it would likely show up under “Extensions”

I can think of lots of uses, but I think the most useful at the moment is rotate around the normal to a best-fit of a selection of atoms, as you’re doing now.

“Rotate around the axis of two atoms” also sounds pretty useful.

But “reflect through the best-fit plane” also seems useful as a separate script.

Hope that helps?

Thank you, Yes yes they were good.
But they arent over…actually the exams are back to back.So, might not be regular

I am not able to find the options in any of the menus after installing the plugins


Also kindly check the code which I have written above for rotating about the normal to the plane

Thank you

Also in reflect through the best-fit plane, The whole molecule must be reflected right unlike rotation where only selected atoms must be rotated

That’s a good point. Hmm, I’m trying to think what makes the most sense for reflect through the best-fit plane. I think you’re right … the selection implies the reflection, but the whole molecule should be reflected.

As far as checking the rotation … the easiest thing would be if you can share a GitHub link so I can just check the plugin, or to upload a .cjson file. That would take less time for me than copy-pasting from your result and checking if the result is correct. Thanks.

Actually I wasn’t very sure that if the code was correct or not …hence hadn’t pushed the code to github

So here are the rotate and reflect plugins

Could you please go through them, I feel that they might not be correct.

Also, do you have any solution or alternative for this. Is this problem… due to some build or configuration issues?

Thanks

Hello,
Are the above scripts upto the mark or are any modifications needed?
Actually exams are lined up till jan mid hence am not able to be regular…Apologies for the same

Do let me know if any changes are required…or should I send it to avogadro…I’ll do the needful at the earliest.
Thank you very much

@ghutchis
Hello sir,
Are these above scripts apt? Can I send a pr?

Do we need any other such scripts…Can I write one for “reflecting across a bond” …Actually I was thinking if can we have a reflect option which can reflect across a specific line of reflection…or do we have a reflect-duplication plugin ?.. I felt it would be helpful for aromatic benzene like compounds.

Any breakthroughs for this ? Will I have to build it again?

Also apart from plugins are there any other things I can contribute to? I even know C++ …Particularly If there are any analysis or math related issues or requirements then do let me know …I am less occupied the upcoming week so… will try to be regular in contributing

Thank you.

Hello,
I have sent a pr for the rotation and reflection plugins …Kindly have a look…Your feedback is awaited
Thanks