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
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Need help with Bend-Deformer in Object-Plugin

    Bugs
    r23 2023 python windows
    3
    6
    1.5k
    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.
    • ThomasBT
      ThomasB
      last edited by ThomasB

      Hi,
      I created a function in Object_Plugin which creates a leaf.
      The leaf is created with a plane. In the SplineData in the Attribute-Manager you can shape the leaf shape, like in the hair-Material and depending on how much points the user adds, the plane subdivides. Also I move the points of the plane so that the axis is exact on the tip/bottom of the plane.

      Then I added a bend deformer and wanted to fit it to the parent but I had to do it twice, so at the end i also had to move the deformer , so it behaves a little weird. It doesn`t really fit.
      At the the end the deformer is in the right place but it just refresh if the user moves a point in the splinedata.
      So the code is just a test and far from finished.
      At the end I convert all into a polygon object.

      • why does I have to call fit to parent twice
      • why does it not refresh

      So what is the problem here, can you help me.
      So I added the code and for the res-file, I think the header and str file is in this case not necessary.
      I also attached the plugin file and a small video do demonstate, what I mean.

      Cheers
      Thomas

      ivy_maker.zip

      import c4d
      from c4d import plugins, bitmaps
      import os
      import math
      
      PLUGIN_ID = 999999999
      doc = c4d.documents.GetActiveDocument()
      
      
      def create_leaf(op):
      
          spline_data = op[c4d.PY_SPLINE]
          point_cnt = spline_data.GetKnotCount()
          leaf = c4d.BaseObject(c4d.Oplane)
          leaf[c4d.PRIM_PLANE_WIDTH] = 12
          leaf[c4d.PRIM_PLANE_HEIGHT] = 12
          leaf[c4d.PRIM_AXIS] = 2 #5
          leaf[c4d.PRIM_PLANE_SUBH] = 1
          leaf[c4d.PRIM_PLANE_SUBW] = 2
      
          if op[c4d.PY_CHECK_EXCLUDE]:
              if point_cnt >= 2:
                  leaf[c4d.PRIM_PLANE_SUBH] = point_cnt - 1
      
                  leaf = c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_CURRENTSTATETOOBJECT, list=[leaf], doc=op.GetDocument())[0]
                  for tag in leaf.GetTags():
                      if tag.IsInstanceOf(c4d.TAG_POLYSELECTION) or tag.IsInstanceOf(c4d.Tedgeselection):
                          tag.Remove()
      
                  for index, pos in enumerate(leaf.GetAllPoints()):
                      leaf.SetPoint(index, pos + c4d.Vector(0, 0, 6))
      
                  knots = spline_data.GetKnots()
                  counter = 0
                  for index in range(0, (leaf.GetPointCount() - 1) - 1, 3):
                      knot_pos = knots[counter]["vPos"]
                      x = -knot_pos.y / 8
                      y = knot_pos.z
                      z = knot_pos.x / 2
                      counter += 1
      
                      leaf.SetPoint(index, c4d.Vector(x, y, z))
                      if index + 1 == 1:
                          z_var = 0
                      elif index + 1 == leaf.GetPointCount() - 2:
                          z_var = 0
                      else:
                          z_var = 0 #x / 3
                      leaf.SetPoint(index + 1, c4d.Vector(0, z_var, z))
                      leaf.SetPoint(index + 2, c4d.Vector(-x, y, z))
      
      
              bend = c4d.BaseObject(c4d.Obend)
              bend.InsertUnder(leaf)
      
              bend[c4d.DEFORMOBJECT_ALIGNMENT] = 5
              if c4d.threading.GeIsMainThread():
                  c4d.CallButton(bend, c4d.DEFORMOBJECT_FITTOPARENT)
              bend[c4d.DEFORMOBJECT_STRENGTH] = c4d.utils.DegToRad(30)
              bend[c4d.DEFORMOBJECT_ANGLE] = c4d.utils.DegToRad(90)
              bend[c4d.DEFORMOBJECT_ALIGNMENT] = 0
              if c4d.threading.GeIsMainThread():
                  c4d.CallButton(bend, c4d.DEFORMOBJECT_FITTOPARENT)
              bend[c4d.DEFORMOBJECT_SIZE] = c4d.Vector(17, 50, 0)
              bend.SetRelPos(c4d.Vector(0, 0, 25))
      
              leaf = \
                  c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_CURRENTSTATETOOBJECT, list=[leaf], doc=op.GetDocument())[
                      0]
              for tag in leaf.GetTags():
                  if tag.IsInstanceOf(c4d.TAG_POLYSELECTION) or tag.IsInstanceOf(c4d.Tedgeselection):
                      tag.Remove()
      
          else:
              leaf = \
              c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_CURRENTSTATETOOBJECT, list=[leaf], doc=op.GetDocument())[0]
              for tag in leaf.GetTags():
                  if tag.IsInstanceOf(c4d.TAG_POLYSELECTION) or tag.IsInstanceOf(c4d.Tedgeselection):
                      tag.Remove()
      
              for index, pos in enumerate(leaf.GetAllPoints()):
                  leaf.SetPoint(index, pos + c4d.Vector(0, 6, 0))
      
          return leaf
      
      
      class Ivy_Maker(plugins.ObjectData):
      
          def __init__(self):
              self.SetOptimizeCache(True)
      
          def Init(self, op):
              self.InitAttr(op, bool, c4d.PY_CHECK_EXCLUDE)
              op[c4d.PY_CHECK_EXCLUDE] = True
              return True
      
          def GetVirtualObjects(self, op, hh):
      
              # dirty = op.CheckCache(hh) or op.IsDirty(c4d.DIRTYFLAGS_DATA)
              # if not dirty:
              #     return op.GetCache(hh)
              leaf = create_leaf(op)
              return leaf
      
          def GetDDescription(self, op, description, flags):
              if not description.LoadDescription(op.GetType()):
                  return False
      
              spline_data = c4d.DescID(c4d.PY_SPLINE)
              in_exclude = c4d.DescID(c4d.PY_INEXCLUDE)
              db = description.GetParameterI(spline_data)
              db2 = description.GetParameterI(in_exclude)
              if op[c4d.PY_CHECK_EXCLUDE] == 0:
      
                  db.SetBool(c4d.DESC_HIDE, True)
              else:
                  if db2:
                      db2.SetBool(c4d.DESC_HIDE, True)
      
              return (True, flags | c4d.DESCFLAGS_DESC_LOADED)
      
          def GetDEnabling(self, op, did, t_data, flags, itemdesc):
      
              # if did[0].id == c4d.PY_SPLINE and op[c4d.PY_CHECK_EXCLUDE] == 1:
              #     return False
              return True
      
          def Message(self, op, type, data):
      
              if type == c4d.MSG_MENUPREPARE:
                  spline_data = c4d.SplineData()
                  spline_data.MakeLinearSplineLinear(lPoints=6)
                  spline_data.SetKnot(0, c4d.Vector(0, 10, 0), c4d.FLAG_KNOT_T_BREAK, interpol=c4d.CustomSplineKnotInterpolationLinear)
                  spline_data.SetKnot(1, c4d.Vector(10, 40, 0), c4d.FLAG_KNOT_T_BREAK, interpol=c4d.CustomSplineKnotInterpolationLinear)
                  spline_data.SetKnot(2, c4d.Vector(30, 75, 0), c4d.FLAG_KNOT_T_BREAK, interpol=c4d.CustomSplineKnotInterpolationLinear)
                  spline_data.SetKnot(3, c4d.Vector(52, 73, 0), c4d.FLAG_KNOT_T_BREAK, interpol=c4d.CustomSplineKnotInterpolationLinear)
                  spline_data.SetKnot(4, c4d.Vector(75, 50, 0), c4d.FLAG_KNOT_T_BREAK, interpol=c4d.CustomSplineKnotInterpolationLinear)
                  spline_data.SetKnot(5, c4d.Vector(105, 0, 0), c4d.FLAG_KNOT_T_BREAK, interpol=c4d.CustomSplineKnotInterpolationLinear)
      
                  op[c4d.PY_SPLINE] = spline_data
      
              return True
      
      
      if __name__ == "__main__":
          path, file = os.path.split(__file__)
          file = "icon.tif"
          new_path = os.path.join(path, "res", file)
          bitmap = bitmaps.BaseBitmap()
          bitmap.InitWith(new_path)
          plugins.RegisterObjectPlugin(id=PLUGIN_ID, str="ivy_maker", g=Ivy_Maker, description="ivy_maker", icon=bitmap,
                                       info=c4d.OBJECT_GENERATOR)
      
      

      res file:

      CONTAINER Ivy_Maker //Grossbuchstaben
      {
      NAME IVY_MAKER; //dieser Name wird im Attribute Manager angezeigt
      INCLUDE Obase;

      GROUP ID_OBJECTPROPERTIES
      {
          BOOL PY_CHECK_EXCLUDE {}
      
          SPLINE PY_SPLINE
          {
              SHOWGRID_H;
              SHOWGRID_V;
      
              MINSIZE_H 120;
              MINSIZE_V 120;
      
              EDIT_H;
              EDIT_V;
      
              X_MIN 0;
              X_MAX 120;
      
              Y_MIN 0;
              Y_MAX 120;
      
              X_STEPS 1;
              Y_STEPS 1;
          }
          IN_EXCLUDE PY_INEXCLUDE
          {
      
              ACCEPT { Obase; };
      
          }
      
      }
      

      }

      Thanks,
      T.S.B

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

        Hello @ThomasB,

        Thank you for reaching out to us. As announced here, Maxon is currently conducting a company meeting. Please understand that our capability to answer questions is therefore limited at the moment.

        Without debugging your plugin, I cannot give you here an exact answer, and I currently do not have the time to do that (extensively). Here are some pointers/ideas:

        1. The problem is located in GetVirtualObjects (GVO), i.e., create_leaf, with on certainty bordering probability.
        2. You use SendModelingCommand(SMC) in your create_leaf and with that GVO. Using SMC in GVO is possible, but you must adhere to threading restrictions.
        3. You violate threading restrictions when executing the SMC MCOMMAND_CURRENTSTATETOOBJECT in doc=op.GetDocument(), i.e., modify the document the node is part of while the caches of that document are being built (that is why GVO is being called).
        4. I personally would just write a function which generates the uvw mapped plane, and one which deforms it, but a middle ground could be to execute passes as shown below.
        5. There were also some other problems that I spotted, see code listing.

        I could only partially reproduce your problem (in 2023.2) but the code might help you anyways. I have marked my changes with [FH].

        Cheers,
        Ferdinand

        Result:
        1d48419f-6332-41fb-8908-e44dd125d2e5-image.png

        Code:

        import c4d
        from c4d import plugins, bitmaps
        import os
        import math
        
        PLUGIN_ID = 999999999
        doc = c4d.documents.GetActiveDocument() # No, just, no. Never do this in a plugin.
        
        class Ivy_Maker(plugins.ObjectData):
        
            def __init__(self):
                self.SetOptimizeCache(True)
        
            def Init(self, op):
                """[FH] Added spline parameter and value init.
                """
                self.InitAttr(op, bool, c4d.PY_CHECK_EXCLUDE)
                self.InitAttr(op, c4d.SplineData, c4d.PY_SPLINE)
        
                op[c4d.PY_CHECK_EXCLUDE] = True
                spline: c4d.SplineData = c4d.SplineData()
                spline.MakeLinearSplineLinear(lPoints=6)
                spline.SetKnot(0, c4d.Vector(0, 10, 0), c4d.FLAG_KNOT_T_BREAK, 
                               interpol=c4d.CustomSplineKnotInterpolationLinear)
                spline.SetKnot(1, c4d.Vector(10, 40, 0), c4d.FLAG_KNOT_T_BREAK, 
                               interpol=c4d.CustomSplineKnotInterpolationLinear)
                spline.SetKnot(2, c4d.Vector(30, 75, 0), c4d.FLAG_KNOT_T_BREAK, 
                               interpol=c4d.CustomSplineKnotInterpolationLinear)
                spline.SetKnot(3, c4d.Vector(52, 73, 0), c4d.FLAG_KNOT_T_BREAK, 
                               interpol=c4d.CustomSplineKnotInterpolationLinear)
                spline.SetKnot(4, c4d.Vector(75, 50, 0), c4d.FLAG_KNOT_T_BREAK, 
                               interpol=c4d.CustomSplineKnotInterpolationLinear)
                spline.SetKnot(5, c4d.Vector(105, 0, 0), c4d.FLAG_KNOT_T_BREAK, 
                               interpol=c4d.CustomSplineKnotInterpolationLinear)
                op[c4d.PY_SPLINE] = spline
        
                return True
        
            def GetVirtualObjects(self, op, hh):
                """[FH] 
                """
                def GetLeaf() -> c4d.PolygonObject:
                    """[FH] This is your #create_leaf.
                    """
                    # Using SMC to create a plane is overkill, I personally would just write a function that
                    # does create that PolygonObject manually. A middle ground could be executing the cache
                    # pass on #plane so that we can hold of its cache.
                    plane = c4d.BaseObject(c4d.Oplane)
                    if not plane:
                        raise MemoryError(f"{plane = }")
        
                    spline: c4d.SplineData = op[c4d.PY_SPLINE]
                    plane[c4d.PRIM_PLANE_WIDTH] = 12
                    plane[c4d.PRIM_PLANE_HEIGHT] = 12
                    plane[c4d.PRIM_AXIS] = 2
                    plane[c4d.PRIM_PLANE_SUBH] = max(1, (spline.GetKnotCount() - 1))
                    plane[c4d.PRIM_PLANE_SUBW] = 2
        
                    temp: c4d.documents.BaseDocument = c4d.documents.BaseDocument()
                    temp.InsertObject(plane)
                    temp.ExecutePasses(c4d.threading.GeGetCurrentThread(), False, False, True, 
                                       c4d.BUILDFLAGS_NONE)
        
                    cache: c4d.PolygonObject = plane.GetCache().GetClone() if plane.GetCache() else None
                    if cache is None:
                        raise RuntimeError("Could not build plane generator cache.")
        
                    # Do your SplineData based profile deformation.
                    knots = spline.GetKnots()
                    counter = 0
                    for index in range(0, (cache.GetPointCount() - 1) - 1, 3):
                        knot_pos = knots[counter]["vPos"]
                        x = -knot_pos.y / 8
                        y = knot_pos.z
                        z = knot_pos.x / 2
                        counter += 1
        
                        cache.SetPoint(index, c4d.Vector(x, y, z))
                        if index + 1 == 1:
                            z_var = 0
                        elif index + 1 == cache.GetPointCount() - 2:
                            z_var = 0
                        else:
                            z_var = 0 #x / 3
                        cache.SetPoint(index + 1, c4d.Vector(0, z_var, z))
                        cache.SetPoint(index + 2, c4d.Vector(-x, y, z))
        
                    cache.Message(c4d.MSG_UPDATE)
        
                    # Get the bend deformation, removed never to be executed CallButton code since we are
                    # not on the MT here. Instead, we use the bounding box of #cache to fit the bend object.
                    boundingBox: c4d.Vector = cache.GetRad() * 2
                    bend: c4d.BaseObject = c4d.BaseObject(c4d.Obend)
                    if not bend:
                        raise MemoryError(f"{bend = }")
        
                    bend.InsertUnder(cache)
                    bend[c4d.DEFORMOBJECT_STRENGTH] = c4d.utils.DegToRad(30)
                    bend[c4d.DEFORMOBJECT_SIZE] = c4d.Vector(0, boundingBox.z, boundingBox.x)
        
                    # Rotate and move the bend deformer into place.
                    bend.SetMg(c4d.Matrix(off=cache.GetMp()) * 
                               c4d.utils.MatrixRotY(c4d.utils.DegToRad(90)) * 
                               c4d.utils.MatrixRotZ(c4d.utils.DegToRad(90)))
        
                    # Get the deform cache for that bend deformation.
                    temp.InsertObject(cache)
                    temp.ExecutePasses(c4d.threading.GeGetCurrentThread(), False, False, True, 
                                       c4d.BUILDFLAGS_NONE)
                    if not cache.GetDeformCache():
                        raise RuntimeError("Could not build plane generator deform cache.")
        
                    result: c4d.PolygonObject = cache.GetDeformCache().GetClone()
                    temp.Flush()
        
                    return result
        
                return GetLeaf()
        
            def GetDDescription(self, op, description, flags):
                if not description.LoadDescription(op.GetType()):
                    return False
        
                spline_data = c4d.DescID(c4d.PY_SPLINE)
                in_exclude = c4d.DescID(c4d.PY_INEXCLUDE)
                db = description.GetParameterI(spline_data)
                db2 = description.GetParameterI(in_exclude)
                if op[c4d.PY_CHECK_EXCLUDE] == 0:
        
                    db.SetBool(c4d.DESC_HIDE, True)
                else:
                    if db2:
                        db2.SetBool(c4d.DESC_HIDE, True)
        
                return (True, flags | c4d.DESCFLAGS_DESC_LOADED)
        
            def GetDEnabling(self, op, did, t_data, flags, itemdesc):
        
                # if did[0].id == c4d.PY_SPLINE and op[c4d.PY_CHECK_EXCLUDE] == 1:
                #     return False
                return True
        
            def Message(self, op, type, data):
                """[FH] 
                """
                # [FH] The message MSG_MENUPREPARE is not the place to init a parameter, unless you 
                # intentionally want to overwrite user settings.
                return True
        
        
        if __name__ == "__main__":
            path, file = os.path.split(__file__)
            file = "icon.tif"
            new_path = os.path.join(path, "res", file)
            bitmap = bitmaps.BaseBitmap()
            bitmap.InitWith(new_path)
            plugins.RegisterObjectPlugin(
                id=PLUGIN_ID, str="ivy_maker", g=Ivy_Maker, description="ivy_maker", icon=bitmap,
                info=c4d.OBJECT_GENERATOR)
        
        

        MAXON SDK Specialist
        developers.maxon.net

        ThomasBT 2 Replies Last reply Reply Quote 1
        • maxonM maxon moved this topic from Cinema 4D SDK on
        • ThomasBT
          ThomasB @ferdinand
          last edited by

          @ferdinand

          I could only partially reproduce your problem (in 2023.2) but the code might help you anyways. I have marked my changes with [FH].
          

          And how that helped me. Thank you very much that was very clear for me to understand.
          Thanks alot

          Tom

          Thanks,
          T.S.B

          1 Reply Last reply Reply Quote 0
          • ThomasBT
            ThomasB @ferdinand
            last edited by ThomasB

            @ferdinand
            Hi Ferdinand, I reproduced your code and changed the things with this cache and ExecutePasses etc. in my code.
            But there is still a problem with it, when I call Reload Python Plugins or when I call the plugin from Menue. The leaf is not bended. Only when I click on the splinedata field to move a point does the leaf jump into the curved shape.

            When I click make editable directly after I called it from the menue, the leaf is bended as polygon object, but not in the view after calling it from menue.

            Thanks,
            T.S.B

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

              Hello @ThomasB,

              I cannot reproduce this here. On which version and OS are you? Judging by the code you have sent earlier, I would say you have simply reintroduced a bug.

              ivy.gif

              My code should not be able to return an unbent leaf, it either returns a leaf or throws and error. One of the problems of your code was that you had this call-button code in there which more or less led to a race condition between fit-command and the cache building of your leaf. I also removed quite a bit of questionable branching in your code (if elif else). I would suggest that you debug your plugin to see its branching when the bug occurs. We cannot debug your plugin for you.

              Cheers,
              Ferdinand

              MAXON SDK Specialist
              developers.maxon.net

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

                Hello @ThomasB,

                without further questions or postings, we will consider this topic as solved by Monday 05/06/2023 and flag it accordingly.

                Thank you for your understanding,
                Maxime.

                MAXON SDK Specialist

                Development Blog, MAXON Registered Developer

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