Hi, Maxime, great!
if I come across any additional issues of a similar nature, I'll update this post.
The argument self should not appear in any of the following static method declarations that are part of the generated Python C4D API skeletal/placeholder code, since any good Python IDE will complain and/or flag correct user code that use these methods.
In the future, you guys can do something like the following in ...resource\modules\python\libs:
# You can also add the -n flag to the first grep in the pipe chain below, to add line number info after the file name
grep -R -A2 -H "@staticmethod" * 2>&1 | grep -v "MAXON_" | grep -PB2 "def.*?self"
I am not sure if the above grep comprehensively captures every one of these issues, but 99-100% of them should have been caught:
python39/c4d/bitmaps/__init__.py- def AllocWrapper(self, bmp):
python39/c4d/modules/mograph/__init__.py- def Create(self, flags, thread, doc, currentThreadIndex, threadCount, inputs, callers):
python39/c4d/utils/__init__.py- def PickObject(self, bd, doc, x, y, rad, flags):
python39/c4d/__init__.py- def GetDistance(self, v1, v2):
python39/maxon/decorators.py- def Auto(self, *args):
python39/maxon/decorators.py- def ReferenceConvert(self, *args):
python39/maxon/decorators.py- def Wrap(self, *args):
python39/maxon/decorators.py- def NativeDataOrBuiltin(self, *args):
python39/maxon/decorators.py- def _MaxonConvertOrFail(self, *args):
python39/maxon/interface.py- def Free(self):
python39/maxon/interface.py- def GetLocal(self):
python39/maxon/interface.py- def AllocEmpty(self):
python39/maxon/interface.py- def AllocFromUuid(self, uuid):
python39/maxon/interface.py- def AllocFromString(self, uuidStr):
python39/maxon/interface.py- def DescribeIO(self):
python39/maxon/interface.py- def CreateUuidString(self):
python39.win64.framework/lib/pathlib.py- def link_to(self, target):
Also, for all of the following, the @classmethod attribute should be changed to @staticmethod and the self argument should be removed, just like for the above cases:
python39.win64.framework/lib/importlib/_bootstrap.py: def create_module(self, spec):
python39.win64.framework/lib/importlib/_bootstrap.py: def exec_module(self, module):
python39.win64.framework/lib/_collections_abc.py: def _from_iterable(self, it):
python39.win64.framework/lib/_collections_abc.py: def _from_iterable(self, it):
It seems like S24 is too sensitive when it comes to deciding whether a user intended to hold a key down for a period of time so as to temporarily toggle a state and revert back once the key is released or if the intent was to permanently change to that state.
For example, the lowercase L key can be used to:
Unfortunately, the actual length of time used for the decision by C4D as to the user's intent seems to be too short. I don't know where exactly the threshold lies, but it seems to be in the 100 ms ballpark. There should be a way to change the number of milliseconds that a key must be held before C4D assumes that the user's intent was to toggle (and not permanently enter a state/mode). I have not been able to find this setting if it exists it in the preferences. Or, is this value "hard coded" into the app and cannot be modified by the user?
This issue effects both mice and keyboards that allow for macros, since sometimes they can send the key down even followed by a key up event with some delay in between that cannot be modified via configuration and happens to be long enough to confuse C4D into thinking that the user's intent was to temporarily toggle. I should add that such interpretation can occur intermittently due to key down/key up delay jitter making things even more confusing.
On the other hand, even using a decent wired keyboard, if I press a key reasonably briefly, but not really fast, I sometimes get a temporary toggle instead of a permanent mode switch. This is true for those commands/modes that are toggleable. One almost has to put in a mental effort when pressing a key on the keyboard so as not to wind up holding it down for what C4D considers too long a time (where "too long a time" could be as little as somewhere in the 100-150 ms ballpark...). Often when I press a key, without concentrating on pressing it really quickly, C4D thinks my intent was to hold the key down so as to temporarily toggle, and reverts the setting back once I release the key.
There should be some way to control this delay and increase it in the software to make C4D wait longer before assuming that the user intended to temporarily toggle a command (say 250-500 ms would be a reasonable delay as compared to the unnecessarily brief delay it is presently set to by default (50-100 ms??).
I should note that this issue was present in R23 as well, but the delay was longer and could be lived with (although some users of Bluetooth keyboards and other forms of wireless keyboards as well as macro capable mice experienced the issue). As of S24, the delay has gotten so short, that it even affects users with common run-of-the-mill wired keyboards like myself, unless they can press/release keyboard keys really briefly (as an analogy for piano players out there, think of playing staccato notes vs tenuto notes).
OS: Windows 10
Hardware/PC: Very fast modern desktop
Keyboards attempted: Velocifire TKL02/wired, an old Dell wired keyboard, an old Logitech wired keyboard
Thanks for confirming that I was not missing something in the docs and this is just not possible.
@zipit I am hopeful that @r_gigante will take a look at my reply to his post and address the question in Step 2 of said post:
How do you "Hand color information to Cinema 4D inside Draw() (or where else??), so that it can use it to correctly draw the object in a Viewport" ?
@zipit I tried this route and it doesn't work, at least not in the way I expected, because for this to work, it is critical that the ordering between Draw() and Output() functions across all object nodes and their respective tags in the node hierarchy, has to be in a certain well-defined and compatible (with this idea) way. Otherwise, the color you cache in Draw() based on the TextureTag received is not necessarily the correct color when Output() gets called (and if Output() gets called, because it almost never called unless you render, but Draw() gets called a lot).
The reason things don't work as expected using this method of caching color info in Draw(), to be used later in Output (if object info is not available), is because either the object changed or the "current" texture tag is different by the time Output() is called, but often Draw() has not yet been called to update the cached color info. Also, aside from rendering, as I mentioned previously, calls to output are far and few in between. For example, if you use the mouse to move or rotate objects in a view, only Draw() gets called (for each texture tag/object), with no subsequent call to Output() as far as I can tell.
I understand what you are saying in your bullet point 1:
I made a typo when I used pShaderData several times, both in the code which I wrote from memory and the original post. Obviously, there is no *VolumeData vd member variable in the BaseShader class, so where I originally typed pShaderData, I was referring to pChannelData - my apologies.
I have edited the original post and corrected this. I just want to add that I have no issue during the rendering process, since I do exactly what you described in your first bullet point (using pChannelData of course). It's "outside a rendering process" where I run into problems and so let's go to your bullet point 2:
The issue is not one of finding the color of interest in Draw(), this part is easy! The issue is giving per object tag based color information back to Cinema 4D at "the right time" for this information to be used to draw in Viewports that show the object (and by the way where does this happen??).
During renders, within the Output() function, it is clear that you return the color vector from your shader and this is how you tell Cinema 4D what color your shader returns.
But, during Viewport drawing, it is not clear how to give this color information from a custom shader (after getting color information based on the BaseTag parameter to Draw()) back to Cinema 4D, so that it uses it as part of the Material of an object.
Unlike the Output() function, Draw() returns a boolean value, not a ColorVector, so how do I give a ColorVector back to Cinema 4D so that it knows how to draw an object, one that has a TextureTag using a material with my shader, correctly in a Viewport?
That's my dilemma, having solved the render question.
Instead of seeing this in the viewport (with red indicating that my shader could not get object info for purposes of viewport display and the default error (exception) color is being used, because I do not know how to supply the per object color back to Cinema 4D [presumably in the call to Draw()]):
Incorrect Viewport Display
I want to see this - objects get correctly colored, after you explain to me how to perform Step #2, below:
Inside the call to Draw():
Step 1) Fetch the color info from the object's texture tag's user data (how to do this is obvious)
Step 2) Hand this information to Cinema 4D inside Draw() (or where else??), so that it can use it to correctly draw the object in a Viewport
Correct Viewport Display
Thanks for your help and advice,
I have a question about the correct path from the ShaderData::Output() function which gets overriden by a custom shader to the texture tag that links to the material which hosts the shader in one of its slots. Before stating the problem, let me explain the reason why I would like to do this:
I am trying to reuse the same material across multiple objects. This material hosts a custom shader called Object Controlled Shader in its Color channel's Texture slot, as indicated by the orange arrow in the image below:
Object Controlled Shader
This custom shader is meant to enable me to drive the color from a custom user data Color property that is associated with a texture tag attached to an object. I can use this user data Color property to set a per object texture tag color for the material and be able to modify it based on various criteria, without having to physically change the material, since it is also shared by other objects. I can do this while reusing all other aspects of the material. So, in effect, this would enable me to parameterize various aspect of a material via custom shaders on a per object per texture tag basis. This is somewhat similar to what the Mograph Color Shader currently does, except that the Color Shader allows for only one point of customization control, since it is hard wired to an Object's Basic Color (ID_BASEOBJECT_COLOR) property.
It should be noted that the built in Mograph Color Shader works correctly to set the color both in Cinema 4d's Viewports and at render time, which is important as I will explain, below.
We start with the following overriden function in my custom shader:
Vector MyShader::Output(BaseShader* pShader, ChannelData* pChanelData)
Now let's look at what we have available in this function:
After much trial and error, I have been able to figure out that the following code (potentially) gets me the texture tag user data based color property I am trying to extract and use that to return a per object texture tag specific color from the Output() function:
Vector MyShader::Output(BaseShader* pShader, ChannelData* pChanelData)
int MY_COLOR_ID=1; // Id inside a texture BaseTag's user data
Vector COLOR_VECTOR_NOT_PRESENT=Vector(1.0, 0.0, 0.0); // Bright red color - to indicate errors
// If the ChannelData has a valid pointer to a VolumeData object (vd), and this object points to a valid
// TexData object (tex), which itself points to a valid BaseTag object (link)
if (pChannelData && pChannelData->vd && pChannelData->vd->tex && pChannelData->vd->tex->link)
// Then, I can get the texture tag linked to the material hosting my custom shader
// ..., and that also means that I can get any UserData associated with this texture tag (if present)
const BaseContainer *pUserData=pTag->GetDataInstance()->GetContainerInstance(ID_USERDATA);
// If user data is present
// ..., and it contains a Vector data item at MY_COLOR_ID with a color (i.e., Vector) property type
// Return the color vector from the user data to customize the color of the shader on a per texture tag
// basis (or COLOR_VECTOR_NOT_PRESENT, bright red, if the texture tag does not have this user data item)
This seems to work, but it only works during renders. I cannot, for the life of me, figure out how to get the texture tag of an object that is assigned a material using my custom shader for purposes of display in Cinema 4d's Viewports, regardless of the Viewport's Display Shading setting. I have tried, seemingly, every possible path through the pointers available in the objects passed by pointer to the Output() function.
I have read many reports online that code like this: pChannelData->vd->op->link->GetCacheParent() , assuming of course that all pointers along the way are valid, gets the object that hosts the texture tag linked to the material hosting the custom shader, but this does not work in S22 - the latest C4D release. I only get some internal "virtual" or perhaps "cached" object named "Object" back and every object path through the hierarchy that I have tried using that link pointer seems to get me no closer - it's always BaseObject objects named "Object" which are clearly not my object but some sort of cached copies. I am trying to get my actual object (e.g., an object named "Cube" in the Object Manager's node hierarchy), so that I can access its texture tag and get the per object per texture tag specific user data I am looking for in order to properly set the color that my shader returns from its Output() function when called for that object to display it in a Viewport.
Is there any possible way to get from a shader to a texture tag (or a BaseObject that hosts that tag) during calls to Output() while an object is being drawn in a Viewport, so that in Output() I can use some user data from this texture tag to set the color vector that my custom shader returns, similar to how I do it in the code above which works for renders. Perhaps my understanding is flawed and the Output() function is not what Viewports use to texture objects, but then where is the correct place to do this in my ShaderData derived custom shader and what code do I need to do this?
As shown in the shader usage example in this post, my custom shader would enable me to get each object's color in the Viewport to match the color as specified in the user data associated with its assigned Texture tag and make each object get colored differently, assuming my custom shader is used in a material's Color layer's Texture slot.
Of course, the ultimate goal of this exercise is to be able to use the custom shader in any place of a material where a shader can be placed (i.e., any Texture property in any channel of the material) and be able to customize the material on a per object texture tag user data basis.
Update with additional info:
Over the weekend, it has become clear to me that my custom shader's override of the ShaderData::Draw() function, gets called when an object that is assigned a material with my shader in a Texture Tag, gets refreshed and/or drawn in a Cinema 4D Viewport. What is not clear at all is how do I communicate any object texure tag specific customizations from my shader during the call to Draw(), assuming that this is the right place to do that for purposes of Viewport display of an object's material.
To be clear, unlike the Output() member function which returns a Color Vector containing the color calculated by my custom shader, based on the input parameters supplied to Output(), namely: BaseShader* sh, ChannelData* cd, the Draw() function only returns a Bool success or failure value.
BaseShader* sh, ChannelData* cd
It is not clear to me, assuming of course that the Draw() function is the right place to make per object per texture tag changes/updates on behalf of an object and based on my shader's custom rules, how I go about communicating this information back to Cinema 4D, so that the object can be properly drawn in a Viewport.
Perhaps this can be done by making changes to some member variables of the objects, pointers to which are passed to Draw(). That is: BaseShader * sh, BaseObject *op, BaseTag *tag, BaseDraw *bd, BaseDrawHelp *bh .
BaseShader * sh, BaseObject *op, BaseTag *tag, BaseDraw *bd, BaseDrawHelp *bh
If this is the right way to do it. It is not clear to me what changes to make to what members of those objects, or what member functions to call using the pointers to the various objects passed to Draw(), in order to send a Color vector back to Cinema 4D so that a particular object in the scene can be drawn in a custom way using a material that holds my shader in the Texture property of its Color layer.
Also, wanted to share the result of following the ideas in this thread on a parametric Cissoid object being deformed by a Twist modifier.
The green stars and blue circles indicate the deformed spline points/knot positions, respectively, and the magenta stars and orange circles indicate the pre-deformation spline points/knot positions, respectively. (Types of) Intermediate Points were set to Natural and Number was set to 4, resulting in about four times as many knots (circles) as spline points (stars), as can be seen from the image. You can see some of the effects of the Natural type on the rightmost vertically oriented curve at the bottom of the deformed Cissoid coming to its lowest point in the image. As a result of being relatively straight, it has a more sparse arrangement of spline points (and knots) than the more curved vertical portion immediately to its left.
The varying sizes of the circles/star point markers in the image are not due to perspective distortion, at least not in total, but rather due to them being scaled based on the distance of surrounding spline points and knots, respectively, so as to try to reduce collisions among the markers with respect to other markers of the same type (the null shape radii were multiplied by 40% of the smaller of the distances between a point and its surrounding points resulting in a tiny gap between markers of the same type, at least when viewed along the normal of any particular point):
@m_adam Hi Maxime, thanks for your detailed reply. I had a feeling that EventAdd() performed an asynchronous operation (i.e., placed an event on the event queue) and therefore it didn't matter, since control of the main thread would not be relinquished until both EndUndo() were called (regardless of sequence). However, as C4DS and you yourself pointed out, it makes more sense to call EndUndo() prior to EventAdd().
This is not only true from a safety perspective, but the calls to StartUndo() and EndUndo() can be wrapped in a Python Context Manager, along exception handling and cleanup to be done automatically and correctly when a scope is exited. If one is going to undo whatever operation in case of an error, anyways (e.g., as part of said exception handling), it makes sense to call c4d.EventAdd() after we know that the entire action was successfully performed.
I am going on the assumption that if my code performs some action inside of a StartUndo() / EndUndo() sequence, possibly consisting of multiple sub-actions and changes resulting in multiple AddUndo() calls, then sees mid-action that there is an and it cannot continue, and assuming the Start/End undo sequence is wrapped in a Python context manager class. Python will call EndUndo() on my behalf, as part of exiting the block governed by the context manager and then in the exception handler, I can perform an undo of the last action, undoing whatever sub-action did perform successfully (and added their AddUndo() pieces). Since this will hopefully leave everything in the same state that it was before the action was started, I am going to assume that there is no point in calling c4d.EventAdd() after the undo of the action is performed in my exception handler.
To summarize, here is an example scenario:
with MyUndoContextManager(doc) as undo_manager: # Calls StartUndo() as part of its Context Manager __enter__() method
# undo_manager will make the calls to AddUndo() based on our actions
DoOneLastThingAndCallAddUndoOnTheDoc(undo_manager); # Oh, oh, fails and throws exception
# Note: EndUndo() will get called as part of the implicit call to MyUndoContextManager's __exit__() method
# which will automatically get called when we exit this block due to the exception being thrown
c4d.EventAdd(); # This code will not get reached due to exception being thrown above
# An error occurred, get back to the initial state prior to the action
# Undo whichever action sub-steps were performed successfully and AddUndo()s got called for, if any
# Since the undo_manager object no longer exists, call DoUndo() on the doc object, directly
# Since, we reverted to the initial state before the action, there is no need to call c4d.EventAdd(), right?