SOLVED Constant tracking of object position with Python

Hello everyone,

I am trying to figure out if it is possible to retrieve the position of an object while you are dragging it around with the mouse. This would mean to pass the message with the instant position to an already running plugin to update dinamically the GUI.

For the moment I thought about getting the position with a button after the mouse movement but is very annoying for the final user.

I looked for posts regarding similar topics and I thought it could be a case for threadding or using a python tag, but I am pretty much lost here.

Any help would be appreciated.

Daniel

Hello @dglopez,

thank you for clarifying your question. There are unfortunately some things still unclear to me, so let me start with a set of assumptions of mine:

  • The other plugin uses a GeDialog and is most likely some CommandData plugin.
  • There is a known set of C4DAtom, or more specifically BaseObject, you want to track.
  • There is not necessarily a second plugin in your case.

So, there are basically two major ways you can do this. You can either A. push the information into the other plugin from the action of the objects being moved. This option does necessarily need a second plugin which is pushing the information into your other plugin. Or you can B. grab the information from the objects when they have been moved from within the other plugin. This option does not need necessarily a second plugin. Which might sound like semantics is an important distinction, since you cannot move data without restrictions in the message system of Cinema 4D.

When the the other plugin is some GeDialog, option B lends itself best via core messages and EVMSG_CHANGE, see end of posting for an example. However, when the other plugin is some kind of classic API node, e.g., an object itself, you will have to push the information into that node, e.g., with MSG_BASECONTAINER. But depending on how you structure all that, you might need a third plugin in between to handle the transfer. Your question is here unfortunately so broad that I cannot give you anything more concrete.

I hope this helps and cheers,
Ferdinand

The result:
234243.gif
The code:

"""Example for tracking object matrices in a GeDialog.

This can be run as a script manager script and will create a dialog which
tracks the global transform matrices of the objects that have been selected
when the dialog was created.

As discussed in:
    https://plugincafe.maxon.net/topic/13643/
"""

import c4d

class ObjectTrackerDialog (c4d.gui.GeDialog):
    """Example dialog that tracks the transforms of a set of objects.
    """
    ID_GADGETS_START = 1000
    ID_GADGET_GROUP = 0
    ID_GADGET_LABEL = 1
    ID_GADGET_TEXT = 2

    GADGETS_STRIDE = 10

    def __init__(self, objects: list[c4d.BaseObject]) -> None:
        """
        """
        self._objects = objects

    def CoreMessage(self, mid: int, msg: c4d.BaseContainer) -> bool:
        """Receives core messages.

        We react here to EVMSG_CHANGE to update all our object matrices. 
        EVMSG_CHANGE is the message for EventAdd(), i.e., is being fired 
        ALOT. To make this less throttling, we could also use 
        GeDialog.SetTimer and GeDialog.Timer to update the matrices in a
        fixed interval. What is better depends on how time-consuming you
        would consider your updates to be. But the timer approach will also
        consume process time when nothing changed. So, the best approach
        would be to mix the two. I.e., only react to EVMSG_CHANGE when a
        certain time span X has passed since the last time you did react to 
        it.
        """
        if mid == c4d.EVMSG_CHANGE:
            self.UpdateValues()

        return True

    def CreateLayout(self) -> bool:
        """Creates a static text and a text box for each tacked object.
        """
        self.SetTitle("ObjectTrackerDialog")
        for i, item in enumerate(self._objects):
            gid = self.ID_GADGETS_START + i * self.GADGETS_STRIDE
            name = item.GetName()

            self.GroupBegin(gid + self.ID_GADGET_GROUP, 
                c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=2)
            self.AddStaticText(gid + self.ID_GADGET_LABEL, 
                c4d.BFH_LEFT | c4d.BFV_SCALEFIT, name=name)
            self.AddEditText(gid + self.ID_GADGET_TEXT, 
                c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT)
            self.GroupEnd()
        return True

    def InitValues(self) -> bool:
        """Initializes the dialog values.
        """
        return self.UpdateValues()

    def UpdateValues(self) -> bool:
        """Updates the dialog values.
        """
        for i, item in enumerate(self._objects):
            gid = gid = self.ID_GADGETS_START + i * self.GADGETS_STRIDE
            self.SetString(gid + self.ID_GADGET_TEXT, item.GetMg())
        return True

def main():
    """Creates an instance of the ObjectTrackerDialog.
    """
    items = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE)
    if not isinstance(items, list) or len(items) < 1:
        raise RuntimeError("Please select at least one object.")
    # Please do not do this in a production environment. This is just a hack
    # to keep an async dialog alive from the script manager. Which will lead
    # to problems when used in production.
    global dialog
    dialog = ObjectTrackerDialog(items)
    dialog.Open(c4d.DLG_TYPE_ASYNC)

if __name__ == '__main__':
    main()

Hello @DGLopez,

thank you for reaching out to us. We will answer your question more thoroughly, but to be able to do so, we will need more information. Your topic is missing:

  • The targeted operating systems expressed as tags on your topic.
  • The plugin interface(s) used by the already running plugin.
  • Provide code or a description of what the already running plugin is doing.
  • Describe more precisely what you want to do exactly: Should the dragging be a tool which can be activated by the user, or should it happen all the time or should it only happen to specific objects?

Please read our Forum Guidelines for details on the support process.

Cheers,
Ferdinand

Hello @ferdinand, thanks for the quick reply and sorry for the lack of information.

I cannot add tags to the original post but I am using windows as the operating system.
An example of the GUI used by the already running plugin could be somewhat similar to this (I'm working on a simpler GUI but the back-end will be the same):

60f2491b-a6f9-47e2-8a4c-106b2d3d66f5-image.png

As you can see, when I press "add model" I get the global coordinates of the selected cube and printed in the GUI. However I would like those values (Global x, Global y and Global Z) to be updated when I drag the cube around without the need of refreshing it manually.

I hope this clarifies the topic.

Daniel.

Hello @dglopez,

thank you for clarifying your question. There are unfortunately some things still unclear to me, so let me start with a set of assumptions of mine:

  • The other plugin uses a GeDialog and is most likely some CommandData plugin.
  • There is a known set of C4DAtom, or more specifically BaseObject, you want to track.
  • There is not necessarily a second plugin in your case.

So, there are basically two major ways you can do this. You can either A. push the information into the other plugin from the action of the objects being moved. This option does necessarily need a second plugin which is pushing the information into your other plugin. Or you can B. grab the information from the objects when they have been moved from within the other plugin. This option does not need necessarily a second plugin. Which might sound like semantics is an important distinction, since you cannot move data without restrictions in the message system of Cinema 4D.

When the the other plugin is some GeDialog, option B lends itself best via core messages and EVMSG_CHANGE, see end of posting for an example. However, when the other plugin is some kind of classic API node, e.g., an object itself, you will have to push the information into that node, e.g., with MSG_BASECONTAINER. But depending on how you structure all that, you might need a third plugin in between to handle the transfer. Your question is here unfortunately so broad that I cannot give you anything more concrete.

I hope this helps and cheers,
Ferdinand

The result:
234243.gif
The code:

"""Example for tracking object matrices in a GeDialog.

This can be run as a script manager script and will create a dialog which
tracks the global transform matrices of the objects that have been selected
when the dialog was created.

As discussed in:
    https://plugincafe.maxon.net/topic/13643/
"""

import c4d

class ObjectTrackerDialog (c4d.gui.GeDialog):
    """Example dialog that tracks the transforms of a set of objects.
    """
    ID_GADGETS_START = 1000
    ID_GADGET_GROUP = 0
    ID_GADGET_LABEL = 1
    ID_GADGET_TEXT = 2

    GADGETS_STRIDE = 10

    def __init__(self, objects: list[c4d.BaseObject]) -> None:
        """
        """
        self._objects = objects

    def CoreMessage(self, mid: int, msg: c4d.BaseContainer) -> bool:
        """Receives core messages.

        We react here to EVMSG_CHANGE to update all our object matrices. 
        EVMSG_CHANGE is the message for EventAdd(), i.e., is being fired 
        ALOT. To make this less throttling, we could also use 
        GeDialog.SetTimer and GeDialog.Timer to update the matrices in a
        fixed interval. What is better depends on how time-consuming you
        would consider your updates to be. But the timer approach will also
        consume process time when nothing changed. So, the best approach
        would be to mix the two. I.e., only react to EVMSG_CHANGE when a
        certain time span X has passed since the last time you did react to 
        it.
        """
        if mid == c4d.EVMSG_CHANGE:
            self.UpdateValues()

        return True

    def CreateLayout(self) -> bool:
        """Creates a static text and a text box for each tacked object.
        """
        self.SetTitle("ObjectTrackerDialog")
        for i, item in enumerate(self._objects):
            gid = self.ID_GADGETS_START + i * self.GADGETS_STRIDE
            name = item.GetName()

            self.GroupBegin(gid + self.ID_GADGET_GROUP, 
                c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=2)
            self.AddStaticText(gid + self.ID_GADGET_LABEL, 
                c4d.BFH_LEFT | c4d.BFV_SCALEFIT, name=name)
            self.AddEditText(gid + self.ID_GADGET_TEXT, 
                c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT)
            self.GroupEnd()
        return True

    def InitValues(self) -> bool:
        """Initializes the dialog values.
        """
        return self.UpdateValues()

    def UpdateValues(self) -> bool:
        """Updates the dialog values.
        """
        for i, item in enumerate(self._objects):
            gid = gid = self.ID_GADGETS_START + i * self.GADGETS_STRIDE
            self.SetString(gid + self.ID_GADGET_TEXT, item.GetMg())
        return True

def main():
    """Creates an instance of the ObjectTrackerDialog.
    """
    items = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE)
    if not isinstance(items, list) or len(items) < 1:
        raise RuntimeError("Please select at least one object.")
    # Please do not do this in a production environment. This is just a hack
    # to keep an async dialog alive from the script manager. Which will lead
    # to problems when used in production.
    global dialog
    dialog = ObjectTrackerDialog(items)
    dialog.Open(c4d.DLG_TYPE_ASYNC)

if __name__ == '__main__':
    main()

Hello @ferdinand ,

this example was perfect for me, thank you very much!