I would like to draw a spline similar way it's drawing in the editor, with gradient from white to blue, and the points.
As well as I'd like the points to be selectable.
I've came to this for now:
def Init(self, op):
self.LINE_DRAW = None
def GetVirtualObjects(self, op, hh):
hierarchyCloneRes = op.GetAndCheckHierarchyClone(hh, op.GetDown(), c4d.HIERARCHYCLONEFLAGS_ASSPLINE, False) if op.GetDown() else None
spline = c4d.SplineObject(0, c4d.SPLINETYPE_LINEAR)
if not hierarchyCloneRes["dirty"]:
if hierarchyCloneRes["clone"].IsInstanceOf(c4d.Ospline) or hierarchyCloneRes["clone"].IsInstanceOf(c4d.Oline):
spline = hierarchyCloneRes["clone"]
sh = c4d.utils.SplineHelp()
if sh.InitSplineWith(spline, c4d.SPLINEHELPFLAGS_RETAINLINEOBJECT):
self.LINE_DRAW = sh.GetLineObject().GetClone()
def Draw(self, op, drawpass, bd, bh):
doc = bh.GetDocument()
if drawpass != c4d.DRAWPASS_OBJECT or self.LINE_DRAW is None or self.LINE_DRAW.GetPointCount() == 0 or not bh.IsActive() or not doc.IsEditMode():
# this draw is ignored
bd.DrawPolygonObject(bh, self.LINE_DRAW, c4d.DRAWOBJECT_USE_CUSTOM_COLOR, None, c4d.GetViewColor(c4d.VIEWCOLOR_SPLINESTART),
if doc.GetMode() == c4d.Mpoints:
opPoints = self.LINE_DRAW.GetAllPoints()
pSelection = self.LINE_DRAW.GetPointS().GetAll(len(opPoints))
selected = [opPoints[index] for index, isSelected in enumerate(pSelection) if isSelected]
nonselected = [opPoints[index] for index, isSelected in enumerate(pSelection) if not isSelected]
if __name__ == "__main__":
c4d.plugins.RegisterObjectPlugin(id = 1000001, str = 'SplineTest', g = SplineTest, description = "osplinetest", icon = None, info = c4d.OBJECT_GENERATOR | c4d.OBJECT_INPUT | c4d.OBJECT_ISSPLINE)
Issue is object lines are not being drawn with custom color.
Instead this object is drawn as maybe with c4d.VIEWCOLOR_ACTIVEBOX (visually its drawn as orange in R25)
Thank you for reaching out to us. I will answer in bullet points.
So, your code is 'correct' for the most part, at least for a polygon object. You return something in GVO to let Cinema 4D handle the polygon shading and then draw vertices and edges on top of that (which is what you are supposed to do). However, what at least I would not do, is storing the point data in self.LINE_DRAW. I would suggest accessing the cache of the node instead.
In the end it is also not quite clear what you want to do. I understand that you want to draw the vertices and 'gradient' for the line segments of a spline. What is not quite clear to me is if you actually need an object (the stated could also be a tool) and if the user should be able to select and manipulate vertices. I would be best if you could clarify this.
Especially regarding the point of wanting to draw a gradient of line segments, I would strongly suggest moving on to C++, as you will have much better chances to implement such a custom thing in that language.
@ferdinand huge thanks for detailed reply, as always.
I didn't shared whole code here, but in GVO I'm also caching the spline as self._spline property and in GetSpline method I'm returning self._spline.GetClone()
When dealing with child input object GetAndCheckHierarchyClone has no alternatives except weird combination of CSTO + Touch()
Hm, that is sometimes true, but at least from your example code this was not obvious. And I would make if this is really the only way to go. There come penalties with implementing a spline in this manner, the major one being, that you will always have another layer of caches, which in turn will impact other things. A node which implements a spline via GetVirtualObjects will return a SplineObject as its cache (or part of its cache). This SplineObject will then a have a LineObject cache. A node which implements a spline via GetContour, will return a LineObject directly.
So, the solution might be to draw tiny polygons with gradient in vertex colors? Any suggestion for draw pass in that case - DRAWPASS_OBJECT or DRAWPASS_HANDLER?
Current issue is that my object is drawn automatically, and I have no idea if I can prevent automatic drawing. I can render custom color only in the case my object is not selected.
Yes, sort of. I was thinking more of line segments than polygons, but that is the general idea. I would also not draw in object space, but in screen space, to be as efficient and smooth as possible. When you draw in object space, and you choose the spacing to wide, the gradient will be visibly segmented when projected to pixel screen space. When you choose the spacing to tightly in object space, you will end up with many unnecessary drawing operations which are not going to be visible in screen space anyways.
The drawpass in which you should be drawing depends on what you want to do, but usually DRAWPASS_OBJECT is the one to go. The more important information is here the z-depth for drawing the line segments, which should be 4 or greater, so that the line segments are drawn over shaded polygons and other stuff.
But I would here point again that Python is not a good match for this task due to performance restrictions. If your splines are simple and drawing a gradient for them just means <= 10,000 draw calls in screen space, you will be fine in Python. But when your splines are complex, and drawing a gradient for them means drawing 100,000s of pixels (i.e., draw calls), Python will bottleneck you hard. The old viewport API is not the fastest even in C++, so such complex tasks should really be done there. I would suggest drawing only the vertices in Python and expressing the direction of the spline by colouring the vertices.
PointObject::DisplayPoints is only available under C++? Didn't found any references in both Py and Cpp documentation
As I said, this method is non-public. Some of the public interfaces/types have non-public methods. I simply pointed out that we use this method internally, and that it does a lot of things regarding displaying vertices in the viewport. So, when you implement a OBJECT_POINTOBJECT ObjectData plugin, it is somewhat intended from the internal perspective that you use then ::DisplayPoints. I was simply informing you that the inaccessibility of DisplayPoints is one of the obstacles you must overcome.
Yeah, thanks for suggestion to not store source line, as it's not reflecting further deformer changes. But I found that Draw is called so frequent, and converting spline to line takes sufficient time, so it becomes uncomfortable to work with.
When you implement your spline in GetContour this will be more straight forward, as the cache will then be directly the line-object, but you can do more or less the same withGetVirtuaalObjects, you only must deal with a slightly more complex cache then. But it should beat storing and retrieving the data manually in any case.
I wanted to render vertices and spline direction not for manipulation, but for visual check.
And thought to implement point selection feature for selection tag creating, maybe.
I have provided a very simple pattern for that at the end. I did not implement the selecting, creating, and moving vertices stuff, as this would be quite a bit of work. I also went the route of shading the vertices and not the line segments, as this is much simpler to do
def Draw(self, op, drawpass, bd, bh):
if drawpass != c4d.DRAWPASS_OBJECT:
super().Draw(op, drawpass, bd, bh)
# Only display the vertices when the object is selected.
if (not op.GetBit(c4d.BIT_ACTIVE)):
cache = op.GetCache()
if not isinstance(cache, c4d.LineObject):
# Draw in object space with a zoffset of 4 over most things Cinema 4D will draw as polygons,
# edges, and vertices.
bd.SetMatrix_Matrix(op, op.GetMg(), zoffset=4)
# Now we simply will draw a dot for each vertex in the LineObject, with a gradient going
# from the first vertex to the last vertex. Note that we are drawing the vertices of the
# LineObject, not the ones of the SplineObject. I.e., we are not drawing the control
# vertices of the spline, but the actual vertices which have been interpolated between the
# control vertices. In this case we will draw 3 * 64 vertices, since we define 4 control
# points of a non-closed spline (i.e., 3 segments between them) and a uniform interpolation
# of 64.
count = cache.GetPointCount()
cStart = c4d.Vector(1, 0, 0)
cEnd = c4d.Vector(0, 0, 1)
colors = 
# We are going to use a single draw call for doing this, DrawPoints, which also allows for
# drawing each point in a different color. But it expects the colors in a bit annoying
# format as an array of floats: [r, g, b, r, g, b, r, g, b, ...].
for i in range(count):
ci = c4d.utils.MixVec(cStart, cEnd, i / count)
colors += [ci.x, ci.y, ci.z]
# Draw all points in one go, this is very performant.
bd.DrawPoints(vp=cache.GetAllPoints(), vc=colors, colcnt=3)
return super().Draw(op, drawpass, bd, bh)
def GetContour(self, op, doc, lod, bt):
# I am not going to comment the geometry construction I am doing here. For details on
# polygon and spline generation, see https://bit.ly/3lxBBlt
node = c4d.SplineObject(4, c4d.SPLINETYPE_LINEAR)
size = 100.0
node.SetAllPoints([c4d.Vector(0, 0, 0),
c4d.Vector(size, 0, 0),
c4d.Vector(size, size, 0),
c4d.Vector(0, size, 0)])
# I am just going to be lazy and not tie all the interpolation parameters in and instead
# set them manually here.
node[c4d.SPLINEOBJECT_INTERPOLATION] = c4d.SPLINEOBJECT_INTERPOLATION_UNIFORM
node[c4d.SPLINEOBJECT_SUB] = 64
if __name__ == "__main__":
id = 1000001,
str = 'Editable Spline',
g = EditableSplineData,
description = "oeditspline",
icon = None,
info = c4d.OBJECT_GENERATOR | c4d.OBJECT_ISSPLINE)
@ferdinand thanks for explanations and example.