SendModelingCommand Failing



  • As part of my quest to improve the FBX export of Cinema 4D I've been asked to do some automatic cleanup of meshes before the export.

    I'm using several calls of SendModelingCommand() to perform these clean up operations. The requested commands are:

    c4d.MCOMMAND_OPTIMIZE
    c4d.MCOMMAND_ALIGNNORMALS
    c4d.MCOMMAND_REVERSENORMALS

    Optimize and Align Normals are run on every polygonal object as checked with IsInstanceOf(c4d.Opolygon). Reverse Normals is run selectively only on meshes with no boundary edges (i.e. closed meshes) whose normals are pointing inward. I can get all of these to work properly in simple test documents, but when I test it with a full fledged production scene the c4d.MCOMMAND_ALIGNNORMALS and c4d.MCOMMAND_REVERSENORMALS calls fail and SendModelingCommand() returns False. Is there anyway to get more information about why SendModelingCommand() is failing?

    Here's the applicable part of my code:

    if self.align:
        poly_objs = [o for o in self._GetAllObjects(self.doc.GetFirstObject()) if o.IsInstanceOf(c4d.Opolygon)]
        c4d.StopAllThreads() #Not sure if this is necessary
        res = c4d.utils.SendModelingCommand(c4d.MCOMMAND_ALIGNNORMALS, poly_objs, doc=self.doc) #Align normals for all polygon objects
        if res:
            print("Successfully aligned normals.") #Works in simple scenes
        else:
            print("Could not align normals.") #Doesn't work in production scenes
                
        if self.reverse:
            closed_objs = [o for o in poly_objs if self._IsClosedMesh(o)]
            reversed_objs = [o for o in closed_objs if self._IsReversed(o)]
            c4d.StopAllThreads()
            res = c4d.utils.SendModelingCommand(c4d.MCOMMAND_REVERSENORMALS, reversed_objs, doc=self.doc) #Reverse the normals of all closed polygon objects whose normals are currently reversed
            if res:
                print("Successfully fixed reversed normals.") #Works in simple scenes
            else:
                print("Could not fix reversed normals.") #Doesn't work in production scenes
    
    
    if self.optimize:
        poly_objs = [o for o in self._GetAllObjects(self.doc.GetFirstObject()) if o.IsInstanceOf(c4d.Opolygon)]
        tool_bc = c4d.BaseContainer()
        tool_bc[c4d.MDATA_OPTIMIZE_UNUSEDPOINTS] = True
        c4d.StopAllThreads()
        res = c4d.utils.SendModelingCommand(c4d.MCOMMAND_OPTIMIZE, poly_objs, bc=tool_bc, doc=self.doc) #Optimize unused points for all polygon objects
        if res:
            print("Successfully removed unused points.") #This one works in production scenes too! For some reason?
        else:
            print("Could not remove unused points.")
    

    Edit: there are 1200 or so polygonal objects in the production scene, only 3 of them are closed meshes with reversed normals. I doubt it's an issue of list length. Many of the polygonal objects are children of generators (Symmetry, Cloner, Subdivision Surface) but I can't imagine that should make a difference.



  • Quick update: I've copied all of the polygonal objects out of the source document for testing. The first thing I tried was selecting all of the objects and running Align Normals and Reverse Normals from the menu.

    The commands do not complete successfully. It seems that there's something about these objects that these commands don't like. Perhaps there's a single non-manifold object hiding in the bunch that is causing the operation to fail for all of them? If that's the case then I feel it might be more worthwhile for Cinema 4D to complete the commands on those objects that it can and print a warning to the console for those that it cannot.

    I'm wracking my brain now for methods to manually remove non-manifold surfaces from the list of polygonal objects. I've had very mixed results in the past trying to use python to access information from Mesh Check. I suppose the alternative would be checking the structure of the objects myself to make sure that no edge is shared by more than 2 polygons?

    Any ideas out there? 🙏



  • Hi,

    I do not think that non-manifold meshes are the reason for your problems. Reversing normals is a completely topology agnostic function (it just shuffles the polygon point indices around) and aligning them does depend on the topology, but it should not fail on a non-manifold mesh, just give you a result a human would consider incorrect.

    Have you checked if your imported geometry comes with cached normals? The whole thing sounds like you are having these "fixed" normal tags, c4d uses when importing files with specific normal data.

    Cheers
    zipit



  • @zipit There is no imported geometry. The geometry was created in Cinema 4D and has no user normals. However, it looks like you're correct that it's not an issue of non-manifold geometry as the operations still fail if I remove the unnecessary polygons.

    It looks like Reverse Normals will fail on an open mesh if a polygon selection is not provided (which seems strange) but my plugin is only attempting it on closed meshes and it still fails.

    My workaround for the time being is to call SendModelingCommand() individually for each polygonal object which I'm sure is adding a fair amount of overhead but it's not unbearably slow so I'm going to leave it be for now. If anyone has any other ideas about why these commands might fail I'm all ears! 👂



  • Hi,

    sorry, my bad, I somehow misread the FBX part as an import operation. Well, this all sounds very mysterious and without more details is probably hard to answer / a guessing game. It also sounds more like a general bug rather than an API bug (which probably means that you won't get any help here). However, flipping the normals is really trivial and calling SMC for each polygon is wildly inefficient. Below is a short script which shows you how to flip all normals of a polygon object manually.

    Aligning the normals is not too hard either, but will require some work. What you have to do in principle, is evaluate the angle between two polygons sharing an edge in comparison to the angle between their normals.

    To evaluate the angle between the two polygons you can obviously not use the dot product of their normals. One way to do it, is to find the unit vector that is pointing from a shared point in the shared edge to the adjacent point in the polygon that is not part of the edge. Doing this for both polygons, you can determine if they are facing away form each other or not (if the dot product of these two unit vectors is bigger than zero).

    The exclusive NOR of this "facing away or not" boolean and the facing of the normals (here you have to test for the opposite: In our example if the dot product of the two normals is smaller than zero) gives you then the decision if you have to flip one polygon in respect to another.

    Cheers
    zipit

    import c4d
    
    def _get_reveresed_normal_cpolygon(c_poly):
        """ Returns a CPolygon with a reversed normal for c_poly.
        """
        if not isinstance(c_poly, c4d.CPolygon):
            msg = "Expected a CPolygon as input. Received: {}."
            raise TypeError(msg.format(c_poly))
    
        # We are just switching the edges adjacent to A around, as switching them
        # will change the normal of the polygon due to the right hand rule of the
        # cross product (i.e. how polygon normals work).
        if c_poly.IsTriangle():
            return c4d.CPolygon(c_poly.a, c_poly.c, c_poly.b, c_poly.b)
        else:
            return c4d.CPolygon(c_poly.a, c_poly.d, c_poly.c, c_poly.b)
    
    def reverse_normals(op):
        """ Reverses all normals for a polygon object.
        """
        if not isinstance(op, c4d.PolygonObject):
            return False
    
        # We just iterate over all polygons. We cannot edit a CPolygon in place,
        # so we have to create a new one.
        for pid, cpoly in enumerate(op.GetAllPolygons()):
            new_poly = _get_reveresed_normal_cpolygon(cpoly)
            op.SetPolygon(pid, new_poly)
        # Tell the object that its geometry has changed.
        op.Message(c4d.MSG_UPDATE)
        return True
    
    def main():
        reverse_normals(op)    
        c4d.EventAdd()
            
    if __name__=='__main__':
        main()
    


  • Hello,

    I've marked this thread as a question. see this thread, on how to use it :)

    About your issue;
    it's hard to reproduce.

    There's no error message in the console (python or the default one) ?
    You can launch Cinema 4D with the parameter g_console=true to see if you have more information there.

    If you can send us the geometry that is causing the problem we will be able to reproduce that, understand what is happening, open a BugEntry if needed or send that to the modeling team. you can use our email sdk_support@maxon.net if you don't want to share it here.

    Cheers,
    Manuel



  • Hi wuzelwazel, thanks for reaching out us.

    With regard on knowing more about the causes of SendModelingCommand failing on MCOMMAND_ALIGNNORMALS and MCOMMAND_REVERSENORMALS you could :

    • check in the Python Console
    • check the C4D Console started along the main app using passing g_console=true in the command line

    These two are the places where potential messages about the failure might be printed out.

    Finally, as pointed out by @zipit, this looks to me more like a modelling issue rather than an API one. Assuming that nothing gets printed out in both console, I warmly suggest considering to privately share your asset and let us investigate the issue together with the mesh.

    Best, Ricardo



  • It must be the geometry, but I can't figure out what it is about this geometry that's problematic. Since I've switched to using SendModelingCommand on individual objects instead of a full list I have a bit more visibility into the objects that are failing. However, the 4 pieces that MCOMMAND_ALIGNNORMALS consistently fails on don't have much in common.

    To be clear, the failure does not produce an error. I've also tried running the plugin with g_console=true and there are no errors or warnings produced. SendModelingCommand simply returns False and the normals are not aligned.

    I'll work on collecting the scene and WIP plugin for your perusal.



  • hello,

    I forgot to ask, where are you running those commands ? on a script, a dialogbox, a nodedata ?

    Thanks for your time trying to isolate the problem. Don't hesitate to send us an heavy scene, we can see why the command isn't working.

    Cheers,
    Manuel


Log in to reply