hi ,
your problem is more a design problem than a technical one.
you should use the GetCursorInfo
to retrieve the information of the mouse and check there what are the object / point you are overlapping.
Something that could help you is the static function (static mean you don't need an instance of that object to call/use it) PickObject.
You can use that function to retrieve the object you have under the mouse, and after that you can use GetNearestPoint to retrieve the points for each object (or the closest as i did).
once you have collected those information you have to draw them. (I've used a Vector(-1) to see if my coordinates is updated or not, but a bool would be better.
I've update my example (without your addition sorry). If i remember correctly you were trying to draw a line between two points
of course i don't check for example if the object is a polygon object or not and some other check that should be there.
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"]
radius = self.parameters['radius']
if self.GroupBegin(id=1000, flags=c4d.BFH_SCALEFIT, cols=1, rows=1):
self.GroupBorderSpace(10, 10, 10, 10)
# Creates a Static text and a number input
self.AddColorChooser(id=1001, flags=c4d.BFV_TOP | c4d.BFH_LEFT, initw=120, layoutflags=c4d.DR_COLORFIELD_NO_BRIGHTNESS)
if self.GroupBegin(id=1000, flags=c4d.BFH_SCALEFIT, cols=2, rows=1):
# Creates a Static text and a number input
self.AddStaticText(id=1002, flags=c4d.BFH_MASK, initw=120, name="Radius", borderstyle=c4d.BORDER_NONE)
self.AddEditNumberArrows(id=1003, flags=c4d.BFH_MASK)
self.GroupEnd()
# Defines the default values
self.SetFloat(id=1003, value=radius, min=0, max=300)
# 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)
elif messageId == 1003:
self.parameters['radius'] = self.GetFloat(1003)
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) # needed to draw a line to the mouse cursor
self.circleCoordinate = c4d.Vector(-1) # will serve to draw the circle (spanned point)
self.data = {"line_color":c4d.Vector(0, 0, 1), "radius": 20.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 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
self.lines.append(self.circleCoordinate)
# 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(bd.WS(pa), bd.WS(pb))
bd.DrawLine(pa, pb, c4d.NOCLIP_Z | c4d.NOCLIP_D)
#Draw the circle
if self.circleCoordinate == c4d.Vector(-1):
# draw the circle around the mouse in red
bd.SetPen(c4d.Vector(1, 0, 0))
bd.DrawCircle2D(self.mouseCoordinate.x, self.mouseCoordinate.y, self.data["radius"])
else:
# the circle is snapped, draw it in green
bd.SetPen(c4d.Vector(0, 1, 0))
screeCoordinate = bd.WS(self.circleCoordinate)
bd.DrawCircle2D(screeCoordinate.x, screeCoordinate.y, self.data["radius"])
# 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))
if self.circleCoordinate != c4d.Vector(-1):
bd.DrawLine2D(bd.WS(lastPoint), bd.WS(self.circleCoordinate))
else:
bd.DrawLine2D(bd.WS(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.circleCoordinate = c4d.Vector(-1)
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()
# Set the radius of the search function
radius = self.data["radius"]
mouseCursor = c4d.Vector(x, y, 0)
# Retrieves the object that are near the mouse using the static method PickObject
objects = c4d.utils.ViewportSelect.PickObject(bd, doc, x, y, radius, c4d.VIEWPORT_PICK_FLAGS_NONE)
# Creates a new viewport select
vpSelect = c4d.utils.ViewportSelect()
frame = bd.GetFrame()
w = frame['cr'] - frame['cl'] + 1
h = frame['cb'] - frame['ct'] + 1
if objects:
nearestPoints = [] # stores the nearest point for each object
# Init the ViewportSelect with the object
if not vpSelect.Init(w, h, bd, objects, c4d.Mpoints, True, c4d.VIEWPORTSELECTFLAGS_NONE):
raise ValueError("can't init the viewportSelect with the object")
for op in objects:
point = vpSelect.GetNearestPoint(op, x, y, radius)
if point:
# Store de point coordinate in world coordinates
nearestPoints.append(op.GetPoint(point['i']) * op.GetMg())
else:
nearestPoints.append(c4d.Vector(-1))
# Calculates the point distance to the mouse
pointsDistance = [ (bd.WS(p) - mouseCursor).GetLengthSquared() for p in nearestPoints]
# Retrieves the index of the closest point
index = pointsDistance.index(min(pointsDistance))
self.circleCoordinate = nearestPoints[index]
self.mouseCoordinate = mouseCursor
else:
self.mouseCoordinate = mouseCursor
self.circleCoordinate = c4d.Vector(-1)
# 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), "radius" : 20.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