Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode.
Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).
I found this thread giving me a RuntimeError: Object is not initialized yet for c4d.utils.ViewportSelect()
RuntimeError: Object is not initialized yet
c4d.utils.ViewportSelect()
Re: utils.ViewportSelect() Problem But no beginner solution just a hunch in this thread.
Anyway I can't get the SDK example to post any results I guess I am missing some crucial setup of the function itself. I have a Mousinput (mx, my) which I would like to feed into this util and get the GetNearestPoint
(mx, my)
GetNearestPoint
def MouseInput(self, doc, data, bd, win, msg): # 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) vpSelect = c4d.utils.ViewportSelect() # Error thrown here infoPoint = vpSelect.GetPixelInfoPoint(mx, my) if not infoPoint: return i = infoPoint["i"] # int op = infoPoint["op"] # c4dBaseObject z = infoPoint["z"] # float # Pushes an update event to Cinema 4D c4d.EventAdd() return True
Goal would be to get the Coordinates of the Point.
Thank you for your time. mogh
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.
GetCursorInfo
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
Hi,
as the exception told you, you have to init your ViewportSelect (like most types in c4d.utils). Check the docs for details on ViewportSelect.Init()
ViewportSelect
c4d.utils
ViewportSelect.Init()
Cheers, zipit
thanks Zipit even though I am walking in the dark I got some output.
Ok i put the below code at the beginning of my mousinput, but i get an Error which does not make sense for me. I have an selected polygon object in point mode in my scene ?
vpSelect.Init(width, height, bd, self.op, c4d.Mpolyedgepoint, True, c4d.VIEWPORTSELECTFLAGS_IGNORE_HIDDEN_SEL) ReferenceError: the object 'c4d.PolygonObject' is not alive```
bd = doc.GetActiveBaseDraw() frame = bd.GetFrame() left = frame["cl"] right = frame["cr"] top = frame["ct"] bottom = frame["cb"] width = right - left + 1 height = bottom - top + 1 vpSelect = c4d.utils.ViewportSelect() vpSelect.Init(width, height, bd, self.op, c4d.Mpolyedgepoint, True, c4d.VIEWPORTSELECTFLAGS_IGNORE_HIDDEN_SEL)
thanks in advance mogh
first of all - you do not need to get the active BaseDraw since MouseInput gives you the BaseDraw it is called for, its the argument called bd in your example. Secondly objects in c4d can be alive or not, which is a colorful metaphor for the condition that a pointer to an object - which you effectively store when you do something like self.op = some_object - is not pointing to a memory region anymore where an object is defined. You can check that via c4d.C4DAtom.Isalive().
BaseDraw
MouseInput
bd
self.op = some_object
c4d.C4DAtom.Isalive()
Some simple solutions:
[self.op]
self.op
Cheers zipit
Thank you for your time Zipit, I trie to be as precise as posible when asking as a beginner but sometimes we have to run around chasing hunches and asking stupid questions in forums to get somewhere. hope you don't mind.
anyway I set self.op in init (strangely self.op = doc.GetActiveObject() does not work)
self.op = doc.GetActiveObject()
def __init__(self): doc = c4d.documents.GetActiveDocument() self.op = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE) # .... snippet # .... snippet if self.op.C4DAtom.Isalive(): vpSelect.Init(width, height, bd, self.op, c4d.Mpolyedgepoint, True, c4d.VIEWPORTSELECTFLAGS_IGNORE_HIDDEN_SEL) infoPoint = vpSelect.GetPixelInfoPoint(mx, my)
to give you all the info here is the whole code so far current error is depending on my isalive usage ...
Traceback (most recent call last): *File "C:\Users\weigajan\AppData\Roaming\Maxon\Maxon Cinema 4D R21_64C2B3BD\plugins\Py-AVT\clickline.pyp", line 142, in MouseInput if op.c4d.C4DAtom.Isalive(): NameError: global name 'op' is not defined
AttributeError: 'list' object has no attribute 'C4DAtom' Traceback (most recent call last): File "C:\Users\weigajan\AppData\Roaming\Maxon\Maxon Cinema 4D R21_64C2B3BD\plugins\Py-AVT\clickline.pyp", line 142, in MouseInput if self.op.C4DAtom.Isalive(): AttributeError: 'list' object has no attribute 'C4DAtom'*
import c4d import os from c4d import gui, plugins, utils PLUGIN_ID = 1000001 #Dummy ID # Be sure to use a unique ID obtained from www.plugincafe.com 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) # 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): # 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): doc = c4d.documents.GetActiveDocument() self.op = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE) #self.op = doc.GetActiveObject() self.lines = [] self.mouseCoordinate = c4d.Vector(-1) self.data = {"line_color":c4d.Vector(1, 1, 0)} #self.vpSelect = c4d.utils.ViewportSelect() def GetState(self, doc): if len(doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_0))!=1: return False if doc.GetMode() != c4d.Mpoints: return 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): frame = bd.GetFrame() left = frame["cl"] right = frame["cr"] top = frame["ct"] bottom = frame["cb"] width = right - left + 1 height = bottom - top + 1 vpSelect = c4d.utils.ViewportSelect() # 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) if self.op.C4DAtom.Isalive(): vpSelect.Init(width, height, bd, self.op, c4d.Mpolyedgepoint, True, c4d.VIEWPORTSELECTFLAGS_IGNORE_HIDDEN_SEL) infoPoint = vpSelect.GetPixelInfoPoint(mx, my) #if not infoPoint: return #i = infoPoint["i"] # int #op = infoPoint["op"] # c4dBaseObject #z = infoPoint["z"] # float # Pushes an update event to Cinema 4D c4d.EventAdd() return True def Draw(self, doc, data, bd, bh, bt, flags): # 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) #bd.DrawLine(pa, pb, 0) # 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): # 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): 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())
I'll answer in ABP style:
ViewpostSelect.Init()
BaseObject
self.op = [doc.GetActiveObject()]
None
if op.c4d.C4DAtom.Isalive():
BaseList2D
GelistNode
C4DAtom
if op.IsAlive(): #yadayada
ToolData
class MyCoolTool(ToolData): """ """ def __init__(self): """ """ self._active_object = None @property def active_object(self): """ You could incorporate all kinds of logic here, like for example allowing the user to switch objects mid-tool-execution. This version will just grab the primary active object the first time it is called and handle a dead reference on later calls by returning None then. """ if self._active_object is None: doc = c4d.documents.GetActiveDocument() # Note that _active_object can still be None after the follwoing line. self._active_object = doc.GetActiveObject() if self._active_object is not None and self._active_object.IsAllive(): return self._active_object # Not needed as methods implcitly return None, just for clarity here return None def method_that_relies_on_active_object(self, *args, **kwargs): """ """ if self.active_object is None: # do soemthing to signal that there is no operand for your tool operation return False op = self.active_object # ... return True
I hope this helps.
@zipit said in How to setup c4d.utils.ViewportSelect() and GetNearestPoint to work:
IsAllive()
Thanks Zipit, your code IsAllive() should be IsAlive() I guess ...
IsAlive()
anyway I managed to get a Solid "none" feedback now. but need to sleep more to get somewhere ...
if self.active_object is None: print "no active Object" # do soemthing to signal that there is no operand for your tool operation return False op = self.active_object vpSelect.Init(width, height, bd, [op], c4d.Mpolyedgepoint, True, c4d.VIEWPORTSELECTFLAGS_IGNORE_HIDDEN_SEL) infoPoint = vpSelect.GetPixelInfoPoint(mx, my) print infoPoint nearest = vpSelect.GetNearestPoint(op, mx, my, 5, onlyselected=True) print nearest
Is GetNearestPoint better for perfomance than PointInRange ? I found an old C++ code which uses PointInRange
PointInRange
@m_magalhaes
This morning: uff more learning and work. This Afternoon lets try this code ... mind blown ... thank you !
I'll mark this as solved hence I think ist a good way to close a thread with a working prototype for others to learn.
Thank You again, @zipit & @m_magalhaes mogh