I need a bit of help from those of you further down the geometry node rabbit hole. I am a civil engineer that is interested in seeing whether Blender's geometry nodes can visually represent cut/fill scenario in a different way to how 12D or Civil3D.

The geometry node setup intends to do the following:

  • Create a grid of cylinders

  • Have them originate from the existing surface and extrude +z for fill and -z for cut until they meet the proposed surface.

  • Colour the cylinders based on the relative distance from the existing surface to the proposed surface.

I have got it mostly completed but needed some help with a few elements

  • I am finding that the top and bottom faces of the cylinders are not colouring the same as the sides. I first thought this was due to shading however, the zoomed images below are with diffuse colour only. QUESTION: what can i do to ensure each cylinder is coloured the same.

  • I would prefer to be able to see the material colour in workbench and create an isometric diagram. When I select colour by material it doesn't seem to work. QUESTION: How do i present the cut / fill materials within the workbench render.

Blend file link -->

Some other questions:

  • If there is a better way of doing any of achieving this cut/fill presentation outcome please share your ideas.

  • I would love to be able to better control the colours ranges depending on the max cut/fill value.

  • Can you export the spreadsheets per chance? So i could get the z,y,z and cut/fill height in a excel/csv file.

Existing Surface Existing Surface

Proposed Surface Proposed Surface

Cut / Fill (with top and bottom faces) Cut / Fill (with top and bottom faces)

Cut / Fill (with top and bottom faces) Cut / Fill (with top and bottom faces)

Cut / Fill (with top and bottom faces) Cut / Fill (without top and bottom faces)

Cut / Fill (without top and bottom faces) Cut / Fill (without top and bottom faces)

Geometry Node Tree

EDIT 01:

Hi all,

thank you for all your comments and input. I picked this up again recently. It is a bit of a mash up of various suggestions. It was really helpful to have this conversation.

Please find the latest iteration below

Blender 4.1

  • $\begingroup$ i like your question because you provide a lot of info, unfortunately your node tree image cannot be read....maybe split them in 2 or 3 and upload again? $\endgroup$
    – Chris
    Jul 5, 2023 at 9:11
  • $\begingroup$ to answer one of your questions: the color is different because you store your color on face domain: [1]: i.sstatic.net/TgJIS.png if you want to have a single color per instance, you have to save the color on instance domain. It might be, that your calculation of the cut height must be different then $\endgroup$
    – Chris
    Jul 5, 2023 at 9:17
  • $\begingroup$ @ojremary could you please upload your file to blend-exchange.com This way your post will not be broken in the future, because the file will not be deleted. $\endgroup$ Jul 5, 2023 at 20:57
  • $\begingroup$ @AndréZmuda - thanks for letting me know. I have know uploaded the file and amended the post $\endgroup$
    – ojremary
    Jul 6, 2023 at 0:24
  • $\begingroup$ @chris - Thanks for you comments. Does having the file available now available help? $\endgroup$
    – ojremary
    Jul 6, 2023 at 0:25

2 Answers 2


I took a shot as well, combined isHit with Z position to only raycast once per point per surface (twice per point):

I also moved coloring to the shader though arguably you may want to calculate it in geonodes for performance + this way you could use statistics to figure the range of colors…

In face orientation preview, all instances have correct orientation, I would think sometimes scaling them by negative single axis should result in wrong normals? Anyone cares to explain?

End Result From Above

Looking from above with a copy of the starting surface visible and solidified up:

Looking from above with a copy of the target surface visible and solidified up:

End Result from Below

Looking from below with a copy of the starting surface visible and solidified down:

Looking from below with a copy of the target surface visible and solidified down:


To get results as above, multiply resolution by 10 and divide radius by 10 (I didn't want weaker PCs to crash when opening the file).

Premature Optimization Is the Root of All Evil

Just to make sure if this optimization even made sense, I tested in another project:

  • 1
    $\begingroup$ Thanks for this insight :-) - Every iteration node seams to create an iteration. There seems to be no internal optimization of the execution paths in geometry nodes. $\endgroup$ Jul 8, 2023 at 5:53
  • $\begingroup$ Hey, @Markus. I've just made a version without raycasting at all, using Sample Nearest of stored Z on the surfaces scaled to 0 in Z. It made me wonder... My method must be using a BVH-tree, as must raycasting? The main work must be in constructing the BVH... Is GN clever enough not to throw it away, so 2 raycasts don't cost significantly more than 1? $\endgroup$
    – Robin Betts
    Jul 9, 2023 at 8:18
  • $\begingroup$ @RobinBetts That's smart, as long as the surface doesn't have caves (which I think is a very reasonable assumption for OP's problem). Also consider a possibility that the BVH is generated for the object once, and since you dynamically change it, it regenerates it and surprisingly makes it slower (no idea if it actually happens). $\endgroup$ Jul 9, 2023 at 9:58
  • 1
    $\begingroup$ @RobinBetts I made some tests: blend-exchange.com/b/q0Y4poAX enable and apply the modifier on the plane, then choose and enable a modifier on the cube. Raycast: 23639-26022 ms, small optimization using hit distance: 23384-23677 ms, nearest surface: 30076-31214 ms, I thought maybe capturing the attribute adds a lot, but when I added a "store named attribute" to raycast it didn't seem to add much: 23202-24010 ms i.imgur.com/jwyW81C.png i.imgur.com/0GFlvfn.png $\endgroup$ Jul 9, 2023 at 9:58
  • 1
    $\begingroup$ @RobinBetts this test doesn't specifically test the OP's case of having 2 surfaces, it just tests raycast against sample nearest surface, so the spawned grid for testing just lies on Z=0. No need to scale to Z0 then. As for sample nearest→sample index that's much better at 16271-16940 ms i.imgur.com/hfa6ffC.png , so even though it doesn't interpolate, wherever Blender starts to struggle that's definitely a nice optimization. One more thing, scaling to Z0 and storing an attribute can be applied on the surface. Then it's… 16195-16967 ms so no change… i.imgur.com/4RbJ18E.png $\endgroup$ Jul 9, 2023 at 10:44

I will first explain a way to solve the color-problem. Below that, you will find a way to control the color ranges depending on the max cut/fill value.

What can i do to ensure each cylinder is colored the same

In this solution, I use cylinders instead of constructing them from pieces. This eliminates the varying colour:


close up

How does it work

I realized it in three steps, as can be seen by the following node setup:

  • Create a grid and calculate the dimensions and positions of the cylinders.
  • Create, position and size the cylinders
  • Colourize the cylinders

I will explain these steps in more detail below.

complete node setup

Create a grid and calculate the dimensions and positions of the cylinders

The following image shows the nodes for creating the grid and calculating the dimensions of the cylinders.

create grid

We create the grid on z=0.

In the upper frame the distance to the Existing Surface is calculated in z-direction for every point of the grid. As you did, we expect the Existing Surface to be above 0. Thus we need only 1 raycast pointing upwards. The result is stored on the corresponding point by using the Capture Attribute node.

In the lower frame the distance to the Proposed Surface is calculated in z-direction for every point of the grid. The Proposed Surface may have points below 0. Thus, we use two raycasts - one up and one down.

When it comes to performance, it may be better to calculate the minimum or maximum z of both surfaces and place the grid below or respectively above this value. This way you could eliminate one Raycast node. But I did no check the execution times.

Or you may do it as Markus von Broady does in his solution and place the grid manually above or below.

Those points of the grid, that do not lie above or below the Proposed Surface get deleted.

Instead of deleting those points you could initially size the grid by using the minimum/maximum x- and y-coordinates of the proposed surface. When it comes to performance, this would surely be better. When you crank up the grid density in my example, most of the computation time is used by creating the grid and deleting unnecessary points.

Markus von Broady uses this approach in his solution. He takes the minimum/maximum of the Existing Surface. Which of the surfaces you take into account, the Existing Surface, the Proposed Surface or both, depends on your policy of how to cope with columns, that cover only one of both surfaces.

When you don't have a rectangular surface, that is aligned to x and y, you would still have to delete points or skip them on instantiation, even when right sizing the grid. But even then, under performance aspects it would be good to use the right size for the grid.

Create, position and size the cylinders

We create the cylinders with a depth/height of 1 and place them with the bottom on z=0 by rasing them by 0.5. When instantiating the cylinders, we scale them in z by their individual height. The height can be calculated by the absolute difference of ground-z and tip-z, which we calculated before.

create cylinders

Colorize the cylinders

For colourizing the cylinders, we split them up in fill- and cut-cylinders. Therefore, we use the difference between tip-z and ground-z, which is greater 0, if the tip is above the ground. After setting the corresponding materials, we join it together again and realize it.


Control the color ranges depending on the max cut/fill value

In the node setup below, the fill height and cut height are normalized by dividing them through there maximum. When multiplying this maximum with a factor, you can adjust this manually. You may expose this factor as an input parameter.

If you would like to control the fill height and cut height separately, you may use the difference between tip and ground instead of the calculated height. Thus, you can use the maximum of it for the fill height and the minimum * (-1) for the cut height.

control color ranges

  • $\begingroup$ Consider using Boolean Math: NOT AND node 👍 $\endgroup$ Jul 7, 2023 at 15:33
  • $\begingroup$ @MarkusvonBroady, good hint, thanks - only, it should be NOT OR instead (True when both inputs are false) ;-) $\endgroup$ Jul 7, 2023 at 15:47
  • $\begingroup$ Oh, you mean I made you realize an error? You probably want to delete if at least one ray misses? Because then you don't have enough data to present anything meaningful. In my case (coming shortly) I just add two and compare with 2, because I want both rays to hit, same thing in the end of the day. $\endgroup$ Jul 7, 2023 at 15:51
  • $\begingroup$ Sorry, I am not sure, if I get you right. Do you talk about this (NOT A AND NOT B) nodes after the two raycasts? At that point, I only check, if the tip lies below or above 0. Thus, I really want to delete the corresponding point only in the case, that both of them don't hit. $\endgroup$ Jul 7, 2023 at 15:59
  • $\begingroup$ Here i.imgur.com/gJFStvp.png you delete a point if there's no information to obtain because either raycast fails (there aren't two surfaces at the same XY). But the logic should be $¬ (A ∧ B)$, that is, not (isHit1 and isHit2). If both raycasts fail, you get 00, notting each gives 11, AND gives 1, the point is deleted. But if only one fails you get 01 or 10 (notting just swaps bits around, so in these two cases it doesn't matter), and then AND gives 0, so a vertex is not deleted if only 1 raycast hit (but you have nothing to calculate with just 1 surface? $\endgroup$ Jul 7, 2023 at 16:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .