How to simulate "Cappucino" behavior?
-
Hi community,
I want to rebuild a tool similar to "Cappucino" but bake the camera and some parameters,
so :- Move our camera
- Start record then the camera then it changed first time
- Start animate of the doc, like we hit play
- Add a keyframe we want every frame
- End if we hit the end of timeline
- but how to simulate a "Play" in timeline, I test the animate.py, it finished immediately but not in "real time",time.sleep(1/doc.GetFps()) will freeze c4d, how to avoid that?
- how to avoid freezing with run the timeline with bake and draw the camera?
Cheers~
DunHou -
Hey @Dunhou,
Thank you for reaching out to us. While your question contains a clear description of your problem (thanks!), there is still one open question for me: What do you want to record, and how? You tell us that you want to move the camera, but leave it open as to what "moving the camera" means? Translation only, or also rotation? Does moving the camera also include operating the camera like controlling its zoom?
In general, what you are trying to do has no clear cut development path and therefore can lead to problems. If I remember correctly, we had internally also some 'fun' with Cappuccino as it violates a few rules of what plugins should do and what not. Here are a few unsorted points of high level advice:
- There once has been the thread is-it-possible-to-control-camera-with-keyboard-wasd-like-a-fps-game which could be of interest for you. I posted at the end a very drafty code example. I assume that making the connection from code that just offers "live-control" to code that also offers the key-framing of that input is trivial for you.
- I see three major problems with your goal(s):
- Tool Conflict : You do not clarify how you want to move the camera. But the implication is that you want to use the builtin move tool. For what you want to do, this could lead to problems, depending on the route you take. You might have to reimplement controls or move gadgets. This depends on what route you take.
- Animation Conflict: You seem to imply that you want to just press play and then have your tool record the camera. This can again result in conflicts, depending on your approach. As a fix, when you have a static scene, you can just animate/update the camera yourself as I did in my dialog example in (1). But when you animate the camera in an also animated scene, you would have also to manually animate the scene. I.e., for each frame you "animate", you must also advance the active frame of the active document and then execute the passes on the document, so that the scene does update. Remember that as a rule of thumb, you must execute the passes three times in a row to fully update a scene (due to some complex dependencies things like MoGraph can build).
- Implementation Conflict: There is not a perfect plugin type and way to do this, all choices have drawbacks. A scene hook is technically a good choice, as it gives you a high frequency input stream and is otherwise fairly low level. But it is C++ only and more importantly should not be used for such things due to its nodal nature. Which leaves two options:
- GeDialog: The idea is here to manually step through the document, either with a timer like I did in my example, or in a bit more complex way like Cappuccino does, it basically sends itself messages to advance to the next frame and with that assures that it is in sync with Cinema 4D's main event loop. This is also centred around
EVMSG_ASYNCEDITORMOVE
to only capture things when the users is actually interacting with an editor. I showed this simpler part below. The sending itself messages part to update each frame and play the timeline in the mean time is not reproducible in Python (at least I think so). You would to use the timer approach in Python. - ToolData: The other and a bit more natural (at least in my opinion) route would be to implement a tool or description tool (C++ only). The idea is here that you overwrite
Draw
,MouseInput
, andKeyboardInput
and handle everything there. The problem is here that you might get event starved, i.e.,MouseInput
and other methods might not be called as often as you would like to.
- GeDialog: The idea is here to manually step through the document, either with a timer like I did in my example, or in a bit more complex way like Cappuccino does, it basically sends itself messages to advance to the next frame and with that assures that it is in sync with Cinema 4D's main event loop. This is also centred around
Long story short, there is no super good way to do this, especially in Python. Python might be intrinsically too slow for this as you only have a very small window of 1/30 of a second - or even less - to carry out quite a bit of computations. And you also cannot just pre-compute/cache thing as you are dependent on user inputs.
I personally would explore the tool route when I have some time to waste, as there is at least the loose promise of finding a cleaner solution. The dialog route using a timer which I showed in the camera thread is guaranteed to work but is bound to perform poorly in 'heavy' scenes. The route that the 'Cappuccino' tool takes is more performant, but I am not so sure that you can emulate in Python all the message shenanigans it does to achieve its features, you probably must use a timer instead.
Cheers,
Ferdinand"""Demonstrates a core mechanism of the Cappuccino tool, the async move messages. Run this script in the script manager of Cinema 4D. Drag objects in the viewport and watch the console to see the message stream. This will also work with an editor camera, as it is also just a hidden object in the scene. """ import c4d import ctypes class MessageDialog(c4d.gui.GeDialog): """Realizes a simple dialog. """ def CreateLayout(self) -> bool: """Creates the layout for the dialog. """ self.SetTitle("MessageDialog") return True def Message(self, msg: c4d.BaseContainer, result: c4d.BaseContainer) -> None: """Called by Cinema 4D to process messages for the dialog. We use it here to catch the async move message broadcasted by the editor. """ # This is an async move message broadcasted by the editor. if (msg.GetId() == c4d.BFM_SYNC_MESSAGE and msg.GetInt32(c4d.BFM_CORE_ID) == c4d.EVMSG_ASYNCEDITORMOVE): # Get the payload for this core message, we have to dig a bit around in the internals # of Cinema 4D. This code just "casts" the #capsule payload to an integer value, as # that is what the C++ code does. capsule: "PyCapsule" | None = msg.GetVoid(c4d.BFM_CORE_PAR1) if capsule is None: return c4d.gui.GeDialog.Message(self, msg, result) ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_int ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object] event: int = ctypes.pythonapi.PyCapsule_GetPointer(capsule, None) if (event == c4d.MOVE_START): print ("EVMSG_ASYNCEDITORMOVE: MOVE_START") elif (event == c4d.MOVE_CONTINUE): print ("EVMSG_ASYNCEDITORMOVE: MOVE_CONTINUE") elif (event == c4d.MOVE_END): print ("EVMSG_ASYNCEDITORMOVE: MOVE_END") return c4d.gui.GeDialog.Message(self, msg, result) if __name__=='__main__': # Establish a global instance of the dialog so that we can have an async dialog. This will result # in a dangling dialog, please do not do this in production code. You always need a command data # plugin or another owning entity for an async dialog in production code. dlg: MessageDialog = MessageDialog() dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=200, default=100)
-
Hey @ferdinand , thanks for your super useful answer here!
@ferdinand said in How to simulate "Cappucino" behavior?:
What do you want to record, and how?
- Mostly is the PSR change if camera, or some params( maybe slow, but I think if we can make it work, it is same as the psr data).
- How: maybe a 3D mouse or regular mouse input, that you hint me it can cause problem.
@ferdinand said in How to simulate "Cappucino" behavior?:
Animation Conflict: You seem to imply that you want to just press play and then have your tool record the camera. This can again result in conflicts, depending on your approach. As a fix, when you have a static scene, you can just animate/update the camera yourself as I did in my dialog example in (1). But when you animate the camera in an also animated scene, you would have also to manually animate the scene. I.e., for each frame you "animate", you must also advance the active frame of the active document and then execute the passes on the document, so that the scene does update. Remember that as a rule of thumb, you must execute the passes three times in a row to fully update a scene (due to some complex dependencies things like MoGraph can build)
- wow , super strange of muti passes, I'm a little curious why we have a flag that can force update in one function?
@ferdinand said in How to simulate "Cappucino" behavior?:
Long story short, there is no super good way to do this, especially in Python. Python might be intrinsically too slow for this as you only have a very small window of 1/30 of a second - or even less - to carry out quite a bit of computations. And you also cannot just pre-compute/cache thing as you are dependent on user inputs.
Thank you for all your suggestions!
Cheers~
DunHou -
Hey,
- Mostly is the PSR change if camera, or some params( maybe slow, but I think if we can make it work, it is same as the psr data).
- How: maybe a 3D mouse or regular mouse input, that you hint me it can cause problem.
You probably want to use the builtin move tool, right? The only thing that helps here, is implement a test ballon dialog akin to what I did above. And then try to read out the position of the active object in an
EVMSG_ASYNCEDITORMOVE
loop while the user is dragging the object, panning the camera. There is a chance that you get event starved by the GIL. It is unlikely that this happens I would say, but that would be the first thing I would test as cheaply as possible (so that I do not waste a lot of time on something which has a principal flaw).- wow , super strange of muti passes, I'm a little curious why we have a flag that can force update in one function?
Hm, I think you misunderstood me. At least I do not understand your question .
BaseDocument::ExecutePasses
obviously does not have a builtin loop value, you can just write this in two lines yourself both in Python and C++:for _ in range(3): doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_NONE)
for (int _ = 0; _ < 3; ++_) doc->ExecutePasses(nullptr, true, true, true, BUILDFLAGS_NONE);
The reason why this must be done, is because you can have some crazy dependencies like for example some Sphere object generator deformed by a Bend object influenced by a Point Object field for a Cube object generator, which is deformed itself by a Surface deformer linked to Landscape generator object. Cinema 4D evaluates its object graph in a dumb top-down manner, it has no dependency graph. It has no clue that it should start with the Landscape generator object to correctly unravel this graph in one go.
The (expensive) solution to this is to just build the caches a few times in a row, so that you can rely on each pass on the output you have built in the prior pass. You can find this hack all over our code base. And when you do not do this, usually things do not break completely, because for the first pass, you still have the caches from the last frame/scene state. But you then of course partially lag behind one frame in your scene state.
Technically speaking, with 2024 and the NodeData data access system, there is now a "kind of" dependency graph in Cinema 4D which mitigates the need of this "hack". But
GetAccessedObjects
and co. are not a true dependency graph, as they must be manually curated (Ole, who wrote it, really made a point of that when I documented the system). And you also cannot yet use it in Python.PS: But before you go down that rabbit hole, you should really test the basics as I did in a very quick and dirty manner below. This seems to work, so this might be easier than anticipated (famous last words, I know).
Cheers,
FerdinandOutput
I just wildly orbited my camera like a madman.
frame = 28, camera.GetMg() = Matrix(v1: (-0.409, 0, -0.913); v2: (0.16, 0.985, -0.072); v3: (0.899, -0.175, -0.402); off: (-425.3, 140.674, 308.534)) frame = 29, camera.GetMg() = Matrix(v1: (-0.409, 0, -0.913); v2: (0.16, 0.985, -0.072); v3: (0.899, -0.175, -0.402); off: (-425.3, 140.674, 308.534)) frame = 30, camera.GetMg() = Matrix(v1: (-0.409, 0, -0.913); v2: (0.16, 0.985, -0.072); v3: (0.899, -0.175, -0.402); off: (-425.3, 140.674, 308.534)) frame = 31, camera.GetMg() = Matrix(v1: (-0.365, 0, -0.931); v2: (0.182, 0.981, -0.071); v3: (0.913, -0.195, -0.358); off: (-432.802, 151.119, 285.545)) frame = 31, camera.GetMg() = Matrix(v1: (-0.252, 0, -0.968); v2: (0.231, 0.971, -0.06); v3: (0.94, -0.238, -0.244); off: (-446.79, 173.468, 226.356)) frame = 32, camera.GetMg() = Matrix(v1: (-0.06, 0, -0.998); v2: (0.286, 0.958, -0.017); v3: (0.956, -0.287, -0.057); off: (-455.251, 198.714, 128.826)) frame = 33, camera.GetMg() = Matrix(v1: (0.288, 0, -0.958); v2: (0.348, 0.931, 0.105); v3: (0.892, -0.364, 0.268); off: (-421.632, 238.909, -40.579)) frame = 33, camera.GetMg() = Matrix(v1: (0.388, 0, -0.921); v2: (0.362, 0.92, 0.153); v3: (0.847, -0.393, 0.357); off: (-398.36, 254.047, -86.875)) frame = 33, camera.GetMg() = Matrix(v1: (0.462, 0, -0.887); v2: (0.359, 0.915, 0.187); v3: (0.811, -0.405, 0.423); off: (-379.373, 260.047, -121.029)) frame = 34, camera.GetMg() = Matrix(v1: (0.59, 0, -0.807); v2: (0.34, 0.907, 0.249); v3: (0.732, -0.422, 0.535); off: (-338.187, 268.985, -179.531)) frame = 35, camera.GetMg() = Matrix(v1: (0.687, 0, -0.727); v2: (0.319, 0.899, 0.301); v3: (0.653, -0.439, 0.617); off: (-297.149, 277.844, -222.164)) frame = 35, camera.GetMg() = Matrix(v1: (0.788, 0, -0.616); v2: (0.273, 0.896, 0.35); v3: (0.551, -0.444, 0.706); off: (-244.15, 280.776, -268.366)) frame = 36, camera.GetMg() = Matrix(v1: (0.825, 0, -0.565); v2: (0.251, 0.896, 0.367); v3: (0.506, -0.444, 0.739); off: (-220.534, 280.776, -285.612)) frame = 36, camera.GetMg() = Matrix(v1: (0.878, 0, -0.48); v2: (0.208, 0.901, 0.38); v3: (0.432, -0.433, 0.791); off: (-182.04, 274.913, -312.519)) frame = 37, camera.GetMg() = Matrix(v1: (0.931, 0, -0.366); v2: (0.157, 0.904, 0.398); v3: (0.331, -0.427, 0.841); off: (-129.37, 271.972, -338.636)) frame = 37, camera.GetMg() = Matrix(v1: (0.975, 0, -0.223); v2: (0.09, 0.914, 0.394); v3: (0.204, -0.405, 0.891); off: (-63.09, 260.123, -364.708)) frame = 38, camera.GetMg() = Matrix(v1: (0.996, 0, -0.087); v2: (0.033, 0.924, 0.38); v3: (0.08, -0.382, 0.921); off: (1.219, 248.133, -379.921)) frame = 38, camera.GetMg() = Matrix(v1: (1, 0, -0.012); v2: (0.004, 0.929, 0.37); v3: (0.011, -0.37, 0.929); off: (37.319, 242.087, -384.121)) frame = 39, camera.GetMg() = Matrix(v1: (1, 0, 0.026); v2: (-0.009, 0.931, 0.364); v3: (-0.024, -0.364, 0.931); off: (55.535, 239.052, -385.172)) frame = 39, camera.GetMg() = Matrix(v1: (0.999, 0, 0.038); v2: (-0.014, 0.931, 0.364); v3: (-0.036, -0.364, 0.931); off: (61.609, 239.052, -384.972)) frame = 40, camera.GetMg() = Matrix(v1: (0.998, 0, 0.057); v2: (-0.021, 0.931, 0.364); v3: (-0.053, -0.364, 0.93); off: (70.714, 239.052, -384.529)) frame = 40, camera.GetMg() = Matrix(v1: (0.996, 0, 0.088); v2: (-0.031, 0.936, 0.351); v3: (-0.083, -0.352, 0.932); off: (86.081, 232.956, -385.735)) frame = 40, camera.GetMg() = Matrix(v1: (0.992, 0, 0.126); v2: (-0.043, 0.94, 0.338); v3: (-0.118, -0.341, 0.933); off: (104.599, 226.828, -385.995)) frame = 41, camera.GetMg() = Matrix(v1: (0.986, 0, 0.169); v2: (-0.056, 0.944, 0.324); v3: (-0.16, -0.329, 0.931); off: (126.239, 220.668, -384.954)) frame = 41, camera.GetMg() = Matrix(v1: (0.973, 0, 0.231); v2: (-0.07, 0.952, 0.297); v3: (-0.22, -0.305, 0.927); off: (157.444, 208.261, -382.771)) frame = 42, camera.GetMg() = Matrix(v1: (0.941, 0, 0.339); v2: (-0.089, 0.965, 0.247); v3: (-0.327, -0.263, 0.908); off: (213.338, 186.31, -372.828)) frame = 42, camera.GetMg() = Matrix(v1: (0.902, 0, 0.432); v2: (-0.098, 0.974, 0.204); v3: (-0.421, -0.226, 0.879); off: (261.947, 167.283, -357.623)) frame = 43, camera.GetMg() = Matrix(v1: (0.854, 0, 0.52); v2: (-0.099, 0.982, 0.162); v3: (-0.511, -0.189, 0.839); off: (308.833, 148.089, -336.772)) frame = 43, camera.GetMg() = Matrix(v1: (0.778, 0, 0.628); v2: (-0.088, 0.99, 0.109); v3: (-0.622, -0.14, 0.771); off: (366.611, 122.285, -301.29)) frame = 44, camera.GetMg() = Matrix(v1: (0.689, 0, 0.725); v2: (-0.07, 0.995, 0.066); v3: (-0.722, -0.096, 0.686); off: (418.472, 99.554, -257.027)) frame = 44, camera.GetMg() = Matrix(v1: (0.608, 0, 0.794); v2: (-0.042, 0.999, 0.032); v3: (-0.793, -0.052, 0.607); off: (455.677, 76.728, -215.945)) frame = 45, camera.GetMg() = Matrix(v1: (0.557, 0, 0.831); v2: (-0.012, 1, 0.008); v3: (-0.831, -0.015, 0.557); off: (475.202, 57.119, -189.743)) frame = 46, camera.GetMg() = Matrix(v1: (0.52, 0, 0.854); v2: (0.009, 1, -0.005); v3: (-0.854, 0.01, 0.519); off: (487.516, 44.039, -170.451)) frame = 46, camera.GetMg() = Matrix(v1: (0.509, 0, 0.861); v2: (0.014, 1, -0.009); v3: (-0.861, 0.017, 0.509); off: (490.833, 40.77, -164.815)) frame = 47, camera.GetMg() = Matrix(v1: (0.498, 0, 0.867); v2: (0.025, 1, -0.015); v3: (-0.867, 0.029, 0.498); off: (493.983, 34.231, -159.084)) frame = 48, camera.GetMg() = Matrix(v1: (0.498, 0, 0.867); v2: (0.025, 1, -0.015); v3: (-0.867, 0.029, 0.498); off: (493.983, 34.231, -159.084)) frame = 49, camera.GetMg() = Matrix(v1: (0.498, 0, 0.867); v2: (0.047, 0.999, -0.027); v3: (-0.866, 0.054, 0.497); off: (493.499, 21.164, -158.806)) frame = 49, camera.GetMg() = Matrix(v1: (0.503, 0, 0.864); v2: (0.058, 0.998, -0.034); v3: (-0.862, 0.067, 0.502); off: (491.519, 14.636, -161.431)) frame = 49, camera.GetMg() = Matrix(v1: (0.567, 0, 0.824); v2: (0.102, 0.992, -0.07); v3: (-0.817, 0.123, 0.563); off: (468.259, -14.655, -192.903)) frame = 49, camera.GetMg() = Matrix(v1: (0.598, 0, 0.802); v2: (0.124, 0.988, -0.092); v3: (-0.792, 0.154, 0.59); off: (455.104, -30.844, -207.373)) frame = 50, camera.GetMg() = Matrix(v1: (0.741, 0, 0.671); v2: (0.194, 0.957, -0.214); v3: (-0.642, 0.289, 0.71); off: (377.146, -100.916, -269.518)) frame = 51, camera.GetMg() = Matrix(v1: (0.92, 0, 0.392); v2: (0.181, 0.886, -0.426); v3: (-0.347, 0.463, 0.815); off: (223.605, -191.579, -324.473)) frame = 51, camera.GetMg() = Matrix(v1: (0.96, 0, 0.279); v2: (0.145, 0.855, -0.497); v3: (-0.239, 0.518, 0.821); off: (167.33, -220.049, -327.577)) frame = 52, camera.GetMg() = Matrix(v1: (0.981, 0, 0.194); v2: (0.108, 0.832, -0.545); v3: (-0.161, 0.555, 0.816); off: (126.941, -239.351, -324.814)) frame = 52, camera.GetMg() = Matrix(v1: (0.994, 0, 0.113); v2: (0.066, 0.814, -0.577); v3: (-0.092, 0.581, 0.809); off: (90.985, -252.798, -321.04)) frame = 53, camera.GetMg() = Matrix(v1: (0.998, 0, 0.069); v2: (0.041, 0.803, -0.595); v3: (-0.056, 0.596, 0.801); off: (72.046, -260.723, -316.992)) frame = 53, camera.GetMg() = Matrix(v1: (0.999, 0, 0.044); v2: (0.027, 0.795, -0.606); v3: (-0.035, 0.606, 0.794); off: (61.395, -265.946, -313.668)) frame = 54, camera.GetMg() = Matrix(v1: (1, 0, 0); v2: (0, 0.784, -0.621); v3: (0, 0.621, 0.784); off: (43.207, -273.686, -308.067)) frame = 54, camera.GetMg() = Matrix(v1: (1, 0, -0.025); v2: (-0.016, 0.776, -0.631); v3: (0.019, 0.631, 0.776); off: (33.066, -278.782, -303.855)) frame = 55, camera.GetMg() = Matrix(v1: (0.998, 0, -0.069); v2: (-0.044, 0.768, -0.639); v3: (0.053, 0.641, 0.766); off: (15.625, -283.826, -298.893)) frame = 55, camera.GetMg() = Matrix(v1: (0.991, 0, -0.137); v2: (-0.091, 0.747, -0.658); v3: (0.103, 0.664, 0.74); off: (-10.357, -296.205, -285.498)) frame = 56, camera.GetMg() = Matrix(v1: (0.97, 0, -0.242); v2: (-0.164, 0.735, -0.658); v3: (0.178, 0.678, 0.713); off: (-49.505, -303.469, -271.26)) frame = 56, camera.GetMg() = Matrix(v1: (0.937, 0, -0.35); v2: (-0.242, 0.722, -0.648); v3: (0.253, 0.692, 0.676); off: (-88.406, -310.607, -252.193)) frame = 57, camera.GetMg() = Matrix(v1: (0.885, 0, -0.465); v2: (-0.33, 0.704, -0.629); v3: (0.327, 0.71, 0.623); off: (-127.188, -319.925, -224.871)) frame = 57, camera.GetMg() = Matrix(v1: (0.831, 0, -0.557); v2: (-0.4, 0.695, -0.597); v3: (0.387, 0.719, 0.578); off: (-158.201, -324.497, -201.035)) frame = 58, camera.GetMg() = Matrix(v1: (0.763, 0, -0.647); v2: (-0.471, 0.686, -0.555); v3: (0.444, 0.728, 0.523); off: (-187.776, -329.009, -172.841)) frame = 58, camera.GetMg() = Matrix(v1: (0.689, 0, -0.724); v2: (-0.536, 0.672, -0.51); v3: (0.487, 0.74, 0.463); off: (-210.28, -335.666, -141.76)) frame = 59, camera.GetMg() = Matrix(v1: (0.608, 0, -0.794); v2: (-0.588, 0.672, -0.45); v3: (0.534, 0.74, 0.409); off: (-234.562, -335.666, -113.423)) frame = 60, camera.GetMg() = Matrix(v1: (0.541, 0, -0.841); v2: (-0.622, 0.672, -0.401); v3: (0.565, 0.74, 0.364); off: (-251.012, -335.666, -90.099)) frame = 60, camera.GetMg() = Matrix(v1: (0.482, 0, -0.876); v2: (-0.649, 0.672, -0.357); v3: (0.589, 0.74, 0.324); off: (-263.407, -335.666, -69.368)) frame = 61, camera.GetMg() = Matrix(v1: (0.403, 0, -0.915); v2: (-0.67, 0.681, -0.295); v3: (0.624, 0.732, 0.275); off: (-281.47, -331.243, -43.774)) frame = 61, camera.GetMg() = Matrix(v1: (0.333, 0, -0.943); v2: (-0.674, 0.7, -0.238); v3: (0.66, 0.714, 0.233); off: (-300.269, -322.218, -22.109)) frame = 62, camera.GetMg() = Matrix(v1: (0.243, 0, -0.97); v2: (-0.676, 0.717, -0.169); v3: (0.696, 0.697, 0.174); off: (-319.155, -312.958, 8.455)) frame = 62, camera.GetMg() = Matrix(v1: (0.119, 0, -0.993); v2: (-0.655, 0.751, -0.079); v3: (0.746, 0.66, 0.09); off: (-345.351, -293.756, 52.392)) frame = 62, camera.GetMg() = Matrix(v1: (-0.012, 0, -1); v2: (-0.621, 0.784, 0.008); v3: (0.784, 0.621, -0.01); off: (-364.953, -273.686, 104.049)) frame = 63, camera.GetMg() = Matrix(v1: (-0.144, 0, -0.99); v2: (-0.575, 0.814, 0.083); v3: (0.805, 0.581, -0.117); off: (-376.397, -252.798, 159.892)) frame = 63, camera.GetMg() = Matrix(v1: (-0.254, 0, -0.967); v2: (-0.522, 0.842, 0.137); v3: (0.814, 0.539, -0.214); off: (-381.1, -231.145, 210.528)) frame = 64, camera.GetMg() = Matrix(v1: (-0.332, 0, -0.943); v2: (-0.478, 0.862, 0.169); v3: (0.813, 0.507, -0.287); off: (-380.328, -214.437, 248.148)) frame = 64, camera.GetMg() = Matrix(v1: (-0.379, 0, -0.925); v2: (-0.444, 0.877, 0.182); v3: (0.812, 0.48, -0.333); off: (-379.805, -200.225, 272.277)) frame = 65, camera.GetMg() = Matrix(v1: (-0.414, 0, -0.91); v2: (-0.417, 0.889, 0.19); v3: (0.809, 0.458, -0.368); off: (-378.585, -188.677, 290.619)) frame = 66, camera.GetMg() = Matrix(v1: (-0.47, 0, -0.882); v2: (-0.364, 0.911, 0.194); v3: (0.804, 0.412, -0.429); off: (-375.819, -165.137, 322.053)) frame = 66, camera.GetMg() = Matrix(v1: (-0.562, 0, -0.827); v2: (-0.274, 0.944, 0.186); v3: (0.781, 0.331, -0.53); off: (-363.825, -122.671, 374.98)) frame = 67, camera.GetMg() = Matrix(v1: (-0.661, 0, -0.75); v2: (-0.162, 0.976, 0.143); v3: (0.733, 0.216, -0.645); off: (-338.899, -62.967, 435.027)) frame = 67, camera.GetMg() = Matrix(v1: (-0.766, 0, -0.643); v2: (-0.043, 0.998, 0.051); v3: (0.641, 0.067, -0.765); off: (-291.362, 14.636, 497.111)) frame = 68, camera.GetMg() = Matrix(v1: (-0.87, 0, -0.493); v2: (0.066, 0.991, -0.116); v3: (0.489, -0.134, -0.862); off: (-212.106, 119.045, 548.08)) frame = 68, camera.GetMg() = Matrix(v1: (-0.899, 0, -0.437); v2: (0.104, 0.971, -0.215); v3: (0.425, -0.239, -0.873); off: (-178.93, 173.645, 553.919)) frame = 69, camera.GetMg() = Matrix(v1: (-0.92, 0, -0.392); v2: (0.124, 0.948, -0.292); v3: (0.372, -0.317, -0.873); off: (-151.205, 214.479, 553.639)) frame = 70, camera.GetMg() = Matrix(v1: (-0.934, 0, -0.357); v2: (0.134, 0.927, -0.351); v3: (0.331, -0.376, -0.866); off: (-129.918, 245.157, 550.059)) frame = 70, camera.GetMg() = Matrix(v1: (-0.936, 0, -0.351); v2: (0.144, 0.912, -0.384); v3: (0.32, -0.411, -0.854); off: (-124.382, 263.201, 543.923)) frame = 71, camera.GetMg() = Matrix(v1: (-0.918, 0, -0.397); v2: (0.207, 0.853, -0.479); v3: (0.339, -0.522, -0.783); off: (-134.388, 321.02, 507.01)) frame = 71, camera.GetMg() = Matrix(v1: (-0.834, 0, -0.552); v2: (0.366, 0.749, -0.553); v3: (0.413, -0.663, -0.624); off: (-173.002, 394.593, 424.458)) frame = 71, camera.GetMg() = Matrix(v1: (-0.642, 0, -0.767); v2: (0.621, 0.586, -0.52); v3: (0.449, -0.81, -0.376); off: (-191.825, 471.361, 295.231)) frame = 72, camera.GetMg() = Matrix(v1: (-0.391, 0, -0.92); v2: (0.831, 0.43, -0.353); v3: (0.395, -0.903, -0.168); off: (-163.698, 519.722, 186.691)) frame = 73, camera.GetMg() = Matrix(v1: (-0.176, 0, -0.984); v2: (0.935, 0.313, -0.167); v3: (0.309, -0.95, -0.055); off: (-118.501, 544.005, 127.8)) frame = 73, camera.GetMg() = Matrix(v1: (0.054, 0, -0.999); v2: (0.974, 0.22, 0.052); v3: (0.22, -0.975, 0.012); off: (-72.2, 557.515, 92.905)) frame = 74, camera.GetMg() = Matrix(v1: (0.249, 0, -0.969); v2: (0.95, 0.197, 0.244); v3: (0.191, -0.98, 0.049); off: (-57.208, 560.051, 73.436)) frame = 75, camera.GetMg() = Matrix(v1: (0.421, 0, -0.907); v2: (0.891, 0.187, 0.413); v3: (0.169, -0.982, 0.078); off: (-45.666, 561.163, 58.1)) frame = 76, camera.GetMg() = Matrix(v1: (0.529, 0, -0.849); v2: (0.833, 0.192, 0.519); v3: (0.163, -0.981, 0.101); off: (-42.224, 560.632, 46.103)) frame = 76, camera.GetMg() = Matrix(v1: (0.551, 0, -0.834); v2: (0.818, 0.197, 0.541); v3: (0.164, -0.98, 0.109); off: (-43.04, 560.091, 42.362)) frame = 76, camera.GetMg() = Matrix(v1: (0.611, 0, -0.792); v2: (0.774, 0.207, 0.598); v3: (0.164, -0.978, 0.127); off: (-42.847, 558.983, 32.97)) frame = 77, camera.GetMg() = Matrix(v1: (0.69, 0, -0.724); v2: (0.701, 0.248, 0.668); v3: (0.179, -0.969, 0.171); off: (-50.715, 554.053, 9.98)) frame = 78, camera.GetMg() = Matrix(v1: (0.814, 0, -0.581); v2: (0.551, 0.317, 0.772); v3: (0.184, -0.949, 0.258); off: (-52.954, 543.453, -35.271)) frame = 78, camera.GetMg() = Matrix(v1: (0.88, 0, -0.475); v2: (0.44, 0.375, 0.816); v3: (0.178, -0.927, 0.33); off: (-49.903, 532.213, -72.849)) frame = 78, camera.GetMg() = Matrix(v1: (0.927, 0, -0.375); v2: (0.339, 0.428, 0.838); v3: (0.16, -0.904, 0.397); off: (-40.639, 520.057, -107.641)) frame = 79, camera.GetMg() = Matrix(v1: (0.962, 0, -0.272); v2: (0.237, 0.491, 0.838); v3: (0.134, -0.871, 0.472); off: (-26.624, 503.2, -146.688)) frame = 80, camera.GetMg() = Matrix(v1: (0.995, 0, -0.1); v2: (0.081, 0.591, 0.803); v3: (0.059, -0.807, 0.588); off: (12.227, 469.661, -206.694)) frame = 80, camera.GetMg() = Matrix(v1: (0.999, 0, -0.049); v2: (0.038, 0.623, 0.782); v3: (0.03, -0.783, 0.622); off: (27.281, 456.954, -224.457)) frame = 81, camera.GetMg() = Matrix(v1: (0.999, 0, 0.034); v2: (-0.025, 0.672, 0.74); v3: (-0.023, -0.741, 0.671); off: (54.795, 435.169, -250.204)) frame = 82, camera.GetMg() = Matrix(v1: (0.992, 0, 0.13); v2: (-0.088, 0.736, 0.671); v3: (-0.095, -0.677, 0.73); off: (92.733, 402.003, -280.474)) frame = 82, camera.GetMg() = Matrix(v1: (0.987, 0, 0.16); v2: (-0.104, 0.76, 0.641); v3: (-0.121, -0.65, 0.75); off: (106.267, 387.792, -291.198)) frame = 83, camera.GetMg() = Matrix(v1: (0.982, 0, 0.19); v2: (-0.118, 0.783, 0.61); v3: (-0.149, -0.621, 0.769); off: (120.512, 373.014, -300.993)) frame = 83, camera.GetMg() = Matrix(v1: (0.979, 0, 0.202); v2: (-0.12, 0.806, 0.58); v3: (-0.163, -0.592, 0.789); off: (127.802, 357.701, -311.433)) frame = 83, camera.GetMg() = Matrix(v1: (0.977, 0, 0.214); v2: (-0.123, 0.817, 0.564); v3: (-0.175, -0.577, 0.798); off: (134.085, 349.861, -315.855)) frame = 84, camera.GetMg() = Matrix(v1: (0.974, 0, 0.226); v2: (-0.126, 0.831, 0.542); v3: (-0.188, -0.557, 0.809); off: (140.889, 339.209, -321.813)) frame = 85, camera.GetMg() = Matrix(v1: (0.974, 0, 0.226); v2: (-0.12, 0.848, 0.517); v3: (-0.192, -0.53, 0.826); off: (142.879, 325.595, -330.384)) frame = 85, camera.GetMg() = Matrix(v1: (0.974, 0, 0.226); v2: (-0.115, 0.861, 0.496); v3: (-0.195, -0.509, 0.838); off: (144.405, 314.484, -336.956)) frame = 85, camera.GetMg() = Matrix(v1: (0.984, 0, 0.177); v2: (-0.083, 0.882, 0.463); v3: (-0.156, -0.471, 0.868); off: (124.395, 294.588, -352.493)) frame = 86, camera.GetMg() = Matrix(v1: (0.998, 0, 0.065); v2: (-0.025, 0.925, 0.379); v3: (-0.06, -0.38, 0.923); off: (74.303, 247.252, -381.022)) frame = 87, camera.GetMg() = Matrix(v1: (0.996, 0, -0.086); v2: (0.024, 0.958, 0.284); v3: (0.082, -0.285, 0.955); off: (0.264, 197.923, -397.637)) frame = 87, camera.GetMg() = Matrix(v1: (0.991, 0, -0.136); v2: (0.036, 0.964, 0.265); v3: (0.131, -0.267, 0.955); off: (-25.029, 188.492, -397.557)) frame = 88, camera.GetMg() = Matrix(v1: (0.975, 0, -0.222); v2: (0.049, 0.976, 0.213); v3: (0.217, -0.218, 0.951); off: (-69.843, 163.111, -395.862)) frame = 88, camera.GetMg() = Matrix(v1: (0.942, 0, -0.337); v2: (0.053, 0.988, 0.147); v3: (0.333, -0.157, 0.93); off: (-130.097, 130.991, -384.691)) frame = 88, camera.GetMg() = Matrix(v1: (0.928, 0, -0.372); v2: (0.049, 0.991, 0.122); v3: (0.369, -0.132, 0.92); off: (-148.922, 118.047, -379.577)) frame = 89, camera.GetMg() = Matrix(v1: (0.886, 0, -0.463); v2: (0.044, 0.996, 0.084); v3: (0.461, -0.094, 0.882); off: (-197.057, 98.551, -359.886)) frame = 90, camera.GetMg() = Matrix(v1: (0.865, 0, -0.502); v2: (0.038, 0.997, 0.065); v3: (0.5, -0.075, 0.863); off: (-217.439, 88.776, -349.617)) frame = 90, camera.GetMg() = Matrix(v1: (0.849, 0, -0.529); v2: (0.027, 0.999, 0.043); v3: (0.528, -0.05, 0.848); off: (-231.847, 75.721, -341.916)) frame = 90, camera.GetMg() = Matrix(v1: (0.828, 0, -0.56); v2: (0.021, 0.999, 0.031); v3: (0.56, -0.038, 0.828); off: (-248.449, 69.188, -331.492)) frame = 0, camera.GetMg() = Matrix(v1: (0.81, 0, -0.586); v2: (0.015, 1, 0.02); v3: (0.586, -0.025, 0.81); off: (-261.962, 62.651, -322.3)) frame = 0, camera.GetMg() = Matrix(v1: (0.76, 0, -0.65); v2: (0.008, 1, 0.01); v3: (0.65, -0.013, 0.76); off: (-295.44, 56.112, -296.134)) frame = 1, camera.GetMg() = Matrix(v1: (0.686, 0, -0.728); v2: (-0.009, 1, -0.009); v3: (0.727, 0.012, 0.686); off: (-335.688, 43.032, -257.832)) frame = 2, camera.GetMg() = Matrix(v1: (0.595, 0, -0.804); v2: (-0.02, 1, -0.015); v3: (0.804, 0.025, 0.594); off: (-375.453, 36.493, -210.158)) frame = 2, camera.GetMg() = Matrix(v1: (0.538, 0, -0.843); v2: (-0.037, 0.999, -0.024); v3: (0.842, 0.044, 0.537); off: (-395.55, 26.689, -180.362)) frame = 2, camera.GetMg() = Matrix(v1: (0.478, 0, -0.878); v2: (-0.05, 0.998, -0.027); v3: (0.877, 0.056, 0.477); off: (-413.54, 20.158, -149.275)) frame = 3, camera.GetMg() = Matrix(v1: (0.478, 0, -0.878); v2: (-0.05, 0.998, -0.027); v3: (0.877, 0.056, 0.477); off: (-413.54, 20.158, -149.275)) frame = 4, camera.GetMg() = Matrix(v1: (0.422, 0, -0.907); v2: (-0.057, 0.998, -0.026); v3: (0.905, 0.063, 0.421); off: (-428.083, 16.894, -120.059)) frame = 4, camera.GetMg() = Matrix(v1: (0.393, 0, -0.919); v2: (-0.058, 0.998, -0.025); v3: (0.918, 0.063, 0.393); off: (-434.748, 16.894, -105.165)) frame = 5, camera.GetMg() = Matrix(v1: (0.382, 0, -0.924); v2: (-0.064, 0.998, -0.026); v3: (0.922, 0.069, 0.381); off: (-437.081, 13.631, -99.068)) frame = 6, camera.GetMg() = Matrix(v1: (0.37, 0, -0.929); v2: (-0.064, 0.998, -0.025); v3: (0.927, 0.069, 0.369); off: (-439.538, 13.631, -93.024)) frame = 7, camera.GetMg() = Matrix(v1: (0.37, 0, -0.929); v2: (-0.064, 0.998, -0.025); v3: (0.927, 0.069, 0.369); off: (-439.538, 13.631, -93.024)) frame = 7, camera.GetMg() = Matrix(v1: (0.37, 0, -0.929); v2: (-0.064, 0.998, -0.025); v3: (0.927, 0.069, 0.369); off: (-439.538, 13.631, -93.024))
Code
"""I just print the current active camera state here. This all seems to work in this simple manner. The only thing which does not work for me is MOVE_START (never received it). But we probably do not need these sub messages at all. The idea is, open this dialog, press play, and then start moving, panning, etc. a camera in some editor view. The dialog will dump the current frame and camera state to the console. As already said in the ancient camera thread about sort of the same thing: I would postpone creating the keyframes to after the capturing event to have the most performant code possible. I.e., just store the frames and the matrices for each event. When the capture event is done, you can start removing/merging duplicate data, and actually creating the tracks and keyframes. """ import c4d import ctypes IDM_PLAY_FORWARDS: int = 12412 class MessageDialog(c4d.gui.GeDialog): """Realizes a simple dialog. """ def CreateLayout(self) -> bool: """Creates the layout for the dialog. """ self.SetTitle("MessageDialog") return True def Message(self, msg: c4d.BaseContainer, result: c4d.BaseContainer) -> None: """Called by Cinema 4D to process messages for the dialog. We use it here to catch the async move message broadcasted by the editor. """ # This is an async move message broadcasted by the editor. if (msg.GetId() == c4d.BFM_SYNC_MESSAGE and msg.GetInt32(c4d.BFM_CORE_ID) == c4d.EVMSG_ASYNCEDITORMOVE): # For what we want to do here, we do not really have to unpack the message. We are fine # with knowing that EVMSG_ASYNCEDITORMOVE was broadcasted. doc: c4d.documents.BaseDocument = c4d.documents.GetActiveDocument() frame: int = doc.GetTime().GetFrame(doc.GetFps()) bd: c4d.BaseDraw = doc.GetActiveBaseDraw() camera: c4d.BaseObject = bd.GetSceneCamera(doc) or bd.GetEditorCamera() print (f"{frame = }, {camera.GetMg() = }") return c4d.gui.GeDialog.Message(self, msg, result) if __name__=='__main__': # Establish a global instance of the dialog so that we can have an async dialog. This will result # in a dangling dialog, please do not do this in production code. You always need a command data # plugin or another owning entity for an async dialog in production code. dlg: MessageDialog = MessageDialog() dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=200, default=100)
-
PS: I would test this on heavy scenes with a lot of animations, to see if you get at least one data point for each frame (or whatever value you would consider acceptable).