Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware 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
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Help with Matrix Manipulation after c4d.MCOMMAND_JOIN

    Cinema 4D SDK
    python r23 r20
    3
    16
    2.7k
    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.
    • M
      mogh
      last edited by

      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

      1 Reply Last reply Reply Quote 0
      • M
        mogh
        last edited by 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

        1 Reply Last reply Reply Quote 0
        • ferdinandF
          ferdinand
          last edited by

          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

          MAXON SDK Specialist
          developers.maxon.net

          1 Reply Last reply Reply Quote 0
          • M
            mogh
            last edited by

            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

            1 Reply Last reply Reply Quote 0
            • ferdinandF
              ferdinand
              last edited by

              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()
              

              MAXON SDK Specialist
              developers.maxon.net

              1 Reply Last reply Reply Quote 0
              • M
                mogh
                last edited by

                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

                1 Reply Last reply Reply Quote 0
                • M
                  mogh
                  last edited by 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()
                  
                  1 Reply Last reply Reply Quote 0
                  • X
                    x_nerve
                    last edited by

                    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 : [email protected]
                    
                    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()
                    
                    1 Reply Last reply Reply Quote 0
                    • M
                      mogh
                      last edited by

                      @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

                      1 Reply Last reply Reply Quote 0
                      • ferdinandF
                        ferdinand
                        last edited by

                        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()
                        

                        MAXON SDK Specialist
                        developers.maxon.net

                        1 Reply Last reply Reply Quote 0
                        • M
                          mogh
                          last edited by

                          @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.

                          1 Reply Last reply Reply Quote 0
                          • ferdinandF
                            ferdinand
                            last edited by ferdinand

                            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

                            MAXON SDK Specialist
                            developers.maxon.net

                            1 Reply Last reply Reply Quote 0
                            • X
                              x_nerve
                              last edited by x_nerve

                              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 : [email protected]
                              
                              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()
                              
                              
                              1 Reply Last reply Reply Quote 0
                              • ferdinandF
                                ferdinand
                                last edited by

                                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://developers.maxon.net/forum/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://developers.maxon.net/forum/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()
                                

                                MAXON SDK Specialist
                                developers.maxon.net

                                1 Reply Last reply Reply Quote 1
                                • M
                                  mogh
                                  last edited by

                                  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

                                  1 Reply Last reply Reply Quote 0
                                  • ferdinandF
                                    ferdinand
                                    last edited by

                                    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

                                    MAXON SDK Specialist
                                    developers.maxon.net

                                    1 Reply Last reply Reply Quote 0
                                    • M mogh referenced this topic on
                                    • First post
                                      Last post