Why Does Join Curves Graft Output?

Hi all,

When dealing with data trees and components that append an index at the end of the path (for example 1:N components), I have always wondered about the logic behind the Join Curves component.

Specifically: why does Join Curves add an extra path index?

I have never encountered so far a situation where I didn’t need to apply a Trim Tree after it, and I struggle to understand why this component belongs to the “1:N” and similar categories (the ones who grows tree complexity) and in which cases that extra index is actually useful.

If I start with an input data tree with n branches (just a list if n=1), where each branch contains its own number m(i) of curves (being i = 1,…,n), the output produces a maximum of m(i) curves (or less if some curves are successfully joined) for each branch.

This happens without any further subdivision of the original branches, but with an additional trailing index {…;0} in every branch, which appears redundant.

This is what I see everytime. Why does this happen?

And in which practical scenario is this extra path index actually needed or meaningful? I’m not being able to find a case where it actually is. Have any of you find one? I’m curious

Join Curves has the Preserve Input which you can give a list of bools. The added branch depth can be used to identify the index of the in the list of bools that the bool came from… if that’s useful, I don’t know.

I’m having difficulties understanding when Join Curves can actually behave as a one-to-many component :slight_smile:

Given a list of n curves, my current understanding is that the component will always output at most n curves, or fewer if some fragments are successfully joined, but those outputs do not subdivide the input branch further, they remain in the same list/path they came from, rather than branching into multiple result groups

For this reason I think the added {…;0} path index is not representing a “many” output in the usual sense, but rather just signaling that an additional layer of computation took place

I don’t understand. You give Preserve multiple values, you get variations of the the process, for which each has a new {…,n}. That’s the same as any one-to-many component, or am I missing something.

one to many case

CurvesAppend.gh (31.9 KB)

@inno The whole paradigm of components being “one-to-one”, “one-to-many”, or “many to one” (as presented in Andrew Heumann’s “The Deal with Data Trees”) is incomplete, and not universally applicable. At best, we could consider these mappings between pairs of inputs and outputs of a component, and then still you’d have to consider mappings such as “many-to-many”… or “fewer”, or “more”.

The same is true of the paradigm regarding how input matching is done. While generally speaking it is true, that the items of a list are matched by index, and, that a list of insufficient length is expanded by the repetition of the last index to meet the list length of the longest list involved in a method (longest-list, repeat last input matching), it is not true across the board. Dispatch for instance, either expands the Dispatch Pattern list by repetition or truncates the list to match the List input list length. The pattern of Pick’n’Choose, then, on the other hand, truncates the Stream lists to the list length of the Pattern. None of this is consistent.

All of which is to say that design decisions made by David in the development of Grasshopper at some point became “holistic”, or context-based.

Is Join Curves a “one-to-many” component? Well, from the viewpoint of the Preserve input… yes. From one bool, you can get multiple joined geometries. The input matching of components using a bool to influence how a component executes function breaks the input matching paradigm generally. Like, if I give Join Curves a list of curves and list of bools, it’s not going to match a bool to a curve by index, rather it will apply a bool to an entire list of curves. Ostensibly, Join Curves forceably grafts a list of Preserve bools before using it.

Is Join Curves a “one-to-many” component in the sense that a set of input values can be mapped to different output outcomes? No… but yes. Consider this set of curves of bools given to Join Curves,:

a set of lines arranged in a rectangle and set of bools “True, False, True”. Could you tell me the resulting number of curves and to which bool the resulting curves belong?

The answer is “no”. You’d have to know the direction of the lines in the rectangle. So while this specific set of inputs can only lead to one geometric result (one-to-one mapping), on the basis of the number of curves and booleans provided ALONE, multiple results are possible. Which is what necessitates the grafting of an additional depth of hierarchy ({…;+n}).

you are right, I didn’t consider at all the Preserve Direction input :slight_smile: I think I never used it, my eyes don’t even see that anymore :slight_smile: but its presence looks like the perfect explanation for the additional path level

Well, it is rare that you would ever want to give Join Curves more than a single bool. Supposing you ever wanted both the preserved direction and non-direction preserved variants of a curve joining, you’d more likely pull two copies of the component onto the canvas and give one of them the value “True”, the other the value “False”. If the Preserve input was a true toggle of the component, or only respected the first item of a branch, then the adding of a depth of branch hierarchy would not be unnecessary.

I tried and it makes sense, thank you for opening my eyes.. It’s just I have never found or think of a case where I need to do this, and I almost forgot Preserve input even exist, as @inno says.

Speaking of DIspatch, etc.. for what I experience so far, the standard rule for matching is equivalent to component Longest list (repeat last) (also for branch matching when different inputs in the same component have different number of branches), but in the case of patterns of booleans it is Longest list (wrap). I wouldn’t remember other non standard cases at the moment

Based of what I learned now, I would say it is one of those component that belong in a special category (as described here)… maybe many(n)-to-many(m) ?

It looks like it behaves like the component Split Surface

in Split Surface you have the two inputs:

  • Surface: as ONE
  • Curves: as MANY (if you hover it says “as list”)

so when it runs, for every pair of matching lists (one or more depending if they two inputs are lists or trees), it takes every single item in surface input and process with all the items on curves input togheter. It’s like you have grafted the surface input, but since curves input is AS LIST, it does it automatically

split_surface_tree_test.gh (4.4 KB)

for Join Curves it looks the same for what I see now: Curves input is described as MANY (it says “as list”), while Preserve input is described as ONE (doesnt’ say anything), so it works the same way.

Being that none of us have yet used more than one boolean per list, we always need to trim, like we do in the example of split surface if the surface input has always (in a specific instance in a specific script) one item per list

Now it makes more sense. thanks

Oh, they are out there. Definately in the Sets > Tree panel. And as I purposely mentioned above as a counter-example to Dispatch, Pick’n’Choose, which works on the basis of Shortest List.

I am a big fan of what they did in Beegraphy, which is to integrate the input matching type into the context menu of inputs, allowing you to choose from available options. Right now, in Grasshopper, you have to pre-process your data with the Longest List and Shortest List components. I don’t know how this is handled in GH2.

This is a good link. Confirms much of what I’ve intuited and learned through use of Grasshopper. It delves into why all the the extra zeros exist in branch labels. I’ve always wished there was component that allowed you to collapse tree branches, which there is (Shift Paths) but I mean more generally, like from in the middle of a branch label… I suppose you could do that with Path Mapper, but Path Mapper is inflexible.


@Fabio_Franchetti If it bothers you that much you can create a user object:


Join Curves Trimmed.ghuser (5.6 KB)

Nice one, thank you. Never thought about doing a simple Join Curves + Trim Tree cluster, I just put them in succesion automatically like a robot.

there is not but you can build your own, like you did just now. I would do something like this

you can collapse any one or more index of the paths in any position, not just the first(s) or the last(s) as in Shift Paths.

Or maybe you can cull the indexes based on some rule you decide. For example, the infamous (and sometimes i should say also braindead)Suirifyis just an application of this: for every index, it culls it if it is a zero in all branches, and replace all with just a single zero if every index is culled.

I mean, you can do whatever the f you want, it’s the good/bad thing about GH compared to something else (you mention Beegraphy that I heard but never used): a lot is to be set up manually, but if you know the tools the possibilities are endless

Dynamo does this too; they call it “Lacing”.

This can make some operations very simple. To make an array of points for example, you can feed the same Range into X and Y with Cross Product Lacing, which outputs every possible combination of inputs:


(other options are Match Shortest (Dynamo’s default) and Longest (GH default))

It also doesn’t distinguish between item/list operations. There are no separate “tree” and “list” components. You just choose what level of the hierarchy you want to operate on.


(L1 is the list at the end of each “branch”, with higher numbers being higher up the hierarchy. Yes it bothers me they don’t start at L0, although I suppose that’s the Items…)

I never got super deep into data management in my time with Dynamo, but it felt more intuitive than GH’s split between lists and trees. Others with more experience might be able to shed light on the pros/cons of each approach.