Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware 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

    DirectSample doc

    Cinema 4D SDK
    python
    6
    12
    1.8k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • bacaB
      baca
      last edited by baca

      Hi guys,

      Trying to sample some points against the fields in R21. Referring to doc wrote this

        if fields:
          fieldInput = c4d.modules.mograph.FieldInput(pPos, pCount)
          fieldInfo = c4d.modules.mograph.FieldInfo.Create(c4d.FIELDSAMPLE_FLAG_VALUE, None, doc, 0, 1, fieldInput, op)
          fieldOutput = c4d.modules.mograph.FieldOutput.Create(pCount, c4d.FIELDSAMPLE_FLAG_VALUE)
          fields.DirectInitSampling(fieldInfo)
          fields.DirectSample(fieldInput, fieldOutput, fieldInfo)
          fields.DirectFreeSampling(fieldInfo)
      
      

      But it fires error:

      TypeError: argument 2 must be c4d.modules.mograph.FieldOutputBlock, not c4d.modules.mograph.FieldOutput
      

      Tried to change code to:

      fields.DirectSample(fieldInput, fieldOutput, fieldInfo.GetBlock())
      

      Code executes but output values are 0. Any recommendations?

      1 Reply Last reply Reply Quote 0
      • P
        PluginStudent
        last edited by

        I don't know about your code. But a field is typically sampled using the "Sample" functions. There is an example using SampleListSimple().

        1 Reply Last reply Reply Quote 1
        • ManuelM
          Manuel
          last edited by Manuel

          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

          MAXON SDK Specialist

          MAXON Registered Developer

          bacaB 1 Reply Last reply Reply Quote 1
          • bacaB
            baca @Manuel
            last edited by

            @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?

            bacaB 1 Reply Last reply Reply Quote 0
            • bacaB
              baca @baca
              last edited by baca

              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

              1 Reply Last reply Reply Quote 0
              • M
                m_adam
                last edited by m_adam

                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.

                MAXON SDK Specialist

                Development Blog, MAXON Registered Developer

                bacaB 1 Reply Last reply Reply Quote 0
                • bacaB
                  baca @m_adam
                  last edited by baca

                  @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)
                  
                  1 Reply Last reply Reply Quote 0
                  • a_blockA
                    a_block
                    last edited by

                    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.

                    ferdinandF 1 Reply Last reply Reply Quote 0
                    • ferdinandF
                      ferdinand @a_block
                      last edited by ferdinand

                      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 the callers argument and the indicated in the documentation that it is option with typing.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 a BaseList2D, 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

                      MAXON SDK Specialist
                      developers.maxon.net

                      1 Reply Last reply Reply Quote 0
                      • a_blockA
                        a_block
                        last edited by a_block

                        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?

                        ferdinandF 1 Reply Last reply Reply Quote 0
                        • ferdinandF
                          ferdinand @a_block
                          last edited by ferdinand

                          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 wraps FieldInfo::Create(const FieldCallerStack &caller, ...) in C++, and while an empty stack will result in a FieldInfo 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 the c4d.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 a PointObject. Find a simple example at the end of my post.

                          I hope this helps and cheers,
                          Ferdinand

                          The result:
                          fieldblah.gif

                          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()
                          

                          MAXON SDK Specialist
                          developers.maxon.net

                          1 Reply Last reply Reply Quote 0
                          • a_blockA
                            a_block
                            last edited by

                            Thanks, Ferdinand

                            1 Reply Last reply Reply Quote 0
                            • First post
                              Last post