Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Recent
    • Tags
    • Users
    • Login

    Position/Normal under Cursor [SOLVED]

    Scheduled Pinned Locked Moved PYTHON Development
    5 Posts 0 Posters 745 Views
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • H Offline
      Helper
      last edited by

      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

      1 Reply Last reply Reply Quote 0
      • H Offline
        Helper
        last edited by

        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

        1 Reply Last reply Reply Quote 0
        • H Offline
          Helper
          last edited by

          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

          1 Reply Last reply Reply Quote 0
          • H Offline
            Helper
            last edited by

            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://developers.maxon.net/forum/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())
            
            1 Reply Last reply Reply Quote 0
            • H Offline
              Helper
              last edited by

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

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

              1 Reply Last reply Reply Quote 0
              • First post
                Last post