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

    UserArea drag and drop example?

    Cinema 4D SDK
    windows 2024 python
    3
    6
    980
    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 have recently started learning UserArea modules, but there are some principle documents that are not very easy to understand, especially regarding drag and drop, can you provide an example of this and explain more details about the usage of related functions?

      abstraction

      • how do we drag a BaseList2d into a UA and get some data
      • how do we drag a UA item back to Cinema and create BaseList2d depends on the context?

      concretion

      If the abstract problem is relatively vague, presenting the simplest case may be more clear:

      1. we create a dialog with UA,
      2. we can drag a Cube and a Material into UA, create UA item.
      3. we can drag the UA item(Cube) back to OM, and drag the UA item(Material) into Material Manager or OM(on the selected object)

      Hope this can explain my problem clearly, and hope Python can handle these things.

      Cheers~
      DunHou

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

      M 1 Reply Last reply Reply Quote 0
      • M
        m_adam @Dunhou
        last edited by

        Hi @Dunhou speaking more generally about drag and drop in GeUserArea, I would invite you to read the Drag&Drop an image in a gui field topic which covers the basics to receive and user area - drop finished for receiving.

        @Dunhou said in UserArea drag and drop example?:

        • How do we drag a BaseList2d into a UA and get some data
        • how do we drag a UA item back to Cinema and create BaseList2d depends on the context?
        import c4d
        
        
        class CustomUserArea(c4d.gui.GeUserArea):
            
            def __init__(self):
                self.receivedObject = []
            
            def GetMinSize(self):
                """Called by Cinema 4D to know the minimum size of the GeUserArea"""
                return 50, 100
            
            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 message container.
                """
                
                self.OffScreenOn()
                self.SetClippingRegion(x1, y1, x2, y2)
        
                # From 0 pixel to 50 draw a red box. This one will be used to receive drop operation.
                self.DrawSetPen(c4d.Vector(1, 0,0))
                self.DrawRectangle(0, 0, 50, 50)
        
                # From 50 pixel to 100 draw a green box. This one will be used to start the drag operation.
                # Look at the InputEvent method to see how this is done
                self.DrawSetPen(c4d.Vector(0, 1,0))
                self.DrawRectangle(0, 50, 50, 100)
        
            def Message(self, msg, result) :
                if msg.GetId()==c4d.BFM_DRAGRECEIVE:
                    # Discard if lost drag or if it has been escaped
                    if msg.GetInt32(c4d.BFM_DRAG_LOST) or msg.GetInt32(c4d.BFM_DRAG_ESC):
                        return self.SetDragDestination(c4d.MOUSE_FORBIDDEN)
                    
                    # Check drop area and discard if not on the red box or not on the user area
                    mouseY = self.GetDragPosition(msg)["y"] 
                    if not 0 < mouseY < 50 or not self.CheckDropArea(msg, True, True):
                        return self.SetDragDestination(c4d.MOUSE_FORBIDDEN)
                                            
                    # Check if drag is finished (=drop)
                    if msg.GetInt32(c4d.BFM_DRAG_FINISHED) == 1:
                        # Get drag object type and data
                        dragInfo = self.GetDragObject(msg)
                        self.receivedObject = dragInfo['object']
                        print(f"Dropped: {self.receivedObject}")
                        return True
                    
                    # Return current mouse cursor for valid drag operation
                    return self.SetDragDestination(c4d.MOUSE_MOVE)
                
                # Call GeUserAre.Message() implementation so that it can handle all the other messages
                return c4d.gui.GeUserArea.Message(self, msg, result)
        
            def InputEvent(self, msg):
                """Called by Cinema 4D when a click occurs"""
                mouseY = msg[c4d.BFM_INPUT_Y] + self.Global2Local()["y"]
                
                if 50 < mouseY < 100:
                    # Retrieve the first object in the scene to use or otherwise use a newly created cube           
                    if self.receivedObject is None:
                        self.receivedObject = c4d.BaseObject(c4d.Ocube)
                        
                    # Create a list of C4DAtom and pass that to the HandleMouseDrag function that will generate all the drag information
                    self.HandleMouseDrag(msg, c4d.DRAGTYPE_ATOMARRAY, self.receivedObject, 0)
        
                return True
        
        
        class ExampleDialog(c4d.gui.GeDialog):
        
            def __init__(self):
                super().__init__()
                self.ua = CustomUserArea()
        
            def CreateLayout(self):
                """This Method is called automatically when Cinema 4D Create the Layout (display) of the Dialog."""
                self.AddUserArea(10000, c4d.BFH_LEFT | c4d.BFV_TOP)
                self.AttachUserArea(self.ua, 10000)
        
                return True
        
        
        # main
        if __name__ == "__main__":
            global dlg
            dlg = ExampleDialog()
            dlg.Open(dlgtype=c4d.DLG_TYPE_ASYNC, defaultw=50, defaulth=100)
        

        Sadly there is no way for the part that generate the drag operation to be context specific. The GeUserArea generate the drag information, then Cinema 4D forward it to the appropriate UI element, this one receive a BFM_DRAGRECEIVE and then it handle it as it want and have no clue from where it comes.

        Hope it answers your questions,
        Cheers,
        Maxime.

        MAXON SDK Specialist

        Development Blog, MAXON Registered Developer

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

          Hi @m_adam ,

          Thanks for your reply! After some research, I had expanded the code for this case to adapt to the requirements I mentioned earlier:

          • we can drag c4d.BaseObject , c4d.BaseMaterial or Image into UA, create their icon.
          • we can drag the UA item(Object) back to OM, and drag the UA item(Material) into Material Manager or OM(on the selected object), or drag the UA item(ImageRef) into a shader link.

          The UserArea Accept:

          • c4d.BaseObject
          • c4d.BaseMaterial
          • Image file outside Cinema 4D.

          Here is the code in case anyone is interested in this topic in the future.Hope it can be helpful.

          Cheers~
          DunHou

          Animation.gif

          import c4d
          from c4d import Vector
          from c4d.gui import GeUserArea, GeDialog
          
          GADGET_ID_GEUSERAREA = 10000
          
          class DropArea(GeUserArea):
          
              def __init__(self):
                  # data host
                  self.receivedObject: list[c4d.BaseList2D, str] = []
                  # bg color of the pen
                  self.color: Vector = self._get_color()
          
              def _get_color(self, colorId: int = c4d.COLOR_BG) -> Vector:
                  """
                  Get a rgb color from c4d color id
                  """
                  color = self.GetColorRGB(colorId)
                  r = color['r']/255
                  g = color['g']/255
                  b = color['b']/255
                  return c4d.Vector(r,g,b)
          
              def DrawMsg(self, x1, y1, x2, y2, msg):
                  """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 (c4d.BaseContainer): The original message container.
                  """
          
                  # Initializes draw region
                  self.OffScreenOn()
                  self.SetClippingRegion(x1, y1, x2, y2)
          
                  # Defines the color used in draw operation, use c4d backgroud color here.
                  self.DrawSetPen(self.color)
          
                  # Draws a rectangle filling the whole UI
                  self.DrawRectangle(x1, y1, x2, y2)
          
                  # Draw a Icon with the drop object, only consider c4d.BaseList2D and ImageRef here
                  if msg.GetInt32(c4d.BFM_DRAG_FINISHED) == 1:
                                  
                      # If drag info recive a image, draw a icon of image
                      if isinstance(self.receivedObject, str):
                          icon = c4d.bitmaps.InitResourceBitmap(1050500)
                          self.DrawBitmap(icon, x1, y1, x2, y2, 0, 0, icon.GetBw(), icon.GetBh(), mode= c4d.BMP_ALLOWALPHA)
          
                      # If drag info recive a list of BaseList2D, draw a icon of first one.
                      else:
                          if self.receivedObject:
                              # Draw the first drop object's Icon, 
                              # for BaseMaterial, we can get the preview image.
                              # for BaseObject, we can get the icon.
                              if isinstance(self.receivedObject[0], c4d.BaseList2D):
                                  icon = self.receivedObject[0].GetIcon()
                                  self.DrawBitmap(icon['bmp'], x1, y1, x2, y2, icon['x'], icon['y'], icon['w'], icon['h'],mode= c4d.BMP_ALLOWALPHA)
          
              def GetMinSize(self):
                  # do a calculation here, min size.
                  return 200, 200
          
              def Message(self, msg, result) :
                  if msg.GetId()==c4d.BFM_DRAGRECEIVE:
                      # Discard if lost drag or if it has been escaped
                      if msg.GetInt32(c4d.BFM_DRAG_LOST) or msg.GetInt32(c4d.BFM_DRAG_ESC):
                          return self.SetDragDestination(c4d.MOUSE_FORBIDDEN)
                      
                      if not self.CheckDropArea(msg, True, True):
                          return self.SetDragDestination(c4d.MOUSE_FORBIDDEN)
                                              
                      # Check if drag is finished (=drop)
                      if msg.GetInt32(c4d.BFM_DRAG_FINISHED) == 1:
                          # Get drag object type and data
                          dragInfo = self.GetDragObject(msg)
          
                          # Redraw the GeUserArea (will call DrawMsg)
                          self.Redraw()
          
                          #print(type(dragInfo['object']))
                          self.receivedObject = dragInfo['object']
                          print(f"Dropped: {self.receivedObject}")
                          return True
                      
                      # Return current mouse cursor for valid drag operation
                      return self.SetDragDestination(c4d.MOUSE_MOVE)
                  
                  # Call GeUserAre.Message() implementation so that it can handle all the other messages
                  return c4d.gui.GeUserArea.Message(self, msg, result)
          
              def InputEvent(self, msg):
                  """Called by Cinema 4D when a click occurs"""
                  
                  # If the #self.receivedObject host the file path of the image, we generate the file path.
                  # this means we can drag it into a image link like a shader link.
                  if isinstance(self.receivedObject, str):
                      self.HandleMouseDrag(msg, c4d.DRAGTYPE_FILENAME_IMAGE, self.receivedObject, 0)
          
                  else:
                      # Create a list of C4DAtom and pass that to the HandleMouseDrag function that will generate all the drag information
                      self.HandleMouseDrag(msg, c4d.DRAGTYPE_ATOMARRAY, self.receivedObject, 0)        
          
                  return True
              
          class ExampleDialog(GeDialog):
              # The GeUserArea need to be stored somewhere, we will need this instance to be attached to the current Layout
              geUserArea = DropArea()
          
              def CreateLayout(self):
                  """This Method is called automatically when Cinema 4D Create the Layout (display) of the Dialog."""
                  
                  self.SetTitle("Drag Area")
          
                  if self.GroupBegin(0,  c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=1, rows=0, title="", groupflags=0, initw=100, inith=100):
                      
                      self.GroupBorderSpace(8,8,8,8)
                      self.GroupSpace(2, 2)
                      
                      # Adds a Gadget that will host a GeUserArea
                      self.AddUserArea(GADGET_ID_GEUSERAREA, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 200, 200)
              
                      # Attaches the stored GeUserArea to the Gadget previously created
                      self.AttachUserArea(self.geUserArea, GADGET_ID_GEUSERAREA)
                      
                  self.GroupEnd()
                  
                  return True
          
          
          if __name__ == "__main__":
              global dlg
              dlg = ExampleDialog()
              dlg.Open(dlgtype=c4d.DLG_TYPE_ASYNC, defaultw=200, defaulth=200)
          

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

          1 Reply Last reply Reply Quote 3
          • K
            karen
            last edited by karen

            Hi @Dunhou ,Hi @m_adam , thank you for providing the code. I have also been researching drag and drop methods in GeUserArea recently. I noticed that in the DragObject () method, when retrieving an external file, a single file path is returned. May I ask if it is possible to return a list containing multiple file paths when dragging multiple external files at the same time, similar to the DRAGTYPE-ATOMARRAY

            1 Reply Last reply Reply Quote 0
            • K
              karen
              last edited by

              This post is deleted!
              1 Reply Last reply Reply Quote 0
              • K
                karen
                last edited by

                I tried using a timer to solve this problem, but I still want to know if there is a more direct way

                import c4d 
                import threading
                from c4d.gui import GeUserArea, GeDialog
                
                GADGET_ID_GEUSERAREA = 10000
                
                class DropArea(GeUserArea):
                
                    def __init__(self):
                        # Used to store all objects involved in the drag-and-drop operation
                        self.currentDragObjects = []
                        # Flag to indicate whether a drag operation is in progress
                        self.isDragging = False
                        # Define a timer to delay the handling of the drag completion
                        self.dragTimer = None
                
                    def Message(self, msg, result):
                        # Handle drag-and-drop messages
                        if msg.GetId() == c4d.BFM_DRAGRECEIVE:
                            # Check if the drag was lost or canceled
                            if msg.GetInt32(c4d.BFM_DRAG_LOST) or msg.GetInt32(c4d.BFM_DRAG_ESC):
                                self.isDragging = False
                                return self.SetDragDestination(c4d.MOUSE_FORBIDDEN)
                
                            # If the drag just started, clear the previous object list
                            if not self.isDragging:
                                self.currentDragObjects = []  # Initialize the storage list
                                self.isDragging = True  # Mark the beginning of the drag
                
                            # Verify if it is a valid drop area
                            if not self.CheckDropArea(msg, True, True):
                                return self.SetDragDestination(c4d.MOUSE_FORBIDDEN)
                
                            # Get the dragged file object
                            dragInfo = self.GetDragObject(msg)
                            if dragInfo is not None:
                                dragObject = dragInfo['object']
                                # Check if the object already exists in the list to avoid duplicates
                                if dragObject not in self.currentDragObjects:
                                    self.currentDragObjects.append(dragObject)
                
                            # Reset the timer to delay the handling of drag completion
                            if self.dragTimer is not None:
                                self.dragTimer.cancel()
                
                            # Set a short timer (e.g., 0.2 seconds) to determine if the drag operation is complete
                            self.dragTimer = threading.Timer(0.2, self._finalize_drag)
                            self.dragTimer.start()
                
                            # Set the mouse cursor to a valid state
                            return self.SetDragDestination(c4d.MOUSE_MOVE)
                
                        # Call the base class Message() method to handle other messages
                        return c4d.gui.GeUserArea.Message(self, msg, result)
                
                    def _finalize_drag(self):
                        # Delayed execution to ensure all dragged objects have been received
                        self.isDragging = False
                
                        if self.currentDragObjects:
                            # Print all dropped files
                            print(f"Dropped files: {self.currentDragObjects}")
                
                            # Additional logic can be executed here, e.g., handling file paths or other content
                            # Clear the object list for the next drag-and-drop operation
                            self.currentDragObjects = []
                
                        # Redraw the user area (if UI update is needed)
                        self.Redraw()
                
                class ExampleDialog(GeDialog):
                    geUserArea = DropArea()
                
                    def CreateLayout(self):
                        self.SetTitle("Drag Area")
                
                        if self.GroupBegin(0, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=1, rows=0, title="", groupflags=0, initw=100, inith=100):
                            self.GroupBorderSpace(8, 8, 8, 8)
                            self.GroupSpace(2, 2)
                
                            # Add the user area gadget
                            self.AddUserArea(GADGET_ID_GEUSERAREA, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 200, 200)
                            # Attach the user area to the gadget
                            self.AttachUserArea(self.geUserArea, GADGET_ID_GEUSERAREA)
                
                        self.GroupEnd()
                        return True
                
                if __name__ == "__main__":
                    global dlg
                    dlg = ExampleDialog()
                    dlg.Open(dlgtype=c4d.DLG_TYPE_ASYNC, defaultw=200, defaulth=200)
                
                
                1 Reply Last reply Reply Quote 0
                • First post
                  Last post