Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Register
    • Login

    How to simulate "Cappucino" behavior?

    Cinema 4D SDK
    windows python 2024
    2
    5
    635
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • DunhouD
      Dunhou
      last edited by

      Hi community,

      I want to rebuild a tool similar to "Cappucino" but bake the camera and some parameters,
      so :

      1. Move our camera
      2. Start record then the camera then it changed first time
      3. Start animate of the doc, like we hit play
      4. Add a keyframe we want every frame
      5. 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

      https://boghma.com
      https://github.com/DunHouGo

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @Dunhou
        last edited by ferdinand

        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:

        1. 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.
        2. 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, and KeyboardInput 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.

        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)
        

        MAXON SDK Specialist
        developers.maxon.net

        DunhouD 1 Reply Last reply Reply Quote 0
        • DunhouD
          Dunhou @ferdinand
          last edited by

          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

          https://boghma.com
          https://github.com/DunHouGo

          ferdinandF 1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand @Dunhou
            last edited by ferdinand

            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,
            Ferdinand

            Output

            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)
            

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 1
            • ferdinandF
              ferdinand
              last edited by ferdinand

              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).

              MAXON SDK Specialist
              developers.maxon.net

              1 Reply Last reply Reply Quote 1
              • First post
                Last post