/*
 * G-code meta compiler
 *
 * Copyright (C) 2014  B. Stultiens
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 * Trochoidal example
 * ------------------
 * Trochoidal movement for high-speed milling. A path is followed in a circular
 * pattern with given radius and increment. Afterwards, a clean cut is
 * performed to remove the residuals.
 *
 * NOTE: To take full advantage of trochoidal milling, you need to have a CNC
 * program that can look ahead to keep the speed high. For LinuxCNC that means
 * you will need the git version on branch circular-blend-arc-rc3 (or any later
 * branch thereof, until it is merges into the master branch).
 *
 * @@@--svg-toolwidth 0.5 --svg-opacity 0.25@@@
 */

/*
 * Trochoidal point calculation.
 * See: https://en.wikipedia.org/wiki/Trochoid
 */
function trochoid_point(ang, a, b)
{
	ang = to_rad(ang);	// Trochoids are defined in radians
	// The first part is the trochoid, the second part moves the first 180
	// degree point at a relative "0, 0" location so we can scale in any
	// way without having to do hard math
	return [ a * to_none(ang) - b * sin(ang), b - b * cos(ang) ] - [a*pi(), 2.0*b];
}

/*
 * Perform a move from startpoint to endpoint using a trochoidal path.
 * - Cutting at depth cutz (returns to old Z)
 * - Trochoid radius as specified
 * - Increment for each turn as specified
 */
function trochoid_move(startpoint, endpoint, cutz, radius, increment)
{
	local min_ainc = .1deg;  // avoid too long compute times (and log( value<=1))
	local min_rotations = 5; // avoid confusing appearance if too small
	local i;
	local a = increment/(2.0*pi());			// Trochoid step parameter
	local ainc = log10(to_mm(radius)) * 5.0deg;	// Steps are logarithmic based on the radius to reduce small steps
	local oldz = position()[2];
	local vec = endpoint - startpoint;		// Vector denoting path to move

	if (ainc < min_ainc) {
		error("ainc too small: ",ainc," (try: increase trochoid radius)");
		return;
	}

	// If we are not moving, it is an error
	if(length(vec) <= zero) {
		error("trochoid move is not going anywhere");
		return;
	}

	comment("-- trochoid_move at ", cutz, " from ", startpoint, " to ", endpoint, " radius=", radius, " increment=", increment, " --");
	// Calculate the number of *whole* rotations, rounded up, we need to make
	local n = 2.0rad * pi() * to_none(ceil(length(vec) / increment));
	local nrotations = n/(2.0rad * pi());
	if (nrotations < min_rotations) {
		error("too few rotations= ",nrotations," (try: decrease trochoid increment)");
		return;
	}
	if (verbose) {
		comment("debug, rotations= ",nrotations);
		comment("debug, ainc= ",ainc);
	}

	// The path may be arbitrary angled, get the angle for rotating the trochoid
	local rot = atan(vec[1], vec[0]);

	// Go to the trochoid entry-point and move to cutting deph
	goto(startpoint + rotate_xy(trochoid_point(0.0rad, a, radius), rot));
	move([-, -, cutz]);

	// Calculate each next point of the trochoid until we traversed the whole path to the endpoint
	for(i = 0.0deg; i < n; i += ainc) {
		move(startpoint + rotate_xy(trochoid_point(i, a, radius), rot));
	}

	// Return to old Z so we will not bump into stuff
	goto([-, -, oldz]);
	comment("-- trochoid_move end --");
}

/* -------------------- Main Program -------------------- */
//ngcgui: info: troichoid-path example (mm or inch per parm#1 (units) setting, !! DEFAULTS are in mm !!)
//ngcgui: umode = 1;         //, units: 1:mm, 0:inch
//ngcgui: trochoid_r = 5;    //, trochoidal radius
//ngcgui: trochoid_incr = 2; //, trochoidal increment
//ngcgui: path_scale = 25;   //, path scale (all xi,yi)
//ngcgui: xoffset =  0;      //, path x offset
//ngcgui: yoffset =  0;      //, path y offset
//ngcgui: x0 =  0;           //, path x0
//ngcgui: y0 =  1;           //, path y0
//ngcgui: x1 =  2;           //, path x1
//ngcgui: y1 =  2;           //, path y1
//ngcgui: x2 =  0;           //, path x2
//ngcgui: y2 =  4;           //, path y2
//ngcgui: x3 = -1;           //, path x3
//ngcgui: y3 =  2;           //, path y3
//ngcgui: cutdepth = -1;     //
//ngcgui: safez = 5;         // z safe
//ngcgui: x_return = 0;      // x return
//ngcgui: y_return = 0;      // y return
//ngcgui: fnormal = 300;     //, feed normal
//ngcgui: fslow = 150;       //, feed slow
//ngcgui: ffast = 3000;      //, feed fast
//ngcgui: verbose = 1;

include("ensure_units.gcmc"); //avoid preamble conflict
if (umode == 1) {
  zero = 0.0mm;
} else {
  zero = 0.0in;
}
// ngcgui entries are unitless so these additions are used
// to ensure 1) floatingpoint and 2) units per umode setting
   trochoid_r = zero + trochoid_r;
trochoid_incr = zero + trochoid_incr;
      xoffset = zero + xoffset;
      yoffset = zero + yoffset;
           x0 = zero + x0;
           y0 = zero + y0;
           x1 = zero + x1;
           y1 = zero + y1;
           x2 = zero + x2;
           y2 = zero + y2;
           x3 = zero + x3;
           y3 = zero + y3;
     cutdepth = zero + cutdepth;
        safez = zero + safez;
     x_return = zero + x_return;
     y_return = zero + y_return;
      fnormal = zero + fnormal;
        fslow = zero + fslow;
        ffast = zero + ffast;

CUTZ = cutdepth;
SAFEZ = safez;
HOME = [x_return, y_return, safez];

path = { [xoffset + x0, yoffset + y0],
         [xoffset + x1, yoffset + y1],
         [xoffset + x2, yoffset + y2],
         [xoffset + x3, yoffset + y3] };
path *= path_scale;  //path_scale is dimensionless

feedrate(fnormal);
if (verbose) {
  comment("debug, feed normal= ",fnormal);
}
goto([-, -, SAFEZ]);
move([-, -, SAFEZ]);
goto(HOME);

/* Trochoidal high-speed milling of the outline */
if (verbose) {
  comment("debug, feed fast= ",ffast);
}
feedrate(ffast);	// *Really* high-speed milling
repeat(count(path); i) {
	trochoid_move(path[i-2], path[i-1], CUTZ, trochoid_r, trochoid_incr);

}

/* Clean-cutting the object */
if  (verbose) {
  comment("debug, feed slow= ",fslow);
}
feedrate(fslow);	// "Finishing the edge" speed
pathmode(1);		// Exact path mode so we hit the corners exactly
move([-, -, CUTZ]);	// We are at the "outside" of the path, reenter cutting depth
move(path[-1]);		// The first corner
foreach(path; v) {
	move(v);	// Trace the object
}

goto([-, -, SAFEZ]);
goto(HOME);
