Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python 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
    • Recent
    • Tags
    • Users
    • Login
    The Maxon SDK Team is currently short staffed due to the winter holidays. No forum support is being provided between 15/12/2025 and 5/1/2026. For details see Maxon SDK 2025 Winter Holidays.

    ObjectData handles - Unexpected position jitter

    Scheduled Pinned Locked Moved Bugs
    python2026
    8 Posts 2 Posters 99 Views 2 Watching
    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.
    • PixelsInProgressP Offline
      PixelsInProgress
      last edited by PixelsInProgress

      Hi Maxon team,

      I'm creating a python ObjectData plugin which uses handles and I'm experiencing some strange behaviour.

      I can transform the object and then move the handles without issue in any viewport as expected. The issue arises if I have multiple views. So, say I'm using a 2 view setup, if I transform the object in one viewport and immediately move the handle in the other viewport, sometimes it triggers erratic behavior where the handle position appears to be flickering between it's expected position and somewhere in another plane. Once the behaviour is triggered it seems to remain.

      Some anectodal notes:

      • Closing and reopening the file makes the issue dissapear
      • Deleting the object and creating a new one and the issue remains

      Here's a video showing the flickering (starts about 19 seconds) :
      https://www.pixelsinprogress.com/handleissue

      Here's my handles code in it's simplest form:

          def GetHandleCount(self, op):
              return 1
      
          def GetHandle(self, op, i, info):
      
              info.position  = op[c4d.ID_USERDATA,1 ] # this is a simplified example so using UD instead of a parameter
              info.type      = c4d.HANDLECONSTRAINTTYPE_FREE
      
          def SetHandle(self, op, i, p, info):
              op[c4d.ID_USERDATA,1 ] = p
      
      

      Any ideas to what's going on / what I'm doing wrong would be greatly appreciated.

      Thanks,

      Adam

      ferdinandF 1 Reply Last reply Reply Quote 1
      • ferdinandF Offline
        ferdinand @PixelsInProgress
        last edited by ferdinand

        Hey @PixelsInProgress,

        thank you for reaching out to us. That is not possible to answer like this, please provide an executable code example of what you are doing.

        I guess the the reason for your problems are that you use the a bit niche constraint type HANDLECONSTRAINTTYPE_FREE and create a transform feedback loop when you feed your own user data into your handle without constraining that data. Keep in mind that handle code is called view-based. You might have to implement MoveHandle to correctly transform your point. But I have no idea what you are trying to do, so I am mostly guessing.

        I have written a while ago this C++ code example which implements a camera dependent handle, which might be what you are trying to do here.

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 0
        • PixelsInProgressP Offline
          PixelsInProgress
          last edited by

          Hi Ferdinand,

          Thanks for the swift reply. I've built an example where the behaviour is repeatable (although as in the video it sometimes requires transforming the object multiple times before it starts) here:
          https://www.dropbox.com/scl/fo/k76j6qja2u6n0nrmj11o8/AMhO7GwhSFnm-9saHFxhuB8?rlkey=pet07qeoeuev9zpfc5f30qhg8&dl=1

          All I'm expecting from the handle, is for it to behave as it does as in the ealier part of the video I posted i.e if I'm in a parallel viewport and I move the handle, it moves in the plane of that particular viewport and not in the depth axis. Instead of flickering to a different plane in the other viewports. ( As far as I can tell, triggered immediately after transforming in one view and then moving the handle in another straight after)

          WIth regards to the feedback loop. It seems that in the SDK examples that this is how it's normally managed? As in take some stored data, feed it to the handle position in GetHandle and then retreive the updated position is SetHandle and safely store position for the next iteration?

          Thanks for your C++ camera dependent example, that looks like it could be super use to implement a fallback if HANDLECONSTRAINTTYPE_FREE isn't going to work.

          Cheers,

          Adam

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

            Well, what I meant with feedback loop, is that you never constraint your data in any shape or form. All code examples and internal code work like this:

            def GetHandle(self, op, i, info):
                info.position  = self.handle_position # or op[c4d.ID_INTERNAL_HANDLE_STORAGE]
                info.type = c4d.HANDLECONSTRAINTTYPE_SOMETYPE
                info.direction = Vector(...)
            
            def SetHandle(self, op, i, p, info):
                self.handle_position = Clamp(p)
            

            I.e., here Clamp is called on p before it is fed back into handle_position. In my bezier handle example I used MoveHandle to project the current mouse pick point into the plane where I wanted to have it. You could also do the same in SetHandle. The viewport picking algorithm has no idea where you consider to be the 'correct' working plane. It does its best to guess, but you must still check and or help.

            I haven't had a look at your code yet, will do next week (hopefully on Monday). It could be that there is a bug in the Python API, but that unbound nature of your code does strike me as incorrect.

            Cheers,
            Ferdinand

            MAXON SDK Specialist
            developers.maxon.net

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

              Hey @PixelsInProgress,

              so, I had a look. First of all, I came up with reproduction steps which you were lacking (see our Support Procedures). It is important to provide these, because when you just provide a 'sometimes it works, sometimes it doesn't' or a 'it is hard to reproduce' the risk is high, that I or Maxime will just declare something non-reproducible and move on (which almost happened here). We understand this is extra work and that you already put work into boiling down the issue, but it is in your own interest to be more precise with reproduction steps.

              I am not 100% sure if we will consider this a bug, because I do not yet fully understand why this happens, but I have moved this into bugs for now. I also provide a workaround based on MoveHandle as already hinted at before.

              Edit: Since this bug also affects objects provided by Cinema 4D, such as a cloner in linear mode or the field force object, this is now a Cinema 4D and not SDK bug anymore.

              Issue

              ObjectData.SetHandle code can lead to jitter issues when using HANDLECONSTRAINTTYPE_FREE.

              Reproduction

              1. Add the example object plugin to a scene.
              2. Switch to a two panel viewport layout, make the right panel a "Right" projection, the left panel a "Top" projection.
              3. Give the object a non-default transform.
              4. Interact with the handle in one of the panels.
              5. Now move the object in that panel.
              6. Interact with the handle in the other panel.

              Result

              • The handle jitters between the world plane perpendicular to the other view and the 'correct' position.

              Code

              import c4d
              
              PLUGIN_ID = 1067013
              PIP_HANDLE_EXAMPLE_POSITION = 1000
              PIP_HANDLE_EXAMPLE_GROUP_STORAGE_GROUP = 2000
              PIP_HANDLE_EXAMPLE_CONTAINER_INTERNAL_CONTAINER = 3000
              
              class PIP_HandleExample(c4d.plugins.ObjectData):
              
                  def Init(self,op,isCloneInit):
                      self.InitAttr(op, c4d.Vector, c4d.PIP_HANDLE_EXAMPLE_POSITION)
                      if not isCloneInit:
                          op[c4d.PIP_HANDLE_EXAMPLE_POSITION] = c4d.Vector(0,0,0)
              
                      return True
              
                  def GetHandleCount(self, op):
                      return 1
              
                  def GetHandle(self, op, i, info):
                      info.position  = op[c4d.PIP_HANDLE_EXAMPLE_POSITION] 
                      info.type      = c4d.HANDLECONSTRAINTTYPE_FREE
              
                  def SetHandle(self, op, i, p, info):
                      op[c4d.PIP_HANDLE_EXAMPLE_POSITION] = p
              
                  def Draw(self, op, drawpass, bd, bh):
                      if drawpass != c4d.DRAWPASS_HANDLES:
                          return c4d.DRAWRESULT_SKIP
              
                      bd.SetMatrix_Matrix(op, op.GetMg())
                      bd.SetPen(c4d.GetViewColor(c4d.VIEWCOLOR_HANDLES))
              
                      info = c4d.HandleInfo()
                      self.GetHandle(op, 0, info)
                      bd.DrawHandle(info.position, c4d.DRAWHANDLE_BIG, 0)
                      return c4d.DRAWRESULT_OK
              
              if __name__ == "__main__":
                  if not c4d.plugins.RegisterObjectPlugin(
                              id=PLUGIN_ID,
                              str="PIP - Handle example",
                              g=PIP_HandleExample,
                              description="PIP_HandleExample",
                              icon=None,
                              info=c4d.OBJECT_GENERATOR):
                      raise RuntimeError("Failed to register PIP_HandleExample plugin.")
              

              Workaround

              As already hinted at, you can override MoveHandle instead of SetHandle to implement your own handle movement logic. This way you have full control over how the mouse position is interpreted and can work around the jitter issue. See below for an example implementation.

              Files: PIP_HandleExample.zip

                  # def SetHandle(self, op, i, p, info):
                  #     """ Not required as we override MoveHandle.
                  #     """
                  #     op[c4d.PIP_HANDLE_EXAMPLE_POSITION] = p
              
                  def MoveHandle(self, op: c4d.BaseObject, undo: c4d.BaseObject, mouse_pos: c4d.Vector, 
                                 hit_id: int, qualifier: int, bd: c4d.BaseDraw) -> bool:
                      """Called by Cinema 4D when the user interacts with a handle.
                      """
                      # Get the mouse position in world space and then convert it to object space. The issue
                      # of this solution is that it will project the point down to one of the world planes (
                      # the plane to which the the #SetHandle code jitters). So, the axis which is perpendicular
                      # to the view plane will be zeroed out.
                      worldMousePos: c4d.Vector = bd.SW(mouse_pos)
                      localMousePos: c4d.Vector = ~op.GetMg() * worldMousePos
              
                      # To fix that, we must project the point into a plane we consider correct. You could do this
                      # brutishly by for example checking the projection of #bd and then just align the component
                      # of #worldMousePos that is perpendicular to that plane. You could also add some 'carry on' 
                      # logic here which respects previous data, but I didn't do that.
                      projection: int = bd[c4d.BASEDRAW_DATA_PROJECTION]
                      if projection in (c4d.BASEDRAW_PROJECTION_TOP, c4d.BASEDRAW_PROJECTION_BOTTOM):
                          worldMousePos.y = op.GetMg().off.y
                      elif projection in (c4d.BASEDRAW_PROJECTION_FRONT, c4d.BASEDRAW_PROJECTION_BACK):
                          worldMousePos.z = op.GetMg().off.z
                      elif projection in (c4d.BASEDRAW_PROJECTION_LEFT, c4d.BASEDRAW_PROJECTION_RIGHT):
                          worldMousePos.x = op.GetMg().off.x
              
                      op[c4d.PIP_HANDLE_EXAMPLE_POSITION] = ~op.GetMg() * worldMousePos
              
                      return True
              

              Cheers,
              Ferdinand

              MAXON SDK Specialist
              developers.maxon.net

              1 Reply Last reply Reply Quote 2
              • ferdinandF ferdinand moved this topic from Cinema 4D SDK
              • ferdinandF Offline
                ferdinand
                last edited by ferdinand

                I now also see that my example is buggy in the perspective view (and other views I have not implemented). For these cases you would have to do exactly what I did in my ohandlenull example, project the point into a plane placed on the origin of the object with a normal that is the inverse of the camera normal.

                Given that this also affects internal code, it is quite likely that we will fix this. If I were you, I would just keep my old code and ignore this very niche edge case. When you really want this to work you would have to implement MoveHandle and handle the different view projections of Cinema 4D. This can probably be done in 50-100 lines of code or so, but it would be something I would try to avoid doing, as viewport projections can be tricky to handle.

                MAXON SDK Specialist
                developers.maxon.net

                1 Reply Last reply Reply Quote 1
                • PixelsInProgressP Offline
                  PixelsInProgress
                  last edited by

                  Hi Ferdinand,

                  Thanks for the in depth response. The support procedures are new to me and I will be sure to go through them when making future support requests.

                  I have taken your advice and for now moved towards a system using MoveHandle. The above example and your c++ code example earlier in the thread has been super useful in implementing this. I think we now have a solid functioning set of handles in all viewports and using ProjectPointOnPlane along with some camera space calculations.

                  Big thanks,

                  Adam

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

                    Good to hear!

                    MAXON SDK Specialist
                    developers.maxon.net

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