Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python 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
    • Login

    draw line into 3d Space with two mouse clicks

    Cinema 4D SDK
    python
    2
    6
    954
    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.
    • M
      mogh
      last edited by mogh

      Hi everyone,

      I searched and found several snipets (e.g. liquid painter example) but my main problem I have is unsolved.
      I need two mouse inputs and no drag input so ...

      Goal:

      1. First MouseClick Corrdinates stored A.
      2. Second MouseClick Coordinates stored B.
      3. Result: Line inbetween thes two clicks converted to 3D Space (don't know if my code is corect yet) in viewport to draw a sticky line.
      4. Bonus Kill line on ESC key presed.

      I got the first click but then my tool sets the first Coordinates also for the second.
      How do I tell to wait for second Mouseclick?
      My drawn line is not sticky when I move my 3d view it disapears.

      Thank yo for your time.
      mogh

          def drawvector (self, p1, p2, bd):     
              pencolor=c4d.Vector(255,255,0)
              bd.SetPen(pencolor)
              doc = c4d.documents.GetActiveDocument()
              camera = bd.GetSceneCamera(doc)
              if camera is None: camera = bd.GetEditorCamera()
              mCam = camera.GetMg()
              
              #bd.SetMatrix_Matrix(camera, mCam, 4) #worked in view as 2D X Y Cords
              bd.SetMatrix_Matrix(None, c4d.Matrix(), 4)
              bd.DrawLine(p1, p2, c4d.NOCLIP_Z)
              c4d.DrawViews( c4d.DA_ONLY_ACTIVE_VIEW | c4d.DA_NO_THREAD | c4d.DA_NO_ANIMATION )
              print "Vector: from P1 to P2: ", p1, p2
              return c4d.TOOLDRAW_HANDLES|c4d.TOOLDRAW_AXIS
       
          def MouseInput(self, doc, data, bd, win, msg):
              bd = doc.GetActiveBaseDraw() 
              pointarray = []
              #pointarray.append(bd.SW(c4d.Vector(100,100, 100)))
              mx = msg[c4d.BFM_INPUT_X]
              my = msg[c4d.BFM_INPUT_Y]
              mz = msg[c4d.BFM_INPUT_Z]
      
              dx = 0.0
              dy = 0.0
      
              if len(pointarray) < 2:
                  if c4d.gui.GetInputState(c4d.BFM_INPUT_MOUSE ,c4d.BFM_INPUT_MOUSELEFT, msg):
                      if msg[c4d.BFM_INPUT_VALUE] == 1: #BFM_INPUT_VALUE
                          print "Left Mouse clicked at cord x,y,z: ", mx, my, mz                    
                          print "3d converted: ", bd.SW(c4d.Vector(mx, my, mz))
      
                          pointarray.append(bd.SW(c4d.Vector(mx, my, mz)))
              
              #print "pointarray: ", pointarray
              
              if len(pointarray) == 2:
                  self.drawvector(pointarray[0], pointarray[1], bd)
              return True
      
      1 Reply Last reply Reply Quote 0
      • ManuelM
        Manuel
        last edited by

        Hi,
        You should store point inside your mouseInput and draw them using the Draw function, you should have a look at this thread

        For your next threads, please help us keeping things organised and clean. I know it's not your priority but it really simplify our work here.

        • Q&A New Functionality.

        Let me know if it's still not working.

        Cheers,
        Manuel

        MAXON SDK Specialist

        MAXON Registered Developer

        1 Reply Last reply Reply Quote 0
        • M
          mogh
          last edited by mogh

          @m_magalhaes said in draw line into 3d Space with two mouse clicks:

          mouseInput

          thanks for pointing me to the forum rules.

          anyway the other thread doesn't help me that much hence it is also just a copy of liquid painter ... with a line ...

          I need "click" + Click + n-click = draw line - not drag.

          And the line painting is a problem (perhaps you are telling me to put the draw into the mouse function to make it sticky I can't tell but i will try)

          but more so my Mouseinput ...

          hope fully somebody has the nerv to help a beginner.
          thanks mogh

          Edit:
          I did some more forum searching and even though this mouse input thing is kinda interesting - the thing i probably need a good example of
          ViewportSelect.GetNearestPoint
          hence my final goal is to draw a line between 2 3d points of a object.

          1 Reply Last reply Reply Quote 0
          • ManuelM
            Manuel
            last edited by Manuel

            hi,

            we are here to help the best we can, beginner or not 🙂

            i've created an example that will do what you want so you have a base to start.
            I've picked the liquid example (that's why it's nice ^^) and modified it.
            I choose to add the point when you left click. But i could also create a dragloop (like in the liquid paint tool) to "wait" until the mouse is released.

            Fill free to ask questions about it.

            About your question GetNearestPoint, can you open a new thread ? (we like one question per thread so it's easier to search for answer)

            import c4d
            import os
            
            # Be sure to use a unique ID obtained from www.plugincafe.com
            PLUGIN_ID = 1000001
            
            
            class SettingsDialog(c4d.gui.SubDialog):
                """
                Dialog to display option in the ToolData, in this case the Sphere size.
                """
                parameters = {}
            
                def __init__(self, arg):
                    # Checks if the argument passed is a dictionary
                    if not isinstance(arg, dict):
                        raise TypeError("arg is not a dict.")
            
                    self.parameters = arg
            
                def CreateLayout(self):
                    """
                    This Method is called automatically when Cinema 4D Create the Layout (display) of the GeDialog.
                    """
                    value = self.parameters["line_color"]
                    if self.GroupBegin(id=1000, flags=c4d.BFH_SCALEFIT, cols=2, rows=1):
                        self.GroupBorderSpace(10, 10, 10, 10)
            
                        # Creates a Static text and a number input
                        self.AddColorChooser(id=1001, flags=c4d.BFV_TOP, initw=120,  layoutflags=c4d.DR_COLORFIELD_NO_BRIGHTNESS)
                        
            
                        # Defines the default values
                        self.SetColorField(id=1001, color=value, brightness = 1.0, maxbrightness = 1.0, flags = 0)
                    self.GroupEnd()
                    return True
            
                def Command(self, messageId, msg):
                    """
                      This Method is called automatically when the user clicks on a gadget and/or changes its value this function will be called.
                      It is also called when a string menu item is selected.
                     :param messageId: The ID of the gadget that triggered the event.
                     :type messageId: int
                     :param bc: The original message container
                     :type bc: c4d.BaseContainer
                     :return: False if there was an error, otherwise True.
                     """
                    # When the user change the Gadget with the ID 1002 (the input number field)
            
                    if messageId == 1001:
                        # Stores the value in the parameter variable
                        self.parameters["line_color"] = self.GetColorField(1001)["color"]
                        # Update the view to redraw the lines
                        c4d.DrawViews(c4d.DA_ONLY_ACTIVE_VIEW | c4d.DA_NO_THREAD | c4d.DA_NO_ANIMATION)
                    
                    return True
            
            
            class DrawLine(c4d.plugins.ToolData):
                """Inherit from ToolData to create your own tool"""
                def __init__(self):
                    self.lines = []
                    self.mouseCoordinate = c4d.Vector(-1)
                    self.data = {"line_color":c4d.Vector(0, 0, 1)}
            
                def GetState(self, doc):
                    """
                    Called by Cinema 4D to know if the tool can be used currently
                    :param doc: The current active document.
                    :type doc: c4d.documents.BaseDocument
                    :return: True if the tool can be used, otherwise False.
                    """
                    return c4d.CMD_ENABLED
            
                def MouseInput(self, doc, data, bd, win, msg):
                    """
                    Called by Cinema 4D, when the user click on the viewport and the tool is active.
                    Mainly the place to do moue interaction from the viewport.
                    :param doc: The current active document.
                    :type doc: c4d.documents.BaseDocument
                    :param data:  The tool settings container.
                    :type data: c4d.BaseContainer
                    :param bd:  The BaseDraw object of the active editor view.
                    :type bd: c4d.BaseDraw
                    :param win: The EditorWindow object for the active editor view.
                    :type win: c4d.gui.EditorWindow
                    :param msg: The original message container.
                    :type msg: c4d.BaseContainer
                    :return: False if a problem occurred during this function.
                    """
                    # Retrieves which clicks is currently clicked
                    device = 0
                    if msg[c4d.BFM_INPUT_CHANNEL ]== c4d.BFM_INPUT_MOUSELEFT:
                        device = c4d.KEY_MLEFT
                    else:
                        return True
               
                    
                    # Retrieves the X/Y screen position of the mouse.
                    mx = msg[c4d.BFM_INPUT_X]
                    my = msg[c4d.BFM_INPUT_Y]
            
                    # adds the position to the array
                    myPoint = c4d.Vector (mx , my, 0)
                    self.lines.append(myPoint)
            
                    
                    # Pushes an update event to Cinema 4D
                    c4d.EventAdd()
                    return True
            
                def Draw(self, doc, data, bd, bh, bt, flags):
                    """
                    Called by Cinema 4d when the display is updated to display some visual element of your tool in the 3D view.
                    :param doc: The current active document.
                    :type doc: c4d.documents.BaseDocument
                    :param data: The tool settings container.
                    :type data: c4d.BaseContainer.
                    :param bd: The editor's view.
                    :type bd: c4d.BaseDraw
                    :param bh: The BaseDrawHelp editor's view.
                    :type bh: c4d.plugins.BaseDrawHelp
                    :param bt: The calling thread.
                    :type bt: c4d.threading.BaseThread
                    :param flags: The current drawing pass.
                    :type flags: TOOLDRAWFLAGS
                    :return: The result of the drawing (most likely c4d.DRAWRESULT_OK)
                    """
            
                    # Resets the drawing matrix to the world space matrix.
                    bd.SetMatrix_Matrix(None, c4d.Matrix())
                    
                    # set the color for already drawn lines
                    color = self.data["line_color"]
                    bd.SetPen(color)
                    if not flags:
                        # draw lines for each group of two points
                        for pa, pb in zip(self.lines[::2], self.lines[1::2]):
                            bd.DrawLine2D(pa, pb)
            
                    # if the len for lines is odd, we need to draw a line to the mouse cursor's position
                    if len(self.lines) % 2 == 1 and self.mouseCoordinate != c4d.Vector(-1):
                        lastPoint = self.lines[-1]
                        # the last line is green
                        bd.SetPen(c4d.Vector(0, 1, 0))
                        bd.DrawLine2D(lastPoint, self.mouseCoordinate)
            
                    return c4d.TOOLDRAW_NONE
            
                def GetCursorInfo(self, doc, data, bd, x, y, bc):
                    """
                    Called by Cinema 4D when the cursor is over editor window to get the state of the mouse pointer.
                    :param doc: The current active document.
                    :type doc: c4d.documents.BaseDocument
                    :param data: The tool settings container.
                    :type data: c4d.BaseContainer
                    :param bd: The editor's view.
                    :type bd: c4d.BaseDraw
                    :param x: The x coordinate of the mouse cursor relative to the top-left of the currently active editor view.
                    :type x: float
                    :param y:The y coordinate of the mouse cursor relative to the top-left of the currently active editor view.
                    :type y: float
                    :param bc: The container to store the result in.
                    :type bc: c4d.BaseContainer
                    :return:
                    """
                    # If the cursor has left a user area, set the mouseCoordinate to -1 and return True
                    if bc.GetId() == c4d.BFM_CURSORINFO_REMOVE:
                        self.mouseCoordinate = c4d.Vector(-1)
                        return True
                    # if the mouse is coming back to the viewport, maybe we need to draw the line from last point, we ask for a redraw
                    c4d.DrawViews()
                    # we set the mouse coordinate
                    self.mouseCoordinate = c4d.Vector(x, y, 0)
                    
                    # Sets the cursor.
                    bc.SetInt32(c4d.RESULT_CURSOR, c4d.MOUSE_POINT_HAND)
                    return True
            
                def AllocSubDialog(self, bc):
                    """
                    Called by Cinema 4D To allocate the Tool Dialog Option.
                    :param bc: Currently not used.
                    :type bc: c4d.BaseContainer
                    :return: The allocated sub dialog.
                    """
            
                    return SettingsDialog(getattr(self, "data", {"line_color": c4d.Vector(1, 0, 0)}))
            
            
            if __name__ == "__main__":
                # Registers the tool plugin
                c4d.plugins.RegisterToolPlugin(id=PLUGIN_ID,
                                               str="draw line",
                                               info=0, icon=None,
                                               help="drawing line",
                                               dat=DrawLine())
            
            

            cheers,
            Manuel

            MAXON SDK Specialist

            MAXON Registered Developer

            1 Reply Last reply Reply Quote 0
            • M
              mogh
              last edited by

              @m_magalhaes said in draw line into 3d Space with two mouse clicks:

              1000001

              Thank you for your detailed example, thats what we beginners need. I am really happy.
              anyway down the rabbit hole 🙂

              I modivied i a little bit (gui) and also implemented the ESC key -> resetting the self.lines = []

              is that propper ?

              Anyway I'll mark this as solved hence its everything I asked (besides the 3d part but I guess this will be a seperate discussion with GetNearestPoint)

              import c4d
              import os
              # Be sure to use a unique ID obtained from www.plugincafe.com
              PLUGIN_ID = 1000001 #Dummy ID
              
              class SettingsDialog(c4d.gui.SubDialog):
                  """
                  Dialog to display option in the ToolData, in this case the Sphere size.
                  """
                  parameters = {}
              
                  def __init__(self, arg):
                      # Checks if the argument passed is a dictionary
                      if not isinstance(arg, dict):
                          raise TypeError("arg is not a dict.")
              
                      self.parameters = arg
              
                  def CreateLayout(self):
                      """
                      This Method is called automatically when Cinema 4D Create the Layout (display) of the GeDialog.
                      """
                      value = self.parameters["line_color"]
                      if self.GroupBegin(id=1000, flags=c4d.BFH_SCALEFIT, cols=1, rows=2):
                          self.GroupBorderSpace(10, 10, 10, 10)
              
                          # Creates a Static text and a number input
                          self.element = self.AddStaticText( id=2003, flags=c4d.BFH_SCALEFIT, initw=1, name="Line Color: ", borderstyle=c4d.BORDER_NONE )
                          self.AddColorChooser(id=1001, flags=c4d.BFV_TOP|c4d.BFH_SCALEFIT, initw=1,  layoutflags=c4d.DR_COLORFIELD_NO_BRIGHTNESS)
                          #self.AddColorField(id=1001, flags=c4d.BFV_TOP|c4d.BFH_FIT, initw=120)  #popup color field
              
                          # Defines the default values
                          self.SetColorField(id=1001, color=value, brightness = 1.0, maxbrightness = 1.0, flags = 0)
                      self.GroupEnd()
                      return True
              
                  def Command(self, messageId, msg):
                      """
                        This Method is called automatically when the user clicks on a gadget and/or changes its value this function will be called.
                        It is also called when a string menu item is selected.
                       :param messageId: The ID of the gadget that triggered the event.
                       :type messageId: int
                       :param bc: The original message container
                       :type bc: c4d.BaseContainer
                       :return: False if there was an error, otherwise True.
                       """
                      # When the user change the Gadget with the ID 1002 (the input number field)
              
                      if messageId == 1001:
                          # Stores the value in the parameter variable
                          self.parameters["line_color"] = self.GetColorField(1001)["color"]
                          # Update the view to redraw the lines
                          c4d.DrawViews(c4d.DA_ONLY_ACTIVE_VIEW | c4d.DA_NO_THREAD | c4d.DA_NO_ANIMATION)
                      
                      return True
              
              class DrawLine(c4d.plugins.ToolData):
                  """Inherit from ToolData to create your own tool"""
                  def __init__(self):
                      self.lines = []
                      self.mouseCoordinate = c4d.Vector(-1)
                      self.data = {"line_color":c4d.Vector(1, 1, 0)}
              
                  def GetState(self, doc):
                      """
                      Called by Cinema 4D to know if the tool can be used currently
                      :param doc: The current active document.
                      :type doc: c4d.documents.BaseDocument
                      :return: True if the tool can be used, otherwise False.
                      """
                      return c4d.CMD_ENABLED
              
                  def Debug(self, msg):
                      c4d.CallCommand(13957)  # Konsole loeschen
                      
                      #print "Konsole geloescht."
                      return True
              
                  def KeyboardInput(self, doc, data, bd, win, msg):
                      key = msg.GetLong( c4d.BFM_INPUT_CHANNEL )
                      cstr = msg.GetString( c4d.BFM_INPUT_ASC )
                      if key == c4d.KEY_ESC:
                          # do what you want
                          print "ESC Key Pressed."
                          self.lines = []
                          print "Lines Flushed."
                          self.Debug(msg)
                          # return True to signal that the key is processed            
                          return True
                      return False
              
                  def MouseInput(self, doc, data, bd, win, msg):
                      """
                      Called by Cinema 4D, when the user click on the viewport and the tool is active.
                      Mainly the place to do moue interaction from the viewport.
                      :param doc: The current active document.
                      :type doc: c4d.documents.BaseDocument
                      :param data:  The tool settings container.
                      :type data: c4d.BaseContainer
                      :param bd:  The BaseDraw object of the active editor view.
                      :type bd: c4d.BaseDraw
                      :param win: The EditorWindow object for the active editor view.
                      :type win: c4d.gui.EditorWindow
                      :param msg: The original message container.
                      :type msg: c4d.BaseContainer
                      :return: False if a problem occurred during this function.
                      """
                      # Retrieves which clicks is currently clicked
                      device = 0
                      if msg[c4d.BFM_INPUT_CHANNEL ]== c4d.BFM_INPUT_MOUSELEFT:
                          device = c4d.KEY_MLEFT
                      else:
                          return True
              
                      # Retrieves the X/Y screen position of the mouse.
                      mx = msg[c4d.BFM_INPUT_X]
                      my = msg[c4d.BFM_INPUT_Y]
              
                      # adds the position to the array
                      myPoint = c4d.Vector (mx , my, 0)
                      my3dpoint = bd.SW(myPoint)
                      self.lines.append(myPoint)
              
                      # Pushes an update event to Cinema 4D
                      c4d.EventAdd()
                      return True
              
                  def Draw(self, doc, data, bd, bh, bt, flags):
                      """
                      Called by Cinema 4d when the display is updated to display some visual element of your tool in the 3D view.
                      :param doc: The current active document.
                      :type doc: c4d.documents.BaseDocument
                      :param data: The tool settings container.
                      :type data: c4d.BaseContainer.
                      :param bd: The editor's view.
                      :type bd: c4d.BaseDraw
                      :param bh: The BaseDrawHelp editor's view.
                      :type bh: c4d.plugins.BaseDrawHelp
                      :param bt: The calling thread.
                      :type bt: c4d.threading.BaseThread
                      :param flags: The current drawing pass.
                      :type flags: TOOLDRAWFLAGS
                      :return: The result of the drawing (most likely c4d.DRAWRESULT_OK)
                      """
              
                      # Resets the drawing matrix to the world space matrix.
                      bd.SetMatrix_Matrix(None, c4d.Matrix())
                      bd.SetMatrix_Camera()
                      
                      # set the color for already drawn lines
                      color = self.data["line_color"]
                      bd.SetPen(color)
                      if not flags:
                          # draw lines for each group of two points
                          for pa, pb in zip(self.lines[::2], self.lines[1::2]):
                              bd.DrawLine2D(pa, pb)
              
                      # if the len for lines is odd, we need to draw a line to the mouse cursor's position
                      if len(self.lines) % 2 == 1 and self.mouseCoordinate != c4d.Vector(-1):
                          lastPoint = self.lines[-1]
                          # the last line is green
                          bd.SetPen(c4d.Vector(0, 1, 0))
                          bd.DrawLine2D(lastPoint, self.mouseCoordinate)
              
                      return c4d.TOOLDRAW_NONE
              
                  def GetCursorInfo(self, doc, data, bd, x, y, bc):
                      """
                      Called by Cinema 4D when the cursor is over editor window to get the state of the mouse pointer.
                      :param doc: The current active document.
                      :type doc: c4d.documents.BaseDocument
                      :param data: The tool settings container.
                      :type data: c4d.BaseContainer
                      :param bd: The editor's view.
                      :type bd: c4d.BaseDraw
                      :param x: The x coordinate of the mouse cursor relative to the top-left of the currently active editor view.
                      :type x: float
                      :param y:The y coordinate of the mouse cursor relative to the top-left of the currently active editor view.
                      :type y: float
                      :param bc: The container to store the result in.
                      :type bc: c4d.BaseContainer
                      :return:
                      """
                      # If the cursor has left a user area, set the mouseCoordinate to -1 and return True
                      if bc.GetId() == c4d.BFM_CURSORINFO_REMOVE:
                          self.mouseCoordinate = c4d.Vector(-1)
                          return True
                      # if the mouse is coming back to the viewport, maybe we need to draw the line from last point, we ask for a redraw
                      c4d.DrawViews()
                      # we set the mouse coordinate
                      self.mouseCoordinate = c4d.Vector(x, y, 0)
                      
                      # Sets the cursor.
                      if len(self.lines) < 2:
                          bc.SetInt32(c4d.RESULT_CURSOR, c4d.MOUSE_CROSS)
                      else:
                          bc.SetInt32(c4d.RESULT_CURSOR, c4d.MOUSE_POINT_HAND)
                      return True
              
                  def AllocSubDialog(self, bc):
                      """
                      Called by Cinema 4D To allocate the Tool Dialog Option.
                      :param bc: Currently not used.
                      :type bc: c4d.BaseContainer
                      :return: The allocated sub dialog.
                      """
              
                      return SettingsDialog(getattr(self, "data", {"line_color": c4d.Vector(1, 0, 0)}))
              
              if __name__ == "__main__":
                  # Registers the tool plugin
                  c4d.plugins.RegisterToolPlugin(id=PLUGIN_ID,
                                                 str="Draw line",
                                                 info=0, icon=None,
                                                 help="drawing line",
                                                 dat=DrawLine())
              
              1 Reply Last reply Reply Quote 0
              • ManuelM
                Manuel
                last edited by

                hi,

                it looks correct. Of course be sure to retrieve a unique id here, from the top bar. (the icon plugin ID)

                Feel free to ask questions.

                Cheers,
                Manuel

                MAXON SDK Specialist

                MAXON Registered Developer

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