DirectSample doc
-
hi ,
thanks for the links @PluginStudent
i'm surprised that you code execute.
GetBlock()
should be used in your FieldOutput.fields.DirectSample(fieldInput, fieldOutput.GetBlock(), fieldInfo) fields.DirectFreeSampling(fieldInfo)
It's working here. Are you sure you are sampling value that should be different than 0 ?
Cheers,
Manuel -
@m_magalhaes Thanks for reply.
I was mistaken with GetBlock() when wrote a post: it should be applied to the FieldOutput object.I clear my issue now - I have custom point positions list (based on object's polygon center points), and I want to sample them.
But code below:pPos = [c4d.Vector(-100, 0, -100), c4d.Vector(100, 0, -100), c4d.Vector(-100, 0, 100), c4d.Vector(100, 0, 100)] fieldInput = c4d.modules.mograph.FieldInput(pPos, len(pPos)) fieldInfo = c4d.modules.mograph.FieldInfo.Create(c4d.FIELDSAMPLE_FLAG_VALUE, None, doc, 0, 1, fieldInput) fieldOutput = c4d.modules.mograph.FieldOutput.Create(pCount, c4d.FIELDSAMPLE_FLAG_VALUE) fields.DirectInitSampling(fieldInfo) samplingSuccess = fields.DirectSample(fieldInput, fieldOutput, fieldInfo) fields.DirectFreeSampling(fieldInfo) print samplingSuccess, fieldOutput._value
prints False [0.0, 0.0, 0.0, 0.0]
I see that documentation FieldInfo says callers parameter is optional, but is it true?
Any recommendations? -
I made a hack with temporary object
pPos = [c4d.Vector(-100, 0, -100), c4d.Vector(100, 0, -100), c4d.Vector(-100, 0, 100), c4d.Vector(100, 0, 100)] pCount = len(pPos) tmpPoly = c4d.BaseObject(c4d.Opolygon) tmpPoly.ResizeObject(pCount , 0) tmpPoly.SetAllPoints(pPos) fieldInput = c4d.modules.mograph.FieldInput(pPos, pCount) fieldInfo = c4d.modules.mograph.FieldInfo.Create(c4d.FIELDSAMPLE_FLAG_VALUE, None, doc, 0, 1, fieldInput, tmpPoly) fieldOutput = c4d.modules.mograph.FieldOutput.Create(pCount, c4d.FIELDSAMPLE_FLAG_VALUE) fields.DirectInitSampling(fieldInfo) samplingSuccess = fields.DirectSample(fieldInput, fieldOutput, fieldInfo) fields.DirectFreeSampling(fieldInfo) print samplingSuccess, fieldOutput._value
Seems it works fine - prints True and list of values
Now I'm facing issue with sampling uvw as well:
pPos = [c4d.Vector(-100, 0, -100), c4d.Vector(100, 0, -100), c4d.Vector(-100, 0, 100), c4d.Vector(100, 0, 100)] pCount = len(pPos) uvw = [c4d.Vector(0, 0, 0), c4d.Vector(1, 0, 0), c4d.Vector(0, 1, 0), c4d.Vector(1, 1, 0)] tmpPoly = c4d.BaseObject(c4d.Opolygon) tmpPoly.ResizeObject(pCount , 0) tmpPoly.SetAllPoints(pPos) fieldInput = c4d.modules.mograph.FieldInput(position=pPos, allocatedCount=pCount, transform=op.GetMg(), uvw=uvw) fieldInfo = c4d.modules.mograph.FieldInfo.Create(c4d.FIELDSAMPLE_FLAG_VALUE, None, doc, 0, 1, fieldInput, tmpPoly) fieldOutput = c4d.modules.mograph.FieldOutput.Create(pCount, c4d.FIELDSAMPLE_FLAG_VALUE) fields.DirectInitSampling(fieldInfo) samplingSuccess = fields.DirectSample(fieldInput, fieldOutput, fieldInfo) fields.DirectFreeSampling(fieldInfo) print samplingSuccess, fieldOutput._value
prints False [0.0, 0.0, 0.0, 0.0]
Alternative code:
fieldInput = c4d.modules.mograph.FieldInput(pPos, pCount, op.GetMg(), pCount, None, uvw)
returns error:
StandardError: The arguments don't match any supplied constructors
Would appreciate your help
-
Hi @baca sorry for the long delay we forget you, but here you are.
The culprit is in the FieldInput initialization, that fail (you can check it by calling fieldInput.IsPopulated() will return False in your case).
First, there is a little bug, if no arguments are passed then an empty FieldInfo is returned, but we don't check for keywords only for argument, so be sure to pass at least one parameter, not as a keyword argument.
Then because in C++ you have multiple overrides of a function (aka multiple signatures for one function) but in python, this is not possible and we have to handle it internally by emulating it and by marking argument optimal while they are not really.
So only few constructors are possibles which are:
- no parameters passed = an empty FieldInput is returned.
position
,allocatedCount
.position
,allocatedCount
,transform
.position
,allocatedCount
,transform
,fullCount
.position
,direction
,allocatedCount
,transform
,fullCount
.position
,direction
,uvw
,allocatedCount
,transform
,fullCount
.
So that means if you want to define the uvw you also need to define the position, direction, uvw(of course) the allocatedCount, transform, and the fullCount.
So this gives us
import c4d fields = op[c4d.FIELDS] pPos = [c4d.Vector(-100, 0, -100), c4d.Vector(100, 0, -100), c4d.Vector(-100, 0, 100), c4d.Vector(100, 0, 100)] pCount = len(pPos) uvw = [c4d.Vector(0, 0, 0), c4d.Vector(1, 0, 0), c4d.Vector(0, 1, 0), c4d.Vector(1, 1, 0)] tmpPoly = c4d.BaseObject(c4d.Opolygon) tmpPoly.ResizeObject(pCount , 0) tmpPoly.SetAllPoints(pPos) # This is important to pass pPos without a keyword due to a bug see my post https://developers.maxon.net/forum/topic/12723/directsample-doc/6 fieldInput = c4d.modules.mograph.FieldInput(pPos, direction=uvw, uvw=uvw, allocatedCount=pCount, transform=op.GetMg(), fullCount=pCount) fieldInfo = c4d.modules.mograph.FieldInfo.Create(c4d.FIELDSAMPLE_FLAG_VALUE, None, doc, 0, 1, fieldInput, tmpPoly) fieldOutput = c4d.modules.mograph.FieldOutput.Create(pCount, c4d.FIELDSAMPLE_FLAG_VALUE) fields.DirectInitSampling(fieldInfo) samplingSuccess = fields.DirectSample(fieldInput, fieldOutput.GetBlock(), fieldInfo) fields.DirectFreeSampling(fieldInfo) print(samplingSuccess, fieldOutput._value)
Hope this helps. In any case, I will try to improve the documentation on this topic and I will create a bug report about the fact that if you pass only keyword argument then the default empty FieldList is returned.
Cheers,
Maxime. -
@m_adam said in DirectSample doc:
position, direction, uvw, allocatedCount, transform, fullCount
Thanks Maxime, this is works now. Much appreciated your details and solution.
PS: I noticed it's still necessary to create temporary Polygon Object, otherwise it wouldn't sample
tmpPoly = c4d.BaseObject(c4d.Opolygon) tmpPoly.ResizeObject(pCount , 0) tmpPoly.SetAllPoints(pPos)
-
Sorry to bump this thread.
But I'm also wondering, why the temporary poly object is needed.
I'm currently in R21 (as my customer) and there I also get sample values only if such poly object is passed. The pure list of positions is not enough in results in sampled values always being zero. -
Hello @a_block,
thank you for reaching out to us. @m_adam is on vacation, so I will have to pick up here for now. And I unfortunately struggle with understanding your question.
But I'm also wondering, why the temporary poly object is needed.
I assume it is because the author of the Python function
FieldInfo.Create
has moved thecallers
argument and the indicated in the documentation that it is option withtyping.Optional
?When you look at the C++ documentation, you will see that
caller
is mandatory for all overloads of FieldInfo::Create(). Nothing indicates that you could pass a null pointer for caller, quite the opposite, in the cases where caller is aBaseList2D
, there are instructions that it must be attached to a document. So, the Python implementation is a bit counter intuitive with placing caller at the end of the argument list and also technically incorrect with the optional indicator.If your question is meant more philosophical, as in the pure list of points should be enough to sample that point cloud, I must unfortunately point out that we cannot discuss our APIs unless there is bug or a clear contradiction of functionality vs purpose.
PS: I still have the feeling that I misunderstood something here.
Cheers,
Ferdinand -
Not philosophical at all.
a) Why would we need to pass a list of point positions (or would have an option for it), if the information is taken from an object only. Is it only to restrict the sampled points? Why wouldn't we pass point indices instead of vectors, then?
b) FieldInfo.Create() docs clearly state the callers to be optional.As a Python developer I do not tend to look into the C++ docs, if Python docs are existing.
And Maxime's example above does not insert the temporary poly object, does it?
-
Hello @a_block,
Not philosophical at all [...] Why would we need to pass a list of point positions (or would have an option for it), if the information is taken from an object only. Is it only to restrict the sampled points? Why wouldn't we pass point indices instead of vectors, then? [...] FieldInfo.Create() docs clearly state the callers to be optional.
Well, that is exactly the philosophical question I mentioned Philosophical was probably a poor term to describe what I meant, but there is no good answer for "why do I have to do A when I do B which requires me to do A?". As explained by Maxime in his posting, a caller is simply effectively required. As also indicated by me, the
typing.Optional
is quite questionable although technically true. I could unpack the technical details here as far as I understand them, but that does not help much IMHO (in short:FieldInfo.Create()
in Python effectively wrapsFieldInfo::Create(const FieldCallerStack &caller, ...)
in C++, and while an empty stack will result in aFieldInfo
which is valid, it is practically not very useful, resulting in erroneous sampling attempts).As a Python developer I do not tend to look into the C++ docs, if Python docs are existing.
Yes, I understand that. I often felt the same when I was a user. But it is still advisable to look at the C++ documentation, especially when the Python documentation raises questions or seems ambiguous.
And Maxime's example above does not insert the temporary poly object, does it?
Yes, he does not do this, which is a flaw of his example. It will cause the underlying field sampling to assume the active document to be the relevant document for the sampling. Which pans here out due to the setup, but it is better to pass a caller which is part of the relevant document, as this will also make the code work in scenarios where the sampling does not happen in the active document. Now that I read Maxime's code more closely, I also spotted that he was passing
tmpPoly
as the caller. Which is not specifically wrong, but a bit nonsensical and might have caused the 'philosophical' conundrum of yours. Usually, you pass as the caller the entity (BaseObject
etc.) which owns thec4d.FieldList
which is being sampled.And for clarity: There is also no need to use the more complex sampling with
DirectSample()
if you just want to sample a list of points which are not part of aPointObject
. Find a simple example at the end of my post.I hope this helps and cheers,
FerdinandThe result:
The code:
"""Simple example for sampling a set of points. """ import c4d import typing doc: c4d.documents.BaseDocument # The active document op: typing.Optional[c4d.BaseObject] # The active object, can be None. # The points to sample in the field list, ten points placed with a stride of 25 units on the y-axis. SAMPLE_POINTS: list[c4d.Vector] = [c4d.Vector(0, i * 25, 0) for i in range(10)] def main() -> None: """ """ if not isinstance(op, c4d.BaseObject): raise RuntimeError("Please select an object.") fieldList: c4d.FieldList = op[c4d.FIELDS] if not isinstance(fieldList, c4d.FieldList): raise TypeError(f"The object {op} has no fields parameter.") print (f"The object containing the field list: {op}") # Prepare a field input with the points to sample. inputField = c4d.modules.mograph.FieldInput(SAMPLE_POINTS, len(SAMPLE_POINTS)) # Sample all the points in SAMPLE_POINTS with #op as the caller, i.e., the entity for which is # being sampled. When you use the method DirectSample() instead, which requires specific # initialization and a FieldInfo instance, op would have be passed there in FieldInfo::Create # as the caller. The example by Maxime was there at least a bit odd as it passed the polygon # object instead, which is not specifically wrong, but results in the 'philosophical' conundrum # of 'why am I passing the points when I am passing the point object?'. output = fieldList.SampleListSimple(op, inputField, c4d.FIELDSAMPLE_FLAG_VALUE) for point, weight in zip(SAMPLE_POINTS, output._value): print (f"{point = }, {weight = }") if __name__ == '__main__': main()
-
Thanks, Ferdinand