Help with Matrix Manipulation after c4d.MCOMMAND_JOIN



  • Dear Developers,

    again I am having trouble with matrixes,

    I pass a null (inside Axisnull are lots of polygons) to the join comand and get an object (not inserted jet) that I pass to a matrix manipulation definition to get the position in worldspace after that i place it into the hirachy.

    I want the Axis of Axisnull ( parenmg ) to be my PolygonObject Axis
    and inserted in the hirachy

    I guess some trouble stems from not knowing where the returned object is in Space hence its only in memory ? but I wanted to insert the object after all the manipulation. This should be doable.

    Hirachy dummy:

    CADfilenull (masternull)
    __|-Axisnull (join comand acts here)
    __|-Axisnull (join comand acts here)
    _|-CADfilenull subgroup
    ___|-Axisnull (join comand acts here)
    ___|-Axisnull (join comand acts here)
    ___|-Axisnull (join comand acts here)
    ___|-Axisnull (join comand acts here)
    ___|- ....

    def movematrix(parentmg, op):
        #GetUpMg() is not correct hence wrong parent, axis null is killed by this time, use parentmg !
        mg = op.GetMg()
        ml = op.GetMp()
        ps = op.GetAllPoints()     
        #############################################
        # trouble starts here - I gues this is all not necessary somehow
        center = op.GetMp() # local center from op
        center *= mg  # Global Center
        new_m = c4d.Matrix(parentmg) # Set the matrix including rotation from parent
        new_m.off = center # overwrite the position from the object
        
        loc_m = ~new_m * mg # multiply invers with original matrix
    
        op.SetAllPoints([loc_m.Mul(p) for p in ps ]) 
        op.SetMg(new_m)
        op.Message(c4d.MSG_UPDATE) 
        # trouble end
        #############################################
    
    def JoinCommand(doc, op):
        res = c4d.utils.SendModelingCommand(command = c4d.MCOMMAND_JOIN,
                                    list = [op],
                                    mode = c4d.MODELINGCOMMANDMODE_ALL,
                                    doc = doc)
        
        if c4d.GetC4DVersion() < 21000:
            res[0].SetAbsPos(c4d.Vector())  #found in forum just in case
    
        # Cheks if the command didn't failed
        if res is False:
            raise TypeError("return value of Join command is not valid")
        elif res is True:
            print ("Command successful. But no object.")
        elif isinstance(res, list):        
            op.Remove() # <--- why is this needed somtimes ?
            return res[0] # Returns the first item containing the object of the list.  ??? GetClone() ???
    
    def joinmanagment(n):    
        # n "Axis" null will be not alive in a few steps get everything we need from it
        parent = n.GetUp()    
        parentmg = n.GetMg()
        newobject = JoinCommand(doc, n) # combine the poly objects
        
        if not newobject.IsAlive():
            raise TypeError("Object is not alive.")
            return False
    
        newobject.SetName(str(parent.GetName()))
        movematrix(parentmg, newobject)
        newobject.InsertUnder(parent)
    
    #dummy main for completeness
    def main():
        allachsen = [list of Nulls]
    
        for n in allachsen: 
            secondcounter += 1
            statusbar(null_counter, secondcounter)
            if joinmanagment(n) == False:
                break
    
    

    kind regards
    mogh



  • uff after 4 more hours of shuffling and trying i got it working
    the breakthrough was to insert the object before finaly aplying the matrix to move I guess it makes sense to add it under the null to get the right refferenze ... but I startet out to manipulate verything in memory beforehand because i thought it would be cleaner (less action)

    Anyway Matrix Manipulation gets me everytime. I hate them ...

    further testing ongoning ... I still get moving and rotating .... :-(

    def movematrix(parentmg, op):
            mg = op.GetMg()
            ps = op.GetAllPoints()     
    
            new_m = c4d.Matrix(parentmg) * mg # get full matrix from parent and multiply with world Cords of object
            new_m.off = op.GetMp() * mg  # get local cords of object and multiply with world Cords of object and overwrite the position from the object
            loc_m = ~new_m * mg # multiply invers with original matrix of object 
            op.SetAllPoints([loc_m.Mul(p) for p in ps ]) # move all the points
            op.Message(c4d.MSG_UPDATE)  
    
    def joinmanagment(n):    
            # n "Axis" null will be not alive in a few steps get everything we need from it
            parent = n.GetUp()    
            parentmg = n.GetMg()
            newobject = JoinCommand(doc, n) # combine the poly objects
    
            if not newobject.IsAlive():
                raise TypeError("Object is not alive.")
                return False
    
            newobject.SetName(str(parent.GetName()))
            movematrix(parentmg, newobject)
            newobject.InsertUnder(parent)
            newobject.SetMg(parentmg) #set the matrix of parent
    

    kind regards
    mogh



  • Hi @mogh,

    thank you for reaching out to us and especially for sharing your own solution to your question. Am I correct assuming that your second post answered your question or are there things that remain unclear?

    Cheers,
    Ferdinand



  • Hi Zipit,
    Matrix manipulation still does not behave as I want ... the polygon objects jump and rotate
    I want them to stay in their postion after the join comand ....

    I brute forced almost every combination of matrix , ~matrix, local matrix no luck ...

    I am out of ideas
    kind regards



  • Hi @mogh,

    unfortunately it is not quite clear to me what you are trying to do, because both your snippets are not executable, i.e. are just snippets or include pseudo-code. However, from this general setting and your wording in your first posting, I assume you are simply trying "to move the axis of an object without moving its points", i.e. do what the move axis tool does. Below you find a simple script which shows you how to change the transform of a PointObject without changing the global coordinates of its points.

    If this is not what you are looking for, I would ask you to show us code that is executable and to explain in more detail what kind of transform you are looking for.

    Cheers,
    Ferdinand

    """Demonstrates how to "move" the transform of a point object.
    
    "Moves" the transform of a PointObject without changing the global 
    coordinates of its points, i.e. does what the move axis tool does. 
    
    You have to select a point object and then another object and it will move 
    the axis of the point object to the scond one.
    """
    
    import c4d
    
    def set_point_object_transform(node, transform):
        """Sets the global transform of a point object while keeping its points
        in place.
    
        Args:
            node (c4d.PointObject): The point object to move the axis for.
            transform (c4d.Matrix): The new global transform for the object.
        
        Raises:
            TypeError: When node or transform are not of specified type.
        """
        if (not isinstance(node, c4d.PointObject) or 
            not isinstance(transform, c4d.Matrix)):
            msg = f"Illegal argument types: {type(node)}{type(transform)}"
            raise TypeError(msg)
    
        mg = node.GetMg()
        # Move the points in the global frame and then into the new frame.
        points = [p * mg * ~transform for p in node.GetAllPoints()]
        # Set the points and stuff ;)
        node.SetAllPoints(points)
        node.Message(c4d.MSG_UPDATE)
        node.SetMg(transform)
    
    def main():
        """Runs set_point_object_transform() on the current selection.
        """
        nodes = doc.GetActiveObjects(0)
        if len(nodes) < 2:
            return
    
        node, transform = nodes[0], nodes[1].GetMg()
        set_point_object_transform(node, transform)
        c4d.EventAdd()
    
    if __name__ == "__main__":
        main()
    


  • Thank you for your patience and time Zipit.

    I implemented your code into mine, but sadly it does the same as mine ... all the polygon objects move and rotate.

    I will make a stripped down version of the code so you can have a better look at it.

    Thank you for your time.
    kind regards
    mogh



  • So this is the compact version I could come up with. (including your Code @Zipit)

    Please add some poly Objects under Nulls called "Axis" into your scene, an nest them arbitrary, with random rotation and location to se the problem when they are joined.

    Thank you for your time.

    #!py3
    import c4d, sys, os
    from c4d import gui
    from c4d.documents import GetActiveDocument
    #Version 1.4 Striped version
    # This Script needs Null-Objects Called "Axis" with polygon objects to run
    
    def GetNextObject(op):
        if not op: return
        if op.GetDown(): return op.GetDown()
        while op.GetUp() and not op.GetNext():
            op = op.GetUp()
        return op.GetNext()
    
    def get_all_objects (op):
        allachsen_list = list()
        all_objects_list = list()
        while op:
            if op.GetName() == 'Achsen-Objekt' or op.GetName() == 'Axis' :
                allachsen_list.append(op)
            all_objects_list.append(op)
            op = GetNextObject(op)
        return all_objects_list, allachsen_list
    
    def JoinCommand(doc, op):
        res = c4d.utils.SendModelingCommand(command = c4d.MCOMMAND_JOIN,
                                    list = [op],
                                    mode = c4d.MODELINGCOMMANDMODE_ALL,
                                    doc = doc)
                                    
        # Cheks if the command didn't failed
        if res is False:
            raise TypeError("return value of Join command is not valid")
        elif res is True:
            print ("Command successful. But no object.")
        elif isinstance(res, list):
            if c4d.GetC4DVersion() < 21000: res[0].SetAbsPos(c4d.Vector()) 
            op.Remove()
            return res[0] # Returns the first item containing the object of the list.  ??? GetClone() ???
    
    def set_point_object_transform(node, transform):
        if (not isinstance(node, c4d.PointObject) or 
            not isinstance(transform, c4d.Matrix)):
            msg = f"Illegal argument types: {type(node)}{type(transform)}"
            raise TypeError(msg)
    
        mg = node.GetMg()
        # Move the points in the global frame and then into the new frame.
        points = [p * mg * ~transform for p in node.GetAllPoints()]
        # Set the points and stuff ;)
        node.SetAllPoints(points)
        node.Message(c4d.MSG_UPDATE)
        node.SetMg(transform)
    
    def joinmanagment(n):
        # n "Axis" null will be not alive in a few steps get everything we need from it
        if n.GetUp() :
            parent = n.GetUp()
        else:
            print ("No Parent To Axis Null. Probably not save to run this sript anyway.")
            c4d.StatusClear()
            c4d.StatusSetText ('No Parent found! - Probalby mo CAD import Doc. Script Stopped.')
            exit()
            return False
    
        parentmg = n.GetMg()
        newobject = JoinCommand(doc, n) # combine the poly objects
    
        if not newobject.IsAlive():
            raise TypeError("Object is not alive.")
            return False
    
        newobject.SetName(str(parent.GetName()))
        newobject.InsertUnder(parent)
        
        #node, transform = nodes[0], nodes[1].GetMg()
        set_point_object_transform(newobject, parentmg)
    
    def main():
        c4d.CallCommand(13957) # Konsole löschen
        doc = GetActiveDocument()
        op = doc.GetFirstObject()
    
        c4d.StatusSetSpin()
        all_objects, allachsen = get_all_objects(op) # get two lists
        null_counter = len(allachsen)
    
        if null_counter == 0: # check if found something to do.
            c4d.StatusClear()
            c4d.StatusSetText ('No Axis Objects found, nothing to do here.')
            print ("No Axis Objects found, nothing to do here.")
            exit()
    
        counter = len(all_objects)
        secondcounter = 0    
        c4d.StatusSetText ('%s Objects are processed.' %(null_counter))
    
        for n in allachsen:
            secondcounter += 1
            c4d.StatusSetBar(100*secondcounter/counter) #statusbar
            if joinmanagment(n) == False:
                break
    
        c4d.StatusClear()
        c4d.EventAdd() # update cinema 4d
        print ('END OF SCRIPT')
        return True
    
    if __name__=='__main__':
        main()
    


  • Hi:

    The merge object simply generates a new polygon object, because the merge in the modeling command cannot be used, so it can be resolved in other ways.The answer to your question is, in fact, how the connection generator works.I've written it out, but I can't generate an N-gon face.Create a new blank object, the object to be connected as a subset of the blank object, and then click the middle key of the mouse to select all blank objects and all subsets to achieve the effect of connection generator.

    import c4d
    
    #e-mail : xiuziye@qq.com
    
    def main():
        nodes = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
        if nodes == None : return
        Object = [i for i in nodes
                if i.GetType() == 5100]
    
        node = c4d.BaseObject(5100)
        doc.InsertObject(node)
        c4d.EventAdd()
    
        points = []
    
        Number_One = 0
        while Number_One < len(Object):
    
            for i in Object[Number_One].GetAllPoints():
    
                n = i * Object[Number_One].GetMg()
                points.append(n * ~node.GetMg())
    
            Number_One = Number_One + 1
    
        Polygons = [i.GetAllPolygons() for i in Object]
    
        Cpolygons = Polygons[0]
    
        Polygon_Indexe = []
        if len(Object) > 1:
    
            Number_Two = 0
            while Number_Two < len(Object) - 1:
    
                Point_Count = [ Object[0].GetPointCount() ]
                if Number_Two > 0:
    
                    for i in Object[:-1 * (len(Object) - Number_Two )]:
                        Point_Count.append(Point_Count[-1] + i.GetPointCount())
    
                for i in Polygons[Number_Two  + 1]:
    
                    if str(i).count("d") == 1:
    
                        Polygon_Indexe.append(c4d.CPolygon(i.a + Point_Count[-1] ,i.b +Point_Count[-1] ,i.c + Point_Count[-1] ,i.d + Point_Count[-1] ))
    
                    else:
    
                        Polygon_Indexe.append(c4d.CPolygon(i.a + Point_Count[-1] ,i.b + Point_Count[-1] ,i.c +Point_Count[-1] ))
    
                Number_Two  = Number_Two + 1
    
        Cpolygons += Polygon_Indexe
    
        node.ResizeObject(len(points),len(Cpolygons))
        node.SetAllPoints(points)
    
        for i in range(len(Cpolygons)):
            node.SetPolygon(i,Cpolygons[i])
    
            node.Message(c4d.MSG_UPDATE)
            c4d.EventAdd()
    
        node.Message(c4d.MSG_UPDATE)
        c4d.EventAdd()
    
    if __name__=='__main__':
        main()
    


  • @x_nerve as I understand you copy polygons and point from one object to another, which is a solution, still I do not understand why the join command would not work.

    I get a polygon object from my join comand which is ok, it just needs to reposition on its old place with all the axis and nulls kept, and oriented as it was.

    kind regards
    mogh



  • Hi @mogh,

    thank you @x_nerve for jumping in, we really appreciate that, but I am not quite sure that I can follow, if you think there is a bug in MCOMMAND_JOIN, we would ask you to report that in a separate thread, because as I said, I currently do not really fully grasp the problem.

    @mogh

    I understand your problem better now, and the direct answer you are looking for is to change line 50 in your script from:

    points = [p * mg * ~transform for p in node.GetAllPoints()]

    to

    points = [p * ~transform for p in node.GetAllPoints()]

    You have to do that, because you are operating in local coordinates in the way the rest of the script works. At least this is my current understanding of your goals. At the end of the post you will find both the modified full script and the test scene I did run your script on.

    As a more general advice, since I saw that you did delete my docstring: You should write them for all your methods/functions/classes, because this would have told you that that specific function was operating in global space. Coding is mostly an exercise in breaking stuff down into manageable parts, and doc strings help to express that "divide and conquer" strategy, rather than viewing a script as "a single thing". Which in turn then makes revisiting or debugging your code easier.

    Cheers,
    Ferdinand

    PC12991_scene.c4d

    #!py3
    import c4d, sys, os
    from c4d import gui
    from c4d.documents import GetActiveDocument
    #Version 1.4 Striped version
    # This Script needs Null-Objects Called "Axis" with polygon objects to run
    
    def GetNextObject(op):
        if not op: return
        if op.GetDown(): return op.GetDown()
        while op.GetUp() and not op.GetNext():
            op = op.GetUp()
        return op.GetNext()
    
    def get_all_objects (op):
        allachsen_list = list()
        all_objects_list = list()
        while op:
            if op.GetName() == 'Achsen-Objekt' or op.GetName() == 'Axis' :
                allachsen_list.append(op)
            all_objects_list.append(op)
            op = GetNextObject(op)
        return all_objects_list, allachsen_list
    
    def JoinCommand(doc, op):
        res = c4d.utils.SendModelingCommand(command = c4d.MCOMMAND_JOIN,
                                    list = [op],
                                    mode = c4d.MODELINGCOMMANDMODE_ALL,
                                    doc = doc)
    
        # Cheks if the command didn't failed
        if res is False:
            raise TypeError("return value of Join command is not valid")
        elif res is True:
            print ("Command successful. But no object.")
        elif isinstance(res, list):
            if c4d.GetC4DVersion() < 21000: res[0].SetAbsPos(c4d.Vector())
            op.Remove()
            return res[0] # Returns the first item containing the object of the list.  ??? GetClone() ???
    
    def set_point_object_transform(node, transform):
        if (not isinstance(node, c4d.PointObject) or
            not isinstance(transform, c4d.Matrix)):
            msg = f"Illegal argument types: {type(node)}{type(transform)}"
            raise TypeError(msg)
        
        print ("sp:", node, transform)
        mg = node.GetMg()
        # Move the points in the global frame and then into the new frame.
        points = [p * ~transform for p in node.GetAllPoints()]
        # Set the points and stuff ;)
        node.SetAllPoints(points)
        node.Message(c4d.MSG_UPDATE)
        node.SetMg(transform)
    
    def joinmanagment(n):
        # n "Axis" null will be not alive in a few steps get everything we need from it
        if n.GetUp() :
            parent = n.GetUp()
        else:
            print ("No Parent To Axis Null. Probably not save to run this sript anyway.")
            c4d.StatusClear()
            c4d.StatusSetText ('No Parent found! - Probalby mo CAD import Doc. Script Stopped.')
            exit()
            return False
    
        parentmg = n.GetMg()
    
        newobject = JoinCommand(doc, n) # combine the poly objects
    
        if not newobject.IsAlive():
            raise TypeError("Object is not alive.")
            return False
    
        newobject.SetName(str(parent.GetName()))
        newobject.InsertUnder(parent)
        newobject.SetMg(newobject.GetMl())
        # node, transform = nodes[0], nodes[1].GetMg()(add QA contact)
        set_point_object_transform(newobject, parentmg)
    
    def main():
        c4d.CallCommand(13957) # Konsole löschen
        doc = GetActiveDocument()
        op = doc.GetFirstObject()
    
        c4d.StatusSetSpin()
        all_objects, allachsen = get_all_objects(op) # get two lists
        null_counter = len(allachsen)
    
        if null_counter == 0: # check if found something to do.
            c4d.StatusClear()
            c4d.StatusSetText ('No Axis Objects found, nothing to do here.')
            print ("No Axis Objects found, nothing to do here.")
            exit()
    
        counter = len(all_objects)
        secondcounter = 0
        c4d.StatusSetText ('%s Objects are processed.' %(null_counter))
    
        for n in allachsen:
            secondcounter += 1
            c4d.StatusSetBar(100*secondcounter/counter) #statusbar
            if joinmanagment(n) == False:
                break
    
        c4d.StatusClear()
        c4d.EventAdd() # update cinema 4d
        print ('END OF SCRIPT')
        return True
    
    if __name__=='__main__':
        main()
    


  • @zipit thank you very much - position and rotation seem to be working now. (staying put)

    another problem ocured though "NormalTag" information seems to flip and maually flipping back does not help ... so all the polygons are "shaded" weird now. --- the callcomand "connect and delete" which I want to replace because of speed keeps them intact.

    Should I open a new thread or leafe the problem open ?

    regarding docstring: i just wanted the code to be slim so people can read it fast without scrolling its still in my lengthy script. Will keep it next time.



  • Hi,

    we would appreciate it if you would open a new thread for that. As a quick tip, aside from using SMC, inverting the normal of a polygon just means inverting its point index order. I have shown it for example here. But this can come with multiple problems, especially regarding texture coordinates. I think have dealt with problem here on the forum multiple times (including dealing with secondary mesh attributes like texture coordinates). If any questions remain, please feel free to open a new thread.

    About the doc strings: In the end you should do what you are comfortable with. It was just a tip and a little insight on why many people find it useful.

    Cheers,
    Ferdinand



  • Hi:

    @mogh

    The MCOMMAND_JOIN modeling command is now ready to execute properly.The merged object cannot be directly visible in the document, so the merged object must be inserted into the document.Also, the global matrix of the merged object is the same as the first object in the parent-child list.I also got it from the forum.

    import c4d
    from c4d import utils
    
    #e-mail : xiuziye@qq.com
    
    def main():
    
        nodes = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
        if nodes == None : return
        Objects = [i for i in nodes
                if i.GetType() == 5100]
    
        settings = c4d.BaseContainer()
        settings[c4d.MDATA_JOIN_MERGE_SELTAGS] = True
        
        result = c4d.utils.SendModelingCommand(
            c4d.MCOMMAND_JOIN, Objects ,
            c4d.MODELINGCOMMANDMODE_ALL, settings, doc)
    
        doc.InsertObject(result[0])
        c4d.EventAdd()
        
        points = []
    
        Number_One = 0
        while Number_One < len(Objects):
    
            for i in Objects[Number_One].GetAllPoints():
    
                n = i * Objects[Number_One].GetMg()
                points.append(n * ~result[0].GetMg())
    
            Number_One = Number_One + 1
    
        result[0].SetAllPoints(points)
    
        result[0].Message(c4d.MSG_UPDATE)
        c4d.EventAdd()
        
        print (result[0])
    
    if __name__=='__main__':
        main()
    
    


  • Hi @mogh,

    I initially did not want to make things super complicated here, which is why I did a bit work around the oddities of your script in my answer. Given the other threads of yours which deal with that project, it seems advisable to point out that a cleaner version of your script could look like the script attached at the end. The doc string will tell you about the pro's and con's. You can also try uncommenting the c4d.EventAdd in line 155 to see if you can get Cinema more into sync (regarding https://plugincafe.maxon.net/topic/13007).

    Cheers,
    Ferdinand

    """Demonstrates how to handle MCOMMAND_JOIN while keeping things in place.
    
    You can also use the old version, but there you would have to deal with the
    normal tags. This version mostly just rectifies some minor oddities in your
    original code. This version does not need to transform the vertices, but 
    instead places the new object by the inverse transform of its former parent
    (see line 123).
    
    Things get much more complicated (and computationally expensive) when you use 
    the old version, since you then have to also modify the normals. The 
    disadvantage  of this version is that the axis placement is not "as nice".
    
    See https://plugincafe.maxon.net/topic/13004/ for how to deal with the normal
    tags.
    """
    
    import c4d
    
    
    def get_nodes_by_name(doc, patterns):
        """Returns all nodes in a document whose name appears in patterns.
    
        Note:
            Despite its name, the strings in `patterns` are not evaluated as
            regular expressions at the moment. In fact this function does not
            ensure that the elements in patters are strings at all.
    
        Args:
            doc (c4d.documents.BaseDocument): The document to get the nodes from.
            patterns (list[str]): The name patterns to get the nodes for.
    
        Returns:
            list[c4d.BaseObjeect]: The nodes in `doc` which match patterns.
    
        Raises:
            TypeError: On invalid argument types.
        """
        # Validate arguments.
        if not isinstance(doc, c4d.documents.BaseDocument):
            msg = f"Expected BaseDocument for `doc`. Received: {type(doc)}."
            raise TypeError(msg)
        if not isinstance(patterns, list):
            msg = f"Expected list for `patterns`. Received: {type(patterns)}."
            raise TypeError(msg)
    
        # Get all relevant nodes.
        result, visited = [], []
        node = doc.GetFirstObject()
        while node is not None:
            # Append new nodes to the visited and result lists.
            if node not in visited:
                visited.append(node)
            if node.GetName() in patterns and node not in result:
                result.append(node)
            # Traverse the scene graph depth first.
            node_down = node.GetDown()
            if node_down and node_down not in visited:
                node = node_down
            elif node.GetNext():
                node = node.GetNext()
            else:
                node = node.GetUp()
        return result
    
    
    def join_with_children(node, merge_selection_tags=True):
        """Joins the children of `node` into a single object.
    
        Also adjusts the transform of the resulting node and removes the old
        geometry and inserts the new one. 
    
        Args:
            node (c4d.BaseObject): The node to join the children for.
            merge_selection_tags (bool, optional): If to also merge selections.
             Defaults to `True`.
    
        Returns:
            bool: If the operation was successful.
    
        Raises:
            RuntimeError: When SMC failed.
            TypeError: On invalid argument types.
            ValueError: When `node` is not attached to a document.
        """
        # Validate arguments.
        if not isinstance(node, c4d.BaseObject):
            msg = f"Expected BaseObject for `node`. Received: {type(node)}"
            raise TypeError(msg)
    
        doc = node.GetDocument()
        if doc is None:
            msg = f"Node is not attached to a document: {node}."
            raise ValueError(msg)
    
        # A special condition of yours I am just mimicking here.
        parent = node.GetUp()
        if parent is None:
            return
    
        # Run SMC
        smc_data = c4d.BaseContainer()
        smc_data[c4d.MDATA_JOIN_MERGE_SELTAGS] = merge_selection_tags
        result = c4d.utils.SendModelingCommand(
            command=c4d.MCOMMAND_JOIN,
            list=[node],
            mode=c4d.MODELINGCOMMANDMODE_ALL,
            bc=smc_data,
            doc=doc,
            flags=c4d.MODELINGCOMMANDFLAGS_NONE)
    
        # Evaluate SMC output.
        if (not isinstance(result, list) or
            len(result) == 0 or
                not isinstance(result[0], c4d.BaseObject)):
            msg = f"SendModelingCommand failed on: {node}"
            raise RuntimeError(msg)
    
        # This didn't not make much sense, since you did overwrite the transform
        # of result[0] on a later point anyways.
        # if c4d.GetC4DVersion() < 21000:
        #     result[0].SetAbsPos(c4d.Vector())
    
        # Replace the old with the new geometry.
        result = result[0]
        # Your renaming does not make much sense to me, since every new node
        # will then just be named after its parent. i.e. "CADimport_XXX", feel
        # free to uncomment, if this was intended.
        # result.SetName(parent.GetName())
        result.SetMg(~parent.GetMg())
        result.InsertUnder(parent)
        node.Remove()
    
    
    def main():
        """Does the whole CAD cleanup thing.
        """
        # The name patterns for the nodes to collapse.
        patterns = ["Achsen-Objekt", "Axis"]
        # Get all nodes that match that pattern.
        targets = get_nodes_by_name(doc, patterns)
        count = len(targets)
    
        c4d.StatusSetSpin()
        # Loop over these nodes.
        #
        # Your approach did not respect the special case when an `axis` object
        # is a direct child of another `axis` object. Which will lead to
        # incorrect results, since you (and I here) traverse the scene graph
        # top-down-depth-first. I did not address this to keep the code relatively
        # familiar.
        for i, node in enumerate(targets):
            # Join one of the targets and do some mild interface indication.
            join_with_children(node)
            # Uncomment to let Cinema update for each modification of the scene.
            # c4d.EventAdd()
            c4d.StatusSetText(f"{i}/{count} groups joined.")
        # Clean up after ourselves.
        c4d.StatusSetText("")
        c4d.StatusClear()
        c4d.EventAdd()
    
    
    if __name__ == '__main__':
        main()
    


  • I am building of your script from here on. Thank You.

    FYI zipit:
    While your def get_nodes_by_name(doc, patterns) might be saver and more sound it takes 10x longer then my version to collect all axis ... (which is not the slow party of the script and could be neglegted - I was just buffled by the profiling i ran)

    kind regards
    mogh



  • Hi,

    ten times slower sounds rough. It is probably because I was a bit lazy and used a lookup table (visited) to traverse the scene-graph, which is a rather expensive thing to do. Feel free to use the fixed traversal if performance becomes an issue here.

    Cheers,
    Ferdinand


Log in to reply