|
77 | 77 | #include "SceneRay.h" |
78 | 78 | #include "ScrollBar.h" |
79 | 79 | #include "SculptCache.h" |
| 80 | +#include "Seeker.h" |
80 | 81 | #include "Selector.h" |
81 | 82 | #include "Seq.h" |
82 | 83 | #include "Setting.h" |
|
114 | 115 | #include "ce_types.h" |
115 | 116 | #endif |
116 | 117 |
|
| 118 | +#include <glm/gtc/quaternion.hpp> |
117 | 119 | #include <glm/gtc/type_ptr.hpp> |
118 | 120 | #include <glm/vec3.hpp> |
119 | 121 |
|
@@ -17560,3 +17562,197 @@ pymol::Result<std::unordered_set<const pymol::CObject*>> ExecutiveGetObjectDeps( |
17560 | 17562 | obj_set.erase(&obj); |
17561 | 17563 | return obj_set; |
17562 | 17564 | } |
| 17565 | + |
| 17566 | +/** |
| 17567 | + * Run TM-align on two selections and return results. |
| 17568 | + * |
| 17569 | + * @param mobile_sele mobile selection (will be transformed) |
| 17570 | + * @param target_sele target selection (stays fixed) |
| 17571 | + * @param mobile_state state of mobile selection (0-based) |
| 17572 | + * @param target_state state of target selection (0-based) |
| 17573 | + * @param quiet suppress output |
| 17574 | + * @param transform apply superposition transform |
| 17575 | + * @param oname name for alignment object (empty = don't create) |
| 17576 | + * @param fast use fast mode (fewer iterations) |
| 17577 | + */ |
| 17578 | +pymol::Result<pymol::usalign::TMAlignResult> ExecutiveUSalign(PyMOLGlobals* G, |
| 17579 | + const char* mobile_sele, const char* target_sele, int mobile_state, |
| 17580 | + int target_state, int quiet, int transform, const char* oname, int fast) |
| 17581 | +{ |
| 17582 | + // Resolve selections |
| 17583 | + auto sele_mobile = SelectorIndexByName(G, mobile_sele); |
| 17584 | + if (sele_mobile < 0) |
| 17585 | + return pymol::make_error("Invalid mobile selection: ", mobile_sele); |
| 17586 | + |
| 17587 | + auto sele_target = SelectorIndexByName(G, target_sele); |
| 17588 | + if (sele_target < 0) |
| 17589 | + return pymol::make_error("Invalid target selection: ", target_sele); |
| 17590 | + |
| 17591 | + // Extract CA coordinates and sequences |
| 17592 | + struct ResidueInfo { |
| 17593 | + glm::dvec3 coord; |
| 17594 | + char seq_char; |
| 17595 | + AtomInfoType* ai; |
| 17596 | + }; |
| 17597 | + |
| 17598 | + auto extract_ca = [&](SelectorID_t sele, |
| 17599 | + int state) -> std::vector<ResidueInfo> { |
| 17600 | + std::vector<ResidueInfo> residues; |
| 17601 | + SeleCoordIterator iter(G, sele, state); |
| 17602 | + while (iter.next()) { |
| 17603 | + auto* ai = iter.getAtomInfo(); |
| 17604 | + if (ai->flags & cAtomFlag_guide) { |
| 17605 | + float* c = iter.getCoord(); |
| 17606 | + ResidueInfo ri; |
| 17607 | + ri.coord = glm::dvec3(c[0], c[1], c[2]); |
| 17608 | + ri.seq_char = SeekerGetAbbr(G, LexStr(G, ai->resn), 'O', 'X'); |
| 17609 | + ri.ai = ai; |
| 17610 | + residues.push_back(ri); |
| 17611 | + } |
| 17612 | + } |
| 17613 | + return residues; |
| 17614 | + }; |
| 17615 | + |
| 17616 | + auto mobile_res = extract_ca(sele_mobile, mobile_state); |
| 17617 | + auto target_res = extract_ca(sele_target, target_state); |
| 17618 | + |
| 17619 | + if (mobile_res.size() < 3) { |
| 17620 | + return pymol::make_error("Mobile selection has fewer than 3 guide atoms (", |
| 17621 | + mobile_res.size(), ")"); |
| 17622 | + } |
| 17623 | + if (target_res.size() < 3) { |
| 17624 | + return pymol::make_error("Target selection has fewer than 3 guide atoms (", |
| 17625 | + target_res.size(), ")"); |
| 17626 | + } |
| 17627 | + |
| 17628 | + // Build coordinate vectors and sequences |
| 17629 | + std::vector<glm::dvec3> mobile_ca, target_ca; |
| 17630 | + std::string mobile_seq, target_seq; |
| 17631 | + mobile_ca.reserve(mobile_res.size()); |
| 17632 | + target_ca.reserve(target_res.size()); |
| 17633 | + |
| 17634 | + for (const auto& r : mobile_res) { |
| 17635 | + mobile_ca.push_back(r.coord); |
| 17636 | + mobile_seq.push_back(r.seq_char); |
| 17637 | + } |
| 17638 | + for (const auto& r : target_res) { |
| 17639 | + target_ca.push_back(r.coord); |
| 17640 | + target_seq.push_back(r.seq_char); |
| 17641 | + } |
| 17642 | + |
| 17643 | + // Run TM-align |
| 17644 | + auto result = pymol::usalign::TMalign( |
| 17645 | + target_ca, mobile_ca, target_seq, mobile_seq, fast != 0); |
| 17646 | + |
| 17647 | + if (result.aligned_length < 1) { |
| 17648 | + return pymol::make_error("TM-align failed to find any alignment"); |
| 17649 | + } |
| 17650 | + |
| 17651 | + // Print results |
| 17652 | + if (!quiet) { |
| 17653 | + PRINTFB(G, FB_Executive, FB_Results) |
| 17654 | + " USalign: TM-score= %6.4f (normalized by target, N=%d, d0=%.2f)\n", |
| 17655 | + result.tm_score_target, static_cast<int>(target_ca.size()), |
| 17656 | + result.d0_target ENDFB(G); |
| 17657 | + PRINTFB(G, FB_Executive, FB_Results) |
| 17658 | + " USalign: TM-score= %6.4f (normalized by mobile, N=%d, d0=%.2f)\n", |
| 17659 | + result.tm_score_mobile, static_cast<int>(mobile_ca.size()), |
| 17660 | + result.d0_mobile ENDFB(G); |
| 17661 | + PRINTFB(G, FB_Executive, FB_Results) |
| 17662 | + " USalign: Aligned length= %d, RMSD= %5.2f, Seq_ID=n_identical/n_aligned= " |
| 17663 | + "%4.3f\n", |
| 17664 | + result.aligned_length, result.rmsd, result.seq_identity ENDFB(G); |
| 17665 | + } |
| 17666 | + |
| 17667 | + // Apply transform to mobile object |
| 17668 | + if (transform) { |
| 17669 | + // Convert double-precision Superposition to float TTT |
| 17670 | + // USalign convention: y_aligned = R * x + t |
| 17671 | + // where x = mobile coords, y = target coords |
| 17672 | + // The rotation R and translation t transform mobile -> target space |
| 17673 | + |
| 17674 | + const auto& sup = result.transform; |
| 17675 | + |
| 17676 | + // Build a legacy-style 16-float TTT matrix |
| 17677 | + // TTT format: [R00 R01 R02 pre_x] [R10 R11 R12 pre_y] |
| 17678 | + // [R20 R21 R22 pre_z] [tx ty tz 1] |
| 17679 | + // where pre is the pre-translation (origin), and t is post-translation |
| 17680 | + // For a simple rotation+translation (no origin): pre=0, R=rotation, |
| 17681 | + // t=translation |
| 17682 | + |
| 17683 | + glm::mat3 rot_f(sup.rotation); |
| 17684 | + glm::quat q = glm::quat_cast(rot_f); |
| 17685 | + glm::vec3 t(sup.translation); |
| 17686 | + |
| 17687 | + // Create TTT: pretranslate=0, rotate=q, posttranslate=t |
| 17688 | + pymol::TTT ttt(glm::vec3(0.0f), q, t); |
| 17689 | + |
| 17690 | + // Convert to legacy float[16] format for ExecuteCombineObjectTTT |
| 17691 | + auto legacy = pymol::TTT::as_pymol_2_legacy(ttt); |
| 17692 | + float tttf[16]; |
| 17693 | + std::memcpy(tttf, glm::value_ptr(legacy), 16 * sizeof(float)); |
| 17694 | + |
| 17695 | + // Follow the same pattern as ExecutiveAlign: |
| 17696 | + // 1. Copy target's TTT and state matrix to mobile (reset to same frame) |
| 17697 | + // 2. Combine the alignment transform (reverse_order=true) |
| 17698 | + // Note: Only the first object in the mobile selection is transformed, |
| 17699 | + // matching ExecutiveAlign behavior for multi-object selections. |
| 17700 | + ObjectMolecule* mobile_obj = SelectorGetFirstObjectMolecule(G, sele_mobile); |
| 17701 | + ObjectMolecule* target_obj = |
| 17702 | + SelectorGetSingleObjectMolecule(G, sele_target); |
| 17703 | + if (mobile_obj && target_obj) { |
| 17704 | + ExecutiveMatrixCopy(G, target_obj->Name, mobile_obj->Name, 1, 1, |
| 17705 | + target_state, mobile_state, false, 0, quiet); |
| 17706 | + ExecutiveMatrixCopy(G, target_obj->Name, mobile_obj->Name, 2, 2, |
| 17707 | + target_state, mobile_state, false, 0, quiet); |
| 17708 | + ExecutiveCombineObjectTTT(G, mobile_obj->Name, tttf, true, -1); |
| 17709 | + } |
| 17710 | + } |
| 17711 | + |
| 17712 | + // Create alignment object |
| 17713 | + if (oname && oname[0]) { |
| 17714 | + int align_state = target_state; |
| 17715 | + if (align_state < 0) { |
| 17716 | + align_state = SceneGetState(G); |
| 17717 | + } |
| 17718 | + |
| 17719 | + ObjectMolecule* trg_obj = SelectorGetSingleObjectMolecule(G, sele_target); |
| 17720 | + ObjectMolecule* mob_obj = SelectorGetFirstObjectMolecule(G, sele_mobile); |
| 17721 | + |
| 17722 | + if (trg_obj && mob_obj) { |
| 17723 | + int n_pair = result.aligned_length; |
| 17724 | + pymol::vla<int> align_vla(n_pair * 3); |
| 17725 | + int* id_p = align_vla.data(); |
| 17726 | + |
| 17727 | + for (int k = 0; k < n_pair; k++) { |
| 17728 | + int mi = result.mobile_indices[k]; |
| 17729 | + int ti = result.target_indices[k]; |
| 17730 | + if (mi < static_cast<int>(mobile_res.size()) && |
| 17731 | + ti < static_cast<int>(target_res.size())) { |
| 17732 | + id_p[0] = AtomInfoCheckUniqueID(G, target_res[ti].ai); |
| 17733 | + id_p[1] = AtomInfoCheckUniqueID(G, mobile_res[mi].ai); |
| 17734 | + id_p[2] = 0; |
| 17735 | + id_p += 3; |
| 17736 | + } |
| 17737 | + } |
| 17738 | + |
| 17739 | + ObjectAlignment* obj = nullptr; |
| 17740 | + { |
| 17741 | + pymol::CObject* execObj = ExecutiveFindObjectByName(G, oname); |
| 17742 | + if (execObj && execObj->type != cObjectAlignment) { |
| 17743 | + ExecutiveDelete(G, oname); |
| 17744 | + } else { |
| 17745 | + obj = dynamic_cast<ObjectAlignment*>(execObj); |
| 17746 | + } |
| 17747 | + } |
| 17748 | + obj = ObjectAlignmentDefine( |
| 17749 | + G, obj, align_vla, align_state, true, trg_obj, mob_obj); |
| 17750 | + obj->Color = ColorGetIndex(G, "yellow"); |
| 17751 | + ObjectSetName(obj, oname); |
| 17752 | + ExecutiveManageObject(G, obj, 0, quiet); |
| 17753 | + SceneInvalidate(G); |
| 17754 | + } |
| 17755 | + } |
| 17756 | + |
| 17757 | + return result; |
| 17758 | +} |
0 commit comments