Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login
    1. Maxon Developers Forum
    2. Cairyn
    3. Best
    • Profile
    • Following 0
    • Followers 6
    • Topics 40
    • Posts 387
    • Best 110
    • Controversial 0
    • Groups 0

    Best posts made by Cairyn

    • RE: Select the Children of a Selected Object. Then, Store it in a List

      Your two lines

      GetObjects(kid)
      objList += kid
      

      make no sense. The first one calls the recursion but then throws the result away as it is not stored anywhere (objList is a local variable for the function, not a global, and does not exist beyond the context of the current method execution). The second tries to extend a list by a single object instead of another list (the operator += works like extend, not like append) and fails because it cannot iterate through a single object. (Even if you had used the append method or listified kid it would still fail logically because you have already added the kid in the recursion call and then threw it all away.)

      What you need to do is extend the local objList by the result list of the recursion call:

      objList += GetObjects(kid)
      

      or in context,

      import c4d
      from c4d import gui
      
      def GetObjects(obj):
            objList = [obj]
            for kid in obj.GetChildren(): 
                objList += GetObjects(kid)
            return objList # should return the parent/selected obejct and all its chidlren
      
      def main():
          test = doc.SearchObject("shoulder_jnt")          
          ikJointChain = GetObjects(test)
          print ikJointChain
      
      if __name__=='__main__':
          main()
      
      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Universal "Undo" to Rule them All?

      Now we are at a point where angels fear to tread...

      I can't say what exactly the Start/EndUndo system is doing when not properly used in one level, but it definitely doesn't work in a way that supports nesting. Here I just tried a small modification of my previous scripts:

      import c4d
      
      def main():
          doc.StartUndo();
          obj = doc.GetFirstObject();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
      
          doc.StartUndo();
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          doc.EndUndo();
      
          doc.StartUndo();
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          doc.EndUndo();
      
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
      
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          doc.EndUndo();
          c4d.EventAdd();
      
      if __name__=='__main__':
          main()
      

      I used the same sequence of five renames, but I nested rename 2+3 into a new Start/EndUndo without closing the outer first.
      If you apply Ctrl-Z on that, you will find that the first Ctrl-Z reverses the last three renames (NOT just the last 2), one more Ctrl-Z reverses the second rename, and one more the first. That means that an undo step goes back to the last StartUndo (not EndUndo) that the programmer has set. Any nesting is ignored.

      I did a few more nesting experiments but I'm not posting them here as they all point to the same behavior. I do not get any errors though, even if I completely botch the sequence - two StartUndo in a row, AddUndo before StartUndo, AddUndo after EndUndo... it actually still works, but with the little limitation that each StartUndo that I issue will break up the sequence and require a new Ctrl-Z to undo. (I am not going into full reverse engineering here now - it would be nice if the documentation could tell us how error cases and nesting are actually handled, though.)

      That means that if you have commands that internally perform an Start/EndUndo, you will not be able to get out of the additional Ctrl-Z. Same, I guess, if you issue a StartUndo inside of a function that you wish to reuse (although that may be a problem more suitable for libraries).

      You may argue that this is a design flaw. A nested Start/EndUndo could actually be treated like a single subcommand, with a Ctrl-Z working only on the highest level - which would revert the results of a functionality from the view of the outermost Start/EndUndo, while all nested Start/EndUndo pairs on lower levels do not affect the reversal (so you could use a CallCommand including its internal Undos without enforcing another Ctrl-Z in the end). I'm just not sure whether C4D could handle this conceptually, as it would be easy to lose yourself in a tree of Start/EndUndos throughout the program.

      Anyway. Your best bet may actually be to skip the CallCommands if possible and perform the necessary change by yourself, so you have a better control over the Undo sequences.
      Or you can issue a feature request for a CallCommand with an additional parameter that allows you to suppress the Start/EndUndos that are added internally.

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Save variables during current session (until C4D is closed)

      Since the OP has asked explicitly for scripts, I would like to add: @m_adam 's "Global" method will not work for scripts, as their Python scope is (apparently) only valid for the current execution. It does work for Python tags and XPresso Python nodes where the scope seems to exist for the lifetime of the tag.

      If you work with tags/nodes and have actual variables to store in the globals, you need to account for the timing and the frequency of execution (e.g. do not rely that the next execution is actually the next frame). This will not apply for one-time executions like an initialization, but even there you may want to determine an anchor point (e.g. at frame 0) where the value is reset or calculated for the first time.

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Universal "Undo" to Rule them All?

      @bentraje I am not using Maya so I can't say anything about that. As a programmer though, I know that there ain't nothing like no free launch... uh, lunch. If Maya is able to undo/redo automatically, then the Python API needs to have that functionality built in (automatic call of something like StartUndo at the beginning, EndUndo at the end, and every API call has an AddUndo integrated. That is possible, yes. You have to pay a price for it though, both in development time (of the API programmers) and in execution time. Maybe that is a good tradeoff; I can't judge that. It's certainly easier for the Python programmer

      Personally, I admit that I am a lazy pig and often don't integrate any undo in my scripts. But it is not that difficult as you make it sound right now. You would only have to add an AddUndo at points where you really make a change to the object tree (or related internal data). The actual calculations are unaffected. If you have a really complicated script, it would be best if you create it in layers, with the changes made to the object tree being at as high level in the call hierarchy as possible. This allows for safe exits and controlled error situations. (But we're far off topic now.)

      No, you would not need to press Ctrl-Z five times. Look at the following:

      import c4d
      
      def main():
          doc.StartUndo();
          obj = doc.GetFirstObject();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          doc.EndUndo();
          c4d.EventAdd();
      
      if __name__=='__main__':
          main()
      

      (warning: If you execute this script you MUST have 5 objects at top level, or the thing will crash.)
      Try it, it will rename the objects; one Ctrl-Z is enough to revert all changes.
      Now compare this with the following:

      import c4d
      
      def main():
          doc.StartUndo();
          obj = doc.GetFirstObject();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          doc.EndUndo();
          doc.StartUndo();
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          doc.EndUndo();
          doc.StartUndo();
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          doc.EndUndo();
          doc.StartUndo();
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          doc.EndUndo();
          doc.StartUndo();
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          doc.EndUndo();
          c4d.EventAdd();
      
      if __name__=='__main__':
          main()
      

      Here, each AddUndo is encapsulated into a Start/EndUndo block. Now use the script and undo - you will find that each undo only reverts one rename!
      So, the Start/EndUndo blocks are the ones that control the "container" that is reverted by Ctrl-Z (as mentioned in my previous post). If you want to revert everything at once, just encapsule the whole execution of the script in the main() function with a Start/EndUndo block. It is not necessary to repeat this block with every method you write.

      Now have a look at the following:

      import c4d
      
      def main():
          doc.StartUndo();
          obj = doc.GetFirstObject();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          obj = obj.GetNext();
          doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj);
          obj.SetName("Renamed!");
          doc.EndUndo();
          obj = obj.GetNext();
          obj.SetName("Renamed!");
          obj = obj.GetNext();
          obj.SetName("Renamed!");
          c4d.EventAdd();
      
      if __name__=='__main__':
          main()
      

      Execute and undo... Ooops! Only three renames were reverted! As obvious from the script, two renames are outside of the Start/EndUndo block and have no AddUndo either, so Ctrl-Z will not affect these.

      I leave further exploration to you, but I fear you will not get a super simple undo automation as in Maya.

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Universal "Undo" to Rule them All?

      @bentraje Nope. Only the Start/EndUndo containers determine how often you need to hit Ctrl-Z. Not the AddUndos that you issue in between, or the type of these AddUndos. Look at the following:

      import c4d
      
      def main():
          doc.StartUndo()
          cube = c4d.BaseObject(c4d.Ocube)
          doc.InsertObject(cube, None, None)
          doc.AddUndo(c4d.UNDOTYPE_NEW, cube)
          sphere = c4d.BaseObject(c4d.Osphere)
          doc.InsertObject(sphere, cube, None)
          doc.AddUndo(c4d.UNDOTYPE_NEW, sphere)
          plat = c4d.BaseObject(c4d.Oplatonic)
          doc.InsertObject(plat, None, sphere)
          doc.AddUndo(c4d.UNDOTYPE_NEW, plat)
          doc.EndUndo()
          c4d.EventAdd()
      
      if __name__=='__main__':
          main()
      

      This code creates a hierarchy of three objects, each with their own AddUndo but all in the same Start/EndUndo block. That means all three objects will be removed at one single Ctrl-Z. Try it.

      Also, if you cut out the generation of the objects and put it into a method, this will change nothing, it will just make the Start/EndUndo bracket in your code more visible:

      import c4d
      
      def create():
          cube = c4d.BaseObject(c4d.Ocube)
          doc.InsertObject(cube, None, None)
          doc.AddUndo(c4d.UNDOTYPE_NEW, cube)
          sphere = c4d.BaseObject(c4d.Osphere)
          doc.InsertObject(sphere, cube, None)
          doc.AddUndo(c4d.UNDOTYPE_NEW, sphere)
          plat = c4d.BaseObject(c4d.Oplatonic)
          doc.InsertObject(plat, None, sphere)
          doc.AddUndo(c4d.UNDOTYPE_NEW, plat)
              
      def main():
          doc.StartUndo()
          create()
          doc.EndUndo()
          c4d.EventAdd()
      
      if __name__=='__main__':
          main()
      

      So you can just do that and put the Start/EndUndo bracket in the top call, and then work your way through your code by only using the proper AddUndos.

      Now you will ask, why the different undo types? This helps C4D to determine the extent of change that will be done (mostly, AddUndo comes before the change...), and therefore minimize the amount of data stored in the undo list. For example, if you only change a flag on a polygon object with 100 000 polys, then it would be an excessive waste to store the full object in the undo list. It's quite sufficient to store the BaseContainer, or even a single value from that container, to revert and redo the change. (Keep in mind that you don't just want to undo but at times also to redo an undone operation.)

      If you know what the AddUndo really stores for future Undos and Redos, you can save yourself some AddUndos. Try the following code:

      import c4d
      
      def create():
          cube = c4d.BaseObject(c4d.Ocube)
          doc.InsertObject(cube, None, None)
          doc.AddUndo(c4d.UNDOTYPE_NEW, cube)
          sphere = c4d.BaseObject(c4d.Osphere)
          doc.InsertObject(sphere, cube, None)
          plat = c4d.BaseObject(c4d.Oplatonic)
          doc.InsertObject(plat, None, sphere)
              
      def main():
          doc.StartUndo()
          create()
          doc.EndUndo()
          c4d.EventAdd()
      
      if __name__=='__main__':
          main()
      

      There is only one AddUndo while three objects are generated. But since the two other new objects are linked as children of the first new object, the whole substructure will behave as one - if you undo, you will still see all three objects disappear, and - more important! - if you redo, all three will appear again!

      Now, I have no access to the C4D undo code, but obviously the undo cuts the new object from the hierarchy including everything that is attached to it, and keeps the structure intact for a later redo.

      If you had linked one of the child objects somewhere else, you would need an additional AddUndo for it, naturally. E.g. if you create the Platonic as top level object, it would no longer be affected by the Undo of a parent object.

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Python Tag vs Expresso for Set Driver/Driven Behavior?

      @bentraje The Python tag must be evaluated in every redraw of the scene (not just on every frame but far more often; try this by insert a Print in the code...) because it may not just depend on variables but on values from the scene (e.g. "if the object X is placed farther away than 1000 units then move object Y to..."). Keeping track of original values and comparing them with current values like this is nigh impossible (requiring analysis of code semantics), so the tag will be simply re-evaluated whenever encountered.

      You can insert an "early exit" in the tag, of course, by testing for changed variables yourself (as you as programmer have more information on the semantics than the Python interpreter/compiler). But you will need to store the values to compare with somewhere, and you will need to take into account that the execution may not happen in frame sequence (e.g. when the user scrubs the timeline backwards).

      As for the original question, I tend to put calculations into Python nodes inside the XPresso because plain math calc creates TONS of XPresso nodes that tend to become very confusing. On the other side I keep XPresso nodes whenever I want to see the affected objects in the circuit directly, or when I need a GUI for adapting something (e.g. Range Mapper with spline interface), or when the node does complicated things that I don't want to reprogram in Python (Hair intersection? ... nah, never used that 😉 ). When the whole thing is easier to maintain as a Python tag in the end, I put it into the tag...

      So it's not so much a question of whether the execution is faster, it's pure convenience of the result for me.

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Retrieve the World Position of the Current Selection (from points or edges or faces)?

      You are probably in need of GetModelingAxis() which reacts to object selections or polygon/edge/point selections.

      Here's how I determine the pivot for CollieMouse, which is supposed to be the same pivot that the built-in transformation functionality is using:

      	BaseObject* objcenter = doc->GetRealActiveObject(nullptr, nullptr); // GetActiveObject() fails with multiple selections?
      	if (objcenter != nullptr)
      	{
      		Matrix centermat = objcenter->GetModelingAxis(doc);
      		pivot = centermat.off;
      	}
      

      Note: This is R19 C++, but I can see GetModelingAxis in R20 Python as well.

      If this is not what you're looking for, you can take a point as vector, transform this vector by the object's world matrix, and get the world "matrix" for the point. If you need more than one point, use the average of those points.

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Python: Global Variable vs Plugin ID

      Actually, Python works differently from C++. A "global" variable there is only global for the current execution context. In Python, variables are not declared; Python creates them when they are first used ("first" meaning, there is no such variable in the current execution context already).

      Where a C++ global variable is created at compile time and represents one location in memory throughout, Python globals are created at runtime in their context. Cinema 4D has one context per Python element, so if you are accustomed to C++ globals, the following will irritate you:

      • Create two objects with a Python tag
      • Write the following code into the tags:
      import c4d
      
      def main():
          global x
      
          if doc.GetTime().GetFrame(doc.GetFps()) == 0:
              x = 1
              print "Tag 0 x set to 1"
          else:
              print "Tag 0 x = ", x
              x += 1
      

      for one tag, and

      import c4d
      
      def main():
          global x
      
          if doc.GetTime().GetFrame(doc.GetFps()) == 0:
              x = 0.5
              print "Tag 1 x set to 1/2"
          else:
              print "Tag 1 x = ", x
              x += 1
      

      for the other. Obviously, the first tag counts on full integers and the second on halfs, both are reset on frame 0.

      The output amounts to

      Tag 0 x set to 1
      Tag 1 x set to 1/2
      Tag 0 x =  1
      Tag 1 x =  0.5
      Tag 0 x =  2
      Tag 1 x =  1.5
      Tag 0 x =  3
      Tag 1 x =  2.5
      Tag 0 x =  4
      Tag 1 x =  3.5
      Tag 0 x =  5
      Tag 1 x =  4.5
      Tag 0 x =  6
      Tag 1 x =  5.5
      Tag 0 x =  7
      Tag 1 x =  6.5
      Tag 0 x =  8
      Tag 1 x =  7.5
      Tag 0 x =  9
      Tag 1 x =  8.5
      Tag 0 x =  10
      Tag 1 x =  9.5
      

      Obviously, although both tags claim a global x, they access different x's. That's because their contexts are different. Which also means that they cannot exchange data through globals this way. (You can try with an external module that you import, but I don't have time to verify that at the moment.)

      If all you want is to store a value between executions of a tag, this is easy to implement this way. However:

      • You must make sure that Python creates the variable at some point. Here, I did that through a first value setting at frame 0. Which also means that every time the timeline passes through 0, the value is reset.
      • You must pay attention to the evaluation of the object tree. I chose a tag here since the tag is evaluated with the tree every time. That is NOT the case for a generator, as there are caching mechanisms at work.
      • You must take care of situations like "jumping around in the timeline", replaying backwards, setting the time explicitly through some other plugin, and such.
      posted in General Talk
      CairynC
      Cairyn
    • RE: Universal "Undo" to Rule them All?

      @bentraje Meh, I just noticed my first code is not a good example because of what I later wrote. Since the top object of the created hierarchy is already determining the fate of the subobjects, the additional AddUndos have no true function. That does not make it wrong but it does not properly prove what I claim 😉

      Try the following:

      import c4d
      
      def create():
          cube = c4d.BaseObject(c4d.Ocube)
          doc.InsertObject(cube, None, None)
          doc.AddUndo(c4d.UNDOTYPE_NEW, cube)
          sphere = c4d.BaseObject(c4d.Osphere)
          doc.InsertObject(sphere, None, None)
          doc.AddUndo(c4d.UNDOTYPE_NEW, sphere)
          plat = c4d.BaseObject(c4d.Oplatonic)
          doc.InsertObject(plat, None, None)
          doc.AddUndo(c4d.UNDOTYPE_NEW, plat)
              
      def main():
          doc.StartUndo()
          create()
          doc.EndUndo()
          c4d.EventAdd()
      
      if __name__=='__main__':
          main()
      

      Here, the three objects are created independently on top level, yet still all disappear with one CTRL-Z.

      (And again, this is true for all types of AddUndo.)

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Retrieve the World Position of the Current Selection (from points or edges or faces)?

      @bentraje Yeah, the second part was meant just in case you have need for a manual process. GetModelingAxis() takes multiselections of elements into account.
      However, I believe GetModelingAxis() works by selected mode (as you can have different selections for points, polys, and edges in parallel depending on whether the point, poly, edge, or object mode is active. So if you explicitly need the point selection and use the method in a script, you may want to switch to the point mode before using GetModelingAxis(). (Also, the actual modeling axis can be changed by the tools, so caution is in order. Personally, this is the behavior I needed, so I didn't make any big experiments.)

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Polygon dimensions

      First, the coordinate manager is not showing the dimensions of the polygon. It's showing the dimensions of the enclosing axis-parallel cuboid, where the axes are either the axes of the object or the world system. So you will not get the width and height information from that (at least not in the way your image suggests).

      The point order is arbitrary. I'm not sure why you expect the points to start in a certain corner?

      Second, your code is extremely specific. A polygon may be nonplanar or rotated in space. The sides of a polygon may not be parallel (as in square, rectangle, parallelogram or trapeze), and even if some sides are parallel, they don't necessarily are parallel to any axis system (e.g. to the ground). I do not know how you place your plane and image, but it looks as if the code would only work if the two sides used as "width" are parallel to ground level (and even then the height is wrong for a trapeze as shown in the image).

      To solve the projection, you need to define first what "width" and "height" should be for an arbitary polygon (a parallelogram e.g. has two heights...), and what kind of restrictions you make on the input (I assume you will only handle planar quads, but do you want to rotate the image, or should it always be parallel to the ground? Do you want to maximize image size on the polygon? Do you want the quads to be trapezes only? Do some quad edges need to be parallel to the ground or not?). Once you have that parameters, you can start thinking about how these widths and heights can be calculated.

      (For measures of classic polygons, you can always google "height parallelogram" e.g.)

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Set Vector Y to 0 (in World Space)

      Without actually trying the code:

      1. newLocalPoints is in local space. This is where you zero the y value. Which means that the point will now have a y of 0 in local space.

      2. Then you multiply the world matrix with the new positions. BUT: This world matrix is already part of the object that contains the points. Now your points have applied the matrix twice, first through the object and then through this multiplication.

      3. While the local y is 0, the multiplication with obj_mat will give the point a global y anyway because obj_mat may contain a translation in space. Moreover, the obj itself does, too. So, your point's y will definitely not be world 0 in the end.

      What you really need to do:

      1. Determine the world vector for the points (it's only a vector and not a matrix since a point does not contain a rotation or scale). This is done by applying obj_mat to each point vector, resulting in a new vector for each point. Why? Because your object's transformation in space (which is the meaning of obj_mat) is affecting all points in the object's local system.

      2. For this global vector, you set the y component to 0.

      3. Then you transform this global vector back to the object's local space by applying inv_obj_mat. Note that after this inverse transformation, the local y may no longer be 0!

      4. Profit.

      Okay, I wrote the script after all. You owe me a beer.

      import c4d
      from c4d import gui
      
      def set_Y_vector(obj, value):
          oldPoints = obj.GetAllPoints()
          obj_mat = obj.GetMg()
          inv_obj_mat = ~obj_mat
      
          newWorldPoints = [p * obj_mat for p in oldPoints]
          newLocalPoints = [inv_obj_mat * c4d.Vector(p[0], p[1]*value, p[2]) for p in newWorldPoints]
      
          obj.SetAllPoints(newLocalPoints)
          obj.Message (c4d.MSG_UPDATE) 
          c4d.EventAdd()
          
      
      def main():
          set_Y_vector(op, 0)
      
      if __name__=='__main__':
          main()
      
      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Close any C4D Window

      That's because there is none. The Windows functionality in the API is very limited. You can open some through CallCommand but identification and handling of specific windows is not supported.

      Only thing you could do is loading a new layout.

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: MULTISTRING field size and font

      As for SCALE_V, this normally works... but all groups above the GUI element need to have the same property:

      CONTAINER Tcommentarytag
      {
      	NAME Tcommentarytag;
      	INCLUDE Tbase;
      
      	GROUP ID_TAGPROPERTIES
      	{
      		SCALE_V;
      
      		STRING COMMENTARY_STRING { CUSTOMGUI MULTISTRING; SCALE_V; }
      	}
      }
      

      This way, it works - remove the SCALE_V from the GROUP and it won't work any more (because the group then is sized only to the necessary minimum vertically, and the string field simply doesn't get any additional space as the group has eaten it all up).

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Modified Pop Up Menu

      @bentraje said in Modified Pop Up Menu:

      Thanks for the response. I tried adding it. It didn't error out but it also didn't close

      Unreached code. You return true when MY_BUTTON is clicked, and never get to the self.Close() part.
      (You can remove two of the three returns from that method.)

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Enabling Team Render in Render Queue

      (Yes, I notice that the thread has been closed, and marked as solved, and I probably shouldn't barge in, but it seems the original script error has never been addressed, so I must mansplain because that's the obnoxious guy I am.)

      Two points:

      1. The error in the script above is that the index used in SetUseNet is wrong.
        The indices in the BatchRender element list begin at 0, so the first valid index is 0, and the last is GetElementCount()-1.
        dBatchCount starts as GetElementCount(), which is fine as counter, but as an index, it is the first unused index.
        Then this counter is increased += 1, so now it is the second unused index after the list.
        When AddFile is called with that index, it simply adds the file as last element because it cannot leave empty gaps in the list. It returns True although it didn't use the desired index - it used index-1.
        Now SetUseNet is called with the same index. The reason it fails is that this index is not the index that AddFile actually used. This index is not even valid. There is no element with that index. So, nothing happens.
        If you use dBatchCount-1 as index at this point, or simply never increase dBatchCount in the first place, the script works fine.

      2. The actual API error is that SetUseNet does not raise an IndexError as the docs claim.
        (Other methods like GetElementStatus do.)

      Okay, I go annoy someone else now.

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: ReferenceError?

      If you have a "return" in a "for" loop, and the return is hit in the very first pass, of course the other nine passes are skipped...

      If you want to have more than one object returned, you need to link them. Try to create a null first, then loop for the 10 cubes, and attach the cubes as children of the null. Then return the null.

      (No guarantee, I can't test that right now)

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Python missing ObjectColorProperties?

      You can go directly through the properties, e.g.

      import c4d
      
      def main():
          selectlist = doc.GetSelection() 
          for obj in selectlist: 
              print obj.GetName(), " ", obj[c4d.ID_BASEOBJECT_COLOR]
              obj[c4d.ID_BASEOBJECT_USECOLOR] = True
              obj[c4d.ID_BASEOBJECT_COLOR] = c4d.Vector(0,0,1)
              obj.Message(c4d.MSG_UPDATE)
              c4d.EventAdd()
      
      if __name__=='__main__':
          main()
      

      So you don't need specific functions.

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Function 12144 (connect objects)

      Apparently the behavior of the earlier versions was found to be fairly useless (as the result is empty) so the R18 version now performs a virtual "make editable" before combining the polygon meshes. (I do not have any of these versions left installed, so this is theoretically speaking.)

      Now, what do you want as the result, and what version are you in? If you want R14/15 to replicate the newer behavior, you could e.g. go through the selected objects and perform the "make editable" on a copy first, before executing the "connect objects" on the copies. If on the other hand you are in R18 and do not want this function to work on parametric objects, you'd need to go through the selected objects as well and refuse execution of "connect objects" if there is any parametric object found.

      posted in Cinema 4D SDK
      CairynC
      Cairyn
    • RE: Universal "Undo" to Rule them All?

      Nah. You can't use Start/EndUndo without defining what to undo through an AddUndo. While there are a few commands that have the whole Undo sequence built in, you will normally want to define your AddUndos yourself so you are in control of the process.

      Imagine it like that: Start/EndUndo define just a "container" which is added to the undo list. When the user presses "Undo", this container will try to restore the previous state by applying all its content (multiple changes) in reverse sequence. If you don't put anything into this container by calling AddUndo, well, the Undo process does not have any information on what to change back. After all, the Undo need to know what the previous state even was.

      For example, if you want to delete an object, you will AddUndo(UNDOTYPE_DELETE, obj) before the deletion happens - because after the deletion, the object is gone. The AddUndo will keep a copy of the deleted object so it can later restore it. Without an AddUndo, there is no such copy and the Undo doesn't know what you deleted and what to restore.

      Now, why is there such a container structure? After all, Maxon could just build an undo into every command?

      Yes they could, but this would create an enormous overhead that may not be needed in all cases (for temporary objects, e.g.). Not defining an undo step explicitly through Start/EndUndo would also mean that the user would have to press Undo for every such substep, instead of pressing it once to revert the results of the whole script. If the script does three hundred object changes, the user would need to press Undo 300 times. Not very practical.

      Could Maxon at least put an AddUndo into every command then, and leave only the Start/EndUndo markers in place? Yes they could, but still, you wouldn't want to burden the system with tons of Undo objects that may never be needed. So, you have to decide yourself what objects to make "undoable" and where to use just straightforward code.

      In fact, undoing is one of the, eh, more interesting topics in software. If you use an undo schema as in C4D, you still have to look out for exceptions and unwanted returns, so you don't accidentally skip an EndUndo. You also may need to trigger an undo in error cases to revert to the original structure before exiting (controlled fail). And naturally you have to think about what AddUndos to actually place.

      Darn, I could have sworn there was a whole chapter on Undoing in the API manual but now I can't find it. 😠

      posted in Cinema 4D SDK
      CairynC
      Cairyn