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