Skip to content

Commit

Permalink
implement cubic bezier curves (c and C commands in svg paths) using @…
Browse files Browse the repository at this point in the history
…evomotor bezier curve library to convert them to lines in the frontend (see #1)
  • Loading branch information
M4GNV5 committed May 12, 2019
1 parent 04fd768 commit f5ac91c
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 46 deletions.
75 changes: 75 additions & 0 deletions interface/bezier.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// bezier to line conversion script
// taken from https://github.com/evomotors/BezierCurvesJS
// written by evomotors (see https://github.com/M4GNV5/EggEsp/issues/1#issuecomment-488673199)

var FactorialLookup = [1.0, 1.0, 2.0, 6.0, 24.0, 120.0, 720.0, 5040.0, 40320.0, 362880.0,
3628800.0, 39916800.0, 479001600.0, 6227020800.0, 87178291200.0,
1307674368000.0, 20922789888000.0, 355687428096000.0, 6402373705728000.0,
121645100408832000.0, 2432902008176640000.0, 51090942171709440000.0,
1124000727777607680000.0, 25852016738884976640000.0, 620448401733239439360000.0,
15511210043330985984000000.0, 403291461126605635584000000.0, 10888869450418352160768000000.0,
304888344611713860501504000000.0, 8841761993739701954543616000000.0, 265252859812191058636308480000000.0,
8222838654177922817725562880000000.0, 263130836933693530167218012160000000.0]

// Calculate points on curve
function GetBezierPoints(b, cpts)
{
var npts = (b.length) / 2;
var p = [];

var icount = 0;
var t = 0;
var step = 1.0 / (cpts - 1);

for (var i1 = 0; i1 != cpts; i1++)
{
if ((1.0 - t) < 5e-6)
t = 1.0;

var jcount = 0;
p[icount] = 0.0;
p[icount + 1] = 0.0;
for (var i = 0; i != npts; i++)
{
var basis = Bernstein(npts - 1, i, t);
p[icount] += basis * b[jcount];
p[icount + 1] += basis * b[jcount + 1];
jcount = jcount + 2;
}

if(isNaN(p[icount]) || isNaN(p[icount + 1]))
debugger;

icount += 2;
t += step;
}

return p;
}

// Calculates Bernstein basis
function Bernstein(n, i, t)
{
var ti;
var tni;

if (t == 0.0 && i == 0)
ti = 1.0;
else
ti = Math.pow(t, i);

if (n == i && t == 1.0)
tni = 1.0;
else
tni = Math.pow((1 - t), (n - i));

return (Factorial(n) / (Factorial(i) * Factorial(n - i))) * ti * tni;
}

function Factorial(n)
{
if(n >= FactorialLookup.length)
return Number.MAX_SAFE_INTEGER;
else
return FactorialLookup[n];
}
3 changes: 2 additions & 1 deletion interface/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
<br />
<button onclick="genGcode()">Generate Instructions</button> <span id="status"></span>

<svg id="image"></svg>
<div id="image"></div>

<script src="bezier.js"></script>
<script src="svg2gcode.js"></script>
<script src="bytecode.js"></script>
<script src="ui.js"></script>
Expand Down
125 changes: 80 additions & 45 deletions interface/svg2gcode.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,51 +70,8 @@ function path2gcode(transform, path)
let y = 0;
let wasRelative = false;

while(split.length > 0)
function addOp(op)
{
let curr = split.shift();
let op;
if(xyOps.hasOwnProperty(curr))
{
op = {
op: xyOps[curr],
x: parseFloat(split.shift()),
y: parseFloat(split.shift())
};
}
else if(xy0Ops.hasOwnProperty(curr))
{
let xy0Op = xy0Ops[curr];
op = {op: xy0Op.op};
op[xy0Op.value] = parseFloat(split.shift());

if(isRelative(op.op))
op[xy0Op.unchanged] = 0;
else
op[xy0Op.unchanged] = xy0Op.unchanged == "x" ? x : y;
}
else if(curr == "z" || curr == "Z")
{
op = {
op: OP_LINE,
x: startX,
y: startY
};
}
//TODO other ops (see https://svgwg.org/specs/paths/#PathElement)
else if(!isNaN(curr)) //assume L/l by default
{
op = {
op: wasRelative ? OP_LINE_REL : OP_LINE,
x: parseFloat(curr),
y: parseFloat(split.shift())
};
}
else
{
throw "Invalid/Unsupported svg path character " + curr;
}

wasRelative = isRelative(op.op);

if(ret.length == 0 && wasRelative)
Expand Down Expand Up @@ -146,10 +103,88 @@ function path2gcode(transform, path)
applyTransformation(transform, op);

if(isNaN(op.x) || isNaN(op.y))
throw "Oops, something went wrong!";
throw new Error("Oops, something went wrong!");
ret.push(op);
}

while(split.length > 0)
{
let curr = split.shift();
if(xyOps.hasOwnProperty(curr))
{
addOp({
op: xyOps[curr],
x: parseFloat(split.shift()),
y: parseFloat(split.shift())
});
}
else if(xy0Ops.hasOwnProperty(curr))
{
let xy0Op = xy0Ops[curr];
while(!isNaN(split[0]))
{
let op = {op: xy0Op.op};
op[xy0Op.value] = parseFloat(split.shift());

if(isRelative(op.op))
op[xy0Op.unchanged] = 0;
else
op[xy0Op.unchanged] = xy0Op.unchanged == "x" ? x : y;

addOp(op);
}
}
else if(curr == "z" || curr == "Z")
{
addOp({
op: OP_LINE,
x: startX,
y: startY
});
}
else if(curr == "c" || curr == "C")
{
let points;
var endIndex = split.findIndex(x => isNaN(x));
if(endIndex == -1)
{
points = split;
split = [];
}
else
{
points = split.splice(0, endIndex);
}

points = points.map(x => parseFloat(x));

//TODO what is a good value for the second argument? Should we let the user define this?
points = GetBezierPoints(points, 20);

for(var i = 0; i < points.length; i += 2)
{
addOp({
op: OP_LINE,
x: points[i],
y: points[i + 1]
});
}
}
//TODO other ops (see https://svgwg.org/specs/paths/#PathElement)
else if(!isNaN(curr)) //assume L/l by default
{
addOp({
op: wasRelative ? OP_LINE_REL : OP_LINE,
x: parseFloat(curr),
y: parseFloat(split.shift())
});
}
else
{
throw "Invalid/Unsupported svg path character " + curr;
}
}

return ret;
}

Expand Down

0 comments on commit f5ac91c

Please sign in to comment.