Any progress in SubD methods?

Thanks for the explanation, I’ll make sure we write additional examples for drawing SubD components.

Which edges are we talking about here, SubDEdges objects or you line display proxy? Can you point me to the documentation page that is lacking information?

The SubDEdges available on a SubDFace:

SubDFace.EdgeAt method (rhino3d.com)

I had initially hoped that they would be in an order to trace the bounding edges of a surface with a consistent surface normal direction if you followed them along in order: (0), (1), etc.

That’s not a request for behavior, just something I thought it might be helpful to document either way as far as what to expect: no expectation about order or whether there’s a consistent order they get placed in.

Oh yeah lots of comments lost in there… The edges should be returned in counter-clockwise order (around the “up” normal of the face), along with the edges orientation so that:

(
face.EdgeDirectionMatchesFaceOrientation(n) ?
    face.EdgeAt(n).VertexTo :
    face.EdgeAt(n).VertexFrom
) == (
face.EdgeDirectionMatchesFaceOrientation(n+1) ?
    face.EdgeAt(n+1).VertexFrom :
    face.EdgeAt(n+1).VertexTo
)

I.e., the end vertex of one edge is the start vertex of the next edge, taking into account the orientation in the face of each edge. If they aren’t, it’s a bug or an invalid SubD (which is likely a bug too).

I’m working on making that easier to work with, for example the C++ SDK has ways to automatically deal with the edge orientation so the user will just see: face.EdgeAt(n).RelativeVertex(1) == face.EdgeAt(n+1).RelativeVertex(0)

Here are the raw C++ comments from the OpenNURBS documentation for the ON_SubDEdges of an ON_SubDFace:

  // Array of m_edge_count edges that form the boundary of the face.
  // The edges are in ordered to form a continuous loop.
  //
  //  The first four are in m_edge4[0], ..., m_edge4[3].
  //  When m_edge_count > 4, the fifth and additional edges
  //  are in m_edgex[0], ..., m_edgex[m_edge_count-5];
  //
  //  The value of ON_SubDEdgePtr.EdgeDirection() is 0 if the
  //  edge's natural orientation from m_vertex[0] to m_vertex[1]
  //  agrees with the face's boundary orientation.
  //
  //  The value of ON_SubDEdgePtr.EdgeDirection() is 1 if the
  //  edge's natural orientation from m_vertex[0] to m_vertex[1]
  //  is opposited the face's boundary orientation.
2 Likes

Related: I just noticed that MeshFace is another ‘orphan’ type. I assume that’s a result of deriving it as needed rather than considering it a permanent part of the Mesh. It would also be handy to have that as a native GeometryBase type.

What are you trying to do?

@stevebaer Good question given all of the unusual requests and comments I’ve been making lately!

I’m trying to:

Display information about selected objects:
A) For things that implement GeometryBase, I show various physical quantities like boundaries, area, volume, centroid
1) for “free” - don’t have to code it myself
2) exactly consistent with the values Rhino would get

B) graphically - it would be nice if when I draw an entity it’s visually consistent with the way Rhino does. So far, this shows up in the Drawing Conduit where not all classes are supported. Two examples of the inconsistency: SubD (Rhino API has no function so I draw the edges), MeshFace (Rhino seems to by default shade the face when selected as a subobject- if there’s a reason for that and if it varies by user settings I’d like to get it for free by calling a function which ‘just does it’ consistently with other Rhino UI.

A(2) can be important for things like summing up the areas of faces or other geometrical values, so if SubD and MeshFace aren’t geometry I have to do various calcs myself and risk being inconsistent. That second part isn’t much of a risk for MeshFace unless I create a bug through not understanding how Rhino passes things (nature of vertices, etc.) in weird corner cases.

Maintain a record of current user selections and run my own tree view UI to describe Rhino objects:
for RhinoObjects obviously I can store the RhinoObject or just an ID.
For objects implementing GeometryBase, I’ve got a class to bundle them in an object variable or create a series of variables for RhinoObject, SubDComponent, MeshFace, and so on.

I understand the rationale of keeping things lean for objects like Line, but I can promote that to LineObject. SubDFace is a relatively complicated thing, and has nontrivial 3D properties both geometrically and visually, so it would be nice if I got Rhino’s power to deal with such things free out of the box instead of trying to reimplement it.

And for the “Why?” I want all of that to work cleanly:

I want to derive and maintain a record of selected objects including components. The user should be able to select in either direction: from my tree display or through graphical or command selection in the usual Rhino way. This lets them pick, say, an Extrusion in Rhino and then view information about the base curve and a wall curve.

Here are some examples (real code, not mockups).

Extrusion, summary of the Extrusion itself:

Extrusion, details of the top profile curve:

PolyCurve, summary of the whole curve:

Polycurve, details of an arbitrarily selected segment:

SubD, the object summary:

SubD, details of a face-this illustrates one of the things I’m asking about. It doesn’t support GeometryBase and doesn’t seem to have an independent implementation of Surface values like area or centroid and I had to ‘fake’ the display by drawing a polygon based on the edges:

I’d like to do the same very cleanly with all Rhino types. There’s a Rhino patch pending to facilitate passing the data from in-Rhino selection of Extrusion components to plugins (now not reasonably possible to get from Rhino-selected temporary Brep component index back to the original profile curve or wall curve or what have you). We have this discussion here about SubD components (Faces, Edges). Mesh Faces would be handy to address as well: in assessing mesh quality for various types of analysis, it would be nice to be able to directly work with the face like any other Rhino polygon or surface to check whether for example it incorporates an unreasonably small angle in one corner.

A MeshFace is even simpler than a Line as it is just 4 ints. We aren’t going to make this derive from GeometryBase, but we are more than happy to provide extra utility functions for working with and drawing mesh faces.

I’d noticed that… what I hadn’t noticed was that in RhinoCommon it’s only a Struct so a value type.

What I’m doing for now is creating a subclass of the PolyLineCurve using these points and it seems to do the job:

            List<Rhino.Geometry.Point3d> pts = new List<Rhino.Geometry.Point3d>();
            pts.Add(new Rhino.Geometry.Point3d(mesh.Vertices[face.A]));
            pts.Add(new Rhino.Geometry.Point3d(mesh.Vertices[face.B]));
            pts.Add(new Rhino.Geometry.Point3d(mesh.Vertices[face.C]));
            if (mesh.Vertices[face.C] != mesh.Vertices[face.D])
            {
                pts.Add(new Rhino.Geometry.Point3d(mesh.Vertices[face.D]));
            }
            pts.Add(new Rhino.Geometry.Point3d(mesh.Vertices[face.A]));

I’d still suggest some kind of Get method to make it more accessible to scripters and Grasshopper people.

For SubD- SubDFace and SubDEdge are:

  1. already classes so you’re already paying the per-instance overhead of class infrastructure
  2. don’t expose much in the way of ability to treat them as surfaces and curves respectively, either for display or for calculation (curve of an edge, surface area of a face, how much curvature is in a face or edge, etc.), which to me seems like something useful to provide for materials consumption and ease of fabrication.

Example: modeling something in SubD which will be built in fiberglass sections. The pieces might not have planar joining edges so it might be easier to select a set of faces to sum up surface area and therefore weight and centers than it would be to try to thicken the surface and then cut with a boolean.

But:

It may be that #2 is not a useful thing to do for people who create and use SubD’s. You caught me out once on not understanding SubD use cases (non-folded cones being a useful default) so this may be another.

Last time I sort of lost the strength to continue, but…
:grimacing:

    SubD s;
    foreach(Rhino.Geometry.SubDFace face in s.Faces){  // this works
    }
    foreach(Rhino.Geometry.SubDVertex vert in s.Vertices){  // this doesn't
    }

(I didn’t even test with edges. Shall we try?)

What I’m doing wrong?
Is there a way to iterate through subd vertexes or it’s the same old story where we are forced to use .First and .Next ? (aka “giving up”)

Sorry I’m just trying to code. Latest 7 or latest Beta are the same.

(i’m trying to easily spot vertexes surrounded by 5+ faces or edges, after some .obj export/import ngons are converted to triangle fans, and I have to manually remove them… anyway, this is just a dumb small case, irrilevant)

SubD history edge bug.zip (148.1 KB)
In this model there are old “child” curves (extracted from edges with history:on) that completely scramble up when doing any edit to the “parent” SubD. Even simply moving the subd.
I was not able to pinpoint the steps to replicate this, but I remember something similar happening to me while using vertex tags…
To remove and then re-add edge loops, I were about: 1 extract edge loops, 2 cut (CTRL-X) the resulting curves, 3 remove edge loops, 4 paste them.
Those edges did have history before cut-paste, and still try to update when parent geometry change, but the original edges are missing.

Anyway, it seems history edges are not linked with vertex tags but index instead OR vertex tag is not reliable in some case? (just a random quess)

Maybe it’s worth checking this … i don’t know…

I’ve been foreach-ing over faces and edges and getting what I need.

Vertices… I haven’t done it yet but I was expecting to get the ID’s from the faces or edges and using Find(id) to get the vertices.

You could look over in OpenNurbs to see how it’s actually stored and whether you just need to ask for something to be exposed in RhinoCommon.

Are you asking for something like the Topology access of Meshes?

I just needed to build a per-vertex algorithm.
… For now I’ve done it by using the control net, as a mesh, and lastly re converting it to SubD…

Hi @maje90,

Thanks for coming back to this thread and showing us what doesn’t work for you. As you noticed, some of the iterators (faces) already work with foreach, while others do not (vertices).

This kind of issues is exactly what I meant above when I wrote that I was working on improving these, the plan is to have all of them work with foreach (and its parallel version for const operations), as well as the iterators for the neighborhoods of vertices, edges and faces. So this is being worked on and will be easier to use soon.

However, I’m still confused about what you’ve been writing about .First, .Next etc. Why does that feel like “giving up” to you? What are you trying to do that fundamentally does not work with that?

The reason I’m asking is that support for foreach will not bring new capabilities to these iterators, it is just syntaxic sugar that saves you a little bit of typing. (Support for Parallel.ForEach is a little bit more involved but let’s keep that out of the discussion for now.) So if First and Next are missing capabilities for you to get your work done, I need to know about it now to fix it now.

Here’s a Python 3 example based on your last attempt:

v = subd.Vertices.First
while v != None:
    if v.FaceCount > 4:
        print(f"{v.Id} ({v.ControlNetPoint}): {v.FaceCount}")
    v = v.Next

Is that what you are looking for?

When SubD.Vertices supports foreach, the above Python code would look like this:

for v in subd.Vertices
    if v.FaceCount > 4:
        print(f"{v.Id} ({v.ControlNetPoint}): {v.FaceCount}")

Easier to read and write for sure, but not fundamentally more capable than the previous version.

FYI, you can reproduce that behavior now with a custom iterable class in Python, and probably in C# as well:

class SubDVertexIterator:
    __slots__ = 'iterator', 'current'

    def __init__(self, subd: Rhino.Geometry.SubD):
        self.iterator = subd.Vertices
        self.current = None

    def __iter__(self):
        self.current = self.iterator.First
        return self
    
    def __next__(self):
        if (current := self.current) is None:
            raise StopIteration
        self.current = current.Next
        return current

for v in SubDVertexIterator(subd):
    if v.FaceCount > 4:
        print(f"{v.Id} ({v.ControlNetPoint}): {v.FaceCount}")
    v = v.Next

DI-165226_PEC_SubD-Iter.py (959 Bytes)
DI-165226_PEC_SubD-Iter.3dm (298.6 KB)

Just that.
What I wanted to do is to easily spot “extraordinary vertices” while working on rhino, without interfering with my workflow.
I reference a subd and let a c# script display something obvious on screen where vertex “valence” is 5 or more (or exactly 5, depending on the case).
When I have SubD models of 1500-2000 faces, iterating vertices one-by-one results in a bit of computation time that makes me feel a “hiccup” every time my subd is edited (and an actual pause when I’m working on old hardware, unbearable).

I want to learn to code as you said, using foreach instead of the index-iterator.
This just happened…

1 Like

This edge history bug I could not fully reproduce. I do see your red curves being sent to wrong places, but without knowing how they were created it’s going to be hard to fix the issue.

I assume they all start from running the DupEdge command on a SubD edge loop? Was there scripting involved at any point? I think this might be more a problem with how the History command interacts with SubD rather than SubD component Ids being broken.

I created a couple of YouTrack issues for us to look at a couple of bugs I noticed when trying to reproduce your issue:

RH-77418 SubD DupEdge with history: break history update after original SubD edges are deleted

RH-77419 SubD DupEdge with history: extracted curve will switch to flat or smooth mode after SubD update

RH-77420 SubD DupEdge with history: Creasing across the reflection plane will mess up history updates

1 Like

Hi @pierrec, could you please point me to the methods ? I have not found something for removing subd components in the docs, despite JoinSubDs

thanks,
c.

2 Likes

@maje90,

you probably have been going through this already, in order to delete a subd face do we still have to convert the subd to mesh, find the control net center of the subdface to remove, get closest mesh face, find if it is part of ngon, delete faces of this ngon, then convert mesh back to subd ?

Imho this process is quite involved, especially if you have to keep the Subd PerFaceColors which do get lost in above method.

thanks,
c.

1 Like

Yes, but given up within minutes every time.
Because 1 the rhinocommon methods for SubD are too much cumbersome to use if compared to other classes, and 2 new interesting features are only for Rhino 8, where the c# editor make any serious experiment completely impossible.

Just tried to check today, still a mess.

  • we still have to iterate through .Next to use vertices
  • no methods to remove faces/edges
  • no .ClosestPoint or .ClosestParameter or anything similar at all
  • no colors
  • no method for intersections
  • topology?
  • etc

2025-02-27 Rhino 8.16.25042.13001
Trying to develop anything about SubDs on Rhino still feels completely impossible for me. Not different from not exposing any method at all. Maybe from c++/c it’s a different story.
You can’t “play” to test stuff like you can do with every other kind of geometry.

Casting the SubD to a mesh and work with that is still the simplest (and necessary) solution.

:frowning_face:

SubD methods should have the same structure and “grammar” like the mesh ones, and more. Simple as that.

1 Like

Thank you Riccardo, i’ve stumbled over this too and found one of your posts to solve it. The documentation has only .First but it does yet not mention .Next so maybe there is room for an improvement.

Yep, i’ve found this example using DissolveOrDelete by @dale but somehow found nothing in RhinoCommon to do the same.

I think this might be possible by converting the subd to a brep with unpacked faces, getting the closest point on the brep (face) and then using ProxyBrepSubDFaceId but i have not tried it yet fearing that the conversion to brep is not fast.

At least a method to find out if the subd has PerFaceColors would help me to. Currently i store all control net face centers and a parallel list of colors per subd face just to re-apply them when i build the new subd from the mesh to replace the old subd.

OK, then i’ll continue it this way.

thanks,
c.

All and every sub-object should be accessible with an index.
Instead some method have “id” others have index, some other only .First and .Next .
This is just wrong. I’m sorry but I stopped trying to make it work like this. Feels like a joke.
Any kind of developing feels impossible.
Maybe I am the problem! Maybe! But I do wonders with c# in GH in Rhino 7…

Indeed, from c# you just don’t do it.

ToBrep is extremely slow. If you want to build a tool to help the user modeling, you need a fast raycast or closest point to interact with the mouse cursor. Currently impossible from c#.
Maybe use the equivalent subdivided mesh? … worth a try, but everything is a gimmick.

The worst part is this whole problem about SubD was from the start, from Rhino 7.
I created this thread when it was already late.

Now we are still discussing this on 8, no progress at all from my viewpoint.
I feel like any progress for 8 is already finished (which is now a regressed product for my workflow) and devs are already speaking about 9 too often to make me believe there will be any relevant change in 8.

Everything about this is frustrating for me. So sad.

:frowning_face: