Hi @del ,
Those are invisible tags.
You don't need to work with them directly, and also you shouldn't delete these tags.
Technically all points and polygons information contained in these tags.
SplineObject has their own set of invisible tags.
Hi @del ,
Those are invisible tags.
You don't need to work with them directly, and also you shouldn't delete these tags.
Technically all points and polygons information contained in these tags.
SplineObject has their own set of invisible tags.
Hi @gaschka ,
I assume that would be percentile value, so you can also add unit, like
squash_and_stretch_container[c4d.DESC_UNIT] = c4d.DESC_UNIT_PERCENT
Hi @ezeuz, @ferdinand,
Back in a days, it was run with python file as a second argument, like so:
c4dpy.exe dummy.py -g_encryptPypFile py-commanddata_dialog_r13.pyp
where dummy.py
just a blank file.
If anyone interested:
def MatrixBlend(mxA, mxB, t):
quatA = c4d.Quaternion()
quatA.SetMatrix(mxA)
quatB = c4d.Quaternion()
quatB.SetMatrix(mxB)
return utils.MatrixMove( utils.MixVec(mxA.off, mxB.off, t) ) * utils.QSlerp(quatA, quatB, t).GetMatrix() * utils.MatrixScale( utils.MixVec(mxA.GetScale(), mxB.GetScale(), t) )
@bentraje exactly.
I keep source files separately.
And I wrote compilation script and config file, which just loads separate plugin files, scan for local dependencies to load them as well.
So it combines everything into single .pyp
file.
For distribution it encrypts final compilation into .pypv
file.
Not much effort for this script gives development flexibility.
Hi nightmate @love_me_render
Question: how to program plugins with python?
Example: I want to reprogram my "tree-generator" written in C++ as a plugin with Python and I can't get it to work.
You can start from Python samples from official GitHub
and documenation.
One problem in the script-manager is the modification of the generated polygon object or other by user data. After I press "execute" in the script-manager, the script executes but does not respond to any user data afterwards. Is there a solution? Using "message" or something?
Assume you want to make a polygon generator.
Prototype can be done as Python Generator, with some User Data as configuration parameters.
And as a next step you can go with Python plugin.
It's regular python script file, but extension has to be .pyp
rather than .py
with a Python class and plugin registration code — look for RegisterObjectPlugin
. You'll need to generate unique plugin id
Just refer to GitHub samples.
Next problem:
The script manager can't save .pyp files, only py. What is the script manager for anyway?
And the files must all be in the special folder "C:\Users\AppData\Roaming\MAXON\Maxon Cinema 4D R25_1FE0824E\library\scripts" or something like that.
But these are then py files, so no .pyp plugin files.
Plugins must, at least from R20 or so, be in a directory that is specified in the program settings.
But even if you write a .pyp file into this directory, how can you test it while the C4D program is running? Or do you have to restart C4D every time to test it, like with C++?
There're multiple ways to load plugins into Cinema4D:
C:\Program Files\Maxon Cinema 4D R25\plugins
subfolderC:\Users\AppData\Roaming\MAXON\Maxon Cinema 4D R25_1FE0824E\plugins
Plugins/Search Paths
Once plugin is loaded and registred, you'll be able to reload python plugin with Reload Python Plugins
command.
But this would reload logic only, so if you changed description files — you'll neeed to restart Cinema.
In C++ and also in Python there is a function "GetVirtualObjects()". Do I need it? But if I use it, I have to register the plugin. This doesn't work for me, it always says "pyp-file not found".
You'll need to implement that method.
Hi @love_me_render,
It seems like you're a bit overthinking the process.
But it's much simpler.
Python plugin executes in runtime, and there's no compilation needed.
It's required that pyp
file implements a class and registers that class as a plugin (usually in the footer of the pyp
file). That's it.
Also plugin file structure should follow same scheme as c++ plugin, so Cinema would handle resources automatically.
Once again — you just need to download any python plugin (or all of them) from the official GitHub, put them into Cinema4D/plugins
folder (or whatever appropriate place) and start Cinema4D.
Switch to "Script" layout (not necessary, but it's comfortable to see the console).
You'll see those plugins in the "Extension" menu -> click on any item, and it should generate an instance in object manager (if it's an object plugin obviously).
Then you can use any text editor to edit corresponding pyp
file.
Just add some print to the console on Init, or on Update class callback,
save it,
hit "Reload Python Plugins" in Cinema4D,
and create new plugin instance once again
— you'll see message(s) in the python console.
And that would be a time to create new subject with more specific questions
As a side note,
Python computation is relatively slow, and it doesn't support multiprocessor execution in Cinema. Also there're some limitations comparable to c++ api.
In the defence of c++ plugin development — with new Maxon subscription model, you might assume most of users are on the latest release.
So there might be an option for you to maintain latest release versions only.
@love_me_render those are manually created,
luckily all of them also text files.
I'm not really familiar with cpp development, thought it's same manual process...
Btw there's plugin structure overview in official doc: developers.maxon.net/docs/py/2024_0_0a/misc/pluginstructure.html
Hi @mikeudin,
I changed my python plugins to
def Init(self, op, isCloneInit=False):
So in older versions of Cinema4D it supposed to act as it act before
Hi @gaschka
Nothing really confusing in your code, python is not very strict...
I can only suggest to move out constants of the function scope,
and there are plenty space to add verifications...
And c4d.EventAdd()
might be executed just once, in the end of the parent function where you creating objects and attaching tags
Also don't forget to use document's StartUndo()
AddUndo()
EndUndo()
methods when creating your objects.
SOME_PYTHON_TAG_CODE = """
import c4d
def main():
# my code for the Python tag
# Execution
if __name__ == '__main__':
main()
"""
def add_python_tag(obj):
if not isinstance(obj, c4d.BaseObject):
return None
python_tag = obj.MakeTag(c4d.Tpython)
python_tag[c4d.TPYTHON_CODE] = SOME_PYTHON_TAG_CODE
return python_tag
Hi @ezeuz, @ferdinand,
Back in a days, it was run with python file as a second argument, like so:
c4dpy.exe dummy.py -g_encryptPypFile py-commanddata_dialog_r13.pyp
where dummy.py
just a blank file.
Thanks @ferdinand ,
Appreciate your answer.
Since cloning might dramatically reduce performance — I'll pay attention to this information, and will try to find the issue.
Currently the thing is — my plugin worked for years (starting R25 if I remember correctly), and this part of the code worked as expected all this time without modifications.
It just iterates through fieldlist layers and calculate dirty value in order to redraw generator within GetVirtualObject() scope.
After updating to 2024.4 I get reported — my plugin stoped to work.
It was hard to debug the issue, since I printed layer or result of IsAlive() — issue didn't replicated. So the quickest resolution was to yield cloned layer.
It's not easy for me to share sample which reproduces the issue, I have complex structure of code. Maybe I'll spend sometime later if I'll not be able to get rid of the cloning.
But since I spend several hours to resolve that issue, I wanted to leave a comment for those who just uses your sample in their code, and it stop to work.
@ferdinand said in Field Refusing Object:
def getFieldLayers(node): """ """ visited = [] end = node.GetUp() if isinstance(node, c4d.GeListNode) else None while node: # If the current node is a newly encountered one. if node not in visited: visited.append(node) # When it is a field layer, yield it and deal with group # fields ... if isinstance(node, c4d.modules.mograph.FieldLayer): yield node linkedNode = node.GetLinkedObject(doc) if linkedNode and linkedNode.CheckType(c4d.Fgroup): # Get the field list and iterate over it. fieldList = linkedNode[c4d.FIELDGROUP_FIELDS] root = fieldList.GetLayersRoot() for nestedNode in getFieldLayers(root): yield nestedNode # There are more special cases in the field list hierarchy, # e.g., folders, which also have to be treated specifically. # I did not do that here. # Normal depth-first traversal of a node tree if node.GetDown() and node.GetDown() not in visited: node = node.GetDown() elif node.GetNext(): node = node.GetNext() else: node = node.GetUp() if node == end: return
If anyone else relies on this code (many thanks @ferdinand for this great example)
for some reason yield node
in 2024.4 emits exception:
ReferenceError: the object 'c4d.modules.mograph.FieldLayer' is not alive
Currently I went with yield node.GetClone()
— issue is gone and it works as expected.
But I'm not sure if it's proper way.
@Paul-Everett not really
In fact, if it's not convex-hull geometry, some edge might be fully or partially covered by some polygon(s) laying in front if this edge.
@SweepingMotion nice finding
Yes, there are quite a few issues in the sample codes everywhere.
To fix the Python Field, I think it's required to change
outputs._value = [
c4d.utils.RangeMap((~mgEffector * mgInput * p).GetLength(), 100., 0., 0., 1., True)
for p in inputs._position
]
to
outValues = outputs._value
for index, p in enumerate(inputs._position):
outValues[index] = c4d.utils.RangeMap((~mgEffector * mgInput * p).GetLength(), 100., 0., 0., 1., True)
outputs._value = outValues
Hi @SweepingMotion ,
Would be helpful if you'll provide sample project, or at least code for the python field.
Hi @merkvilson ,
try to add check for main thread scope:
def Message(self, node, type, data):
if c4d.threading.GeIsMainThread() and type == c4d.MSG_MENUPREPARE:
node.Remove()
@d_schmidt
If this vertex data is going to be utilized by shaders, or deformers, or fields — you don't need to store any data there, your tag will be just a reference, so you'll need to fined derived tag on the cache.
If you want to grab data exactly from the generator's tag, then this approach doesn't suit you.
Hi @d_schmidt, few releases ago Maxon changed the way how vertex maps are applies to the generators.
I think nowadays generators tag data is not taken in respect.
Cinema automatically propagates generators tags onto cache geometry, and evaluates fields from origin tag to each geometry.
If there are no fields — your maps will have values of zero on your cache geo.
Maybe you'll need to utilize AddToExecution()
method to build geometry, return it as result of GetVirtualObjects()
, then Cinema will propagates origin tags onto cache, and then you'll do your tags logic at priority of "Generators" 0+.
Hi Maxon team,
Just want to bump up the issue, which is still here under the Cinema4D 2024.2