Inventables Community Forum

Sine Waves

Continuing the discussion from Voronoi Generator App.

I did some test cuts with my Sine Wave Easel app. It seems to work as intended. It seems the overall depth to diameter ratio matters quite a lot for the ripple to look convincing. I’ll try some shallower cuts in the future, as I think this effect is best as a subtle one.

Each of the 1.5 cycle ripples took about 45 minutes since each has the same number of lines. The larger 2.5 cycle ripple too about an hour since I stretched the cut settings a bit more for the MDF.


Not yet, these were all done with a 1/8" ball nose.

1 Like

The screenshot of the settings isn’t meant to be a record of the settings I used. I marked the settings for each next to the carve. Also I think the stepover may not be what you’re referring to. In this case it’s more like an overlap as I’m using concentric rings rather than filled circles. The latter would result in a lot of air-milling.

1 Like

Dude, this is epic. Although the wave crests are a bit sharp for my taste, I definitely think that you’re on the right track to easily make something that looks like this. Thank you SO much for doing this. It’s one of those esoteric things that you can’t really do with any other woodworking techniques beside CNC.

This caused huge waves (pun intended) on my woodworking forum. People unaware of CNC asking how it was done.


This is brilliant.

Alright, based on your comment, I’ve rewritten the code specifically for ball nose end mills. Now it uses a calculated step over based on the slope of adjacent isolines, the tool diameter, and a desired scallop height. This means it takes smaller steps in flatter areas and larger steps in areas that are very steep.

And based on @MidnightMaker’s comment I also made it base the number of cuts on the inner and outer radius desired. So one might create a similar design to the example photo posted.

However, Easel has a practical limitation of around 200 objects before it crashes (for me at least), so I also included a minimum override step over distance. For larger waves, this will be what is used in place of very small smoothing step overs.

Essentially, the app now creates the finishing pass. But I have no way to create a first roughing pass, so the cuts simply take a very long time.


Interesting. I’ll have to try it on my home computer. What OS and browser are you using?

After switching to Autodesk for my complete toolchain, I’m not using Easel to generate any more g-code.

So, for those of you still interested in developing for Easel, here is my source code for the Sines application. If you’re a developer, you can use it as it for the above results. It’s only an example of the 1.0 API, so not extremely helpful, but could be useful for basic SVG coding tips.

I’m not a programmer and have never learned or used JS before, so don’t assume anything here is the best practice.

Sines_Easel.js (5.3 KB)

// Copyright 2016 Bill Bucket
// All rights reserved

// Create a circular sinusoidal depth gradient. 
// The size of the gradient shape is determined by the number of  
// rings it has and the cut width.  
// The step over determines how much of a ring overlaps with the  
// previous ring, this means peaks can be sharper and slopes steeper,  
// but the bottom of a trough can only be as narrow as the tool width.

// Define a properties array that returns array of objects representing
// the accepted properties for your application
var properties = [
  {type: 'range', id: "Tool Width [inches]", value: 0.25, min: 0.05, max: 0.5, step: 0.01},
  {type: 'range', id: "Inner Radius [inches]", value: 0.5, min: 0, max: 29, step: 0.1},
  {type: 'range', id: "Outer Radius [inches]", value: 3, min: 0.5, max: 30, step: 0.1},
  {type: 'range', id: "Scallop Height [mils]", value: 10, min: 0.1, max: 40, step: 0.1},
  {type: 'range', id: "Minimum Stepover [%]", value: 5, min: 1, max: 100, step: 1},
  {type: 'range', id: "Sin(Pi) Depth %", value: 70, min: 0, max: 100, step: 1},
  {type: 'range', id: "Sin(0) Depth %", value: 20, min: 0, max: 100, step: 1},
  {type: 'range', id: "Cycles", value: 2, min: 0.1, max: 10, step: 0.1}

// Define an executor function that generates a valid SVG document string,
// and passes it to the provided success callback, or invokes the failure
// callback if unable to do so
var executor = function(args, success, failure) {
  var params = args[0];
  var InnerRadius = params["Inner Radius [inches]"];
  var OuterRadius = params["Outer Radius [inches]"];
  var ToolWidth = params["Tool Width [inches]"];
  var MinStep = params["Minimum Stepover [%]"]/100;
  var ScallopHeight = params["Scallop Height [mils]"];
  var MinGradient = 255 - Math.round(params["Sin(Pi) Depth %"]/100 * 255);
  var MaxGradient = 255 - Math.round(params["Sin(0) Depth %"]/100 * 255);
  var Cycles = params["Cycles"];
  var GradRange = MaxGradient - MinGradient;

  var XCenter = OuterRadius;
  var YCenter = OuterRadius;

  // Array to hold the path strings in cutting order (first to last)
  var SvgPaths = [];
  if (InnerRadius > 0)
    SvgPaths += [
   ' cx="' + (XCenter+InnerRadius) + '"',
   ' cy="' + (YCenter) + '"',    // X and Y center location of gradient 
   ' r="' + (InnerRadius) + '"',
   ' fill="rgb(' + MaxGradient + ',' + MaxGradient + ',' + MaxGradient + ')"',          
   ' stroke="none"',
   ' stroke-width = "none"',
   ' style="stroke-linejoin:round" />' ].join("");
  // We're tracking the radius as we move from the center plateau out to judge completeness
  var CurrentRadius = 0;
  var TravelRadius = (OuterRadius - InnerRadius);

  // Generate the appropriate number of steps with the full number of cycles
  while ((CurrentRadius+ToolWidth) < TravelRadius)
   var RatioComplete = (CurrentRadius/TravelRadius);
   // Calculate the slope at the point in the carve
   var Slope = Math.tan(Math.sin( (RatioComplete) * Cycles * Math.PI + Math.PI/2));
   // The step over depends on the desired scallop height, tool diameter, and the current slope
   var StepOver =  Math.sqrt(((ToolWidth^2)/4)-((ToolWidth/2)-(ScallopHeight/1000))^2)
                 * 2*Math.cos(Slope);
   StepOver = Math.min(StepOver,ToolWidth*MinStep);
   CurrentRadius += StepOver;
   // The radius for this isoline depends on the current radius ad the step over.
   var ShapeRadius = CurrentRadius + InnerRadius;
   // The depth depends on how far through the total steps we are and how
   // many cycles need to fit inside those steps; ride the waves
   var ShadeValue = Math.round(Math.abs(Math.sin( (RatioComplete) * Cycles * Math.PI + Math.PI/2)) * GradRange + MinGradient);
   // Full 360 degree arcs can't be made with the 'a' command, so these
   // arcs fall 0.0001 units short of a complete circle, close enough to
   // close without an artifact.
   // A circle object would be filled and we don't want that
   var GradientRing = [
   '<path d="M ' + (XCenter-CurrentRadius) + ',' + (YCenter),    // X and Y center location of gradient 
   ' a' + (ShapeRadius) + ',' + (ShapeRadius) + ' 0 1,1 0,0.0001 z"',
   ' fill="none"',          
   ' stroke="rgb(' + ShadeValue + ',' + ShadeValue + ',' + ShadeValue + ')"',       
   ' stroke-width = "' + ToolWidth + '"',
   ' style="stroke-linejoin:round" />' ].join("");
   // Add the calculated path to the array for adding to the svg later
   SvgPaths += GradientRing;

  // Combine the entire svg file string
  var svg = [
    '<?xml version="1.0" standalone="no"?>',
    '<svg xmlns="" version="1.0" width="30in" height="30in"',
    ' viewBox="0 0 30 30">',
  // Return the completed string
1 Like