SOLVED draw line into 3d Space with two mouse clicks

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

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

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.

Let me know if it's still not working.

Cheers,
Manuel

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

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

@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())

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