use existing Catmull Clark algorithm
-
Fellow coders,
is there a way to use the already existing Catmull Clark algorithm (which is the default algorithm used in the Subdivision Surface generator) in my own plugin?
My goal is to subdivide and smooth geometry that I created by code.
Please note that I'd like to avoid creating a Subdivison Surface generator instance or calling the Subdivide command with the smoothing option if possible. I'd like to invoke the algorithm without having to create an additional object and without having to invoke the messaging pipeline.
C++ or Python, doesn't matter.
I'm on C4D 2024.Catmull Clark isn't very complex but you know, code shouldn't be duplicated. Also I bet the folks at Maxon got some optimization in their implementation that I'd just like to carry over. Don't reinvent the wheel and all that.
Best regards,
Daniel -
This post is deleted! -
Hello @CJtheTiger,
Thank you for reaching out to us. Our SDS are exposed via
MCOMMAND_SUBDIVIDE
,MDATA_SUBDIVIDE_HYPER
, andMDATA_SUBDIVIDE_SUB
. One must invoke aSendModelingCommand
withMCOMMAND_SUBDIVIDE
and then populate the settings container with the other values. You can also have a look at our Python SMC examples.What to do depends on the plugin, when you write an
ObjectData
plugin, and you want you output to be smoothed, you should strongly consider using an SDS object in your cache. For everything else you can use the modeling command.Code:
#coding: utf-8 """Demonstrates how use the subdivide tool programmatically. Must be run as a script manager script with a polygon object selected. The script will divide the object twice using SDS. """ import c4d import typing doc: c4d.documents.BaseDocument # The currently active document. op: typing.Optional[c4d.BaseObject] # The selected object within that active document. Can be None. def main(doc: c4d.documents.BaseDocument, op: typing.Optional[c4d.BaseObject]) -> None: """Runs the example. Args: doc: The active document. op: The selected object in #doc. Can be #None. """ if not isinstance(op, c4d.PolygonObject): raise TypeError(f"Please select a polygon object.") # The settings of the 'Subdivide' tool. Enable HyperNurbs, a.k.a, SDS, and subdivide twice. bc = c4d.BaseContainer() bc[c4d.MDATA_SUBDIVIDE_HYPER] = True bc[c4d.MDATA_SUBDIVIDE_SUB] = 2 # Execute the command with the undo flag set. It can also be executed in point and edge mode. res = c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_SUBDIVIDE, list=[op], mode=c4d.MODELINGCOMMANDMODE_POLYGONSELECTION, bc=bc, doc=doc, flags=c4d.MODELINGCOMMANDFLAGS_CREATEUNDO) if not res: raise RuntimeError(f"Modelling command failed for {op}.") # Inform Cinema 4D that the document has been modified. c4d.EventAdd() if __name__ == '__main__': # #doc and #op are predefined module attributes as defined at the top of the file. main(doc, op)
Cheers,
Ferdinand -
Hi @ferdinand and thanks for your answer. Always a pleasure hearing from you.
The plugin is an Object plugin.
I do need the subdivided and smoothed geometry to work with it afterwards.
What I'm trying to do
What I'm trying to make is an Object plugin for a disc with quads (in case you've seen the feedback ticket regarding the same topic: I submitted that, but I wanted to help myself in the meantime ). This is also why I can't use the SDS generator (since that wouldn't create a perfect circle).
The approach is this:
- Get the desired amount of subdivisions from the user.
a. For testing purposes I'm going to take them from the User Data which I've added manually. - Create circles (using sin/cos) where amount of points per circle = 4 + 4 * subdivisions and amount of circles = subdivisions.
- Create a square with four points, no subdivions yet. Rotate the square by 45Β° so each point of the square is zeroed out on one of the axis.
- For both dimensions of the square add n "smoothed" points where n = subdivisions.
a. This is where I want to use Catmull Clark. - Connect all outer points of the (now smoothed) square with the relevant points of the closest circle.
The result should look something like this:
(Disclaimer: I stole the image from http://www.scriptspot.com/3ds-max/scripts/tags/quad and modified it slightly for my purposes.)Just to be clear: I'm not expecting help on figuring out the details of this specific implementation. Just thought I'd explain my situation further.
I'm aware that I could use a Remesher to get a similiar result but since I want to use this in a larger rig that would result in constant remeshing (because the disc changes a lot) I really want this as a parametric object instead. It would certainly work but it wouldn't be ideal.
Best regards,
Daniel - Get the desired amount of subdivisions from the user.
-
Hi @CJtheTiger ,
Just build / rebuild your base shape and then apply
Subdivide
command.
And then continue with subdivided geo, to adjust it's look.There's some logic how new points are added during subdivision.
But I'm not it sure if it's good idea to create a square mesh, subdivide it and then remap to your circle somehow.As an example I started from 4-side disc, and applied subdivision command onto it five times. And the only issue is left β is to maintain boundary size and circular shape.
-
This post is deleted! -
@baca thanks for your suggestion.
And the only issue is left β is to maintain boundary size and circular shape.
Both of these issues are the reason why I want to create the points myself. The radius needs to be exact, and the circle needs to be as perfect as possible.
I created a Python script for a Python Generator that creates this:
And subdividing that has the same result.
https://github.com/YoYoFreakCJ/C4D-NestedSquarePythonGenerator
I could do a loop selection around the outer loop and apply the fit circle command but that's hacky as hell.
-
Hi @CJtheTiger,
When you must build upon the subdivision, insert modelling steps after it, you can use SMC when build the cache of your object. But I would always try to avoid that when possible and instead use an
Osds
instead in your cache wrapping around your output.I would not recommend reimplanting the SDS, because at the end of the day, they are not that trivial to implement. Using SMC should be fairly performant here, since you do not have to clone any inputs as you construct the input yourself.
I am not sure if I understand the rest of the discussion correctly, but when you want a better circle approximation using cubic interpolation/SDS, you just need more control points, which then leads to more complex geometry when you want a nice topology and no quads. When you do want to deal with terminating quad loops in the center, you could also just cylindrically distort a plane, do what I did here:
Cheers,
Ferdinand -
To close this topic up:
No, it is not possible to use the existing Catmull Clark algorithm, I'd have to implement it myself. -
Hey @CJtheTiger,
I am glad that you found your solution. But your conclusion is not correct. I know that there sometimes can be subjective truths, but I also must keep future readers in mind which might jump right to the end of a thread to look at its outcome; and which then might be led astray by your conclusion.
Our SDS are exposed, I even provided a code example above. And while you are of course free to reimplement things, it is not recommended to do so, as you then lose all the general features of our modeling core such as UV-handling and mnemonization and the subdivision specific features such as edge weighting.
Cheers,
Ferdinand -
Hi @ferdinand,
sorry I did not mean to spread misinformation. However since I specifically asked to not use an SDS and not invoke the message pipeline this post aimed to clarify whether it's possible to reference the actual class or methods to execute the algorithm which from what I gathered in this post is not possible. Correct me if I'm wrong.
Sorry again.
Cheers,
CJ -
Hey @CJtheTiger,
No need to be sorry. Well,
SendModellingCommand
(SMC) is not a message or part of the message stream. It is just an abstraction around calling functions/commands. So instead of callingfoo(1, 2, 3)
you callcommand(ID_FOO, (1, 2, 3)
which then callsfoo
for you. There is also nodo_catmull_clark()
function in our code base which could be called. There is the subdivision command which also includes SDS routines. In the backend this is also all part of the new modeling core which does fancy things like mnemonization for you which is important for performance (a user has 100 instances of your plugin with the same settings and just pays the price of one without and explicit instance objects).SMC can sometimes be disadvantageous when the inputs are already part of an active document and oneself is bound to threading restrictions (think of a polygon object input for some generator object). When you then want SMC such input, you cannot do that because that would be a threading violation. So, you must copy the data into a dummy document to do your computations there. Which is not only expensive but can also get tricky when one then must evaluate the dependencies of that input (or do brutish things like clone the whole document).
But this does not apply in your case here since you own your input. The threading restrictions are also not a problem unique to SMC and also apply to calling functions. Calling SMC with
MCOMMAND_SUBDIVIDE
is calling our SDS function.Cheers,
Ferdinand -
Good morning @ferdinand,
thanks for clearing things up. This made it click for me:
SendModellingCommand (SMC) is not a message or part of the message stream. It is just an abstraction around calling functions/commands. So instead of calling foo(1, 2, 3) you call command(ID_FOO, (1, 2, 3) which then calls foo for you.
I was under the assumption that SMC invokes the messaging pipeline.
It's great getting all of these insights. Thank you so much!
If I could I'd mark your last response as the answer but unfortunately it seems like this is not a thing anymore in the new forums?
Have a nice day,
Daniel -
Hey @CJtheTiger,
yes, we disabled Ask as question because it was a lot of extra work for us to maintain because 60%-80% of users set their topics never as solved.
Cheers,
Ferdinand