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:
- First MouseClick Corrdinates stored A.
- Second MouseClick Coordinates stored B.
- 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.
- 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.
moghdef 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,
You should store point inside your mouseInput and draw them using the Draw function, you should have a look at this threadFor 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 moghEdit:
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 holeI 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