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).
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()
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.
@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.
@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.
@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.
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.
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:
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.)
global x
x
If all you want is to store a value between executions of a tag, this is easy to implement this way. However:
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.
@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.)
@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.)
Use any of the functions
PointObject.GetPointH(self) PolygonObject.GetPolygonH(self) PolygonObject.GetEdgeH(self)
@will_blizzard said in Modify rotation of clone child objects:
have no issue rendering to picture viewer with python effector, so not sure what you're referring to
The error is in the function moData.GetFalloffs(), which you are not using, so you are not affected I guess.
moData.GetFalloffs()
I don't know where it's defined, but the console tells me it's c4d.ID_BASEOBJECT_GENERATOR_FLAG.
c4d.ID_BASEOBJECT_GENERATOR_FLAG
(You are aware that you can drag the label "Enabled" et al. to the Python console to get the access constant? And press Return to get the current value, although that is not shown as constant, at least up to R23...)
@ymoon
selgmg is a c4d.Vector, while GetStringFromClipboard expects a string, so Python uses the str function to convert, which results in an unwieldy string like Vector(50, 0, 0) If you want to put that string back into a vector, you need to parse it - I don't think there is an API function that does that.
selgmg
c4d.Vector
GetStringFromClipboard
str
Vector(50, 0, 0)
However, you can solve the parsing issue pretty quickly by putting a JSON string to the clipboard instead which represents the three components of the vector:
import c4d import json def main(): sel_pt = op.GetPointS() id_sel_pt = 0 for i in range(op.GetPointCount()): if sel_pt.IsSelected(i): id_sel_pt = i objectmg = op.GetMg() selgmg = op.GetPoint(id_sel_pt) * objectmg selgmgStr = json.dumps([selgmg.x, selgmg.y, selgmg.z]) print(selgmg, selgmgStr) c4d.CopyStringToClipboard(selgmgStr) if __name__ == '__main__': main()
Then the readback would be just as easy:
import c4d import json def main(): selgmgStr = c4d.GetStringFromClipboard() selgmgList = json.loads(selgmgStr) selgmg = c4d.Vector(selgmgList[0], selgmgList[1], selgmgList[2]) print(selgmg, selgmgList, selgmgStr) sel_pt = op.GetPointS() id_sel_pt = 0 for i in range(op.GetPointCount()): if sel_pt.IsSelected(i): id_sel_pt = i objectmg = op.GetMg() op.SetPoint(id_sel_pt, selgmg * ~objectmg) op.Message (c4d.MSG_UPDATE) c4d.EventAdd() if __name__ == '__main__': main()
Python for C4D scripting: https://www.patreon.com/cairyn
@user168462
Not that I know of
In a parallel camera, the view frustrum is a rectangular box/"cube". The dimensions depend on the render setting dimensions and the camera's zoom value. For some reason the neutral zoom is 1.024, not 1, so: if your render settings define an image of 1000x500 pixels, and the camera zoom is 1.024, then an object with the dimensions 1000x500 units parallel to the camera's view plane will appear as filling the image. (Obviously, in parallel projection the distance of the object does not matter.) A zoom of 0.512 will show the object as half size (500x250 pixels, plusminus whatever the antialias settings screw up), a zoom of 2.048 will show the object as double size, etc. As the geometric frustrum is simple, the calculations for the volume (up to the focus distance) are trivial.
Note: I did this on R23, so if the later versions changed the neutral zoom, I wouldn't know.
Aaaand of course I forgot a crucial point: After modifying the clone positions in marr, you need to write them back to the MoData, but the last parameter apply_strength must be False:
apply_strength
moData.SetArray(c4d.MODATA_MATRIX, marr, False)
This is at first glance unintuitive since we want the strength to be present but when looking closely, you can fathom that the function will internally multiply with the falloff strength that it calculated itself, and that probably comes from GetFalloffs() again, so it's 0 and nothing is won. We do not want SetArray to do that at all, because we have already applied the falloffs that we calculated ourselves, and SetArray should keep its buggy hands off it!
GetFalloffs()
SetArray
@wdp That's not quite correct, the CallCommand IDs and the simple property calls will (almost) never set a key, for example, if you change the subdivision property in a cube, this will be a static change and not set any animation key either. The visibility flags behave exactly the same. Triggering the key-setting is just another functionality, which must be called separately.
CallCommand
If you're interested in this specific group of functions, I have a course https://www.patreon.com/cairyn which explains this stuff in section 14. (Though I am also sure that the PluginCafé has the answer for free already, I remember the topic being discussed, but you may need to search for it, and perhaps study the API some more. Also, as @m_magalhaes said, there is a Maxon Github example where you can copy and paste code from.)
Once you understand the way keys are handled, you can create a function that works for any DescID (or copy it...), which is probably what you want.
DescID
As for your other question, you are calling the EditorMode functions on the op variable which is a predefined variable containing a single selection. If there are no objects selected OR there is more than one object selected, then op will be None so your call causes an error. To handle more than one selected object at a time, you need to retrieve a list of selections by calling GetActiveObjects ( @m_magalhaes said that as well) and then go through a for loop calling the functions for every object in the list.
EditorMode
op
None
GetActiveObjects
for
@m_magalhaes Meanwhile, I found one (more or less): calculating the falloff myself. I just need to access the field list and perform a SampleList on the clones' positions. This actually works within the render process as well. Looks approx. like this:
SampleList
md = mo.GeGetMoData(op) if not md: return False cnt = md.GetCount() marr = md.GetArray(c4d.MODATA_MATRIX) fieldlist = op[c4d.FIELDS] matCloner = gen.GetMg() inputField = c4d.modules.mograph.FieldInput([marr[i].off * matCloner for i in range(cnt)], cnt) info = c4d.modules.mograph.FieldInfo.Create(c4d.FIELDSAMPLE_FLAG_VALUE, None, doc, 0, 1, inputField, gen) outputs = c4d.modules.mograph.FieldOutput.Create(cnt, c4d.FIELDSAMPLE_FLAG_VALUE) fieldlist.SampleList(info, inputField, outputs) # print (outputs.GetValue(0)) for i in range(0, cnt): fieldStrength = outputs.GetValue(i) # usw usf
Okay, Parameter Mode is even worse when it comes to falloffs. GetFalloffs() results in an actual error raised. GetArray(c4d.MODATA_FALLOFF_WGT) results in None. GetArray(c4d.MODATA_WEIGHT) returns a proper array, but it's filled with zeroes no matter where the field is.
GetArray(c4d.MODATA_FALLOFF_WGT)
GetArray(c4d.MODATA_WEIGHT)
Also, the auto-gen sample code (at least in R23) is faulty: it uses an attribute data which is undefined, causing an error (I found on the forum that the mode can be read via GetBlendID, though); and it reads the MoData from the gen variable instead of op, which results in GetCurrentIndex always returning 0.
data
GetBlendID
MoData
gen
GetCurrentIndex
It doesn't work in the expected way anyhow. The vector returned by the Python code for ID_MG_BASEEFFECTOR_POSITION is not an absolute, but kind of a weight for the value set in the GUI. I'd need to return the final value though, which cannot be expressed as a product. This works in Full Control mode, but as the falloffs will not work there either with a field while rendering, I guess... I lost the game.
ID_MG_BASEEFFECTOR_POSITION
Ouch! In R25 still? Thanks for the confirmation; so I may need to rewrite my effector in Parameter mode (since you mentioned the mode, I suppose it'll work this way?)