creating a subdivision surface with fields falloff via python generator



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



  • 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



  • 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



  • 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


Log in to reply