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

    Place multiple objects on spline and align

    Cinema 4D SDK
    python 2023
    2
    5
    1.6k
    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.
    • P
      pim
      last edited by pim

      I want to place multiple objects on fixed distances and align these objectsto the spline.
      For a straight spline this is ok, but for a circle things go wrong.

      Here is the script:

      from typing import Optional
      import c4d
      import random
      from c4d.utils import SplineHelp, SplineLengthData
          
      doc: c4d.documents.BaseDocument  # The active document
      op: Optional[c4d.BaseObject]  # The active object, None if unselected
      
          
      def PlaceCube(Pspline, PsplineLength, PxPosition) -> c4d.Vector:   
          # calculate x position on spline, using given x position of the object to be placed
          
          splineOffset = PxPosition / PsplineLength   # must be between 0 and 1
          point = Pspline.GetSplinePoint(splineOffset, segment=0)
          print ("splineOffset, PxPosition, point: ", splineOffset, PxPosition, point)
          return point
      
      def main() -> None:
      
          sh = SplineHelp()                 #Add an instance of the spline helper class 
          sh.InitSpline(op)		#Dont forget to initialize it to the spline
          splineLength = sh.GetSplineLength()  
              
          rXPos = 0   
          for i in range(5):       
      
              cone1 = c4d.BaseObject(c4d.Ocone) 
              cone1[c4d.PRIM_CONE_BRAD] = 100
              
              xn = rXPos + cone1[c4d.PRIM_CONE_BRAD]/2
              point = PlaceCube(op, splineLength, xn)           
      
              # align to spline
              objmtx = cone1.GetMg()	     #Get the object's global matrix
              objmtx.off = point                     #Set the matrix positon to the point array
      
              splineOffset = xn / splineLength                     # must be between 0 and 1
              normal = sh.GetNormal(splineOffset)	      #Get the normal values where the object currently is
              tangent = sh.GetTangent(splineOffset)	    #Get the tangent values where the object is ?
           
              objmtx.v3 = normal         #Align the Z axis with the normal vector
              objmtx.v1 = tangent        #Up vector to stabilize it ?            
              cone1.SetMg(objmtx)	    #Update the object's position along the spline 
              doc.InsertObject(cone1)
      
              rXPos = rXPos + cone1[c4d.PRIM_CONE_BRAD] + 100
             
                  
          c4d.EventAdd()
              
      if __name__ == '__main__':
          main()
          
      

      bed48f39-7053-4f57-aa50-602d018a8fae-image.png

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

        Hello @pim,

        Thank you for reaching out to us. While I mostly understand your problem, the problem description 'for a circle things go wrong' is quite ambiguous. So I had to interpret here bit.

        There are some problems with your code, primarily that you did not set the y/v2/j component of the frame you assigned to your cone objects, likely producing non-orthogonal matrices which then cause problems further down the road. See the code example at the end for details.

        But you also seem to be unaware of the quality of a normal returned by c4d.utils.SplineHelp.GetNormal, or generally the fact that the naive definition of a curve normal is often not what humans would consider useful (see Fig. I). The orientation of a normal at a point on a curve is defined by its curvature, causing the normals to change direction and length with the curvature of the curve. At the inflection points of a curve, the normals are technically null. This collides with the human notion of an inside and outside of a curve and that normals should face into consistent direction.

        cruveature_spline.png
        Fig. I: Top - The orientation and length of a normal of point on a curve depends on its curvature. At inflection points (red) the curvature of the spline is zero, and so is the normal. Bottom - Normals for a curve as humans would consider it useful. There is a clear sense of direction, an inside and an outside, and all normals are of equal length.

        The solution to this is parallel transport, a broader mathematical concept to maintain the orientation of a vector at multiple locations on a surface or curve that qualifies as "the same" for a local observer. SplineHelp.GetNormal yields mathematical normals while SplineHelp.GetMatrix yields transported frames/matrices for a spline. This and alternative algorithms have been discussed before here. Below I have provided a Python Programming tag to display the normals returned by SplineHelp to get a better idea of what the type does (Fig. II).

        Fig II: The video shows the Python Programming tag drawing the two different notions of spline normals. When displaying the mathematical normals for the flower object, we can see the change of curvature affecting the normals. SplineHelp does interpolate between mathemical normals when told to do so, but computes them in a a bit unusal way - the normal is defined only by the current and next vertex instead of the previous, current, and next vertex. Note that parallel transport is always a workaround because what humans consider to be intuitively correct is not clearly and mathematically defined in this context. There are stronger algorithms than simple PTF as for example Rotation Minimizing Frames, but given a weird enough spline, they will also fail. See spline_normals.c4d for the scene file used in this video.

        Cheers,
        Ferdinand

        Code:

        import c4d
        
        def main() -> None:
            """
            """
            if not op:
                raise RuntimeError("No object selected.")
        
            splineHelp = c4d.utils.SplineHelp()
            splineHelp.InitSplineWithUpVector(op, c4d.Vector(0, 1, 0))
            splineLength: float = splineHelp.GetSplineLength()  
            
            offset: float = 100
            count: int = 5
        
            for i in range(count):
        
                cone = c4d.BaseObject(c4d.Ocone)
                cone[c4d.PRIM_CONE_BRAD] = 100
        
                # Not quite sure what this was supposed to do.
                # xn = rXPos + cone1[c4d.PRIM_CONE_BRAD]/2
        
                # Compute the real offset in the spline.
                t = (i * offset) / splineLength
        
                # You ended up using SplineObject.GetSplinePoint instead of your SplineHelp 'sh' to get the
                # offset. The problem with SplineObject.GetSplinePoint is that it operates in object space.
                # You probably also want to operate with real offsets.
                off = splineHelp.GetPosition(t, realoffset=True)
                v3 = ~splineHelp.GetNormal(t, realoffset=True)
                v1 = ~splineHelp.GetTangent(t, realoffset=True)
        
                # Since this is a new object, getting its global matrix does not make too much sense,
                # it will always be equal to the identity matrix, i.e., c4d.Matrix().
                # objmtx = cone1.GetMg() #Get the object's global matrix
        
                # You did not set the v2 component of your 'objmtx', causing its old value to linger. Which 
                # then very likely resulted in a non-orthogonal matrix. Once such non-orthogonal matrix is 
                # being set as an object matrix, Cinema 4D will silently orthogonalize it. But Cinema 4D 
                # does not know that v1 and v3 are the 'good' components in this case, and v2 the one to
                # update, and instead will bring the matrix just into some orthogonal form.
                mg = c4d.Matrix(off=off, v1=v1, v2=~(v3 % v1), v3=v3)
        
                # But this would be frame for the mathematical normal at this point, which is likely not 
                # what you want. So you just want to call 'GetMatrix' instead.
                mg = splineHelp.GetMatrix(t, realoffset=True)
                cone.SetMg(mg)
                doc.InsertObject(cone)
        
                # Related to the xn thing, I am once again not quite sure what this was supposed to do.
                # rXPos = rXPos + cone1[c4d.PRIM_CONE_BRAD] + 100
        
            c4d.EventAdd()
                
        if __name__ == '__main__':
            main()
        

        MAXON SDK Specialist
        developers.maxon.net

        P 1 Reply Last reply Reply Quote 2
        • P
          pim @ferdinand
          last edited by

          Hi Ferdinand,
          Thanks for the extensive answer.
          I have to look into it in more detail.

          Sorry for the unclear code,
          nx was meant to place the object on the spline with a distance between the cones.
          Perhaps it is easier to make an example script that places 5 cones divided and aligned over the circle spline.

          Something like this.
          9bcb4266-30b8-4d0b-9da3-05b41b836877-image.png

          Your code now gives me this. The cones are not aligned to the spline.
          f38a7de5-2a30-415f-83bf-be5bfaac59a9-image.png

          If you could provide an example for placing the cones as in figure 1, that would help me very much.

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

            Hey @pim,

            I am not quite sure what constitutes as 'my code' in this context. But if you mean the code listing I posted above, effectively using SplineHelp.GetMatrix, and you want the cones to be differently oriented, you will have to swizzle the matrix to your liking. As explained in the Matrix Manual, a matrix is just a way to express a coordinate system over three axis and an offset. So when you have an orthogonal matrix, you can simply switch its v1(x), v2(y), and v3(z) axis in any manner you like to reorient the object along the principal planes. You can also invert axis to flip things, but then you must make sure that the handed-ness of the matrix remains correct, which simply means that you cannot only flip one axis but must always flip two axes at a time.

            Cheers,
            Ferdinand

            Result
            5afd50e4-891d-4ceb-b0a7-66b2108e650f-image.png
            Code

            import c4d
            
            def main() -> None:
                """
                """
                if not op:
                    raise RuntimeError("No object selected.")
            
                splineHelp = c4d.utils.SplineHelp()
                splineHelp.InitSplineWithUpVector(op, c4d.Vector(0, 1, 0))
                splineLength: float = splineHelp.GetSplineLength()  
                
                offset: float = 100
                count: int = 5
            
                for i in range(count):
            
                    cone = c4d.BaseObject(c4d.Ocone)
                    cone[c4d.PRIM_CONE_BRAD] = 100
            
                    t = (i * offset) / splineLength
                    mg = splineHelp.GetMatrix(t, realoffset=True)
                    
                    # Swizzle things into the form we need.
                    temp = mg.v2
                    mg.v2 = mg.v3
                    mg.v3 = temp
            
                    cone.SetMg(mg)
                    doc.InsertObject(cone)
            
                c4d.EventAdd()
                    
            if __name__ == '__main__':
                main()
            

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 0
            • P
              pim
              last edited by

              And once again, thank you!

              1 Reply Last reply Reply Quote 0
              • ferdinandF ferdinand referenced this topic on
              • First post
                Last post