-
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/handleissueHere'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 ] = pAny ideas to what's going on / what I'm doing wrong would be greatly appreciated.
Thanks,
Adam
-
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_FREEand 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 implementMoveHandleto 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 -
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=1All 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
-
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
Clampis called onpbefore it is fed back intohandle_position. In my bezier handle example I usedMoveHandleto project the current mouse pick point into the plane where I wanted to have it. You could also do the same inSetHandle. 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 -
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
MoveHandleas 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.SetHandlecode can lead to jitter issues when usingHANDLECONSTRAINTTYPE_FREE.Reproduction
- Add the example object plugin to a scene.
- Switch to a two panel viewport layout, make the right panel a "Right" projection, the left panel a "Top" projection.
- Give the object a non-default transform.
- Interact with the handle in one of the panels.
- Now move the object in that panel.
- 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
MoveHandleinstead ofSetHandleto 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 TrueCheers,
Ferdinand -
F ferdinand moved this topic from Cinema 4D SDK
-
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
ohandlenullexample, 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
MoveHandleand 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. -
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 usingProjectPointOnPlanealong with some camera space calculations.Big thanks,
Adam
-
Good to hear!
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.