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.
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
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 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.
# 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
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.
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.
No one might see this here, as notifications are currently broken, but there is an all new licensing manual and example in the SDK which should answer the questions asked here. Feel free to follow up, in case something remains unclear.
On December the 3rd, 2025, Maxon Computer unveiled Maxon One 2026.1 in its December release. For an overview of the new features please refer to the news posting.
We are aware that the notifications are currently malfunctioning on the forum. Cloudflare unfortunately still does interfere with our server cache. You might have to refresh your cache manually to see new data when you read this posting within 24 hours of its release.
Let me answer a few things, I although I still have the feeling we are not at the bottom of things yet.
So my idea(based off a maya tool used at work) is to be able to select those loops myself with edges, and have it run the interpolation on all the loops at once(well, at once as far as clicking apply once and it does each loop for you).
You can of course implement a point, edge, or polygon loop or ring selection yourself. But my advice would be to use the builtin tool programmatically unless you really have to implement your own tool. Because while a simple loop selection is relatively trivial, a production level loop tool is then quite a bit of work, due to all the edge cases you have to handle.
The issues I am running into is that it seems like working with edge selections is very cumbersome in Cinema. [...] I don't know I just am having a hard time wrapping my head around these concepts in Cinema. Is it possible to pass in an edge ID and work with edge ID's or do you have to go through the polygon info and all of that to get them?
Cinema 4D does not store edges explicitly, as this would unnecessarily increase the size of a scene. One can sufficiently describe polygonal geometry as a set of points for the vertices, and a set of ordered quadruples of point indices for each polygon. This is common practice in 3D applications and called 'indexed face set'. You always have to go through the polygons and points to work with edges. Edges are effectively just a smoke and mirrors convenience feature for end users. One could argue how much front-end wrappers for edges an API requires to be easy to use, but I would say Cinema 4D is there at least okay. You can find helper functions for edges on PolygonObject and SendModelingCommand supports edge selections directly.
In short, for each perceived user edge E_p, exist n 'real' or 'raw' edges for the indexed face set, where n is either 1 or 2 when the is mesh manifold (or larger when non-manifold). If n=1, then E_p is a boundary edge, otherwise it is an internal edge shared by two polygons. This is due to these two polygons having two have opposite winding orders for that shared edge when the polygons are meant to face into the same direction. The following diagram illustrates this (arrows indicate the winding order of the polygons):
Fig. 1: Two polygons P and Q sharing the user perceived edge E_p defined by the points b and c. The lower case labels denote unique point identifiers in the indexed face set, not a point order within the polygon. The polygon P is defined as (a, b, c, d) and the polygon Q as (b, e, f, c), i.e., a and b are the first vertex of each polygon respectively. The arrows describe the winding order of the polygons.
The global raw edge index is defined as rawEdgeIndex = polygonIndex * 4 + localEdgeIndex. E.g., when P would have the polygon index 2 and Q the polygon index 6, then the user perceived edge E_p would correspond to the two raw edges indices p_bc = 2 * 4 + 1 = 8 (edge bc in P which is the second edge, i.e. local index 1) and q_cb = 6 * 4 + 3 = 27 (edge cb in Q which is the fourth edge, i.e. local index 3).
Here are some code examples and forum posts about working with edges in Cinema 4D's Python API:
geometry_polgyon_edges_2024: This is the official example script showing how to work with polygon edges in Cinema 4D 2024. It explains how to access and identify edges in a polygon object.
Select Edges by Length: An example that shows how to select edges based on their length.
Select Polygons Facing into the Same Direction: Not directly related to edges, but I talk here about the fundamental concept of a winding order, which is important when working with polygon edges.
it is kind of hard to follow your question and solution, as the former lacks a scene for context and for the latter I am not quite sure what you fixed. But when it works for you I am happy
Thank you for your clarification. I think I understand a bit better what you want, but there are still some question marks for me.
The edge loop and ring loop tool work via point indices (and a bunch of other settings). I.e., you set MDATA_LOOP_FIRST_VERTEX and MDATA_LOOP_SECOND_VERTEX and that determines the edge you want to loop (the screenshot above is for toolloopselection.h, the file which defines the loop tool IDs). As I said above, some of the modelling command options can be bit finnicky, but I would help you crossing that bridge when we are there. But we would have to have a foundation we can work on to get started.
Sorting Selections
But from what I understand, you do not primarily want to select loops, but rather sort existing selections into loops, or more generalized: edge strips you consider to be semantically/topologically connected. For something like shown in your screenshot, this is trivial, but a general solution is not. That you have an existing selection is almost irrelevant, it is only slightly easier to solve than the general problem of sorting all edges of a mesh into strips.
I wrote something like this myself a long time ago (while still at university at not a member of Maxon) and struggled quite a bit then to get it right, because you have then to deal with intersecting strips, self intersections of strips, etc. The problem of yours lies generally in the field of topological sorting, which is pretty wide, the concrete term used for the particular problem is often 'edge extraction in mesh processing'. The popular Python mesh processing library PyVista implements for example edge extraction.
If your concrete problem is solvable, depends entirely on how well you define it. For just an given input set of edges, there exist often many solutions. You have to define things like a maximum angle of change between edges in a strip, how to deal with intersections of strips, if you want continuity of non-manifold edges, etc. And even then, you will often end up with multiple solutions. E.g., in the example below, there are three possible ways to sort these edges into two strips (and even more when you do set this conditions to minimize the number of strips). Which can be deemed undesirable.
Implementing an Edge Selection Tool Yourself
I do not understand all details of your tool, but when edge selections are part of it, you could also 'simply' integrate them. Your tool looks currently more like a dialog/command and you could also do it there, but an actual ToolData would be better.
The general drill would be to add a button/mode with which the user can select edges. Once running, you would use c4d.utils.ViewportSelect to get the points/edges the user is clicking or hovering. Once the user wants to commit to a selection, you get the two relevant hoovered points (i.e., edge) and run ID_MODELING_LOOP_TOOL with it. Because you would inspect the user creating the loops one by one yourself, you would naturally know what is a strip in the final selection.
Here are also some hoops to jump through and there might also be obstacles you cannot overcome which I do not see at first glance either (as actual ToolData tools are not very often implemented in Python and therefore a bit niche), but generally this should be possible and this might be less costly than writing a full blown edge extraction solver.
Sorting Edge Selections Without Intersections
If you want to sort edge selections without intersections (or touching), this is relatively easy to implement, you only have to get the selected edges, convert them into point pairs, and then sort them into strips by finding point pairs where one point matches the end of another pair. The problem of this route is that it is pretty much guaranteed not to suffice in real world scenarios.
Thank you for reaching out to us. Yes, that is possible but we cannot write the script for you. We can only help you when you make the first steps yourself. You will find all the necessary building blocks in the modeling example scripts.
Create a plane generator object.
Get its cache to get an editable polygon object. For more complex geometry or in general you could also run MCOMMAND_CURRENTSTATETOOBJECT.
The run SendModellingCommand with ID_MODELING_LOOP_TOOL. Sometimes modelling commands can be a bit bumpy ride, when you want to do more niche things. But at the first glance everything you will need seems to be there:
As I hinted at above, markers are eternally persistent. I.e., you can unload, load, and modify a scene S or the non-cache* node N in it, the node N will always have the same marker.
The flag you found has a very special purpose and also very misleading documentation (I just fixed that). Each node in a scene must have a unique marker, as otherwise not only BaseLink but also things like undo and more won't work properly when not each node has exactly one marker and each marker exactly one node (i.e., a bijective or 'one-to-one' relation).
But there can be bad actors such as bugs in our codebase or third party plugins which violate that rule. To combat that, Cinema 4D checks the uniqueness of markers of nodes when a scene is being loaded. When it finds duplicate markers, it will open a question dialog, asking the user if he/she wants to repair that scene. When the user says 'yes', only the nodes which had duplicate markers will get a new marker each (so that they are in a bijective marker relation again).
This flag you found will suppress this whole behavior, i.e., it will let you load corrupted scenes as is. I would not recommend using it. I am currently not 100% sure what happens when your LoadDocument call is not DIALOGSALLOWED, it looks a bit like that this check then always never runs (which sounds a bit dangerous).
Cheers,
Ferdinand
edit: okay now I see it, the question dialog part has been commented out, so this runs always without asking the user (and with that also without DIALOGSALLOWED), unless you pass the flag NONEWMARKERS.
[*] Object and other nodes in caches, i.e., the partial scene graph returned by BaseObject::GetCache and generated by ObjectData::GetVirtualObjects, are allocated each time the cache is being built and therefore also have a new marker each time. But you cannot (or better should not) try to build base links, undos, xrefs, etc. for object and other nodes in caches. TLDR: Markers do not work for caches.
I currently use INCLUDE Massign in my .res file. However i would like to do this dynamically so that I can change the order at which it is placed in the Material Editor and Attribute Editor. Is there a way to do this?
There is no INCLUDE which you could call programmatically in GetDDescription, but there is Description::LoadDescription. When Cinema 4D is asking you to update a certain part of your description, you could try to call it on your description instance but not with its id, but Massign. This is however very likely to fail or cause crashes, because your description is already being processed, I never tried this myself though.
Another approach could be to allocate a dummy description, then load your resource (e.g., Massign) into that dummy description, to then copy parameters bit by bit into your active/actual description passed into GetDDescription.
But the whole approach of a 'dynamic include' is a bit backwards without context why it has to be this way. Because when you dynamically include something, this also means you have to remove existing data where it shall not be included anymore. Which is possible but unnecessary extra work. Also copying a whole partial description is not a great idea performance wise, GetDDescription is being called a lot.
The better approach would be to just include your partial description in your res file in the a place where it might appear and then dynamically change the parent of that container. As this workflow would be the common dynamic description workflow.
Additionally Also is there a way to hide settings from the Obaselist group or hide settings in the Material Editor but keep them in the Attribute editor?
No, both managers are more or less just a DescriptionCustomGui, they will always show the same data, unless you have access to the implementation (which you do not). When you would implement your own material dialog, with your own DescriptionCustomGui in it, you could mangle what is shown in this particular description view to your liking, there are multiple methods to highlight, filter, and modify a description on that class. To operate this dialog you would then set this description GUI to the material, shader or whatever BaseList2D you want to display, and then either filter or outright modify the description which is being displayed.
I forgot this thread a bit, sorry. That sounds all very mysterious. When I summarize your issue, I would describe it as follows. I assume that is the gist of it?
I have a workstation and experience there micro- and macro-stutters in the viewport performance. This happens primarily in low performing scenes with a heavy payload. The odd thing is though, that these performance dips only happen on one machine. On other comparable machines I have an an order of magnitude better performance.
This is very hard to debug, but I would say there are two main cases of what could be going wrong.
Your GPU is damaged or you use corrupted drivers. Outdated drivers alone cannot explain the numbers you report. It could also be the mainboard, although somewhat unlikely.
You have another VRAM intensive app running on this machine. Cinema 4D and this app then play the VRAM memory battle, causing data to be constantly cycled in and out of VRAM, causing the performance dips. But this is somewhat unlikely as you probably would be aware of this.
What I would do:
Update all your drivers.
Go into Preferences/Viewport Hardware and start dialing knobs to see if you can make a difference. Cinema 4D has unfortunately not a software viewport renderer anymore with which you could do a baseline test.
Use a GPU monitoring tool (like MSI Afterburner) to see if your GPU is running at full load, or if there are spikes in VRAM usage. There are also more fancy tools with which you can monitor memory allocations over time.
Run GPU tests such as FurMark or 3DMark to see if your GPU behaves stable there.
Install a different GPU (not fun to do, but the best way to rule out hardware issues).
There could also be a bug in Cinema 4D but with numbers as shown below that strikes me as a very unlikely cause. This sounds more like damaged hardware or a really nasty driver issue.
Welcome to the Maxon developers forum and its community, it is great to have you with us!
Getting Started
Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.
Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment.
Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support.
Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads.
Do you really mean you are on R25? Or do you mean you are on 2025? Anyway, please share an example scene and the code you have so far. Otherwise we won't be able to help you.
Thank you for your question. First of all, your code is more complicated than it has to be. You can just call BaseObject.GetTag to get the first tag of a specific type on an object. Also, your code already does what you are asking for, it selects the tag and therefore causes Cinema 4D to draw the vertex map in the viewport (equivalent to single clicking the tag).
To have the Paint Tool enabled (i.e., what happens when you double click a vertex map), you could either activate the tool yourself, or just send MSG_EDIT to the tag, the message which is sent to scene elements when they are double clicked.
Cheers,
Ferdinand
Code
import c4d
doc: c4d.documents.BaseDocument # The currently active document.
op: c4d.BaseObject | None # The primary selected object in `doc`. Can be `None`.
def main() -> None:
"""Called by Cinema 4D when the script is being executed.
"""
if not op:
return c4d.gui.MessageDialog("Please select an object.")
tag: c4d.BaseTag | None = op.GetTag(c4d.Tvertexmap)
if not tag:
return c4d.gui.MessageDialog("The selected object has no Vertex Map tag.")
doc.SetActiveTag(tag, c4d.SELECTION_NEW)
tag.Message(c4d.MSG_EDIT)
c4d.EventAdd()
if __name__ == '__main__':
main()
Thank you for your question. A BaseLink always requires a document within which it shall be resolved, when you do not provide one, it just takes the active document.
So, your question is a bit ambivalent. Internally, a BaseLink is based on GeMarker which identifies a scene element (C4DAtom) over the mac address of the creating machine, the time of creation, and a random UUID. The value is a constant attached to the scene element expressed as 16 bytes of data.
So, this value is persistent over reload boundaries and does not care for the document it is in - although copying a scene element will usually cause it to get a new marker, unless you tell Cinema 4D explicitly to also copy the marker. You can easily query multiple documents for containing a given C4DAtom via a BaseLink. But unless you deliberately forced markers to be copied, it is not possible that multiple documents contain the same C4DAtom.
yes, that is the correct answer. The subject comes up from time to time, here is an answer of mine which is about the very case of yours - discovering substance channels.
Yes, it is intentional that the handle draw pass is only being drawn when the object is selected. You can try the box pass, I think it is also drawn when the object is not selected and might not be subject to viewport effects. The highlight pass is also only drawn when an object is selected. The X-Ray pass should be drawn when an object is not selected, but should also be subject to viewport effects. But the box pass is also subject to camera transforms, just as the object pass, which makes it not a very good choice to draw HUD information.
All statements above are from my recollection and could be wrong, you just have to check yourself.
But what you are trying to do is also a bit unusual. Typically, objects should not draw persistent HUDs (what I assume you are trying to do) into the viewport. That would be the job of a SceneHookData plugin. But that hook has never been exposed to Python, mostly out of performance concerns.
Your code is also missing the OCIO handling the C++ example showcases. You will be drawing with incorrect colors (unless the bitmap(s) you draw happen to have the color profile of the render space of the document - which is very unlikely).