Position/Normal under Cursor [SOLVED]



  • On 14/01/2015 at 17:51, xxxxxxxx wrote:

    I've done a decent amount of research on this and only found partial solutions so far. I'm trying to create a tool that allows me to move the selected object along the surface of any object in the view. I'm essentially looking to get something close to the behavior of 3D Snapping in Polygon mode, with the added effect of rotating the object to match the surface normal underneath the cursor and ensuring that it's the base of the object that slides along the surface, not the axis.

    I'm able to get a list of objects underneath my cursor using ViewportSelect.PickObject(), but when I'm trying to calculate the exact position/normal of the intersection I run into the problem of the objects not being polygon objects.

    What is the best way to take my list of objects and convert them into polygon objects (or even a single polygon object)? GetCache() works for me some of the time, but not all objects have caches, and sometimes the caches are hierarchies that are many levels deep. What I want is a 1:1 conversion of the objects I have to poly objects.

    Thanks in advance!

    Donovan

    The current state of my code is below:

    """Place On Surface"""
      
    import c4d
    import os
      
    PLUGIN_ID = 1034435
      
    def polygonize_objects(objects) :
        if objects is None:
            return
        poly_doc = c4d.BaseDocument()
      
    class PlaceTool(c4d.plugins.ToolData) :
        def InitTool(self, doc, data, bt) :
            """Called each time tool is selected."""
            print "InitTool()"
            return True
      
        def FreeTool(self, doc, data) :
            """Called each time the user chooses another tool."""
            print "FreeTool()"
            return
      
        def MouseInput(self, doc, data, bd, win, msg) :
            """Called when the user clicks with the mouse in any of the editor views."""
            print "MouseInput()"
            #Get Mouse Coordinates
            mouse_x = msg[c4d.BFM_INPUT_X]
            mouse_y = msg[c4d.BFM_INPUT_Y]
            print "Mouse Coords: (%s, %s)" % (mouse_x, mouse_y)
            #Create a vp select helper
            viewport_select = c4d.utils.ViewportSelect()
            #Retrieve the picked objects
            pick_objects = viewport_select.PickObject(bd, doc, mouse_x, mouse_y, rad=0, flags=c4d.VIEWPORT_PICK_FLAGS_0)
            print pick_objects
            return True
      
        def KeyboardInput(self, doc, data, bd, win, msg) :
            """Called when the user types something in any of the editor views."""
            print "KeyboardInput()"
            return True
      
        def GetState(self, doc) :
            """Called to check if the tool should be enabled, checked or not."""
            return c4d.CMD_ENABLED
      
    if __name__ == "__main__":
        bmp = c4d.bitmaps.BaseBitmap()
        dir, file = os.path.split(__file__)
        fn = os.path.join(dir, "res", "liquid.tif")
        bmp.InitWith(fn)
        c4d.plugins.RegisterToolPlugin(id=PLUGIN_ID, str="CV-Place on Surface",
                                        info=0, icon=bmp,
                                        help="Instances the selected object and places it on the surface under the cursor.",
                                        dat=PlaceTool())
    

    Edit: Andreas reformatted code



  • On 14/01/2015 at 21:09, xxxxxxxx wrote:

    Getting closer...

    def polygonize(obj) :   
      """Returns a polygon version of obj. If obj can't be converted to Polys, nothing is returned"""
      
      cache = obj.GetCache()
        if (cache is not None) and (cache.GetType() == c4d.PolygonObject) :
            return cache
      
        point_obj_list = c4d.utils.SendModelingCommand(
            command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
            list = [op.GetClone()],
            mode = c4d.MODELINGCOMMANDMODE_ALL,
            doc = obj.GetDocument())
      
        point_obj = point_obj_list[0]
      
      if point_obj.GetType() == c4d.PolygonObject:
            return point_obj
        else:
            return None
    

    But this sometimes gives me back a complicated hierarchy w/ lots of nested objects. The next problem is trying to convert them all into a single polygon object that I can feed into a GeRayCollider.

    Edit: Andreas reformatted code



  • On 15/01/2015 at 12:51, xxxxxxxx wrote:

    The amount of code has exploded in order to get a list of poly objects from a given object. There are still some issues with it including non-essential objects for ray testing, but it just might work.

    """Polygonize Selected
    Takes the selected object, converts it to polygon objects, and inserts those poly
    objects at the top of the scene hierarchy"""
      
    import c4d
    from c4d import gui
      
    def GetNextNode(op, stop_at=None) :
        """Returns the next node in the BaseList 2D. If you only want to get the children of an object, pass that object as `stop_at`."""
      
        if op is None:
            return None
      
        if op.GetDown() :
            return op.GetDown()
      
        while not op.GetNext() and op.GetUp() and op.GetUp() != stop_at:
            op = op.GetUp()
      
        return op.GetNext()
      
    def GetNodes(first_node, stop_at=None) :
        """Returns a list of BaseList 2D nodes"""
      
        if (first_node is None) or (not first_node.IsAlive()) :
            return None
      
        nodes = []
      
        cur_node = first_node
        while cur_node is not None:
            nodes.append(cur_node)
            cur_node = GetNextNode(cur_node, stop_at=stop_at)
      
        return nodes
      
    def hierarchy_to_poly_list(op) :
        if op is None:
            return []
      
        op_and_children = GetNodes(op, stop_at=op)
        poly_objects = []
        for obj in op_and_children:
            if obj.CheckType(c4d.Opolygon) :
                poly_objects.append(obj.GetClone())
      
        return poly_objects
      
    def current_state_to_poly_object_list(op) :
        """Returns a list of polygon objects from op
      
        Known Limitations
        -----------------
        - Returns child objects that aren't needed for generation
        """
        if op is None:
            return
      
        deform_cache = op.GetDeformCache()
        if deform_cache is not None:
            return hierarchy_to_poly_list(deform_cache)
      
        csto_list = c4d.utils.SendModelingCommand(
            command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
            list = [op.GetClone()],
            mode = c4d.MODELINGCOMMANDMODE_ALL,
            doc = op.GetDocument())
      
        csto_result = None
        if csto_list:
            csto_result = csto_list[0]
      
        return hierarchy_to_poly_list(csto_result)
      
    def main() :
        if op is None:
            return
      
        poly_ops = current_state_to_poly_object_list(op)
        for poly_op in poly_ops:
            print poly_op
            doc.InsertObject(poly_op)
        c4d.EventAdd()
      
      
    if __name__=='__main__':
        main()
    

    Edit: Andreas reformatted code



  • On 19/01/2015 at 08:09, xxxxxxxx wrote:

    Here's what I came up with.
    It's not perfect and still has its flaws, but it should cover the main questions asked in this thread.

    """
    Place On Surface
    """
      
    import c4d
    import os
    import sys
      
    PLUGIN_ID = 1000001  # CHANGE
      
    def CalcFaceNormal(polyobj, cpoly) :
        # littledevil and NiklasR in https://plugincafe.maxon.net/topic/7073/8007_move-polygon-along-normal
        points = polyobj.GetAllPoints()
        p1, p2, p4 = points[cpoly.a], points[cpoly.b], points[cpoly.d]
        nrm = (p2 - p1).Cross(p4 - p1).GetNormalized()
        return nrm
      
      
    def GetNearestPoly(vpsel, polyobj, width, height, bd, mouse_x, mouse_y) :
        vpsel.Init(width, height, bd, [polyobj], c4d.Mpolygons, True, c4d.VIEWPORTSELECTFLAGS_IGNORE_HIDDEN_SEL)
        return vpsel.GetNearestPolygon(polyobj, mouse_x, mouse_y, 2) # max_rad may need to be adjusted to needs, without max_rad, the z-distance check may fail on clones
      
    def FindNearestPoly(vpsel, picked_objs, obj_active, width, height, bd, mouse_x, mouse_y, zMin = sys.float_info.max) :
      
        """Runs over all picked objects, polygonizing as need and caring for deform caches. Returns a nearest poly dict."""
      
        np_result = None
      
        if picked_objs is not None:
            for obj in picked_objs:
                if obj is None:
                    continue
      
                # if the active object got hit by mouseclick, we want to do nothing
                if obj == obj_active:
                    continue
      
                # polygon objects may have a deform cache
                if obj.GetType() == c4d.Opolygon:
                    deformcache = obj.GetDeformCache()
                    if deformcache is not None:
                        np = GetNearestPoly(vpsel, deformcache, width, height, bd, mouse_x, mouse_y)
                    else:
                        np = GetNearestPoly(vpsel, obj, width, height, bd, mouse_x, mouse_y)
                    if np is not None:
                        if np["z"] < zMin:
                            zMin = np["z"]
                            np_result = np
                    continue
      
                # at this point we may have virtually any type of object(-hierarchy), except polygonal objects
                # we don't care for the actual hierarchy, but directly want use the cache
                cache = obj.GetCache()
                if (cache is None) :
                    # cache not available, we'll force one
                    point_obj_list = c4d.utils.SendModelingCommand(
                            command = c4d.MCOMMAND_CURRENTSTATETOOBJECT,
                            list = [obj.GetClone()],
                            mode = c4d.MODELINGCOMMANDMODE_ALL,
                            doc = obj.GetDocument())
                    if point_obj_list == False:
                        continue
                    np = FindNearestPoly(vpsel, point_obj_list, obj_active, width, height, bd, mouse_x, mouse_y, zMin)
                    if np is not None:
                        zMin = np["z"]
                        np_result = np
                else:
                    # the cache may be a hierarchy
                    np = FindNearestPoly(vpsel, [cache], obj_active, width, height, bd, mouse_x, mouse_y, zMin)
                    if np is not None:
                        zMin = np["z"]
                        np_result = np
                    np = FindNearestPoly(vpsel, cache.GetChildren(), obj_active, width, height, bd, mouse_x, mouse_y, zMin)
                    if np is not None:
                        zMin = np["z"]
                        np_result = np
                    np = FindNearestPoly(vpsel, [cache.GetNext()], obj_active, width, height, bd, mouse_x, mouse_y, zMin)
                    if np is not None:
                        zMin = np["z"]
                        np_result = np
        return np_result
      
      
    class PlaceTool(c4d.plugins.ToolData) :
        def InitTool(self, doc, data, bt) :
            """Called each time tool is selected."""
      
            print "InitTool()"
            return True
      
        def FreeTool(self, doc, data) :
            """Called each time the user chooses another tool."""
      
            print "FreeTool()"
            return
      
        def MouseInput(self, doc, data, bd, win, msg) :
            """Called when the user clicks with the mouse in any of the editor views."""
      
            #print "MouseInput()"
      
            # Get the active object, which will be placed on surface
            oactive = doc.GetActiveObject()
            if oactive is None:
                # nothing to place on surface
                return True
      
            #Get Mouse Coordinates
            mouse_x = int(msg[c4d.BFM_INPUT_X])
            mouse_y = int(msg[c4d.BFM_INPUT_Y])
            # print "Mouse Coords: (%s, %s)" % (mouse_x, mouse_y)
      
            # Create a vp select helper and get values for init
            viewport_select = c4d.utils.ViewportSelect()
            frame = bd.GetFrame()
            left = frame["cl"]
            right = frame["cr"]
            top = frame["ct"]
            bottom = frame["cb"]
            width = right - left + 1
            height = bottom - top +1
      
            #Retrieve the picked objects
            pick_objects = viewport_select.PickObject(bd, doc, mouse_x, mouse_y, rad=0, flags=c4d.VIEWPORT_PICK_FLAGS_0)
            # in pick_objects we now have a list of all objects that were hit by the mouse click
            # these objects may very well be for exaple cloners, where a clone got hit
      
            nearest_poly = FindNearestPoly(viewport_select, pick_objects, oactive, width, height, bd, mouse_x, mouse_y)
            if nearest_poly is not None:
                # if we found a polygon to place the object on:
                #   - get camera coordinates for the hit point and convert into world coordinates => position of object
                #   - get normal vector and translate with global matrix of the surface object => rotation of the object
                camCoord = viewport_select.GetCameraCoordinates(mouse_x, mouse_y, nearest_poly["z"])
                pos = bd.CW(camCoord)
                surface_obj = nearest_poly["op"]
                poly = surface_obj.GetPolygon(nearest_poly["i"])
                vec_face_normal = CalcFaceNormal(surface_obj, poly)
                mg_surface_obj = surface_obj.GetMg().GetTensorMatrix()
                rot = c4d.utils.VectorToHPB(vec_face_normal * mg_surface_obj) #+ nearest_poly["op"].GetAbsRot() ### + c4d.utils.MatrixToHPB(nearest_poly["op"].GetUpMg())  # + c4d.utils.MatrixToHPB(nearest_poly["op"].GetMg().__invert__())
      
                oactive.SetAbsPos(pos)
                oactive.SetAbsRot(rot)
                c4d.EventAdd()
      
            return True
      
        def KeyboardInput(self, doc, data, bd, win, msg) :
            """Called when the user types something in any of the editor views."""
      
            print "KeyboardInput()"
            return True
      
        def GetState(self, doc) :
            """Called to check if the tool should be enabled, checked or not."""
      
            return c4d.CMD_ENABLED
      
    if __name__ == "__main__":
        bmp = c4d.bitmaps.BaseBitmap()
        dir, file = os.path.split(__file__)
        fn = os.path.join(dir, "res", "some_icon.tif")
        bmp.InitWith(fn)
        c4d.plugins.RegisterToolPlugin(id=PLUGIN_ID, str="CV-Place on Surface",
                                    info=0, icon=bmp,
                                    help="Instances the selected object and places it on the surface under the cursor.",
                                    dat=PlaceTool())
    


  • On 19/01/2015 at 12:11, xxxxxxxx wrote:

    Andreas - Thanks for that! It got me 95% of the way there. :-)


Log in to reply