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

    creating a subdivision surface with fields falloff via python generator

    Cinema 4D SDK
    python r21 sdk
    2
    4
    1.0k
    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.
    • D
      datamilch
      last edited by datamilch

      hi there, first entry here ...
      so i'd like to use a python generator to subdivide a child object.
      for the child it should be possible to be a baseobject or a polyobject.
      i'd like to restrict the subdivision to certain areas via fields.

      what works so far for the restriction:
      polyobject + poly selection tag
      polyobject + poly selection tag with fields activated
      baseobject + python generated selecton tag

      what is not working yet:
      baseobject + python generated selecton tag with fields activated (fields from userdata)

      what i tried to make it work:

      1. create a clone of the baseobject in cache and convert it to polygonobject
      2. create a selection tag in cache and assign it to the cloned polygonobject
      3. activate the "use fields" option and assign the fields from the user data.
      4. copy the baseselect of the tag to the baseselect of the cloned polygonobject
      5. subdivide selected polygons.
      6. return the cloned polygonobject.

      i think the problem is in 3. or between 3. and 4.
      when i read the baseselect, it does not read the selection of the fields, but the seletion saved in the tag. so, i'm not sure how to access the selection of the field propperly. any suggestions on that?

      when i convert the python generator, the resulting polygonobject has a tag with fields activated and the selection is working. so i suspekt it has something to do with the tag only being in cache.

      here is the file:
      subdeformer_02.c4d

      import c4d
      from c4d import BaseSelect
      
      def main():
          first = op.GetDown()
          if not first: return
      
          dic = op.GetAndCheckHierarchyClone(hh, first, c4d.HIERARCHYCLONEFLAGS_ASPOLY, False)
          if not dic["dirty"] and not op.IsDirty(c4d.DIRTY_DATA) and op.GetCache():
              return op.GetCache().GetClone()
          obj = dic["clone"]
          
          sub_levels = op[c4d.ID_USERDATA, 1]
          sub_offset = op[c4d.ID_USERDATA, 3]
          sub_hyper = op[c4d.ID_USERDATA, 2]
          sel_fields = op[c4d.ID_USERDATA, 4]
      
      
          bs = obj.GetPolygonS()
          c4d.BaseSelect.DeselectAll(bs)
      
          new_sel_tag = obj.MakeTag(c4d.Tpolygonselection)
          new_sel_tag.SetName("poly_selection")
          new_sel_tag_bs = new_sel_tag.GetBaseSelect()
          #new_sel_tag_bs.SelectAll(obj.GetPolygonCount())
          new_sel_tag[c4d.POLYGONSELECTIONTAG_ENABLEFIELDS] = True
          new_sel_tag[c4d.POLYGONSELECTIONTAG_FIELDS] = sel_fields
      
          bs.Merge(new_sel_tag_bs)
      
      
          settings = c4d.BaseContainer()
          settings[c4d.MDATA_SUBDIVIDE_HYPER] = sub_hyper
          settings[c4d.MDATA_SUBDIVIDE_SUB] = 1 #sub_levels handled by iteration + shrinking
      
          for x in range(sub_levels):
              if bs.GetCount()>0:
                  c4d.utils.SendModelingCommand(
                      command = c4d.MCOMMAND_SUBDIVIDE,
                      list = [obj],
                      mode = c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
                      bc = settings,
                      doc = c4d.documents.GetActiveDocument())
      
              if x >= sub_offset:
                  c4d.utils.SendModelingCommand(
                      command = c4d.MCOMMAND_SELECTSHRINK,
                      list = [obj],
                      mode = c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
                      doc = c4d.documents.GetActiveDocument())
      
          return obj
      

      despite all that. do you think the route via the selection tag is ok (to me it seems practical)?
      or might there be a better / more direct route to access the fields?

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

        hi @datamilch and welcome to the forum,

        In your case, you should create an ObjectData plugin. Check this roundTube example you will see GetVirtualObject is the place where you can build the cache of your generator.
        Using a python generator in that particular case is something that will not really work for what you need.

        For example you want an object as input. You have to touch this object if you want it to not appear in the render. This function destroy the cache and reset the dirtyness of the object. That reset will make your python generator to create an infinite loop.

        That's also why you have to return a clone of your cache to make it "work"

        For your problem, you can directly sample the fieldlist, check the value of each point for each polygon and add it to the selection if needed.

        In case of refresh problem, you can also check the dirtyness of the fieldlist.
        In your case, as you are working in a clone of the hierarchy, the object is not inserted in the document. So you can't use the SampleListSimple to sample the FieldList. You have to use SampleList so you can specify the document. SampleListSimple will use obj.GetDocument() witch in that case doesn't exist and will fail.

        import c4d
        from c4d import BaseSelect
        
        
        
        
        def main():
            first = op.GetDown()
            if not first:
                return
        
            dic = op.GetAndCheckHierarchyClone(hh, first, c4d.HIERARCHYCLONEFLAGS_ASPOLY, False)
        
        
            if not dic["dirty"] and not op.IsDirty(c4d.DIRTY_DATA) and op.GetCache():
                return op.GetCache() #Do not know why op.GetCache() fail sometime but returning a cloned works like a charm !
            obj = dic["clone"]
            
            # only use for this particular context. Touching the object will destroy it's cache and reset the dirtyness
            first.Touch()
        
        
            sub_levels = op[c4d.ID_USERDATA, 1]
            sub_offset = op[c4d.ID_USERDATA, 3]
            sub_hyper = op[c4d.ID_USERDATA, 2]
            sel_fields = op[c4d.ID_USERDATA, 4]
        
        
        
        
            bs = obj.GetPolygonS()
            c4d.BaseSelect.DeselectAll(bs)
        
            new_sel_tag = obj.MakeTag(c4d.Tpolygonselection)
            new_sel_tag.SetName("poly_selection")
            new_sel_tag_bs = new_sel_tag.GetBaseSelect()
        
            pointCount = obj.GetPointCount()
        
            # Prepares field input with the points to sample
            inputField = c4d.modules.mograph.FieldInput(obj.GetAllPoints(), pointCount)
            # Creates the FieldInfo so we can specify the doc even if the object is not inserted inside the document
            info = c4d.modules.mograph.FieldInfo.Create(c4d.FIELDSAMPLE_FLAG_VALUE, None, doc, 0, 1, inputField, obj)
            # Creates an FieldOutput
            outputs = c4d.modules.mograph.FieldOutput.Create(pointCount, c4d.FIELDSAMPLE_FLAG_VALUE)
            # Sample all points
            sel_fields.SampleList(info, inputField, outputs)
        
            # Check for each polygon if the sum of all points is over 0.5, if so, the polygon is selected.
            polys = obj.GetAllPolygons()
            for polyIndex, cPoly in enumerate(polys):
                polyValue = 0.0;
                if cPoly.c == cPoly.d:
                    polyValue = outputs._value[cPoly.a] +  outputs._value[cPoly.b] + outputs._value[cPoly.c]
                else:
                    polyValue = outputs._value[cPoly.a] +  outputs._value[cPoly.b] + outputs._value[cPoly.c] + outputs._value[cPoly.d]
                if  polyValue > 0.5:
                    bs.Select(polyIndex)
        
        
            settings = c4d.BaseContainer()
            settings[c4d.MDATA_SUBDIVIDE_HYPER] = sub_hyper
            settings[c4d.MDATA_SUBDIVIDE_SUB] = 1 #sub_levels handled by iteration + shrinking
        
            for x in range(sub_levels):
                if bs.GetCount()>0:
                    c4d.utils.SendModelingCommand(
                        command = c4d.MCOMMAND_SUBDIVIDE,
                        list = [obj],
                        mode = c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
                        bc = settings,
                        doc = c4d.documents.GetActiveDocument())
        
                if x >= sub_offset:
                    c4d.utils.SendModelingCommand(
                        command = c4d.MCOMMAND_SELECTSHRINK,
                        list = [obj],
                        mode = c4d.MODELINGCOMMANDMODE_POLYGONSELECTION,
                        doc = c4d.documents.GetActiveDocument())
        
            return obj
        

        cheers,
        Manuel

        MAXON SDK Specialist

        MAXON Registered Developer

        1 Reply Last reply Reply Quote 1
        • D
          datamilch
          last edited by

          hi manuel,
          thank you so much for this quick and super detailed response. it was very useful!

          concerning the ObjectData plugin, i will definitely check all your links, but i must say it's a litte overwelming, since i'm more of a self tought artist, not a real programmer. so forgive me, if i stay with the diry hack, for now.

          this infinite loop, is it tolerable or will it lead to a crash eventually?

          your code for the fliedlist works fine and i already encountered the refresh problem. so i will see if i get my head around the dirtyness check 🙂

          i had to correct your code a tiny bit, to average the polygon weight propperly:

          if cPoly.c == cPoly.d:
          	polyValue = outputs._value[cPoly.a] +  outputs._value[cPoly.b] + outputs._value[cPoly.c]
          	polyValue =/3
          else:
          	polyValue = outputs._value[cPoly.a] +  outputs._value[cPoly.b] + outputs._value[cPoly.c] + outputs._value[cPoly.d]
          	polyValue =/4
          

          but for reasons of speed i went with only evaluating cPoly.a for all polygons, that's precise enough for now.

          so here is a litte image of what can be done with it so far.
          doesn't look very useful, but surely has alot of applications ...
          i will propably post another verion of the file at some time.
          or some more questions 🙂

          dynamic_subdivide_01.JPG

          thanks for now, sebastian

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

            Hi,

            I often use the python generator to create some prototype myself. So it's ok for me. But if you want to give or sell your result, you should move to a plugin.
            It's not hard at all, if you already did that prototype, the plugin is easy, and we are here in case you have some issue.

            Nice result 🙂

            Cheers,
            Manuel

            MAXON SDK Specialist

            MAXON Registered Developer

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