Tesselation Algorithm for Laser Ablation Scanner

DOF(Depth of Field) Tesselation is a tesselating algorithm developed for Laser Ablation Machining Center.

Whereas traditional CNC would continuously change its tool position to carve out material, since laser ablation scanner head is capable of working on certain area(usually within few cm of domain) on a fixed position, head is stationary on each ablation process.


▲ test run of ablation machining after exporting each tesselated pattern(as dxf) with laser head position profile data.

Ablation Laser Scanner has effective range of focus that energy per beam cross section is kept within norminal range. Such term is called DOF(Depth of field), and is varied over etching depth and working material. Scanner we used has effective working area of 80mm x 80mm from a fixed position, and effective DOF ranges from 100 um to 5 um.

To maximize the speed of machining process and quality, it is best to keep tesselations to as large as possible while Z range from normal of each tesselation is within the range of DOF and working area.


▲ Automated pattern exporting iteration (hatch is generated as laser scanning line on each export)


This algorythm recursively clusters ajacent mesh faces and recalculates optimal normal that minimizes Z depth to make largest cluster of mesh faces possible.

▲ Example showing tesselation grouping results at different DOF distance. Each group of mesh faces are color coded according to its area. Notice that tesselation is smaller where surface curvature is high.


This component works as part of Patterning Software (Grasshopper Plug-in) for Hwacheon LP840 machining center. Entire algorithm cannot be opened to public due to corporate confidentiality.

▲ GH component
dof (double) : effective focal length of laser scanner

scanner limit (double) : effective working radius at fixed normal
A (brep) : output grouping of faces


DOF Tesselation Code(C#)

private void RunScript(List<Brep> surfaces, double dof, ref object A)

int path = 0;
List<Brep> srfs = new List<Brep>(surfaces);
DataTree<Brep> clusters = new DataTree<Brep>();

while(srfs.Count > 2){

clusters.AddRange(srfCluster(ref srfs, dof), new GH_Path(path));


A = clusters;


//sort remaining faces by their proximity from sharpest corner
public List<Brep> sortedSrf (List<Brep> surfaces){

//Get faces
List<Brep> srfs = new List<Brep>(surfaces);
//Get Center of each face
List<Point3d> srfCtr = new List<Point3d>();
foreach(Brep srf in srfs){


//Point at corner
Point3d cornerPt = srfCtr[cornerIndex(srfs)];

//sort surfaces by proximity from corner point
srfs = srfs.OrderBy(p => ngonCenter(p).DistanceTo(cornerPt)).ToList();
return srfs;


//recursively add adjacent faces into cluster while boundingbox Z is smaller than DOF distance
public List<Brep> srfCluster(ref List<Brep> surfaces, double dof){

//Get Sorted Surfaces
surfaces = sortedSrf(surfaces);
//Get Center of each srf
List<Point3d> srfCtr = new List<Point3d>();
foreach(Brep srf in surfaces){



List<Brep> cluster = new List<Brep>();
List<Point3d> clusterPt = new List<Point3d>();
Double h = 0;
bool BreakFlag = true;
for(int i = 0;i < 2;i++){



while(h < dof && BreakFlag){

if(surfaces.Count > 0){

//get bounding box and get Z length
Plane frame = new Plane();
Plane.FitPlaneToPoints(clusterPt, out frame);
Box bbox = new Box();
List<Point3d> ptsFromSrf = new List<Point3d>();
foreach(Brep srf in cluster){

foreach(Point3d pt in srf.DuplicateVertices().ToList()){




Curve.CreateInterpolatedCurve(ptsFromSrf, 1).GetBoundingBox(frame, out bbox);
h = bbox.Z.Length;


BreakFlag = false;



return cluster;


//get center of srf
public Point3d ngonCenter(Brep srf){

Point3d[] ctr = srf.DuplicateVertices();
Point3d cen = new Point3d();
cen.X = ctr.Average(p => p.X);
cen.Y = ctr.Average(p => p.Y);
cen.Z = ctr.Average(p => p.Z);

return cen;


//get sharpest naked edge corner
public int cornerIndex (List<Brep> surfaces){

List<Brep> joinedSrf = new List<Brep>();
List<Brep> srfs = new List<Brep>(surfaces);
Brep resSrf = new Brep();
joinedSrf = Rhino.Geometry.Brep.JoinBreps(srfs, 0.01).ToList();
if(joinedSrf != null){

resSrf = joinedSrf[0];


List<Curve> nakedEdges = new List<Curve>(resSrf.DuplicateNakedEdgeCurves(true, true).ToList());
nakedEdges = Rhino.Geometry.Curve.JoinCurves(nakedEdges).ToList();
Polyline pline = new Polyline();
nakedEdges[0].TryGetPolyline(out pline);
List<Point3d> plToPts = new List<Point3d>(pline.ToList());
plToPts.RemoveAt(plToPts.Count – 1);

//get vector for angle calculation
Point3d[] pts = plToPts.ToArray();
Point3d[] ptsm = shiftLeft(pts);
Point3d[] ptsp = shiftRight(pts);
List<double> angles = new List<double>();
for(int i = 0;i < pts.Length;i++){

Vector3d vX = new Vector3d(ptsm[i] – pts[i]);
Vector3d vY = new Vector3d(ptsp[i] – pts[i]);
double angle = Vector3d.VectorAngle(vX, vY);

Point3d ptAtCorner = new Point3d();

//get smallest angle and find corresponding point
double agl = angles[0];
for(int i = 0;i < angles.Count;i++){

if(angles[i] < agl){

agl = angles[i];
ptAtCorner = pts[i];


Point3d brepClosestPt = new Point3d(srfs[0].ClosestPoint(ptAtCorner));
double distanceToSrf = brepClosestPt.DistanceTo(ptAtCorner);
int indexOfClosestSrf = 0;
for(int i = 0;i < srfs.Count;i++){
Point3d brepClosestPoint = new Point3d(srfs[i].ClosestPoint(ptAtCorner));
double thisDistanceToSrf = brepClosestPoint.DistanceTo(ptAtCorner);
if(thisDistanceToSrf < distanceToSrf){
indexOfClosestSrf = i;
return indexOfClosestSrf;


//shift array left(-)
public Point3d[] shiftLeft(Point3d[] arr){

Point3d[] pts = new Point3d[arr.Length];
for (int i = 0; i < arr.Length – 1; i++){

pts[i] = arr[i + 1];

pts[pts.Length – 1] = arr[0];
return pts;

//shift array right(+)
public Point3d[] shiftRight(Point3d[] arr){

Point3d[] pts = new Point3d[arr.Length];
for (int i = 1; i < arr.Length; i++){

pts[i] = arr[i – 1];

pts[0] = arr[pts.Length – 1];
return pts;





Leave a Reply