The Maxon SDK Team is currently short staffed due to the winter holidays. No forum support is being provided between 15/12/2025 and 5/1/2026. For details see Maxon SDK 2025 Winter Holidays.
  • Python: Global Variable vs Plugin ID

    Moved python sdk
    6
    0 Votes
    6 Posts
    2k Views
    M
    Hi while previous answers give some hints I would like to provide more in-depth answers to these questions. First, what is a global variable? In Python, a global variable is a variable that is stored in the dictionary returned by "globals()". The globals() function returns a dictionary that is local to the current Python Scope. Note that a Python scope only lives for a given time and is not persistent. This means it doesn't save its state or current data to a file so if you restart Cinema 4D, it starts with a fresh new Python Scope) but as long as the host object of the Python Scope is alive the data will be stored. So few things to know about Python implementation within Cinema 4D. Each BaseList2D that implements a Python implementation (Python Generator, Python Scripting Tag, Python Xpresso, Field Layer) has its own Python Scope. That means if I have two Python Generator they don't share the same Python Scope. But they both have a distinct one. Now in a case of a plugin, it's a bit tricky, if you use a global variable let's say in an ObjectData. The global variable will be available for all instances (BaseObject) of this ObjectData. This is because a BaseObject is an instance of an ObjectData implementation. And since there is only one implementation (your ObjectData that uses the global variable) they all use the same global variable. Now when you refer to plugin ID, it's a kind of term abuse. A plugin ID is a unique ID (aka a number who is unique into Cinema 4D context). And can be retrieved from https://developers.maxon.net/forum/pid (you must be logged). So a Plugin ID can be used to register a plugin. When you write BaseObject(100000) Cinema 4D will look at the ObjectData that is registered at the ID 100000 and create a BaseObject that will implement this particular ObjectData. But you can also use a PluginId (or aka a Unique ID) to register a data into a BaseContainer. But what is a BaseContainer? BaseDocument, Objects, tag and pretty much everything a user can find in C4D inherit from BaseList2D, and get a BaseContainer. A BaseContainer is a kind of array (or a dictionary in python), where data are stored according to a given ID. This BaseContainer is owned by the host BaseList2D (seems logical but it's an important point). This way Cinema 4D has a generic way of storing and reading data. So Cinema 4D can automate few things such as saving automatically the data stored in this BaseContainer into a File (aka Cinema 4D File) and read them back when loading the Cinema 4D file. So back to our topic, if we assign a particular value to a "Plugin ID/Unique ID" you can assume that nothing within in Cinema 4D will overwrite this particular value. (Third-party developer can access this data however and may erase them, so that's why we recommend using a Unique ID when you store data in a BaseContainer, this way you are sure to not erase other people data and you're is ok). Of course, this works because everyone plays the game if I can tell. Now back to the initial questions. What's the use case of a global variable in Python in Cinema 4D ecosystem? Storing data across multiple frames. (Can also be done with a BaseContainer, but you have to convert to a DataType that BaseContaienr accept, so list, dict are not supported so you have to "bake" which is slow) Data are only alive for the current lifetime of the host and in maximum the Cinema 4D session. What's the use case of data stored in a BaseContainer in Cinema 4D? Storing data across multiple frames. (See the previous point in some case it may be ineffective). Storing persistent data over multiple Cinema 4D sessions (the data will be stored in the host, e.g. the Python Generator Object by itself, so if you open the file in Cinema 4D, the Python Generator will restore the data in the BaseContainer). Exposing data to others (Maybe other Objects want to know the current value stored in a particular Python Generator Object). Finally, BaseContainer should be primarily chosen but as stated in some condition it makes more sense to use a global variable. And here is how to define a global variable within a Python Generator: import c4d # Checks if the test variables exist in the global dict, if not assign a value of 10 global test if 'test' not in globals(): test = 10 def main(): global test print test return c4d.BaseObject(c4d.Ocube) I hope it answers your questions. Additionally please read BaseContainer Manual. Cheers, Maxime.
  • HUD on OpenGl previews

    python
    10
    0 Votes
    10 Posts
    2k Views
    A
    @m_adam Ah OK great will test it and get back to you, Thanks sir.
  • Direction vector

    3
    0 Votes
    3 Posts
    643 Views
    WickedPW
    Thanks Ricky, but I'm not sure if that's what I'm after. That looks like normal object rotation, but I'm trying to make a direction vector. The example given in a previous post did something like this: CreateRay(Ray* dst,Real x,Real y) { CameraMatrix = MyCam->GetMg(); Vector H = CameraMatrix.v1; Vector V = CameraMatrix.v2; Vector A = CameraMatrix.v3; Vector direction_vector = H + V + A; // how do I "add" 4.5° yaw to the direction_vector? // similarly for pitch dst->v = direction_vector; dst->p = CameraMatrix.off; } How do I "add" 4.5 degrees to the yaw with something like this (taking into account the camera's rotation as well)? I'd like to see the math so I can play around with that. If it's easier, think of it like this - I'm trying to create the standard camera ray without any lens effects (trying to keep it simple). How is the standard camera ray done? WP.
  • Can I reset the keyframe of an object through Python script?

    2
    3
    0 Votes
    2 Posts
    816 Views
    M
    Hi, @happygrass_cn first of all welcome in the plugincafe community. While you have deleted your topic (probably because you solved it). I had a solution that explores a few areas in Cinema 4D that are not so well documented so I restored it. So my solution uses a cloner with 2 clones (one animated and the other one similar but without animation). I also define a thinking particle channel data( a boolean "isMoving"). A python effector will read this attribute and according to this attribute will affect the displayed cloned object to be either the animated one or not. Of course, this does not provide interpolation between animation so it can look a bit odd. Finally in Python SetPData does not support boolean, so "IsMoving" is not a boolean (DTYPE_BOOL) but an integer(DTYPE_LONG) that I thread as a boolean. The Python generator does the next things: Thinking Particles Creation. Custom Data Channel Creation/Assignation. Movement of particles. import c4d def GetParticleSystemAndRootGroup(): # Retrieves the Particles System of the current document pSys = doc.GetParticleSystem() if pSys is None: raise RuntimeError("op is none, please select one object.") # Retrieves the Root group (where all particles belongs as default) rootGrp = pSys.GetRootGroup() if rootGrp is None: raise RuntimeError("Failed to retrieve root group of tp master system.") return pSys, rootGrp def GetIsMovingChannelId(): # Retrieves the Particles System and the root group of the current document pSys, rootGrp = GetParticleSystemAndRootGroup() for x in xrange(pSys.NumDataChannels()): if pSys.DataChannelName(x) == "isMoving(Integer)": return x return False def CreateParticle(count): # Retrieves the Particles System and the root group of the current document pSys, rootGrp = GetParticleSystemAndRootGroup() if pSys.NumParticles() >= count: return # Allows each particles to get a custom colors rootGrp[c4d.PGROUP_USE_COLOR] = False # Creates 90 Particles particlesIds = pSys.AllocParticles(count) if not particlesIds: raise RuntimeError("Failed to create X TP particles.") # Check if the "isMoving"" Channel data already exist and if not create it # This Boolean "isMoving" will store the moving state of a particle if GetIsMovingChannelId() is False: pSys.AddDataChannel(c4d.DTYPE_LONG, "isMoving") isMovingId = GetIsMovingChannelId() if isMovingId is False: raise RuntimeError("Failed to retrieve IsMoving particle Data") # Assigns position and colors for each particles for particleId in particlesIds: # Checks if particles ID is ok if particleId == c4d.NOTOK: continue # Defines a lifetime of 1000 frame for each particles pSys.SetLife(particleId, c4d.BaseTime(1000)) # Calculates a position sin, cos = c4d.utils.SinCos(particleId) pos = c4d.Vector(sin * 300.0, cos * 300.0, particleId * 30.0) # Assigns position pSys.SetPosition(particleId, pos) # Calculates a color hsv = c4d.Vector(float(particleId) * 1.0 / count, 1.0, 1.0) rgb = c4d.utils.HSVToRGB(hsv) # Assigns color pSys.SetColor(particleId, rgb) # Assigns a "freeze" data to indicate either the particle is stoped or not pSys.SetPData(particleId, isMovingId, False) return particlesIds def MoveParticle(frame): # Retrieves the Particles System and the root group of the current document pSys, rootGrp = GetParticleSystemAndRootGroup() isMovingId = GetIsMovingChannelId() if isMovingId is False: raise RuntimeError("Failed to retrieve IsMoving particle Data") for particleId in xrange(pSys.NumParticles()): # If particle don't belong to the root group if pSys.Group(particleId) != rootGrp: continue sin, cos = c4d.utils.SinCos(particleId + (frame/40.00)) pos = c4d.Vector(sin * 300.0, cos * 300.0, particleId * 30.0) # Assigns position pSys.SetPosition(particleId, pos) # Assigns a "freeze" data to indicate either the particle is stoped or not pSys.SetPData(particleId, isMovingId, True) def FreezeParticle(): # Retrieves the Particles System and the root group of the current document pSys, rootGrp = GetParticleSystemAndRootGroup() isMovingId = GetIsMovingChannelId() if isMovingId is False: raise RuntimeError("Failed to retrieve IsMoving particle Data") for particleId in xrange(pSys.NumParticles()): # If particle don't belong to the root group if pSys.Group(particleId) != rootGrp: continue # Assigns a "freeze" data to indicate either the particle is stoped or not pSys.SetPData(particleId, isMovingId, 0) def main(): currentFrame = doc.GetTime().GetFrame(doc.GetFps()) if currentFrame == 0: CreateParticle(20) # Don't move particles from frame 150 to 250 elif 150 <= currentFrame <= 250: if currentFrame == 150: FreezeParticle() else: MoveParticle(currentFrame) The Python Effector set in Full Control does the next things: Read the data for each particle of the Custom Data Channel Defines the cloned used according to this value import c4d def GetParticleSystemAndRootGroup(): # Retrieves the Particles System of the current document pSys = doc.GetParticleSystem() if pSys is None: raise RuntimeError("op is none, please select one object.") # Retrieves the Root group (where all particles belongs as default) rootGrp = pSys.GetRootGroup() if rootGrp is None: raise RuntimeError("Failed to retrieve root group of tp master system.") return pSys, rootGrp def GetIsMovingChannelId(): # Retrieves the Particles System and the root group of the current document pSys, rootGrp = GetParticleSystemAndRootGroup() for x in xrange(pSys.NumDataChannels()): if pSys.DataChannelName(x) == "isMoving(Integer)": return x return False def main() : moData = c4d.modules.mograph.GeGetMoData(op) if moData is None: return False cnt = moData.GetCount() mdClone = moData.GetArray(c4d.MODATA_CLONE) hasField = op[c4d.FIELDS].HasContent() # Retrieves the Particles System and the root group of the current document pSys, rootGrp = GetParticleSystemAndRootGroup() # Retrieve the isMovingData channel ID isMovingId = GetIsMovingChannelId() if isMovingId is False: raise RuntimeError("Failed to retrieve IsMoving particle Data") # Maybe there is more particles allocated than what's is used in the cloner (aka mutlipe particle group) # So we loop over them cloneId = 0 for particleId in xrange(pSys.NumParticles()): # If particle don't belong to the root group if pSys.Group(particleId) != rootGrp: continue if cloneId > cnt: raise RuntimeError("There is more cloneID than allowed") # Retrieve the internal IsMoving Data and check if the particle is moving, isMoving = pSys.GetPData(particleId, isMovingId) if isMoving == 1: # Defines the float value representing the cloned 0 = First child, 1 = last child mdClone[cloneId] = 0.0 else: mdClone[cloneId] = 1.0 cloneId +=1 moData.SetArray(c4d.MODATA_CLONE, mdClone, hasField) return True And the attached scene with everything together. generatorTp.c4d Cheers, Maxime.
  • OSC Plugin or simple Xpresso Script

    4
    0 Votes
    4 Posts
    1k Views
    G
    Hey Guys! Thank you very much for the insight! All of this makes sense, it helps me understand a little more about how the node part works! I really appreciate the time on this and thanks again for the help! I hope y'all are doing well! Cheers! MattG
  • Automate Turbosquid uploading with Python script

    Moved r21 python jobs
    9
    0 Votes
    9 Posts
    2k Views
    CairynC
    @JasenLux said in Automate Turbosquid uploading with Python script: Even if in the morning I've got a bunch or renders done over night - and it's all automated - I'd be very happy. A more or less automated rendering could be achieved by using the native Render Queue if you create your files with some rigid discipline. Start out by creating a base C4D file that contains the necessary cameras and render settings with some appropriate naming. The render settings should be hierarchically ordered, with the basic setting on top and all the different resolutions as children of that setting, with only the necessary settings overwritten. If there are TurboSquid requirements that you cannot achieve with render settings, you can create some takes that offer the necessary changes (this might be the most difficult part, as you may need to overwrite parameters in tags that you don't have in the basic file, but only create with your project, but that very much depends on what you really need to change, I am not familiar with the TurboSquid requirements. Once that file is set up, you only need to copy your master data into that, save it under the appropriate name, and in the evening add it a few times to the render queue. As you can select the take, rendersetting, and camera for each job, the prepared file can be rendered out without saving multiple versions of it first. (That sounds like some script would make it easier, too, but I just fleetingly looked at the Python and C++ documents about BatchRender, and it doesn't look as if you can control the job settings individually?) I recognize that this would only be good for the rendering part, but... if TurboSquid doesn't offer an official, supported web interface for uploading, then as @zipit said, "hacking" into the protocols would be just one gigantic mess that you don't really want to spend money on as it is very likely to break at the first opportunity. Thanks for mentioning me
  • plugins folder, now you see me, now you don't.

    16
    0 Votes
    16 Posts
    11k Views
    ManuelM
    hello, I confirm it's strange. Could you use the g_console=true and g_logfile=d:\logfile.txt as said in this page (both will contain the same lines) Send us the logfile ([email protected]) if you don't want to share that in public. Cheers, Manuel
  • Mirror a matrix over a defined plane.

    7
    0 Votes
    7 Posts
    2k Views
    D
    @zipit Thank you. That'll help me.
  • A Curl like POST using NetworkHttpHandlerRef?

    r21 c++
    8
    0 Votes
    8 Posts
    1k Views
    ManuelM
    hello, i'll be set this thread as resolved tomorrow if nothing to add. Cheers Manuel
  • Changing CUSTOMGUI_RANGE

    5
    0 Votes
    5 Posts
    1k Views
    r_giganteR
    Hi gsmetzer, with regard the use of the range slide in a resource file please have a look at <Cinema 4D folder>/resource/modules/olod.res: the LOD_BAR parameter is a range slider With regard to using the NodeData::GetDDescription() please check: py-dynamic_parameters_object_r18.pyp py-preference_r19.pyp py-spherify_modifier_r13.pyp Best, Riccardo
  • QuadRemesher - looking for beta testers

    1
    0 Votes
    1 Posts
    490 Views
    No one has replied
  • R20 - R21 Brakes xpresso setup

    6
    0 Votes
    6 Posts
    2k Views
    M
    Hi sorry for the late reply, unfortunately it's a limitation of the sweep nurbs, since he was rewritten. So you need to create it again and rewire things. Sorry about it. Cheers, Maxime.
  • Applying outline selection by script ?

    5
    0 Votes
    5 Posts
    1k Views
    ferdinandF
    Hi, I am glad it does help you. I forgot to mention one thing, which is worth pointing out, when somebody stumbles over this thread, searching for performing edge selections. The way I treat edge selections in this script only works because of the specific scenario - selecting edges that are only associated with one polygon. In all other cases you have to select two edge ids (one for each polygon) for each edge or use the convenience methods of c4d.BaseSelect for that. Otherwise you will end up with a corrupted edge selection. Cheers zipit
  • Python on Linux missing submodule?

    2
    0 Votes
    2 Posts
    635 Views
    r_giganteR
    Hi fyeng, thanks for reaching out us. With regard to your question, I confirm that the workflow you're trying to achieve using the python executable found among the Linux CLR modules is not possible since only c4dpy, as you've already seen, has access to all the needed modules. Best, Riccardo
  • requirenments for building Cinema 4D extension

    3
    0 Votes
    3 Posts
    885 Views
    r_giganteR
    Hi angel23, thanks for reaching out us. With regard to your question the answer can be pretty wide depending on the depth you're aiming to tackle. Cinema 4D comes as @bentraje properly stated with C++ and Python APIs: whilst the first is bigger and more comprehensive the second is a specialized subset of the first and easier to approach. For the Cinema 4D API newcomers we recommend to stick to the Python API which gets rid of all the hurdles of the building process and to get proficient with it. Upon getting used to it and to the foundation Cinema 4D API concepts, moving to C++ won't be too complex. Best, Riccardo
  • random.seed() vs R21

    5
    0 Votes
    5 Posts
    1k Views
    brascoB
    @zipit Thanks for the insight! This makes sense, I vaguely recall typo-ing random.seed and forgetting the () and it threw an error so I fixed that, went writing more of the code and then didn't relate this error back to that brief moment. I don't recall assigning it as a property, but I do have a seed = 12345 elsewhere in the code, so maybe I did! Thanks all for sanity-checking it, solved. cheers, brasc
  • version of python in Cinema 4D R21

    3
    0 Votes
    3 Posts
    797 Views
    A
    @m_adam thanks
  • Dynamically Import All Python Files

    r21 python
    5
    0 Votes
    5 Posts
    2k Views
    B
    @m_adam Thanks for the additional insight and the related links. Appreciate it a lot.
  • Start render in PictureViewer at the moment when a certain file has changed

    10
    0 Votes
    10 Posts
    2k Views
    M
    Hi @daltonmonaie, could you open a new topic for the last things regarding the update of the render path and post all your code if possible so like that we are sure we speak about the same things. Cheers, Maxime. I've created the new topic, please continue the discussion on this one: Update Render Path in an Xpresso Node.
  • C4D plugin - load object possible ?

    3
    0 Votes
    3 Posts
    661 Views
    M
    Hi, @Xplorer do you have any example in Cinema that would have similar behavior? If you could explain in detail or give example especially the second part of this sentence "load in c4d a particular object already modeled, and also have it hidden to stay focused on the settings and sliders available in the plugin." it will be nice. Cheers, Maxime.