Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode.
Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).
Hi @mocoloco for such a parameter, using the bracket operator will be more easy. Find bellow the code within a Python Scripting tag, this work as expected.
from typing import Optional import c4d doc: c4d.documents.BaseDocument # The document evaluating this tag op: c4d.BaseTag # The Python scripting tag flags: int # c4d.EXECUTIONFLAGS priority: int # c4d.EXECUTIONPRIORITY tp: Optional[c4d.modules.thinkingparticles.TP_MasterSystem] # Particle system thread: Optional[c4d.threading.BaseThread] # The thread executing this tag def main() -> None: obj = op.GetObject() previousLightColor = obj[c4d.LIGHT_COLOR] print(previousLightColor) newLightColor = c4d.Vector(0.0, 1.0, 1.9) obj[c4d.LIGHT_COLOR] = newLightColor
But if you really want to use G/SetParameter you can do it with the code bellow. The issue with your code is that DESCFLAGS_GET_PARAM_GET mean to be used within the implementation to notify the system that this parameter have been already retrieved so no need to retrieve it again. So if you pass this flag as an input therefor you will have nothing in return.
DESCFLAGS_GET_PARAM_GET
obj = op.GetObject() previousLightColor = obj.GetParameter(c4d.DescID(c4d.DescLevel(c4d.LIGHT_COLOR, c4d.DTYPE_VECTOR, 0)), c4d.DESCFLAGS_GET_NONE) print(previousLightColor) newLightColor = c4d.Vector(0.0, 0.0, 1.9) obj.SetParameter( c4d.DescID(c4d.DescLevel(c4d.LIGHT_COLOR, c4d.DTYPE_VECTOR, 0)), newLightColor, c4d.DESCFLAGS_SET_NONE )
Cheers, Maxime.
Btw if you install the Cinema 4D Vs Code extension, pyp extension should be added as Python.
How to make VS Code treat a file extensions as a certain language?
Hi @FSS first of all happy new year !
And I'm glad that you found a solution however even if this is in the documentation we don't explicitly support PyCharm and only provide a dummy package to have autocompletion working with any Python instance. But C4dpy is not supported by all IDE and we can't provide any support there.
If you want to have a better IDE workflow we released a plugin for Visual Studio Code, requiring a Cinema 4D plugin and a VS Code plugin.
Finally I moved this topic to general talk as it this is nothing related to Cinema 4D API.
Hi you need to insert the video post as shown in the C++ manual - Redshift Renderer - Set Render Engine to Redshift, adapting it to Python should be pretty straightforward.
Have a nice weekend. Cheers, Maxime.
Ok nervermind it is possible to overwrite the ASSET_PREVIEWIMAGEURL in Python it's just that AssetDescription.StoreUrlMetaData, AssetDescription.StoreMetaData, AssetDescription.EraseMetaData are bugged in Python. So find bellow a workaround for that (it will be fixed in the next version and StoreUrlMetaData will work out of the box).
ASSET_PREVIEWIMAGEURL
AssetDescription.StoreUrlMetaData
AssetDescription.StoreMetaData
AssetDescription.EraseMetaData
StoreUrlMetaData
import c4d import maxon # --- AssetDescriptionInterface monkey patching fix for AssetDescriptionInterface.StoreUrlMetaData() -------------------------- @maxon.interface.MAXON_INTERFACE(maxon.consts.MAXON_REFERENCE_COPY_ON_WRITE, "net.maxon.interface.assetdescription") class AssetDescriptionInterface(maxon.AssetBaseInterface): @maxon.interface.MAXON_FUNCTION_EXTEND("net.maxon.interface.assetdescription.StoreUrlMetaData") def StoreUrlMetaData(self, metaId, source, kind): return self.GetRepository().StoreUrlMetaData(self, metaId, source, kind) maxon.AssetDescriptionInterface.StoreUrlMetaData = AssetDescriptionInterface.StoreUrlMetaData # --- AssetDescriptionInterface monkey patching fix for AssetDescriptionInterface.StoreUrlMetaData() -------------------------- def main(): repository = maxon.AssetInterface.GetUserPrefsRepository() assetId = maxon.Id("Id of the asset") lst = repository.FindAssets("net.maxon.node.assettype.nodetemplate", assetId, maxon.Id(), maxon.ASSET_FIND_MODE.LATEST) assetDescription = lst[0] currentLanguage = maxon.Resource.GetCurrentLanguage() name = assetDescription.GetMetaString(maxon.OBJECT.BASE.NAME, currentLanguage) print(name) metadata = assetDescription.GetMetaData() imagepUrl = maxon.Url("C:/Users/m_adam/picture.png") # Stores the Url Meta Data, if there was no preview stored previously (aka the default png file for the given category) then it will be refreshed right away # Otherwise Cinema 4D need to be restarted maxon.AssetDescriptionInterface.StoreUrlMetaData = AssetDescriptionInterface.StoreUrlMetaData assetDescription.StoreUrlMetaData(maxon.ASSETMETADATA.ASSET_PREVIEWIMAGEURL, imagepUrl, maxon.AssetMetaData.KIND.PERSISTENT) # Turn off automatic preview metaProperties.Set(maxon.ASSET.METAPROPERTIES.BASE.AUTOMATICPREVIEW, False) assetDescription.StoreMetaData(maxon.ASSETMETADATA.MetaProperties, metaProperties, maxon.AssetMetaData.KIND.PERSISTENT) if __name__ == '__main__': main()
However the limitation for InvalidateCache not being exposed is still there and as said will need a Cinema 4D update to make it work, so for the moment the only way to see new icons is to restart Cinema 4D.
I confirm that overwriting ASSET_PREVIEWIMAGEURL does not work in Python, and InvalidateCache is not exposed to the Python API and would need some internal change to the Cinema 4D code source to make it public so there is no possible workaround for you for the moment.
@baca said in Cinema 4D R2023 - c4d.plugins.RegisterObjectPlugin issue:
Any suggestion how to properly handle that kind of issues -- just catch exception and warn user using message box somehow?
Correct you can catch the OsError and check if there is the word 50 in the exception message.
Would it be reasonable to switch to NodeData from ObjectData -- It depends of your plugin if this is at the end an ObjectData then it's irrelevant, if this is just an object that you store in a list in memory or in a custom GeListHead then NodeData is perfectly relevant.
NodeData
ObjectData
does RegisterNodePlugin has separate 50 plugins registrations?
RegisterNodePlugin
Yes each type have 50 plugins.
What's the logic behind plugin initialization order -- alphabetical, date-based, random?
Folders from g_additionalModulePath are iterated by alphabetical order and each files are then iterated by alphabetical order too.
As explained in the post I linked the limitation is about Python and for each plugin type you have a limitation of 50 (call to RegisterXXXPlugin), wherever they are installed. So you can have 50 ObjectData and 50 TagData. But you can't have 51 ObjectData (the number of C++ plugins does not count). And there is no workaround.
The limitation is there since R11. Cheers, Maxime.
Hi @baca there was always the limitation of 50 python plugins by plugin types. So the only solution is to remove some. This topic have been already discussed here.
Hi @till-niese you are indeed right, the method was updated in C++ but not in Python although the dynamic handling of the python Maxon API make it possible to use it but the maxon.AssetRepositoryTypes.AssetDatabase was not exposed. I will fix it for the next version and properly make the BackgroundEntryTuple optional.
maxon.AssetRepositoryTypes.AssetDatabase
BackgroundEntryTuple
In the meantime you can use the script bellow. GetRepositoryName also changed and is now taking 2 arguments.
GetRepositoryName
#coding: utf-8 import c4d import maxon # The command id for the Asset Browser. CID_ASSET_BROWSER = 1054225 @maxon.MAXON_REGISTRY("net.maxon.registry.assetrepositorytypes") class AssetRepositoryTypes(maxon.Registry): AssetDatabase = maxon.MAXON_DECLARATION("net.maxon.assets.repositorytype.database") def CreateRepositories(): """Creates repositories for all user databases. Doing this is usually not necessary for performing light- to medium-sized asset operations, and the user preferences repository can then be used instead. Only when there is a substantial amount of assets that must be processed, a repository should be constructed to limit the search space for search operations. The method CreateRepositoryFromUrl() used in this example can also be used to create a repository and its underlying database from scratch when the provided URL points to location where no database has been established yet. """ # Wait for all asset databases to be loaded, abort when this is not possible. if not maxon.AssetDataBasesInterface.WaitForDatabaseLoading(): return RuntimeError("Could not load asset databases.") # Get the default language of Cinema 4D (en-US) for retrieving the repository names. defaultLanguage = maxon.Resource.GetDefaultLanguage() # Iterate over all currently mounted databases and create an asset repository for each of them. # Doing this is usually not necessary as user asset databases are automatically part of the # the user preferences repository which is easier to retrieve. Creating a repository for a # specific user asset database can be useful to speed up asset searches. for database in maxon.AssetDataBasesInterface.GetDatabases(): # Create a unique identifier for the repository. rid = maxon.AssetInterface.MakeUuid(str(database._dbUrl), True) # Repositories can be composed out of other repositories which are called bases. In this # case no bases are used to construct the repository. But with bases a repository for all # user databases could be constructed for example. bases = maxon.BaseArray(maxon.AssetRepositoryRef) # Create a writable and persistent repository for the database URL. If #_dbUrl would point # to a location where no database has been yet stored, the necessary data would be created. assetDb = AssetRepositoryTypes.AssetDatabase assetDb = maxon.RegistryInterface.FindEntryValue(assetDb._registry, assetDb._ids) repository = maxon.AssetInterface.CreateRepositoryFromUrl( rid, assetDb, bases, database._dbUrl, True, False, False, None) if not repository: raise RuntimeError("Repository construction failed.") # Access some properties of the newly created repository. repoId = repository.GetId() isWriteable = repository.IsWritable() name = repository.GetRepositoryName(defaultLanguage, True) print(f"{repository} ({name}): id - {repoId}, writeable: {isWriteable}") if __name__ == "__main__": CreateRepositories() c4d.EventAdd()
Hi @InterfaceGuy, this forum is only about Cinema 4D Scripting and plugins, please reach the user support via the maxon website.
Hi @cgweasel hope you had a nice Christmas time
Regarding your question, this has been answered in this post Change the ColorSpace of Redshift Texture Node? Then you can find example about nodes graph in our GitHub repository, And I would advice you to look at modify_port_value.
I think with that you have all the information needed to make it work, if you struggle please let me know and I will be happy to help you. For the future please specify which node system you are using, since you mentioned GvNode I guessed that you used xpresso.
Hi @cgweasel, to retrieve the selected node, like all BaseList2D (BaseOject,BaseTag,BaseMaterial, etc...) you can know there selection state with the method node.GetBit(c4d.BIT_ACTIVE).
BaseList2D
node.GetBit(c4d.BIT_ACTIVE)
So find bellow the complete code to do what you want:
import c4d def main(): # Checks if selected material is valid mat = doc.GetActiveMaterial() if not mat or not mat.CheckType(1036224) : raise ValueError("There is no xpresso redshift material selected") # Retrieve the output nodes, which is also the first node in the node hierarchy. # Therefor we can iterate them as usual BaseList2D node = mat[c4d.REDSHIFT_GRAPH_NODES] while node: # Check if this is a texture node and its selected if node[c4d.GV_REDSHIFT_SHADER_META_CLASSNAME] == 'TextureSampler' and node.GetBit(c4d.BIT_ACTIVE): # Set to Raw, to know the Id and the value I left click on the Filename parameter, User Interface -> Show SubChannel # Then I dragged the ColorSpace parameter to the python console node[c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0,c4d.REDSHIFT_FILE_COLORSPACE] = "RS_INPUT_COLORSPACE_RAW" node = node.GetNext() c4d.EventAdd() if __name__=='__main__': main()
Hi @cgweasel I forked your topic, in the future please create a new topic each time you have a question with a topic that is unrelated to the current discussion.
With that said there is no 100% reliable way to do it, the best way would be to store the neighbor hierarchy (GetUp,GetPred,GetNext) and then add an execution step as Initial so within the Execute method you can check for these objects.
Find bellow and example adapted from py-look-at-camera example. The important methods to check are:
__init__
AddToExecution
isHierarchyChanged
updateCachedHierarchy
Execute
priority == c4d.EXECUTIONPRIORITY_INITIAL
import os import c4d # Be sure to use a unique ID obtained from www.plugincafe.com PLUGIN_ID = 1028284 class LookAtCamera(c4d.plugins.TagData): """Look at Camera""" def __init__(self): self._up = None self._pred = None self._next = None def Init(self, node): """Called when Cinema 4D Initialize the TagData (used to define, default values). Args: node (c4d.GeListNode): The instance of the TagData. Returns: True on success, otherwise False. """ self.InitAttr(node, bool, c4d.PYLOOKATCAMERA_PITCH) node[c4d.PYLOOKATCAMERA_PITCH] = True pd = c4d.PriorityData() if pd is None: raise MemoryError("Failed to create a priority data.") pd.SetPriorityValue(c4d.PRIORITYVALUE_CAMERADEPENDENT, True) node[c4d.EXPRESSION_PRIORITY] = pd return True def AddToExecution(self, tag, prioList): """Called By Cinema 4D to determine when in the Execution Pipeline the Execute method of this tag should be called. Args: tag (c4d.BaseTag): The instance of the TagData. prioList (c4d.plugins.PriorityList): The priority list to add your tag’s execution points to. Returns: True if the hierarchy changed, otherwise False. """ # Retrieve the user defined priority storedPriority = tag[c4d.EXPRESSION_PRIORITY] storedPriorityValue = storedPriority.GetPriorityValue(c4d.PRIORITYVALUE_MODE) + \ storedPriority.GetPriorityValue(c4d.PRIORITYVALUE_PRIORITY) # Add an execution during the Initial phase, this is when we will check for hierarchy change prioList.Add(tag, c4d.EXECUTIONPRIORITY_INITIAL, 0) # Add the user defined execution phase, therefor the Execute method will be called 2 times. prioList.Add(tag, storedPriorityValue, 0) return True def isHierarchyChanged(self, obj): """Check if any of the cached neighbor hierarchy is different from the actual one. Args: obj (c4d.BaseObject): The host object to compare the hierarchy from. Returns: True if the hierarchy changed, otherwise False. """ up = obj.GetUp() if up != self._up: return True pred = obj.GetPred() if pred != self._pred: return True next = obj.GetNext() if next != self._next: return True return False def updateCachedHierarchy(self, obj): """Update the cached neighbor hierarchy. Args: obj (c4d.BaseObject): The host object to retrieve the hierarchy from. """ self._up = obj.GetUp() self._pred = obj.GetPred() self._next = obj.GetNext() def Execute(self, tag, doc, op, bt, priority, flags): """Called by Cinema 4D at each Scene Execution, this is the place where calculation should take place. Args: tag (c4d.BaseTag): The instance of the TagData. doc (c4d.documents.BaseDocument): The host document of the tag's object. op (c4d.BaseObject): The host object of the tag. bt (c4d.threading.BaseThread): The Thread that execute the this TagData. priority (EXECUTIONPRIORITY): Information about the execution priority of this TagData. flags (EXECUTIONFLAGS): Information about when this TagData is executed. """ if priority == c4d.EXECUTIONPRIORITY_INITIAL: if self.isHierarchyChanged(op): self.updateCachedHierarchy(op) print("Hierarchy Changed") # We don't want to execute the logic of our tag during this phase except if the user asked for it storedPriority = tag[c4d.EXPRESSION_PRIORITY] storedPriorityValue = storedPriority.GetPriorityValue(c4d.PRIORITYVALUE_MODE) + \ storedPriority.GetPriorityValue(c4d.PRIORITYVALUE_PRIORITY) if storedPriorityValue != c4d.EXECUTIONPRIORITY_INITIAL: return c4d.EXECUTIONRESULT_OK # Retrieves the current active base draw bd = doc.GetRenderBaseDraw() if bd is None: return c4d.EXECUTIONRESULT_OK # Retrieves the active camera cp = bd.GetSceneCamera(doc) if bd.GetSceneCamera(doc) is not None else bd.GetEditorCamera() if cp is None: return c4d.EXECUTIONRESULT_OK # Calculates the position to target local = cp.GetMg().off * (~(op.GetUpMg() * op.GetFrozenMln())) - op.GetRelPos() # Calculates the rotation to target hpb = c4d.utils.VectorToHPB(local) if not tag[c4d.PYLOOKATCAMERA_PITCH]: hpb.y = op.GetRelRot().y hpb.z = op.GetRelRot().z # Defines the rotation op.SetRelRot(hpb) return c4d.EXECUTIONRESULT_OK if __name__ == "__main__": # Retrieves the icon path directory, _ = os.path.split(__file__) fn = os.path.join(directory, "res", "tpylookatcamera.tif") # Creates a BaseBitmap bmp = c4d.bitmaps.BaseBitmap() if bmp is None: raise MemoryError("Failed to create a BaseBitmap.") # Init the BaseBitmap with the icon if bmp.InitWith(fn)[0] != c4d.IMAGERESULT_OK: raise MemoryError("Failed to initialize the BaseBitmap.") c4d.plugins.RegisterTagPlugin(id=PLUGIN_ID, str="Py - LookAtCamera", info=c4d.TAG_EXPRESSION | c4d.TAG_VISIBLE, g=LookAtCamera, description="Tpylookatcamera", icon=bmp)
Side note, we will be away the 26th and 27th December, so don't be surprise by the delay if you have follow-up questions, for more information see No support on 26/12 and 27/12.
Hello everybody,
On the 26th and 27th of December, the SDK support team will be enjoying their Christmas vacations, which means that we will not be providing support services during these two days. We apologize for any inconvenience this may cause and wish you all the best. We’ll get back with standard support levels on Wednesday, December 28.
We wish a Merry Christmas and a Happy New Year to everybody in this community. For us it was a pleasure to work with you and we are already looking forward to 2023 to see the cool stuff, you can come up with.
See you in 2023, The MAXON SDK Team.
Hi @FSS there is no such built-in function to detect this things, however there is the command MCOMMAND_ALIGNNORMALS that can be used to align the normals.
MCOMMAND_ALIGNNORMALS
settings = c4d.BaseContainer() # Settings settings[c4d.MDATA_ALIGNNORMALS_USERNORMALS] = True # Align user normals. res = c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_ALIGNNORMALS, list=[op], mode=c4d.MODELINGCOMMANDMODE_ALL, bc=settings, doc=doc) c4d.EventAdd()
To know if a face is inwards or outwards you can cast a ray with GeRayCollider having origin in that vertex and direction of vertex normal. Find how many times ray intersects the current polygon object. If it intersects it even number of times ( including 0 ) - normal is facing outwards.
Hi @wanderingstan first of all welcome in hte plugin cafe community. Please create your own topic, it's easier for us to track it. moreover you don't need to post on different topic the same subject. We usually answers within a day. Finally please read our support guideline.
Regarding your question here it work as expected when I run the next code in the Python Console. doc.GetActiveRenderData().GetDataInstance().SetFilename(c4d.RDATA_PATH, r"test")
doc.GetActiveRenderData().GetDataInstance().SetFilename(c4d.RDATA_PATH, r"test")
One crucial information to share when you give us code sample is the context (the python console, a plugin ,the script manager, etc...) In your case at least the second one I expect it to run from the Python Console, so it should be fine.
Regarding your issue I would say please try to restart Cinema 4D, while you tested you may have corrupted the c4d python module by writing something like c4d.RDATA_PATH = "my filename".
c4d.RDATA_PATH = "my filename"
Hi @cgweasel unfortunately this is not exposed, Xpresso is no longer developed except for critical issues, it is very likely that this will not be added to the API.