Solved Strange behavior of UserArea docked with Timeline

Hi,

I am trying to use UserArea to draw a simple 4x4 grid in a dialog, and docking it with the Timeline.
The issue is when I click around the UserArea and the Timeline(which is built-in UserArea??), top and bottom edges of the UserArea behave strangely.
And when I tweak the size of the dialog to redraw the UserArea, it returns to normal.

video:
https://drive.google.com/file/d/1arqRD-4y52WJfyI3QnlgN3oBpsuvfxcj/view?usp=sharing

.pyp:

import c4d
import os

PLUGIN_ID           = 9366042 # Random Number
ID_UA               = 1000

class TestUserArea(c4d.gui.GeUserArea):
    def DrawMsg(self, x1, y1, x2, y2, msg):
        self.OffScreenOn()
        self.SetClippingRegion(x1, y1, x2, y2)

        # Draw Background
        black = c4d.Vector(0)
        self.DrawSetPen(black)
        self.DrawRectangle(x1, y1, x2, y2)

        # Draw Grid
        white = c4d.Vector(1)
        self.DrawSetPen(white)
        w = self.GetWidth()
        h = self.GetHeight()
        for i in range(5):
            xpos = int(i*0.25*w)
            self.DrawLine(xpos, y1, xpos, y2)
            ypos = int(i*0.25*h)
            self.DrawLine(x1, ypos, x2, ypos)

class TestDialog(c4d.gui.GeDialog):
    def CreateLayout(self):
        self.ua = TestUserArea()
        self.AddUserArea(ID_UA, c4d.BFH_SCALEFIT|c4d.BFV_SCALEFIT)
        self.AttachUserArea(self.ua, ID_UA)
        return True

class TestData(c4d.plugins.CommandData):
    dialog = None

    def Execute(self, doc):
        if self.dialog is None:
            self.dialog = TestDialog()
        return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID)

    def RestoreLayout(self, sec_ref):
        if self.dialog is None:
            self.dialog = TestDialog()
        return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref)

if __name__ == "__main__":
    path, file = os.path.split(__file__)
    bmp = c4d.bitmaps.BaseBitmap()
    bmp.InitWith(os.path.join(path, "res", "icon.tif"))
    c4d.plugins.RegisterCommandPlugin(id=PLUGIN_ID, str="TestUserArea", info=0,
                                      help="", dat=TestData(), icon=bmp)

The same issue can be seen with the official plugin sample Py-MemoryViewer.
In this one, Redraw() is called frequently, so it returns to normal display immediately.
https://drive.google.com/file/d/1vGgoPJ3ierUFBNdD92hUl7UuaKjkcE--/view?usp=sharing

I know that this issue is not that critical, but is there any solution? Thank you.

hi,

I was able to reproduce it, after asking to @m_adam it's not a bug it's an optimisation.
The screen redraw happens from top left to bottom right. So, the coordinates sent to the UserArea will only contain what really need to be redraw. In our example, we don't take that into account.

a1ffbaf3-7bd6-4955-8d24-f589faa5eee2-image.png
As you can see in this screenshot, the UserArea is sliced in two but align with the attribut manager menu. (because I've clicked there)
The Draw function is called twice, one for the top part and one for the bottom part. That's why the grid is different and the memory appears various times.

To fix that you can simply not redraw if the size sent to the DrawMsg function are not correct. To know if they are correct you can simply use the Sized function to retrieve the size of the UserArea and store it locally.

  def Sized(self, w, h):
        self.h = h
        self.w = w

    def DrawMsg(self, x1, y1, x2, y2, msg_ref):
        """This Method is called automatically when Cinema 4D Draw the Gadget.

        Args:
            x1 (int): The upper left x coordinate.
            y1 (int): The upper left y coordinate.
            x2 (int): The lower right x coordinate.
            y2 (int): The lower right y coordinate.
            msg_ref (c4d.BaseContainer): The original mesage container.
        
        """
        if x2-x1 < self.w:
            return 
        if y2-y1 < self.h:
            return

Or you can use the same technique than @m_adam in this thread
Catch the draw message and call yourself the DrawMsg with the maximum size. (redraw everything)

def Message(self, msg, result):
        # Catch the draw message to cancel it (return True)
        # and call ourself the DrawMsg with the dimension we expect
        if msg.GetId() == c4d.BFM_DRAW:
            self.DrawMsg(0, 0, self.width, self.height, c4d.BaseContainer())

            return True
    
        return c4d.gui.GeUserArea.Message(self, msg, result)

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

Hi,

i tested your code with S24 and was unable to reproduce the behavior. Wath is the version that you are using?

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

@m_magalhaes
The versions I have checked are R21.207 and S24.037 on Windows.
I also checked with S24 on Mac and there was no problem.

The problem shows up when it is docked next to the timeline, especially in the lower left corner of the animation layout like this.

hi,

I was able to reproduce it, after asking to @m_adam it's not a bug it's an optimisation.
The screen redraw happens from top left to bottom right. So, the coordinates sent to the UserArea will only contain what really need to be redraw. In our example, we don't take that into account.

a1ffbaf3-7bd6-4955-8d24-f589faa5eee2-image.png
As you can see in this screenshot, the UserArea is sliced in two but align with the attribut manager menu. (because I've clicked there)
The Draw function is called twice, one for the top part and one for the bottom part. That's why the grid is different and the memory appears various times.

To fix that you can simply not redraw if the size sent to the DrawMsg function are not correct. To know if they are correct you can simply use the Sized function to retrieve the size of the UserArea and store it locally.

  def Sized(self, w, h):
        self.h = h
        self.w = w

    def DrawMsg(self, x1, y1, x2, y2, msg_ref):
        """This Method is called automatically when Cinema 4D Draw the Gadget.

        Args:
            x1 (int): The upper left x coordinate.
            y1 (int): The upper left y coordinate.
            x2 (int): The lower right x coordinate.
            y2 (int): The lower right y coordinate.
            msg_ref (c4d.BaseContainer): The original mesage container.
        
        """
        if x2-x1 < self.w:
            return 
        if y2-y1 < self.h:
            return

Or you can use the same technique than @m_adam in this thread
Catch the draw message and call yourself the DrawMsg with the maximum size. (redraw everything)

def Message(self, msg, result):
        # Catch the draw message to cancel it (return True)
        # and call ourself the DrawMsg with the dimension we expect
        if msg.GetId() == c4d.BFM_DRAW:
            self.DrawMsg(0, 0, self.width, self.height, c4d.BaseContainer())

            return True
    
        return c4d.gui.GeUserArea.Message(self, msg, result)

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

Hi,

It looks like the problem got solved by Sized function.
Thanks a lot @m_magalhaes and @m_adam !!