Stipple gradient hatch Rhino 6 (need help)

Hi ! I’m looking everywhere to find how to do this : it’s a hatch (no grasshopper) made of dots with a variable density that adjusts to the exterior line chosen to define hach surface. do you know how to do it ? a .pat file ?
Thank you very much !!

Hi John -

Is it a hatch in a Rhino 3dm file? Or in a different program?
A hatch in Rhino is a simple repetitive pattern that can be described in a simple text file. It has no awareness of its defining borders.
-wim

Thank you very much for the response wim. I was also surprised but yes it is a hatch : a .pat file apparently working on rhino but i do not have access to it. That’s why i would like to find an alternative solution.

If your screenshots are from Rhino, then you do have the hatch pattern definition.
Hatch patterns are stored in the 3DM file.

In File > Settings > Hatch, you’ll find the patterns stored in the file.

Thanks but the screenshots are not mine, that’s the problem : i don’t have this hatch…

The only way I can think to “fake” that effect, would be to offset the boundary in, and make 3 separate Hatch applications, with the one closes to the border the most dense, and the one in the center the most open. Then Hide the 2 inner boundary curves.

I see. Thank you very much. I hope to find a better solution though. Seems strange this can’t be made automatically ? Maybe it is possible with grasshopper but that’s too complicated for me X)


Seems it could work with something like that. I choose the two polylines as geometry. But :
-I don’t know how to make grasshopper choose the edges of both to only fill what’s in between them and not the two polylines.

  • I don’t know neither how to densify the edges and not the points to be homogeneous inside all the polyline. I guess you know how to use grasshopper better than i do :wink:

I know this is an old thread but arrived here from google, and so for others, the above images come from this website:Stipple Hatch (not Rhino). In the end I got a similar effect using this grasshopper tutorial https://www.youtube.com/watch?v=VUHD8AkHtcg and adding a bit of a random wiggle to break up the grid

Interesting topic, remind me of the SDF method in Houdini,

https://www.ronja-tutorials.com/post/034-2d-sdf-basics/

You may try to use this code:

  1. Creates a series of offset curves inward from your original shape
  2. Generates ribbon hatches between each consecutive pair of curves
  3. Applies a density gradient with higher density at the boundary and decreasing toward the center
  4. Packages everything into a single block at the center of your original shape
  5. Cleans up temporary geometry leaving only the final block.
import rhinoscriptsyntax as rs
import scriptcontext

def create_ribbon_hatch_gradient():
    # Select closed curve
    curve_id = rs.GetObject("Select closed curve for stipple gradient", rs.filter.curve, True, True)
    if not curve_id: 
        return
    
    # Verify the curve is closed
    if not rs.IsCurveClosed(curve_id):
        print("Selected curve must be closed.")
        return
    
    # Get parameters from user
    num_ribbons = rs.GetInteger("Number of ribbon layers", 10, 2)
    if not num_ribbons: 
        return
    
    total_offset = rs.GetReal("Total inward offset distance", 10.0, 0.1)
    if not total_offset: 
        total_offset = 10.0
    
    # Store original curve ID
    original_curve_id = curve_id
    
    # Get the plane of the curve for proper offsetting
    curve_plane = rs.CurvePlane(curve_id)
    if not curve_plane:
        curve_plane = rs.ViewCPlane()
    
    # Determine which pattern to use
    if "Plus" in rs.HatchPatternNames():
        pattern_name = "Plus"
    else:
        pattern_name = "Grid"
        print("Warning: 'Plus' pattern not found. Using 'Grid' pattern instead.")
    
    # Calculate insertion point (center)
    bbox = rs.BoundingBox(curve_id)
    if bbox and len(bbox) >= 7:
        base_point = [
            (bbox[0][0] + bbox[6][0]) / 2.0,
            (bbox[0][1] + bbox[6][1]) / 2.0,
            (bbox[0][2] + bbox[6][2]) / 2.0
        ]
    else:
        base_point = [0, 0, 0]
    
    # Create array to store all curves (original + offsets)
    all_curves = [curve_id]  # Start with original curve
    
    # Create offset curves (ALWAYS inward)
    current_curve_id = curve_id
    step_offset = total_offset / num_ribbons
    
    for i in range(num_ribbons):
        # Calculate current offset distance (negative for inward)
        current_offset = -(i + 1) * step_offset  # Always negative = always inward
        
        # Offset the curve inward
        offset_curve_ids = rs.OffsetCurve(current_curve_id, curve_plane[0], current_offset)
        
        # Check if we got valid offset curves
        if not offset_curve_ids or len(offset_curve_ids) == 0:
            print("Offset %d failed - curve too small" % (i+1))
            break
        
        # Take the first offset curve
        offset_curve_id = offset_curve_ids[0]
        all_curves.append(offset_curve_id)
        
        # Update current curve for next iteration
        current_curve_id = offset_curve_id
    
    # Create ribbon hatches between consecutive curves
    hatch_objects = []
    
    # Create hatches from outer to inner (boundary to center)
    for i in range(len(all_curves) - 1):
        outer_curve_id = all_curves[i]
        inner_curve_id = all_curves[i+1]
        
        # Calculate scale - smaller values for higher density at boundary
        min_scale = 0.3
        max_scale = 1.5
        scale_range = max_scale - min_scale
        # Higher density at boundary (smaller scale), lower density toward center
        current_scale = min_scale + (i / float(len(all_curves) - 1)) * scale_range
        
        # Create hatch between the two curves
        hatch_id = rs.AddHatch([outer_curve_id, inner_curve_id], pattern_name, current_scale, 0.0)
        
        if hatch_id:
            hatch_objects.append(hatch_id)
            density = 1.0 / current_scale
            print("Ribbon %d: Scale=%.3f, Density=%.3f" % (i+1, current_scale, density))
        else:
            print("Failed to create hatch for ribbon %d" % (i+1))
    
    # Delete all offset curves (keep original curve if no hatches created)
    curves_to_delete = all_curves[1:]  # All curves except the original
    for curve_id in curves_to_delete:
        try:
            rs.DeleteObject(curve_id)
        except:
            pass
    
    # Create NEW block with unique name each time
    if hatch_objects:
        # Alternative: Use a simple incremental approach
        # Check existing block names and find the next available number
        base_name = "RibbonStippleGradient"
        block_name = base_name
        counter = 1
        
        # Check if block name already exists and find next available
        while rs.IsBlock(block_name):
            block_name = "%s_%d" % (base_name, counter)
            counter += 1
        
        # Create block definition
        block_id = rs.AddBlock(hatch_objects, base_point, block_name, True)
        
        if block_id:
            # Insert instance of the block
            block_instance = rs.InsertBlock(block_id, base_point)
            
            # Delete original hatch objects (they're now in the block)
            for obj_id in hatch_objects:
                try:
                    rs.DeleteObject(obj_id)
                except:
                    pass
                    
            rs.Redraw()
            print("\nSUCCESS: Created NEW ribbon stipple gradient block '%s' with %d layers" % (block_name, len(hatch_objects)))
            print("DENSITY GRADIENT: High density at boundary, decreasing toward center")
            print("All ribbons created between consecutive offset curves")
            print("Block created with unique name to avoid conflicts")
        else:
            print("ERROR: Failed to create block definition")
    else:
        print("ERROR: No hatches were created successfully")

# Run the function
create_ribbon_hatch_gradient()

please note for some curve the offset direction is based on the curve direction, so if the offset is outward just flip the curve direction.
like this picture


this is another picture

it would be interesting if there is a way to make it dynamic responsive to the curve adjustment