How to setup c4d.utils.ViewportSelect() and GetNearestPoint to work



  • I found this thread giving me a RuntimeError: Object is not initialized yet for 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

    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.

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

    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



  • Hi,

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

    Some simple solutions:

    1. First of all the method expects a list of objects. So it would be [self.op] anyways, but you could just give it all objects in your scene.
    2. Calculate the object with the bounding box closest to the mouse point(er) in world space coordinates and pass that object as it will also contain the closest point, vertex, edge or polygon. This will be for obvious reasons much more performant.
    3. Make sure the reference to self.op is still alive when the method is called. To give you tips on that we would need more information on where and when you set 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.

    1. Sounds cool but the user normaly has only one object selected multi object selection could be an obtion in the future but sounds to complicated for me at this moment.

    anyway I set self.op in init (strangely self.op = doc.GetActiveObject() does not work)

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


  • Hi,

    I'll answer in ABP style:

    • self.op = doc.GetActiveObject() probably 'does not work' because ViewpostSelect.Init() expects a list of BaseObject as its fourth argument as I already pointed out. So if you just want the primary selected object it would have to be self.op = [doc.GetActiveObject()]. But you always should do input validation (the method can return None).
    • if op.c4d.C4DAtom.Isalive(): raises an exception because you misunderstood a crucial concept at least in this case. I cannot explain it in all detail here, but you should read up on object oriented programming and inheritance. The gist in this case is, that op is a BaseObject, which means that it is also a BaseList2D, which means it is also a GelistNode, which means it is also a C4DAtom. As the name implies C4DAtom is the atomic type of c4d. Basically anything that sits in your scene graph is an C4DAtom (actually almost anything - objects, materials, shaders, tags, animations, viewport, etc. - is even a BaseList2D). So it would be just if op.IsAlive(): #yadayada (its written with a capital a).
    • The ToolData type is somewhat lacking regarding a bultin logic for the target of your tool (hence the need for your self.op thing). How you handle this depends on what you are trying to do. One way to do it could be to add a property to your object. Something like this:
    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.

    Cheers,
    zipit



  • @zipit said in How to setup c4d.utils.ViewportSelect() and GetNearestPoint to work:

    IsAllive()

    Thanks Zipit,
    your code IsAllive() should be IsAlive() I guess ...

    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



  • 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



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


Log in to reply