Basics of Hatching in PySLM

The generation of hatch infills in PySLM is relatively straightforward and can be adapted to suit the user’s needs. Anecdotally, this was a remarkable improvement on performance and simplicity over preexisting code that was used previously during University, which used a combination of Matlab and a ClipperLib library.

The generation of hatch vectors are nearly always a series of adjacent parallel scan vectors that an infill a bounded area. There has been some unusual approaches for infills that have been tried such as spiral [1][2] and fractal [3][4][5] scan strategies. Generally, exploring different raster patterns across an SLM part remains largely unexplored due to the unavailability of opensource tools, code and open access to machine platforms now in research environments. However, potentially this could unlock the ability to micro-structural development in some metal alloys. Attard et al. demonstrates this by modifying the island size throughout the part to promote different microstructures [6].

The first step of generating the hatch is finding the region to ensure scan vector generated guarantees full coverage across the boundary for the chosen hatch angle, h_d. This is needed irrespective of scan strategy. My previous approach was rather cumbersome and required trigonometry to be used to calculate the start and end points according the chosen (\theta_h) to cover the boundary. A far simpler approach was conceived.

Basic method:

The approach simply relies on covering the entire boundary irrespective of hatch angle and letting the polygon clipping library do the actual heavy lifting.

In this method, the bounding box for a closed region is found. A rather simple calculation made by taking the min and max of the boundary coordinates – easily obtained via shapely.

The maximum extent or diagonal length, r, from the bounding box corner to the bounding centre is then calculated. In the diagram, it is observed that this forms a circular locus, which upon choosing any hatch angle, all hatch lines after rotation will be guaranteed to fully cover boundary. Remarkably simple.

The basic code can be found in

# Expand the bounding box
bboxCentre = np.mean(bbox.reshape(2,2), axis=0)

# Calculates the diagonal length for which is the longest
diagonal = bbox[2:] - bboxCentre
bboxRadius = np.sqrt(

From the locus, we assume the hatch coordinate local coordinate system with an origin (-r,-r). A series of parallel hatch vectors with hatch distance, h_d, can be generated conveniently covering the entire square region . This can be done using numpy, using np.tile:

# Construct a square which wraps the radius
x = np.tile(np.arange(-bboxRadius, bboxRadius, hatchSpacing, dtype=np.float32).reshape(-1, 1), (2)).flatten()
y = np.array([-bboxRadius, bboxRadius]);
y = np.resize(y, x.shape)

Unfortunately, ClipperLib by default does not guarantee the order lines are clipped in. The naive approach is to clip these individually in a sequential order, which introduces a significant overhead. Alternatively the clipped hatch lines can be sorted. For achieving this, the PyClipper library was customized to guarantee the sequential order of the scan , which is explained in this post.

The approach to guarantee the z-order is to provide a z-coordinate which defines the order of scanning. This should be in pairs of ascending order which group the coordinates in each scan vector: i.e. 0,0, 1,1,..,2,2 etc. Finally before being clipped, an n\times3 coordinate matrix is formed.

# Generate a linear sorting according to the order of scanning.
# i.e. 0,0,1,1,..,n-1,n-1,n,n 
z = np.arange(0, x.shape[0] / 2, 0.5).astype(np.int64)

# Group the XY coordinates and z order 
coords = np.hstack([x.reshape(-1, 1),
                    y.reshape(-1, 1),
                    z.reshape(-1, 1)])

From this, a 2D rotation matrix, R(\theta_h), and translation to the bounding box centre can be applied to the coordinates of the unclipped hatch lines.

# Create the 2D rotation matrix
c, s = np.cos(theta_h), np.sin(theta_h)
R = np.array([(c, -s, 0),
              (s, c, 0),
              (0, 0, 1.0)])

# Apply the rotation matrix and translate to bounding box centre
coords = np.matmul(R, coords.T)
coords = coords.T + np.hstack([bboxCentre, 0.0])

There is one issue with the method is that using region / island based scan strategies. Many of the regions are not clipped, therefore, for large regions it can become inefficient. However, assuming a normal hatch in-fill is used, the clipping operation mostly amortises the additional cost.


1 Zhang, W., Tong, M., & Harrison, N. M. (2020). Scanning strategies effect on temperature, residual stress and deformation by multi-laser beam powder bed fusion manufacturing. Additive Manufacturing, 36(June), 101507.
2 Qian, B., Shi, Y. S., Wei, Q. S., & Wang, H. B. (2012). The helix scan strategy applied to the selective laser melting. International Journal of Advanced Manufacturing Technology, 63(5–8), 631–640.
3 Ma, L., & Bin, H. (2006). Temperature and stress analysis and simulation in fractal scanning-based laser sintering. The International Journal of Advanced Manufacturing Technology, 34(9–10), 898–903.
4 Catchpole-Smith, S., Aboulkhair, N., Parry, L., Tuck, C., Ashcroft, I., & Clare, A. (2017). Fractal scan strategies for selective laser melting of ‘unweldable’ nickel superalloys. Additive Manufacturing, 15, 113–122.
5 Sebastian, R., Catchpole-Smith, S., Simonelli, M., Rushworth, A., Chen, H., & Clare, A. (2020). ‘Unit cell’ type scan strategies for powder bed fusion: The Hilbert fractal. Additive Manufacturing, 36(July), 101588.
6 Attard, B., Cruchley, S., Beetz, C., Megahed, M., Chiu, Y. L., & Attallah, M. M. (2020). Microstructural control during laser powder fusion to create graded microstructure Ni-superalloy components. Additive Manufacturing, 36, 101432.

Leave a Reply

Your email address will not be published. Required fields are marked *