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

    How to convert the length of a line on the screen to the length of an object.

    Cinema 4D SDK
    2024 python
    2
    4
    463
    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.
    • chuanzhenC
      chuanzhen
      last edited by i_mazlov

      hi,
      I tried to have an object display its coordinate axis at a fixed screen space length, but it didn't work properly. Where did I make a mistake with my code?

      This is a c4d file that displays its coordinate axis using tags.-->

      video:

      code:

      import c4d
      
      doc: c4d.documents.BaseDocument  # The document containing this field object.
      op: c4d.BaseTag  # The Python tag containing this code.
      flags: int  # The execution flags of `main()`. See c4d.EXECUTIONFLAGS for details.
      priority: int  # The execution priority of this tag. See c4d.EXECUTIONPRIORITY for details.
      tp: c4d.modules.thinkingparticles.TP_MasterSystem  # The TP system of the document.
      
      
      def main() -> None:
          """Called by Cinema 4D to execute the tag.
          """
          # Get the object the tag is attached to and its global position.
          pass
      
      
      
      
      
      
      def draw(bd: c4d.BaseDraw) -> bool:
          # Called to display some visual element in the viewport. Similar to TagData.Draw.
          # Write your code here
          if op[c4d.EXPRESSION_ENABLE]:
              size = 40  #  40 pixel length line
              bd.SetMatrix_Screen()
              start = c4d.Vector(200)
              end = start + c4d.Vector(size,0,0)
              bd.SetPen(c4d.Vector(1.0))
              bd.DrawLine2D(start, end)
      
              mg = op.GetMain().GetMg()
              pos = bd.WS(mg.off)
      
      
              length = size/bd.WP_W(pos,True)
      
      
              bd.SetPen(c4d.Vector(1.0,0,0))
              x = bd.WS(mg * c4d.Vector(length,0,0))
              bd.DrawLine2D(pos,x)
              bd.SetPen(c4d.Vector(0,1.0,0))
              y = bd.WS(mg * c4d.Vector(0,length,0))
              bd.DrawLine2D(pos,y)
              bd.SetPen(c4d.Vector(0,0,1.0))
              z = bd.WS(mg * c4d.Vector(0,0,length))
              bd.DrawLine2D(pos,z)
      
      
          return True
      

      Thanks for any help!

      相信我,可以的!

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

        Hey @chuanzhen,

        Thank you for reaching out to us. I do not really understand what your code is meant to do. The line length = size/bd.WP_W(pos,True) makes for example no sense to me, as you are here trying to get the size of a world space unit in screen space units, be that the divisor of a constant you defined. And as the input you use the position of your object, to then later use that value as the size of your axis gizmos.

        Perspective projections, i.e., functions such as BaseView.WS, are non-linear transforms. Other than the linear transforms carried out by Cinema 4D's Matrix class, they do not preserve length relations. In less fancy words: When you project the three axis of a coordinate system from world space (where each has the length of 1) into screen space, you will end up with three vectors with three different lengths. I.e., the operation is non-linear, as it does not preserve the length relations between things one feeds into it; before x, y, z were all of the same length, now they are not anymore. Your code ignores this fact when you mix up world and screen space values in a line as such x = bd.WS(mg * c4d.Vector(length,0,0)) (length is used as a world space component but has been constructed with screen space values).

        I think what you want to do here, is draw a 'thing', e.g., an axis gizmo in a size that is independent of the position of the object in world space. You can do two things here:

        1. Project the 'thing' into screen space and move and normalize it there. One way could be for example to project all vectors into screen space, and then normalize their bounding box, i.e., make the box which encompasses them always the same size. But this would result in a "unsteady" animation, when carried out incorrectly, as the width of the drawing shrinks and grows when the object is rotated around its y-axis (as the x-axis rotates in and out of vision). So, you would have to normalize over the width and height (pick the bigger), and rather then simple normalizing, would have to clamp the bounding box, so that its width and height cannot larger than a value X. This can all get non-trivial quite fast.
        2. The much easier option is just do project the object from a fixed distance and position, which would allow you that 'fixed size'. But also here perspective distortions can be an issue when you then additionally move the drawing (as I did below). This is why such things are usually done in a parallel projection and not a perspective projection.

        The underlying answer is therefore that what you are trying to do, is conceptually impossible, or at least very much non-trivial.

        Cheers,
        Ferdinand

        Result

        Code

        One could do here more to anchor the axis gizmo better to the top left corner of the viewport, I just pick (100, 100, 0) in screen space as the anchor, ignoring all current view frame and safe frame data.

        """Realizes a Python tag that draws an axis gizmo for its hosting object in the top left corner 
        of the viewport at a fixed size in screen space.
        """
        
        import c4d
        import mxutils
        
        def main() -> None: pass
        
        def draw(bd: c4d.BaseDraw) -> bool:
            """Draws 3D data defined in world space at a fixed size in screen space.
            """
            # Get the global matrix of the object hosting the tag in world space.
            mg: c4d.Matrix = mxutils.CheckType(op.GetMain()).GetMg()
        
            # Define the world space offset as a screen space point at a given z-depth and define the axis 
            # gizmos in world space.
            size: float = 100.0 
            offset: c4d.Vector = bd.SW(c4d.Vector(0, 0, 1000))
            xAxis: c4d.Vector = offset + mg.v1 * size
            yAxis: c4d.Vector = offset + mg.v2 * size
            zAxis: c4d.Vector = offset + mg.v3 * size
        
            # Convert all vectors to screen space and then nudge them into the top left corner.
            delta: c4d.Vector = c4d.Vector(100, 100, 0)
            offset = delta - bd.WS(offset)
            xAxis = bd.WS(xAxis) + delta
            yAxis = bd.WS(yAxis) + delta
            zAxis = bd.WS(zAxis) + delta
            
            # Set the drawing space to screen space and draw the axis gizmos in their respective colors.
            bd.SetMatrix_Screen()
            bd.SetPen(c4d.GetViewColor(c4d.VIEWCOLOR_XAXIS))
            bd.DrawLine2D(offset, xAxis)
            bd.SetPen(c4d.GetViewColor(c4d.VIEWCOLOR_YAXIS))
            bd.DrawLine2D(offset, yAxis)
            bd.SetPen(c4d.GetViewColor(c4d.VIEWCOLOR_ZAXIS))
            bd.DrawLine2D(offset, zAxis)
        
            return True
        

        MAXON SDK Specialist
        developers.maxon.net

        chuanzhenC 1 Reply Last reply Reply Quote 0
        • chuanzhenC
          chuanzhen @ferdinand
          last edited by chuanzhen

          @ferdinand Thanks for your help.
          For the unwanted effect of perspective distortion, I tried projecting a fixed size line onto an object to determine the length of the projected line in the world, and then proceeded to draw it.

          code:

          
          
          import c4d
          
          doc: c4d.documents.BaseDocument  # The document containing this field object.
          op: c4d.BaseTag  # The Python tag containing this code.
          flags: int  # The execution flags of `main()`. See c4d.EXECUTIONFLAGS for details.
          priority: int  # The execution priority of this tag. See c4d.EXECUTIONPRIORITY for details.
          tp: c4d.modules.thinkingparticles.TP_MasterSystem  # The TP system of the document.
          
          def GetLineAndPlaneIntersection(plane_pos, plane_normal, line_start, line_dir):
          
              zero_find = line_dir.Dot(plane_normal)
              if zero_find == 0:
                  return False, None
              else:
                  d = (plane_pos - line_start).Dot(plane_normal) / zero_find
          
          
              return True, line_start + d * line_dir
          
          
          def main() -> None:
          
              pass
          
          
          
          
          
          
          
          def draw(bd: c4d.BaseDraw) -> bool:
          
          
              if op[c4d.EXPRESSION_ENABLE]:
                  c_mg = bd.GetMg()
                  c_mi = bd.GetMi()
          
                  plane_normal = c_mg.v3
          
                  plane_100_point = c_mg * c4d.Vector(0,0,100)
                  plane_obj_point = op.GetMain().GetMg().off
          
                  intersect,intersect_100_plane_point = GetLineAndPlaneIntersection(plane_100_point, plane_normal, plane_obj_point, c_mg.off - plane_obj_point)
                  line_end = intersect_100_plane_point + (c_mg * c4d.Vector(5,0,0) - c_mg.off)  # fixed size
                  intersect,intersect_obj_plane_point = GetLineAndPlaneIntersection(plane_obj_point, plane_normal, c_mg.off, line_end - c_mg.off)
                  length = (intersect_obj_plane_point - plane_obj_point).GetLength()
          
          
          
                  mg = op.GetMain().GetMg()
                  pos = bd.WS(plane_obj_point)
                  bd.SetMatrix_Screen()
                  bd.SetPen(c4d.GetViewColor(c4d.VIEWCOLOR_XAXIS))
                  x = bd.WS(mg * c4d.Vector(length,0,0))
                  bd.DrawLine2D(pos,x)
                  bd.SetPen(c4d.GetViewColor(c4d.VIEWCOLOR_YAXIS))
                  y = bd.WS(mg * c4d.Vector(0,length,0))
                  bd.DrawLine2D(pos,y)
                  bd.SetPen(c4d.GetViewColor(c4d.VIEWCOLOR_ZAXIS))
                  z = bd.WS(mg * c4d.Vector(0,0,length))
                  bd.DrawLine2D(pos,z)
          
          
              return True
          

          相信我,可以的!

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

            Hey,

            Yes, parallel/orthographic projection is effectively just the dot product. The modelling examples cover point plane projections with a function very similar to yours. There is also c4d.utils.PointLineSegmentDistance but I never use it, as it strikes me as more cumbersome to use than just doing the math yourself.

            Cheers,
            Ferdinand

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 0
            • First post
              Last post