layer3/MovieScene.cpp (531 lines of code) (raw):

/* * PyMOL Scene C++ implementation * * Author: Thomas Holder * (c) 2014 Schrodinger, Inc. */ #include "os_python.h" #include "PyMOLGlobals.h" #include "Util2.h" #include "Movie.h" #include "P.h" #include "PConv.h" #include "PConvArgs.h" #include "Setting.h" #include "Scene.h" #include "View.h" #include "Selector.h" #include "Executive.h" #include "Err.h" #include "SpecRec.h" #include <algorithm> #include <map> #include <set> #include <sstream> #include <string> #include <vector> #include "MovieScene.h" enum { STORE_VIEW = (1 << 0), STORE_ACTIVE = (1 << 1), STORE_COLOR = (1 << 2), STORE_REP = (1 << 3), STORE_FRAME = (1 << 4) }; void SceneSetNames(PyMOLGlobals * G, const std::vector<std::string> &list); /* * Struct to hold scene stored atom properties */ class MovieSceneAtom { public: int color; int visRep; }; /* * Struct to hold scene stored object properties */ class MovieSceneObject { public: int color; int visRep; }; /* * Struct to hold all scene data */ class MovieScene { public: // bitmask, features stored in this scene int storemask; // global state or movie frame int frame; // text to display (with message wizard) std::string message; // camera view SceneViewType view; // atom properties (color, rep, etc.) std::map<int, MovieSceneAtom> atomdata; // objects properties (enabled, color, reps, etc.) std::map<std::string, MovieSceneObject> objectdata; }; /* * Replacement for pymol._scene_dict and pymol._scene_order */ class CMovieScenes { int scene_counter; public: std::map<std::string, MovieScene> dict; std::vector<std::string> order; CMovieScenes() { scene_counter = 1; } std::string getUniqueKey(); }; /* * Get read-only pointer to G->scenes->order */ const std::vector<std::string> & MovieSceneGetOrder(PyMOLGlobals * G) { return G->scenes->order; } /* * Get a unique scene key */ std::string CMovieScenes::getUniqueKey() { char key[16]; for (;; ++scene_counter) { sprintf(key, "%03d", scene_counter); if (dict.find(key) == dict.end()) return key; } } /* * Change the ordering of scenes * * Examples: * a b c d e * move d behind b with: order("b d", "current") * a b d c e * move c to the top with: order("c", "top") * c a b d e * * names: space separated list of names to (optionally) sort and to move to * given location * location: current|top|botton */ bool MovieSceneOrder(PyMOLGlobals * G, const char * names, bool sort, const char * location) { std::vector<std::string> names_list; std::vector<std::string> new_order; bool is_all = false; if (strcmp("*", names) == 0) { is_all = true; names_list = G->scenes->order; } else { names_list = strsplit(names); // check that all given names are existing scenes for (auto it = names_list.begin(); it != names_list.end(); ++it) { if (G->scenes->dict.find(*it) == G->scenes->dict.end()) { PRINTFB(G, FB_Scene, FB_Errors) " Error: scene '%s' is not defined.\n", it->c_str() ENDFB(G); return false; } } } if (names_list.empty()) { return true; } if (sort) { std::sort(names_list.begin(), names_list.end(), strlessnat); } if (is_all) { new_order = names_list; } else { std::set<std::string> names_set(names_list.begin(), names_list.end()); // sanity check: unique keys? if (names_set.size() != names_list.size()) { PRINTFB(G, FB_Scene, FB_Errors) " Error: duplicated keys.\n" ENDFB(G); return false; } char loc = location ? location[0] : 'c'; // sanity check: valid location identifier? if (loc != 't' && loc != 'c' && loc != 'b') { PRINTFB(G, FB_Scene, FB_Errors) " Error: invalid location '%s'.\n", location ENDFB(G); return false; } if (loc == 't' /* top */) { new_order.insert(new_order.end(), names_list.begin(), names_list.end()); } for (auto it = G->scenes->order.begin(); it != G->scenes->order.end(); ++it) { if (!names_set.count(*it)) { new_order.push_back(*it); } else if (loc == 'c' /* current */ && *it == names_list[0]) { new_order.insert(new_order.end(), names_list.begin(), names_list.end()); } } if (loc == 'b' /* bottom */) { new_order.insert(new_order.end(), names_list.begin(), names_list.end()); } } G->scenes->order = new_order; SceneSetNames(G, G->scenes->order); return true; } /* * Store a scene * * name: name (key) of the scene to store or update * ("new"/empty = unique key) * message: wizard message to display with this scene * store_view: store the camera view * store_color: store colors * store_active: store enabled/disabled * store_rep: store reps * store_frame: store movie frame */ static bool MovieSceneStore(PyMOLGlobals * G, const char * name, const char * message, bool store_view, bool store_color, bool store_active, bool store_rep, bool store_frame, const char * sele) { auto scenes = G->scenes; std::string key(name); // new key? if (key.empty() || key == "new") { key = scenes->getUniqueKey(); scenes->order.push_back(key); } else if (scenes->dict.find(key) == scenes->dict.end()) { scenes->order.push_back(key); } SceneSetNames(G, scenes->order); // set scene_current_name SettingSetGlobal_s(G, cSetting_scene_current_name, key.c_str()); MovieScene &scene = scenes->dict[key]; // storemask scene.storemask = ( (store_view ? STORE_VIEW : 0) | (store_active ? STORE_ACTIVE : 0) | (store_color ? STORE_COLOR : 0) | (store_rep ? STORE_REP : 0) | (store_frame ? STORE_FRAME : 0)); // message scene.message = message ? message : ""; // camera view SceneGetView(G, scene.view); // frame scene.frame = SceneGetFrame(G); // atomdata if (store_color || store_rep) { // fill atomdata dict for (SeleAtomIterator iter(G, sele); iter.next();) { // don't store atom data for disabled objects if (!((CObject*)iter.obj)->Enabled) continue; AtomInfoType * ai = iter.getAtomInfo(); int unique_id = AtomInfoCheckUniqueID(G, ai); MovieSceneAtom &sceneatom = scene.atomdata[unique_id]; sceneatom.color = ai->color; sceneatom.visRep = ai->visRep; } } // objectdata for (ObjectIterator iter(G); iter.next();) { const SpecRec * rec = iter.getSpecRec(); CObject * obj = iter.getObject(); MovieSceneObject &sceneobj = scene.objectdata[obj->Name]; sceneobj.color = obj->Color; sceneobj.visRep = obj->visRep; // store the "enabled" state in the first bit of visRep SET_BIT_TO(sceneobj.visRep, 0, rec->visible); } PRINTFB(G, FB_Scene, FB_Details) " scene: scene stored as \"%s\".\n", key.c_str() ENDFB(G); return true; } /* * Display a message (with the message wizard) */ static void MovieSceneRecallMessage(PyMOLGlobals * G, const std::string &message) { #ifndef _PYMOL_NOPY // we can't just call python functions because we might be in the wrong // thread. Instead, pass a parsable python string to PParse. // To avoid syntax errors, replace all single quotes in *message*. std::string pystr = "/cmd.scene_recall_message(r'''" + message + "''')"; std::replace(pystr.begin() + 30, pystr.end() - 4, '\'', '`'); PParse(G, pystr.c_str()); #endif } /* * Set the frame or state, depending on whether a movie is defined and/or * playing, and depending on the scene_frame_mode setting. */ static void MovieSceneRecallFrame(PyMOLGlobals * G, int frame) { int mode = 4; if (MoviePlaying(G)) { mode = 10; // seek scene } else if (frame == SceneGetFrame(G)) { return; } else { int scene_frame_mode = SettingGetGlobal_i(G, cSetting_scene_frame_mode); if(scene_frame_mode == 0 || (scene_frame_mode < 0 && MovieDefined(G))) return; } #ifdef _PYMOL_NOPY SceneSetFrame(G, mode, frame); #else // PBlock fails with SceneSetFrame. Workaround: call from Python PXDecRef(PYOBJECT_CALLMETHOD(G->P_inst->cmd, "set_frame", "ii", frame + 1, mode)); #endif } /* * Scene animation duration from settings */ static float get_scene_animation_duration(PyMOLGlobals * G) { auto enabled = SettingGetGlobal_i(G, cSetting_scene_animation); if (enabled < 0) enabled = SettingGetGlobal_b(G, cSetting_animation); if (!enabled) return 0.f; return SettingGetGlobal_f(G, cSetting_scene_animation_duration); } /* * Recall a scene * * name: name (key) of the scene to recall * animate: animation duration, use scene_animation_duration if -1 * store_view: restore the camera view * store_color: restore colors * store_active: restore enabled/disabled * store_rep: restore reps */ bool MovieSceneRecall(PyMOLGlobals * G, const char * name, float animate, bool recall_view, bool recall_color, bool recall_active, bool recall_rep, bool recall_frame, const char * sele) { auto scenes = G->scenes; auto it = scenes->dict.find(name); if (it == scenes->dict.end()) { PRINTFB(G, FB_Scene, FB_Errors) " Error: scene '%s' is not defined.\n", name ENDFB(G); return false; } // set scene_current_name SettingSetGlobal_s(G, cSetting_scene_current_name, name); MovieScene &scene = it->second; // recall features if requested and stored recall_view &= (scene.storemask & STORE_VIEW) != 0; recall_active &= (scene.storemask & STORE_ACTIVE) != 0; recall_color &= (scene.storemask & STORE_COLOR) != 0; recall_rep &= (scene.storemask & STORE_REP) != 0; recall_frame &= (scene.storemask & STORE_FRAME) != 0; // keep track of changes // (obj -> repbitmask) stores the rep bits for all reps that changed, // a value of zero means just to invalidate color. std::map<CObject*, int> objectstoinvalidate; // atomdata if (recall_color || recall_rep) { // fill atomdata dict for (SeleAtomIterator iter(G, sele); iter.next();) { AtomInfoType * ai = iter.getAtomInfo(); auto it = scene.atomdata.find(ai->unique_id); if (it == scene.atomdata.end()) continue; MovieSceneAtom &sceneatom = it->second; if (recall_color) { if (ai->color != sceneatom.color) objectstoinvalidate[(CObject*) iter.obj]; ai->color = sceneatom.color; } if (recall_rep) { int changed = (ai->visRep ^ sceneatom.visRep) & cRepsAtomMask; if (changed) objectstoinvalidate[(CObject*) iter.obj] |= changed; ai->visRep = sceneatom.visRep; } } } // disable all objects if (recall_active) { // need to control SpecRec ExecutiveSetObjVisib(G, "*", false, false); } // objectdata for (ObjectIterator iter(G); iter.next();) { CObject * obj = iter.getObject(); auto it = scene.objectdata.find(obj->Name); if (it == scene.objectdata.end()) continue; MovieSceneObject &sceneobj = it->second; if (recall_color) { if (obj->Color != sceneobj.color) objectstoinvalidate[obj]; obj->Color = sceneobj.color; } if (recall_rep) { int changed = (obj->visRep ^ sceneobj.visRep) & cRepsObjectMask; if (changed) objectstoinvalidate[obj] |= changed; obj->visRep = sceneobj.visRep; } // "enabled" state is first bit in visRep int enabled = GET_BIT(sceneobj.visRep, 0); if(recall_active && enabled) { // need to control SpecRec ExecutiveSetObjVisib(G, obj->Name, enabled, false); } } // invalidate for (auto it = objectstoinvalidate.begin(); it != objectstoinvalidate.end(); ++it) { it->first->invalidate(cRepAll, it->second ? cRepInvVisib : cRepInvColor, -1); } // camera view if (recall_view) { if (animate < -0.5) // == -1 animate = get_scene_animation_duration(G); SceneSetView(G, scene.view, true, animate, 1); } // message MovieSceneRecallMessage(G, scene.message); // frame if (recall_frame) { MovieSceneRecallFrame(G, scene.frame); } return true; } /* * Rename or delete a scene * * name: name to rename or delete, or "*" to delete all * new_name: new scene name to rename, or NULL to delete */ static bool MovieSceneRename(PyMOLGlobals * G, const char * name, const char * new_name = NULL) { if (strcmp(name, "*") == 0) { // delete all scenes G->scenes->dict.clear(); G->scenes->order.clear(); SceneSetNames(G, G->scenes->order); return true; } if (!new_name) { new_name = ""; } else if (strcmp(name, new_name) == 0) { return true; } auto it = G->scenes->dict.find(name); if (it != G->scenes->dict.end()) { if (new_name[0]) std::swap(G->scenes->dict[new_name], it->second); G->scenes->dict.erase(it); // does a scene named "new_name" already exist? auto old_new = std::find(G->scenes->order.begin(), G->scenes->order.end(), new_name); // replace in or remove from "G->scenes->order" list auto v_it = std::find(G->scenes->order.begin(), G->scenes->order.end(), name); if (v_it == G->scenes->order.end()) { printf("this is a bug, name must be in G->scenes->order\n"); } else { if (new_name[0]) { // rename v_it->assign(new_name); // overwritten existing key? if (old_new != G->scenes->order.end()) G->scenes->order.erase(old_new); } else { // remove G->scenes->order.erase(v_it); } } SceneSetNames(G, G->scenes->order); // update scene_current_name if (0 == strcmp(name, SettingGetGlobal_s(G, cSetting_scene_current_name))) { SettingSetGlobal_s(G, cSetting_scene_current_name, new_name); } return true; } return false; } /* * Print current scene order */ static bool MovieScenePrintOrder(PyMOLGlobals * G) { PRINTFB(G, FB_Scene, FB_Details) " scene: current order:\n" ENDFB(G); for (auto it = G->scenes->order.begin(); it != G->scenes->order.end(); ++it) { PRINTFB(G, FB_Scene, FB_Details) " %s", it->c_str() ENDFB(G); } PRINTFB(G, FB_Scene, FB_Details) "\n" ENDFB(G); return true; } /* * Based on the "scene_current_name" setting, get the next or previous key. * * If the "scene_loop" setting is false and the key is out of range, return * an empty string. * * next: true = next, false = previous */ static const char * MovieSceneGetNextKey(PyMOLGlobals * G, bool next) { const char * current_name = SettingGetGlobal_s(G, cSetting_scene_current_name); int scene_loop = SettingGetGlobal_b(G, cSetting_scene_loop); if (!current_name[0]) scene_loop = true; auto it = std::find(G->scenes->order.begin(), G->scenes->order.end(), current_name); if (next) { if (it < G->scenes->order.end() - 1) { ++it; } else if (scene_loop) { it = G->scenes->order.begin(); } else { return ""; } } else { if (it != G->scenes->order.begin() && it != G->scenes->order.end()) { --it; } else if (scene_loop) { it = G->scenes->order.end() - 1; } else { return ""; } } return it->c_str(); } /* * Move the current scene (scene_current_name) before or after "key" */ static bool MovieSceneOrderBeforeAfter(PyMOLGlobals * G, const char * key, bool before) { const char * location = NULL; const char * key2 = SettingGetGlobal_s(G, cSetting_scene_current_name); if (before) { auto it = std::find(G->scenes->order.begin(), G->scenes->order.end(), key); if (it == G->scenes->order.begin()) { location = "top"; key = ""; } else { key = (it - 1)->c_str(); } } // order = key + ' ' + key2 std::string order(key); order.append(" ").append(key2); MovieSceneOrder(G, order.c_str(), false, location); return true; } /* * C implementation of the "scene" command */ bool MovieSceneFunc(PyMOLGlobals * G, const char * key, const char * action, const char * message, bool store_view, bool store_color, bool store_active, bool store_rep, bool store_frame, float animate, const char * new_key, bool hand, const char * sele) { auto scenes = G->scenes; std::string prev_name; short beforeafter = 0; bool status = false; PRINTFB(G, FB_Scene, FB_Blather) " MovieScene: key=%s action=%s message=%s store_view=%d store_color=%d" " store_active=%d store_rep=%d animate=%f new_key=%s hand=%d\n", key, action, message, store_view, store_color, store_active, store_rep, animate, new_key, hand ENDFB(G); // insert_before, insert_after if (strncmp(action, "insert_", 7) == 0) { prev_name = SettingGetGlobal_s(G, cSetting_scene_current_name); if (!prev_name.empty()) beforeafter = (action[7] == 'b') ? 1 : 2; action = "store"; } if (strcmp(action, "next") == 0 || strcmp(action, "previous") == 0) { ok_assert(NOSCENES, scenes->order.size()); key = MovieSceneGetNextKey(G, action[0] == 'n'); action = "recall"; } else if (strcmp(action, "start") == 0) { ok_assert(NOSCENES, scenes->order.size()); key = scenes->order[0].c_str(); action = "recall"; } else if (strcmp(key, "auto") == 0) { key = SettingGetGlobal_s(G, cSetting_scene_current_name); } if (strcmp(action, "recall") == 0) { if (strcmp(key, "*") == 0) return MovieScenePrintOrder(G); if (!key[0]) { // empty key -> put up blank screen SettingSetGlobal_s(G, cSetting_scene_current_name, ""); ExecutiveSetObjVisib(G, "*", false, false); MovieSceneRecallMessage(G, ""); } else { status = MovieSceneRecall(G, key, animate, store_view, store_color, store_active, store_rep, store_frame, sele); } } else if (strcmp(action, "store") == 0) { status = MovieSceneStore(G, key, message, store_view, store_color, store_active, store_rep, store_frame, sele); // insert_before, insert_after if (status && beforeafter) status = MovieSceneOrderBeforeAfter(G, prev_name.c_str(), beforeafter == 1); } else if (strcmp(action, "delete") == 0) { status = MovieSceneRename(G, key, NULL); } else if (strcmp(action, "rename") == 0) { status = MovieSceneRename(G, key, new_key); } else if (strcmp(action, "order") == 0) { status = MovieSceneOrder(G, key); } else if (strcmp(action, "sort") == 0) { status = MovieSceneOrder(G, key, true); } else if (strcmp(action, "first") == 0) { status = MovieSceneOrder(G, key, false, "top"); } else { PRINTFB(G, FB_Scene, FB_Errors) " Error: invalid action '%s'\n", action ENDFB(G); } // trigger GUI updates (scene buttons, Tcl/Tk menu) SettingSetGlobal_b(G, cSetting_scenes_changed, 1); SettingGenerateSideEffects(G, cSetting_scenes_changed, NULL, 0, true); return status; ok_exceptNOSCENES: PRINTFB(G, FB_Scene, FB_Errors) " Error: no scenes\n" ENDFB(G); return false; } /* * Init/Free to set up PyMOLGlobals in PyMOL_Start */ void MovieScenesInit(PyMOLGlobals * G) { MovieScenesFree(G); G->scenes = new CMovieScenes; } void MovieScenesFree(PyMOLGlobals * G) { if (G->scenes) { delete G->scenes; G->scenes = NULL; } } /* * PConvToPyObject/PConvFromPyObject * * Convertion to/from Python objects for all MovieScene types */ static PyObject * PConvToPyObject(const MovieSceneAtom &v) { return PConvArgsToPyList(v.color, v.visRep); } static PyObject * PConvToPyObject(const MovieSceneObject &v) { return PConvArgsToPyList(v.color, v.visRep); } static PyObject * PConvToPyObject(const MovieScene &v) { PyObject * obj = PyList_New(6); PyList_SET_ITEM(obj, 0, PConvToPyObject(v.storemask)); PyList_SET_ITEM(obj, 1, PConvToPyObject(v.frame)); PyList_SET_ITEM(obj, 2, PConvToPyObject(v.message.c_str())); PyList_SET_ITEM(obj, 3, PConvToPyObject(v.view, cSceneViewSize)); PyList_SET_ITEM(obj, 4, PConvToPyObject(v.atomdata)); PyList_SET_ITEM(obj, 5, PConvToPyObject(v.objectdata)); return obj; } static bool PConvFromPyObject(PyMOLGlobals *, PyObject * obj, MovieSceneAtom &out) { return PConvArgsFromPyList(NULL, obj, out.color, out.visRep); } static bool PConvFromPyObject(PyMOLGlobals *, PyObject * obj, MovieSceneObject &out) { return PConvArgsFromPyList(NULL, obj, out.color, out.visRep); } static bool PConvFromPyObject(PyMOLGlobals * G, PyObject * obj, MovieScene &out) { std::map<int, MovieSceneAtom> atomdata_old_ids; if (!G) { printf(" Error: G is NULL\n"); return false; } if (!PConvArgsFromPyList(NULL, obj, out.storemask, out.frame, out.message, out.view, atomdata_old_ids, out.objectdata)) /* ignore */; // restore atomdata dict but with converted ids PConvFromPyObject(G, PyList_GetItem(obj, 4), atomdata_old_ids); for (auto it = atomdata_old_ids.begin(); it != atomdata_old_ids.end(); ++it) { int unique_id = SettingUniqueConvertOldSessionID(G, it->first); std::swap(out.atomdata[unique_id], it->second); } return true; } /* * For get_session */ PyObject * MovieScenesAsPyList(PyMOLGlobals * G) { return PConvArgsToPyList(G->scenes->order, G->scenes->dict); } void MovieScenesFromPyList(PyMOLGlobals * G, PyObject * o) { // delete existing scenes MovieSceneRename(G, "*"); PConvArgsFromPyList(G, o, G->scenes->order, G->scenes->dict); SceneSetNames(G, G->scenes->order); } // vi:sw=2:expandtab:cindent