1 /*
2  * PyMOL Scene C++ implementation
3  *
4  * Author: Thomas Holder
5  * (c) 2014 Schrodinger, Inc.
6  */
7 
8 #include "os_python.h"
9 #include "PyMOLGlobals.h"
10 #include "Util2.h"
11 #include "Movie.h"
12 #include "P.h"
13 #include "PConv.h"
14 #include "PConvArgs.h"
15 #include "Setting.h"
16 #include "Scene.h"
17 #include "View.h"
18 #include "Selector.h"
19 #include "Executive.h"
20 #include "Err.h"
21 #include "SpecRec.h"
22 #include "AtomIterators.h"
23 
24 #include <algorithm>
25 #include <map>
26 #include <set>
27 #include <sstream>
28 #include <string>
29 #include <vector>
30 
31 #include "MovieScene.h"
32 
33 enum {
34   STORE_VIEW   = (1 << 0),
35   STORE_ACTIVE = (1 << 1),
36   STORE_COLOR  = (1 << 2),
37   STORE_REP    = (1 << 3),
38   STORE_FRAME  = (1 << 4)
39 };
40 
41 void SceneSetNames(PyMOLGlobals * G, const std::vector<std::string> &list);
42 
43 /**
44  * Struct to hold scene stored atom properties
45  */
46 class MovieSceneAtom {
47 public:
48   int color;
49   int visRep;
50 };
51 
52 /**
53  * Struct to hold scene stored object properties
54  */
55 class MovieSceneObject {
56 public:
57   int color;
58   int visRep;
59 };
60 
61 /**
62  * Struct to hold all scene data
63  */
64 class MovieScene {
65 public:
66   /// bitmask, features stored in this scene
67   int storemask;
68 
69   /// global state or movie frame
70   int frame;
71 
72   /// text to display (with message wizard)
73   std::string message;
74 
75   /// camera view
76   SceneViewType view;
77 
78   /// atom properties (color, rep, etc.)
79   std::map<int, MovieSceneAtom> atomdata;
80 
81   /// objects properties (enabled, color, reps, etc.)
82   std::map<std::string, MovieSceneObject> objectdata;
83 };
84 
85 /**
86  * Replacement for pymol._scene_dict and pymol._scene_order
87  */
88 class CMovieScenes {
89   int scene_counter;
90 
91 public:
92   std::map<std::string, MovieScene> dict;
93   std::vector<std::string> order;
94 
CMovieScenes()95   CMovieScenes() {
96     scene_counter = 1;
97   }
98 
99   std::string getUniqueKey();
100 };
101 
102 /**
103  * Get read-only pointer to G->scenes->order
104  */
MovieSceneGetOrder(PyMOLGlobals * G)105 const std::vector<std::string> & MovieSceneGetOrder(PyMOLGlobals * G) {
106   return G->scenes->order;
107 }
108 
109 /**
110  * Get a unique scene key
111  */
getUniqueKey()112 std::string CMovieScenes::getUniqueKey()
113 {
114   char key[16];
115 
116   for (;; ++scene_counter) {
117     sprintf(key, "%03d", scene_counter);
118 
119     if (dict.find(key) == dict.end())
120       return key;
121   }
122 }
123 
124 /**
125  * Change the ordering of scenes
126  *
127  * Examples:
128  * a b c d e
129  * move d behind b with:    order("b d", "current")
130  * a b d c e
131  * move c to the top with:  order("c", "top")
132  * c a b d e
133  *
134  * @param names space separated list of names to (optionally) sort and to move to
135  *        given location
136  * @param location current|top|botton
137  */
MovieSceneOrder(PyMOLGlobals * G,const char * names,bool sort,const char * location)138 bool MovieSceneOrder(PyMOLGlobals * G, const char * names, bool sort,
139     const char * location)
140 {
141   std::vector<std::string> names_list;
142   std::vector<std::string> new_order;
143   bool is_all = false;
144 
145   if (strcmp("*", names) == 0) {
146     is_all = true;
147     names_list = G->scenes->order;
148   } else {
149     names_list = strsplit(names);
150 
151     // check that all given names are existing scenes
152     for (auto& name : names_list) {
153       if (G->scenes->dict.find(name) == G->scenes->dict.end()) {
154         PRINTFB(G, FB_Scene, FB_Errors)
155           " Error: scene '%s' is not defined.\n", name.c_str() ENDFB(G);
156         return false;
157       }
158     }
159   }
160 
161   if (names_list.empty()) {
162     return true;
163   }
164 
165   if (sort) {
166     std::sort(names_list.begin(), names_list.end(), strlessnat);
167   }
168 
169   if (is_all) {
170     new_order = names_list;
171   } else {
172     std::set<std::string> names_set(names_list.begin(), names_list.end());
173 
174     // sanity check: unique keys?
175     if (names_set.size() != names_list.size()) {
176       PRINTFB(G, FB_Scene, FB_Errors)
177         " Error: duplicated keys.\n" ENDFB(G);
178       return false;
179     }
180 
181     char loc = location ? location[0] : 'c';
182 
183     // sanity check: valid location identifier?
184     if (loc != 't' && loc != 'c' && loc != 'b') {
185       PRINTFB(G, FB_Scene, FB_Errors)
186         " Error: invalid location '%s'.\n", location ENDFB(G);
187       return false;
188     }
189 
190     if (loc == 't' /* top */) {
191       new_order.insert(new_order.end(), names_list.begin(), names_list.end());
192     }
193 
194     for (auto& name : G->scenes->order) {
195       if (!names_set.count(name)) {
196         new_order.push_back(name);
197       } else if (loc == 'c' /* current */ && name == names_list[0]) {
198         new_order.insert(new_order.end(), names_list.begin(), names_list.end());
199       }
200     }
201 
202     if (loc == 'b' /* bottom */) {
203       new_order.insert(new_order.end(), names_list.begin(), names_list.end());
204     }
205   }
206 
207   G->scenes->order = new_order;
208   SceneSetNames(G, G->scenes->order);
209 
210   return true;
211 }
212 
213 /**
214  * Store a scene
215  *
216  * @param name            name (key) of the scene to store or update
217  *                  ("new"/empty = unique key)
218  * @param message         wizard message to display with this scene
219  * @param store_view      store the camera view
220  * @param store_color     store colors
221  * @param store_active    store enabled/disabled
222  * @param store_rep       store reps
223  * @param store_frame     store movie frame
224  */
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,size_t stack)225 static bool MovieSceneStore(PyMOLGlobals * G, const char * name,
226     const char * message,
227     bool store_view,
228     bool store_color,
229     bool store_active,
230     bool store_rep,
231     bool store_frame,
232     const char * sele,
233     size_t stack)
234 {
235   const bool is_defaultstack = stack == cMovieSceneStackDefault;
236   auto scenes = G->scenes + stack;
237   std::string key(name);
238 
239   if (is_defaultstack) {
240     // new key?
241     if (key.empty() || key == "new") {
242       key = scenes->getUniqueKey();
243       scenes->order.push_back(key);
244     } else if (scenes->dict.find(key) == scenes->dict.end()) {
245       scenes->order.push_back(key);
246     }
247 
248     SceneSetNames(G, scenes->order);
249 
250     // set scene_current_name
251     SettingSetGlobal_s(G, cSetting_scene_current_name, key.c_str());
252   }
253 
254   MovieScene &scene = scenes->dict[key];
255 
256   // storemask
257   scene.storemask = (
258       (store_view ? STORE_VIEW : 0) |
259       (store_active ? STORE_ACTIVE : 0) |
260       (store_color ? STORE_COLOR : 0) |
261       (store_rep ? STORE_REP : 0) |
262       (store_frame ? STORE_FRAME : 0));
263 
264   // message
265   scene.message = message ? message : "";
266 
267   // camera view
268   SceneGetView(G, scene.view);
269 
270   // frame
271   scene.frame = SceneGetFrame(G);
272 
273   // atomdata
274   if (store_color || store_rep) {
275 
276     // fill atomdata dict
277     for (SeleAtomIterator iter(G, sele); iter.next();) {
278 
279       // don't store atom data for disabled objects
280       if (!((CObject*)iter.obj)->Enabled && is_defaultstack)
281         continue;
282 
283       AtomInfoType * ai = iter.getAtomInfo();
284       int unique_id = AtomInfoCheckUniqueID(G, ai);
285       MovieSceneAtom &sceneatom = scene.atomdata[unique_id];
286 
287       sceneatom.color = ai->color;
288       sceneatom.visRep = ai->visRep;
289     }
290   }
291 
292   // objectdata
293   for (ObjectIterator iter(G); iter.next();) {
294     const SpecRec * rec = iter.getSpecRec();
295     CObject * obj = iter.getObject();
296     MovieSceneObject &sceneobj = scene.objectdata[obj->Name];
297 
298     sceneobj.color = obj->Color;
299     sceneobj.visRep = obj->visRep;
300 
301     // store the "enabled" state in the first bit of visRep
302     SET_BIT_TO(sceneobj.visRep, 0, rec->visible);
303   }
304 
305   if (is_defaultstack) {
306     PRINTFB(G, FB_Scene, FB_Details)
307     " scene: scene stored as \"%s\".\n", key.c_str() ENDFB(G);
308   }
309 
310   return true;
311 }
312 
313 /**
314  * Display a message (with the message wizard)
315  */
MovieSceneRecallMessage(PyMOLGlobals * G,const std::string & message)316 static void MovieSceneRecallMessage(PyMOLGlobals * G, const std::string &message)
317 {
318 #ifndef _PYMOL_NOPY
319   // we can't just call python functions because we might be in the wrong
320   // thread. Instead, pass a parsable python string to PParse.
321   // To avoid syntax errors, replace all single quotes in *message*.
322 
323   std::string pystr = "/cmd.scene_recall_message(r'''" + message + "''')";
324   std::replace(pystr.begin() + 30, pystr.end() - 4, '\'', '`');
325   PParse(G, pystr.c_str());
326 #endif
327 }
328 
329 /**
330  * Set the frame or state, depending on whether a movie is defined and/or
331  * playing, and depending on the scene_frame_mode setting.
332  */
MovieSceneRecallFrame(PyMOLGlobals * G,int frame)333 static void MovieSceneRecallFrame(PyMOLGlobals * G, int frame)
334 {
335   int mode = 4;
336 
337   if (MoviePlaying(G)) {
338     mode = 10; // seek scene
339   } else if (frame == SceneGetFrame(G)) {
340     return;
341   } else {
342     int scene_frame_mode = SettingGetGlobal_i(G, cSetting_scene_frame_mode);
343     if(scene_frame_mode == 0 || (scene_frame_mode < 0 && MovieDefined(G)))
344       return;
345   }
346 
347 #ifdef _PYMOL_NOPY
348   SceneSetFrame(G, mode, frame);
349 #else
350   // PBlock fails with SceneSetFrame. Workaround: call from Python
351   PXDecRef(PYOBJECT_CALLMETHOD(G->P_inst->cmd, "set_frame", "ii", frame + 1, mode));
352 #endif
353 }
354 
355 /**
356  * Scene animation duration from settings
357  */
get_scene_animation_duration(PyMOLGlobals * G)358 static float get_scene_animation_duration(PyMOLGlobals * G) {
359   auto enabled = SettingGetGlobal_i(G, cSetting_scene_animation);
360   if (enabled < 0)
361     enabled = SettingGetGlobal_b(G, cSetting_animation);
362 
363   if (!enabled)
364     return 0.f;
365 
366   return SettingGetGlobal_f(G, cSetting_scene_animation_duration);
367 }
368 
369 /**
370  * Recall a scene
371  *
372  * @param name            name (key) of the scene to recall
373  * @param animate         animation duration, use scene_animation_duration if -1
374  * @param store_view      restore the camera view
375  * @param store_color     restore colors
376  * @param store_active    restore enabled/disabled
377  * @param store_rep       restore reps
378  * @return False if no scene named `name` exists
379  */
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,size_t stack)380 bool MovieSceneRecall(PyMOLGlobals * G, const char * name, float animate,
381     bool recall_view,
382     bool recall_color,
383     bool recall_active,
384     bool recall_rep,
385     bool recall_frame,
386     const char * sele,
387     size_t stack)
388 {
389   auto scenes = G->scenes + stack;
390   auto it = scenes->dict.find(name);
391 
392   if (it == scenes->dict.end()) {
393     PRINTFB(G, FB_Scene, FB_Errors)
394       " Error: scene '%s' is not defined.\n", name
395       ENDFB(G);
396     return false;
397   }
398 
399   if (stack == cMovieSceneStackDefault) {
400     // set scene_current_name
401     SettingSetGlobal_s(G, cSetting_scene_current_name, name);
402   }
403 
404   MovieScene &scene = it->second;
405 
406   // recall features if requested and stored
407   recall_view &= (scene.storemask & STORE_VIEW) != 0;
408   recall_active &= (scene.storemask & STORE_ACTIVE) != 0;
409   recall_color &= (scene.storemask & STORE_COLOR) != 0;
410   recall_rep &= (scene.storemask & STORE_REP) != 0;
411   recall_frame &= (scene.storemask & STORE_FRAME) != 0;
412 
413   // keep track of changes
414   // (obj -> repbitmask) stores the rep bits for all reps that changed,
415   // a value of zero means just to invalidate color.
416   std::map<CObject*, int> objectstoinvalidate;
417 
418   // atomdata
419   if (recall_color || recall_rep) {
420 
421     // fill atomdata dict
422     for (SeleAtomIterator iter(G, sele); iter.next();) {
423       AtomInfoType * ai = iter.getAtomInfo();
424       auto it = scene.atomdata.find(ai->unique_id);
425       if (it == scene.atomdata.end())
426         continue;
427 
428       MovieSceneAtom &sceneatom = it->second;
429 
430       if (recall_color) {
431         if (ai->color != sceneatom.color)
432           objectstoinvalidate[(CObject*) iter.obj];
433 
434         ai->color = sceneatom.color;
435       }
436 
437       if (recall_rep) {
438         int changed = (ai->visRep ^ sceneatom.visRep) & cRepsAtomMask;
439         if (changed)
440           objectstoinvalidate[(CObject*) iter.obj] |= changed;
441 
442         ai->visRep = sceneatom.visRep;
443       }
444     }
445   }
446 
447   // disable all objects
448   if (recall_active) {
449     // need to control SpecRec
450     ExecutiveSetObjVisib(G, "*", false, false);
451   }
452 
453   // objectdata
454   for (ObjectIterator iter(G); iter.next();) {
455     CObject * obj = iter.getObject();
456     auto it = scene.objectdata.find(obj->Name);
457     if (it == scene.objectdata.end())
458       continue;
459 
460     MovieSceneObject &sceneobj = it->second;
461 
462     if (recall_color) {
463       if (obj->Color != sceneobj.color)
464         objectstoinvalidate[obj];
465 
466       obj->Color = sceneobj.color;
467     }
468 
469     if (recall_rep) {
470       int changed = (obj->visRep ^ sceneobj.visRep) & cRepsObjectMask;
471       if (changed)
472         objectstoinvalidate[obj] |= changed;
473 
474       obj->visRep = sceneobj.visRep;
475     }
476 
477     // "enabled" state is first bit in visRep
478     int enabled = GET_BIT(sceneobj.visRep, 0);
479     if(recall_active && enabled) {
480       // need to control SpecRec
481       ExecutiveSetObjVisib(G, obj->Name, enabled, false);
482     }
483   }
484 
485   // invalidate
486   for (auto& item : objectstoinvalidate) {
487     item.first->invalidate(cRepAll, item.second ? cRepInvVisib : cRepInvColor, -1);
488   }
489 
490   // camera view
491   if (recall_view) {
492     if (animate < -0.5) // == -1
493       animate = get_scene_animation_duration(G);
494 
495     SceneSetView(G, scene.view, true, animate, 1);
496   }
497 
498   // message
499   MovieSceneRecallMessage(G, scene.message);
500 
501   // frame
502   if (recall_frame) {
503     MovieSceneRecallFrame(G, scene.frame);
504   }
505 
506   return true;
507 }
508 
509 /**
510  * Rename or delete a scene
511  *
512  * @param name name to rename or delete, or "*" to delete all
513  * @param new_name new scene name to rename, or NULL to delete
514  */
MovieSceneRename(PyMOLGlobals * G,const char * name,const char * new_name=nullptr)515 static bool MovieSceneRename(PyMOLGlobals * G, const char * name, const char * new_name = nullptr) {
516 
517   if (strcmp(name, "*") == 0) {
518     // delete all scenes
519     G->scenes->dict.clear();
520     G->scenes->order.clear();
521     SceneSetNames(G, G->scenes->order);
522     return true;
523   }
524 
525   if (!new_name) {
526     new_name = "";
527   } else if (strcmp(name, new_name) == 0) {
528     return true;
529   }
530 
531   auto it = G->scenes->dict.find(name);
532 
533   if (it != G->scenes->dict.end()) {
534     if (new_name[0])
535       std::swap(G->scenes->dict[new_name], it->second);
536     G->scenes->dict.erase(it);
537 
538     // does a scene named "new_name" already exist?
539     auto old_new = std::find(G->scenes->order.begin(), G->scenes->order.end(), new_name);
540 
541     // replace in or remove from "G->scenes->order" list
542     auto v_it = std::find(G->scenes->order.begin(), G->scenes->order.end(), name);
543     if (v_it == G->scenes->order.end()) {
544       printf("this is a bug, name must be in G->scenes->order\n");
545     } else {
546       if (new_name[0]) {
547         // rename
548         v_it->assign(new_name);
549 
550         // overwritten existing key?
551         if (old_new != G->scenes->order.end())
552           G->scenes->order.erase(old_new);
553       } else {
554         // remove
555         G->scenes->order.erase(v_it);
556       }
557     }
558 
559     SceneSetNames(G, G->scenes->order);
560 
561     // update scene_current_name
562     if (0 == strcmp(name, SettingGetGlobal_s(G,
563             cSetting_scene_current_name))) {
564       SettingSetGlobal_s(G, cSetting_scene_current_name, new_name);
565     }
566 
567     return true;
568   }
569 
570   return false;
571 }
572 
573 /**
574  * Delete a scene
575  *
576  * @param name scene name or "*" to delete all scenes (for default stack)
577  */
MovieSceneDelete(PyMOLGlobals * G,const char * name,size_t stack)578 bool MovieSceneDelete(PyMOLGlobals* G, const char* name, size_t stack) {
579   if (stack != cMovieSceneStackDefault) {
580     return G->scenes[stack].dict.erase(name) != 0;
581   }
582 
583   // takes also care of scene order and name="*"
584   return MovieSceneRename(G, name, nullptr);
585 }
586 
587 /**
588  * Print current scene order
589  */
MovieScenePrintOrder(PyMOLGlobals * G)590 static bool MovieScenePrintOrder(PyMOLGlobals * G) {
591   PRINTFB(G, FB_Scene, FB_Details)
592     " scene: current order:\n" ENDFB(G);
593 
594   for (auto it = G->scenes->order.begin(); it != G->scenes->order.end(); ++it) {
595     PRINTFB(G, FB_Scene, FB_Details)
596       " %s", it->c_str() ENDFB(G);
597   }
598 
599   PRINTFB(G, FB_Scene, FB_Details)
600     "\n" ENDFB(G);
601 
602   return true;
603 }
604 
605 /**
606  * Based on the "scene_current_name" setting, get the next or previous key.
607  *
608  * If the "scene_loop" setting is false and the key is out of range, return
609  * an empty string.
610  *
611  * @param next true = next, false = previous
612  */
MovieSceneGetNextKey(PyMOLGlobals * G,bool next)613 static const char * MovieSceneGetNextKey(PyMOLGlobals * G, bool next) {
614   const char * current_name = SettingGetGlobal_s(G, cSetting_scene_current_name);
615   int scene_loop = SettingGetGlobal_b(G, cSetting_scene_loop);
616 
617   if (!current_name[0])
618     scene_loop = true;
619 
620   auto it = std::find(G->scenes->order.begin(), G->scenes->order.end(), current_name);
621 
622   if (next) {
623     if (it < G->scenes->order.end() - 1) {
624       ++it;
625     } else if (scene_loop) {
626       it = G->scenes->order.begin();
627     } else {
628       return "";
629     }
630   } else {
631     if (it != G->scenes->order.begin() && it != G->scenes->order.end()) {
632       --it;
633     } else if (scene_loop) {
634       it = G->scenes->order.end() - 1;
635     } else {
636       return "";
637     }
638   }
639 
640   return it->c_str();
641 }
642 
643 /**
644  * Move the current scene (scene_current_name) before or after "key"
645  */
MovieSceneOrderBeforeAfter(PyMOLGlobals * G,const char * key,bool before)646 static bool MovieSceneOrderBeforeAfter(PyMOLGlobals * G, const char * key, bool before)
647 {
648   const char * location = nullptr;
649   const char * key2 = SettingGetGlobal_s(G, cSetting_scene_current_name);
650 
651   if (before) {
652     auto it = std::find(G->scenes->order.begin(), G->scenes->order.end(), key);
653     if (it == G->scenes->order.begin()) {
654       location = "top";
655       key = "";
656     } else {
657       key = (it - 1)->c_str();
658     }
659   }
660 
661   // order = key + ' ' + key2
662   std::string order(key);
663   order.append(" ").append(key2);
664 
665   MovieSceneOrder(G, order.c_str(), false, location);
666   return true;
667 }
668 
669 /**
670  * C implementation of the "scene" command
671  */
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,size_t stack)672 bool MovieSceneFunc(PyMOLGlobals * G, const char * key,
673     const char * action,
674     const char * message,
675     bool store_view,
676     bool store_color,
677     bool store_active,
678     bool store_rep,
679     bool store_frame,
680     float animate,
681     const char * new_key,
682     bool hand,
683     const char * sele,
684     size_t stack)
685 {
686   auto scenes = G->scenes + stack;
687   std::string prev_name;
688   short beforeafter = 0;
689   bool status = false;
690 
691   PRINTFB(G, FB_Scene, FB_Blather)
692     " MovieScene: key=%s action=%s message=%s store_view=%d store_color=%d"
693     " store_active=%d store_rep=%d animate=%f new_key=%s hand=%d\n",
694     key, action, message, store_view, store_color, store_active, store_rep,
695     animate, new_key, hand
696     ENDFB(G);
697 
698   // insert_before, insert_after
699   if (strncmp(action, "insert_", 7) == 0) {
700     prev_name = SettingGetGlobal_s(G, cSetting_scene_current_name);
701     if (!prev_name.empty())
702       beforeafter = (action[7] == 'b') ? 1 : 2;
703     action = "store";
704   }
705 
706   if (strcmp(action, "next") == 0 ||
707       strcmp(action, "previous") == 0) {
708     ok_assert(NOSCENES, scenes->order.size());
709 
710     key = MovieSceneGetNextKey(G, action[0] == 'n');
711     action = "recall";
712   } else if (strcmp(action, "start") == 0) {
713     ok_assert(NOSCENES, scenes->order.size());
714 
715     key = scenes->order[0].c_str();
716     action = "recall";
717   } else if (strcmp(key, "auto") == 0) {
718     key = SettingGetGlobal_s(G, cSetting_scene_current_name);
719   }
720 
721   if (strcmp(action, "recall") == 0) {
722     if (strcmp(key, "*") == 0)
723       return MovieScenePrintOrder(G);
724 
725     if (!key[0]) {
726       // empty key -> put up blank screen
727       SettingSetGlobal_s(G, cSetting_scene_current_name, "");
728       ExecutiveSetObjVisib(G, "*", false, false);
729       MovieSceneRecallMessage(G, "");
730     } else {
731       status = MovieSceneRecall(G, key, animate, store_view, store_color,
732           store_active, store_rep, store_frame, sele, stack);
733     }
734 
735   } else if (strcmp(action, "store") == 0) {
736     status = MovieSceneStore(G, key, message, store_view, store_color,
737         store_active, store_rep, store_frame, sele, stack);
738 
739     // insert_before, insert_after
740     if (status && beforeafter)
741       status = MovieSceneOrderBeforeAfter(G, prev_name.c_str(), beforeafter == 1);
742 
743   } else if (strcmp(action, "delete") == 0) {
744     status = MovieSceneDelete(G, key, stack);
745   } else if (strcmp(action, "rename") == 0) {
746     status = MovieSceneRename(G, key, new_key);
747   } else if (strcmp(action, "order") == 0) {
748     status = MovieSceneOrder(G, key);
749   } else if (strcmp(action, "sort") == 0) {
750     status = MovieSceneOrder(G, key, true);
751   } else if (strcmp(action, "first") == 0) {
752     status = MovieSceneOrder(G, key, false, "top");
753   } else {
754     PRINTFB(G, FB_Scene, FB_Errors)
755       " Error: invalid action '%s'\n", action ENDFB(G);
756   }
757 
758   // trigger GUI updates (scene buttons, Tcl/Tk menu)
759   SettingSetGlobal_b(G, cSetting_scenes_changed, true);
760   SettingGenerateSideEffects(G, cSetting_scenes_changed, nullptr, 0, true);
761 
762   return status;
763 
764 ok_exceptNOSCENES:
765   PRINTFB(G, FB_Scene, FB_Errors)
766     " Error: no scenes\n" ENDFB(G);
767   return false;
768 }
769 
770 /*
771  * Init/Free to set up PyMOLGlobals in PyMOL_Start
772  */
773 
MovieScenesInit(PyMOLGlobals * G)774 void MovieScenesInit(PyMOLGlobals * G) {
775   MovieScenesFree(G);
776   G->scenes = new CMovieScenes[cMovieSceneStack_SIZE];
777 }
778 
MovieScenesFree(PyMOLGlobals * G)779 void MovieScenesFree(PyMOLGlobals * G) {
780   if (G->scenes) {
781     delete[] G->scenes;
782     G->scenes = nullptr;
783   }
784 }
785 
786 /*
787  * PConvToPyObject/PConvFromPyObject
788  *
789  * Convertion to/from Python objects for all MovieScene types
790  */
791 
PConvToPyObject(const MovieSceneAtom & v)792 static PyObject * PConvToPyObject(const MovieSceneAtom &v) {
793   return PConvArgsToPyList(v.color, v.visRep);
794 }
795 
PConvToPyObject(const MovieSceneObject & v)796 static PyObject * PConvToPyObject(const MovieSceneObject &v) {
797   return PConvArgsToPyList(v.color, v.visRep);
798 }
799 
PConvToPyObject(const MovieScene & v)800 static PyObject * PConvToPyObject(const MovieScene &v) {
801   PyObject * obj = PyList_New(6);
802   PyList_SET_ITEM(obj, 0, PConvToPyObject(v.storemask));
803   PyList_SET_ITEM(obj, 1, PConvToPyObject(v.frame));
804   PyList_SET_ITEM(obj, 2, PConvToPyObject(v.message.c_str()));
805   PyList_SET_ITEM(obj, 3, PConvToPyObject(v.view, cSceneViewSize));
806   PyList_SET_ITEM(obj, 4, PConvToPyObject(v.atomdata));
807   PyList_SET_ITEM(obj, 5, PConvToPyObject(v.objectdata));
808   return obj;
809 }
810 
PConvFromPyObject(PyMOLGlobals *,PyObject * obj,MovieSceneAtom & out)811 static bool PConvFromPyObject(PyMOLGlobals *, PyObject * obj, MovieSceneAtom &out) {
812   return PConvArgsFromPyList(nullptr, obj, out.color, out.visRep);
813 }
814 
PConvFromPyObject(PyMOLGlobals *,PyObject * obj,MovieSceneObject & out)815 static bool PConvFromPyObject(PyMOLGlobals *, PyObject * obj, MovieSceneObject &out) {
816   return PConvArgsFromPyList(nullptr, obj, out.color, out.visRep);
817 }
818 
PConvFromPyObject(PyMOLGlobals * G,PyObject * obj,MovieScene & out)819 static bool PConvFromPyObject(PyMOLGlobals * G, PyObject * obj, MovieScene &out) {
820   std::map<int, MovieSceneAtom> atomdata_old_ids;
821 
822   if (!G) {
823     printf(" Error: G is NULL\n");
824     return false;
825   }
826 
827   if (!PConvArgsFromPyList(nullptr, obj,
828         out.storemask,
829         out.frame,
830         out.message,
831         out.view,
832         atomdata_old_ids,
833         out.objectdata))
834     /* ignore */;
835 
836   // restore atomdata dict but with converted ids
837   PConvFromPyObject(G, PyList_GetItem(obj, 4), atomdata_old_ids);
838   for (auto& item : atomdata_old_ids) {
839     int unique_id = SettingUniqueConvertOldSessionID(G, item.first);
840     std::swap(out.atomdata[unique_id], item.second);
841   }
842 
843   return true;
844 }
845 
846 /*
847  * For get_session
848  */
849 
MovieScenesAsPyList(PyMOLGlobals * G)850 PyObject * MovieScenesAsPyList(PyMOLGlobals * G) {
851   return PConvArgsToPyList(G->scenes->order, G->scenes->dict);
852 }
853 
MovieScenesFromPyList(PyMOLGlobals * G,PyObject * o)854 void MovieScenesFromPyList(PyMOLGlobals * G, PyObject * o) {
855   // delete existing scenes
856   MovieSceneRename(G, "*");
857 
858   PConvArgsFromPyList(G, o, G->scenes->order, G->scenes->dict);
859 
860   SceneSetNames(G, G->scenes->order);
861 }
862 
863 // vi:sw=2:expandtab:cindent
864