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.
    • 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