Create Geometry from Python Generator


#1

I want to create a mathematical surface using a Python generator. Essentially, I’m wanting to get a very similar effect to using a formula deformer on a plane, but instead I’m wanting to do it all from within Python. The reason I want to do it from within Python is because I want to model the output of a complex function, in other words, where the input is of the form x + i*z (i is the imaginary unit) and the output y is either the real or imaginary part of some function of that input. This is easy to do with the cmath module and just taking the real and imaginary parts of the output function, but the problem is that I don’t know how to actually generate a surface from it.

In other words, if I am able to specify a set of points (x, y, z), how would I turn this into a surface using a Python generator?

For example, if I have the list of points (x, y, z):
(1, 1, 1), (1, 1, -1)
(0 0, 1), (0, 0, -1)
(-1, 1, 1), (-1, 1, -1)

I would expect something that looks like this:

Any ideas how to do this with a Python generator?


#2

I think it’s more appropriate to use python as a Script rather than a generator for this kind of work.
Typically a generator expects a geometry as input to make changes on it.


#3

Nope, sorry, but I need to correct. A Generator is perfectly fine for this. The native parametric Cube in C4D is a generator.
In regards to the actual question, I recommend to simply check the examples inside the Python SDK, there it is demonstrated.


#4

So in what way is python exactly constrained when used in different forms in C4D ?
There’s the classic script way, it’s in generator form, in Effector form, in field form, in tag form, in Xpresso … How do I know what form suits best ? Is there a constraint in accessing certain libraries depending on the form ?


#5

If you ask it like this, then I’d say it is constrained in the same way as C++ plugins are. There you have the exact same plugin classes, as these basically resemble C4D’s architecture.

In Python you lack a few, though. For example you can’t implement a NodeData (has nothing to do with material or scene node, it’s more a generic plugin class, where plugins which add to a scene or document (objects, materials,…) are based on) plugin in Python. So this is one constraint, but it has no influence on all those Python options you mentioned.

The main constraint is probably speed. While you can write a shader plugin in Python, I doubt, many had fun using it, simply because it will be slow. In the end Python is a script language and this is one of the disadvantages coming with this concept.

Well, by understanding C4D and reading API/SDK documentation.
There is no classic script way. The script way is just a way to implement a CommandData plugin. The Python generator is a way to implement an ObjectData plugin. The Python tag is a way to implement a TagData plugin. And so on. Which one you need or want to implement depends on your needs and goals.
It can be helpful (and that’s what I meant with understanding C4D) to take a look, how C4D is doing things. For example there are no tags in C4D to create a cube (or any other object), tags are rather ways to add or modify existing attributes of objects. On the other hand, you won’t find objects (those blueish or greenish ones) in C4D, which modify existing objects. I’d say, take a look at C4D and think about, in what way differently colored stuff is different.
Or the other way around: If you want to do Fields stuff, then you are probably better of with the “Field form” than with a script…

If I said no, I’d be lying. But in the context of your question, I say no. In the past there were way more restrictions than nowadays, simply because Maxon is constantly working on extending the Python API (and I’d like to say thanks to Maxon for this). The restrictions are rather related to C4D’s architecture (and equally present in the C++ SDK). For example in a Generator plugin (ObjectData) you are not allowed (for several technical reasons) to modify the scene. But as I said this is not a constraint of Python.

Personally I do almost all development in Python nowadays. At least the prototyping. Simply because turnarounds are fast and you can achieve quite a lot without hitting road blocks. Only if a project has very specific needs, either the use of one of those areas of the SDK not covered in Python or speed or if a customer demands it, I consider C++ development. Above mentioned NodeData for example, which is quite rare and has very specific use cases and won’t be an issue for most wanting to do some Python scripting. Or again the other way round: If you have never heard about NodeData, chances are you won’t ever need it.

Now, as I have mentioned speed so often, I’m pretty sure, somebody will come along saying Python is actually quite fast. And I agree, it is. In a way… the slow thing is the border between C4D’s core and the script world. The more you need to cross it, the worse it gets. That’s for example the reason, why shaders in Python are no fun. Because here this border needs to be crossed for each and every sample.

I could go on for a while… but I think, if you are really interested in answers to the above questions, either start learning by reading (SDK docs, examples,…), that’s probably what most C4D plugin developers did. Or use the resources already provided by others. E.g. @Cairyn did put quite a lot of work into his Python guide, parts of it being even free. Or, if you are in a rush, pay somebody to teach you. Again Cairyn comes to mind. I do such lessons tailored to an individual’s needs, too.


#6

Most helpful as always.

What I mean by all that was that if there is no constraints in accessing libraries from any of those forms you could create geometry from anywhere. It’s your decision if it’s going to be a green or blue python port just to be logically coherent with the operation.

About being fast, everyone says python is slow. And everyone contributes that to the fact that it’s an interpreted (well i’ve searched too much for that and there are a lot of different opinions on that matter, I guess using it as a script makes it interpreted). I think what makes Python slow is also the fact that is heavily object oriented. It’s one thing typing a long int variable = 6000 in C and totally different typing a A = [1,"C"] in python. The later drags with it more than 200 lines of code recognizing it as a list structure type with its own special functions that is comprised of mixed type objects each containing a tone of other attributes with them.
What I don’t know is if C4D executes the Python script each time it’s being evoked (even without having changed a single character) or there’s some kind of temporary saved low level interpreted executable created from the first compilation. I guess C4D uses the standard CPython and not others like PyPython or Jython.


#7

I got it. Take a look:

Here’s the code of the python generator:

import c4d
import math
import cmath

min_x = -3.0*math.pi/2.0
max_x = 1.0*math.pi/2.0
min_y = -2.0*math.pi
max_y = 2.0*math.pi

resolution = 10 # number of points along each axis
point_count = resolution*resolution
poly_count = (resolution - 1)*(resolution - 1)


def maprange(xx, min_in, max_in, min_out, max_out):
    return min_out + (xx - min_in)/float(max_in - min_in)*(max_out - min_out)

def getpoints(time_perc):
    tt = 2*math.pi*time_perc*8
    vecs = []
    for yy_r in range(resolution):
        yp = maprange(yy_r, 0, resolution-1, min_y, max_y)
        for xx_r in range(resolution):
            xp = maprange(xx_r, 0, resolution-1, min_x, max_x)
            com = cmath.exp(xp + (yp + tt)*1j)
            zp = com.real
            
            vecs.append(c4d.Vector(xp, zp, yp))
    return vecs

SCALE = 100

def main():
    time = doc.GetTime().Get()
    maxx = doc.GetMaxTime().Get()
    tt = time/maxx
    node = c4d.PolygonObject(pcnt=point_count, vcnt=poly_count)
    
    
    points = [p * SCALE for p in getpoints(tt)]
    node.SetAllPoints(points)

    polynum = 0
    for yy in range(resolution - 1):
        for xx in range(resolution - 1):
            
            aa = xx + yy*resolution
            bb = xx + yy*resolution + 1
            cc = xx + (yy + 1)*resolution
            dd = xx + (yy + 1)*resolution + 1
            
            cpoly = c4d.CPolygon(aa, bb, dd, cc)
            node.SetPolygon(polynum, cpoly)
            polynum += 1

    return node

The blue is using zp = com.imag while the red is using zp = com.real.

Let me know if you have any suggestions or improvements. Thanks.


#8

This is great John, thank you.