SubD vertex tag method not working properly

Hi all.

This might seems a grasshopper post, but it is mainly a rhinocommon problem, I guess. Rhino 7.
Setting a SubD vertex tag result in an odd shape. This can be fixed by moving the whole SubD, but it doesn’t always work, sometime you are left with a null or an invalid SubD.

In this simple case:
subd.Vertices.First.Tag = Rhino.Geometry.SubDVertexTag.Corner;
return the same result as this:


subd vertex tag bug.gh (4.8 KB)

(I remember I did report this months ago, as well as other users…)

Please tell me if i’m using rhinocommon or grasshopper not correctly.


PS

  • GH point deform still doesn’t care to work with SubDs
  • GH SubD components “Short names” (Full names option off) are messy:

    “Extracting” the identifiers are outputted as “I” but setting tags takes identifiers as “E” or “V” … make no sense to me.
    (Other gh components short names rarely make confusion like this)
    Imo changing output/input order or name shouldn’t cause backwards compatibility problems, as apparently not many users are using this components… as abc bugs like this are still present… :smiling_face_with_tear:
  • components to set SubD tags take the tag (crease/corner/smooth/etc) as single item, but identifiers as list! This is tricky because to set many different kind of tags you have to chain multiple gh components, and this is a different mentality than the rest of gh components.

@pierrec

Just bumped into the same issue. Upvoting this topic hopefully to be resolved.

@pierrec - can you look at this?

Years after and still not fixed. Feel free to use this C# patch that actually works.
Input tags “crease” or “corner”, vertices indexing is 0-based (so it matches the Grasshopper logic).

private void RunScript(
	DataTree<SubD> S,
	DataTree<string> T,
	DataTree<int> V,
	ref object A)
{
    this.Component.Message = "SubD Vert Tags";

    GH_Structure<GH_SubD> resultTree = new GH_Structure<GH_SubD>();

    // 1. Iterate through the branches of the SubD tree (S)
    for (int i = 0; i < S.Paths.Count; i++)
    {
        GH_Path path = S.Paths[i];
        List<SubD> sBranch = S.Branches[i];

        // 2. Resolve corresponding branches for Tags and Indices
        // Use Modulo to handle structure mismatches (e.g. S={0;1} vs T={0})
        List<string> tBranch = T.Branches[i % T.Paths.Count];
        List<int> vBranch = V.Branches[i % V.Paths.Count];

        // 3. Process items within the branch
        for (int j = 0; j < sBranch.Count; j++)
        {
            // Extract the geometry safely
            // Since input is DataTree<SubD>, 'wrapper' is already the SubD object
            SubD existingSubD = sBranch[j];
            
            if (existingSubD == null) continue;

            // Create a Deep Copy of the geometry
            // Duplicate() is a method of GeometryBase. We cast the result back to SubD.
            SubD geometry = (SubD)existingSubD.Duplicate();

            // 4. Apply changes
            int opCount = vBranch.Count;
            int tCount = tBranch.Count;

            for (int k = 0; k < opCount; k++)
            {
                // Get Vertex Index
                int vIndex = vBranch[k] + 1; // Get SubD 1-based indexing from Grasshopper 0-based indexing 

                // Get Tag (Safe access with cyclic wrapping for Longest List logic)
                string tagString = tBranch[k % tCount];
                SubDVertexTag tag = ParseTag(tagString);

                // Apply to Geometry
                // We check against geometry.Vertices.Count to prevent crashes
                if (vIndex >= 0 && vIndex < geometry.Vertices.Count)
                {
                    SubDVertex vertex = geometry.Vertices.Find(vIndex);
                    if (vertex != null)
                    {
                        vertex.Tag = tag;
                    }
                }
            }

            // Important: Update the SubD to recalculate the limit surface
            geometry.ClearEvaluationCache();

            // Add to output tree preserving the original path
            resultTree.Append(new GH_SubD(geometry), path);
        }
    }

    A = resultTree;
}

// Helper method to parse strings/numbers into SubD tags
private SubDVertexTag ParseTag(string input)
{
    if (string.IsNullOrEmpty(input)) return SubDVertexTag.Smooth;

    string s = input.ToLower().Trim();

    // Check for Crease
    if (s == "crease" || s == "c" || s == "1") 
        return SubDVertexTag.Crease;
    
    // Check for Corner
    if (s == "corner" || s == "co" || s == "2") 
        return SubDVertexTag.Corner;

    // Default to Smooth
    return SubDVertexTag.Smooth;
}

These should get fixed in the next v8 SR, assuming my latest changes pass verification. See:

The rest of the issues with these components will take a bit more thought, see:

@PetrVacek Thanks for offering a workaround while waiting for the SDK and GH components to be improved. I noticed a couple of issues in your code, explained below if anyone else needs to use similar code now. The changes I made to the RhinoCommon SDK here take care not to reproduce these issues.

In SubDs, component ids are identifiers (based on integers), not indices (into the elements of an array). Contrary to array indices, integer identifiers do not have to start at 0 and do not have to be serial. For example in a SubD with 4 vertices, these 4 vertices can have ids [10, 20, 30, 512]. The range of valid identifiers in a SubD starts at 1 and ends at uint.MaxValue - 1 (both inclusive), whatever the number of components in that SubD.

Before clearing the cache, it is important that you call geometry.UpdateAllTagsAndSectorCoefficients() too, otherwise you will easily create invalid SubDs by setting vertex tags that are incompatible with the current edge tags.

Also, I am not a Grasshopper expert so I can’t tell you for sure if it is wrong, but I’m highly suspicious that this modulo trick here might be hiding more unexpected behavior than it is protecting against anything:

        // Use Modulo to handle structure mismatches (e.g. S={0;1} vs T={0})
        List<string> tBranch = T.Branches[i % T.Paths.Count];

RH-92476 is fixed in Rhino 8 Service Release 30