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://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())
-
On 19/01/2015 at 12:11, xxxxxxxx wrote:
Andreas - Thanks for that! It got me 95% of the way there.