microsoft / maker.js

📐⚙ 2D vector line drawing and shape modeling for CNC and laser cutters.
http://maker.js.org
Apache License 2.0
1.74k stars 264 forks source link

Bezier curve #96

Closed NickNaso closed 7 years ago

NickNaso commented 8 years ago

I need a clarification which is the best way to draw a Bezier curve? Excuse me if a request this in GitHub i understand that is not the appropriate place to do that! If anyone post my an example. Thanks

danmarshall commented 8 years ago

Great question. Currently Maker.js does not support true Bezier curve primitives. Side note - it also does not support Ellipse.

Short answer:

These can be approximated with lines or arcs, similar to what OpenJsCad does. Preferable someone in the community can package these as an NPM package.

Please let me know if approximation is something you can live with, or if there is a reason you need absolute true beziers.

Long answer:

There are a couple of reasons for not having these:

  1. Some CNC services do not support Spline (aka Bezier) or Ellipse in DXF files, and I wanted to make sure Maker.js would create 100% compatible output.
  2. Lots more math code :P
  3. More difficult API surface. The result of an intersection call could potentially have several intersections between two bezier curves instead of the predictable maximum of 2 for arcs, lines and circles. The API result would need to include a collection of "control points" etc.

It was foreseeable that this issue would arise, I'm glad you brought it up for discussion!

danmarshall commented 8 years ago

also - @NickNaso - GitHub is definitely the best place to have these discussions and I am grateful to have your feedback and have you in the community!

gPrio commented 8 years ago

Hi guys, I would contribute with my experience. I think that Bezier curves are important for two main thighs:

I think it is a must have and before the use, anyone should consider the application. I would not limit the library just for few CNCs :-)

daton89 commented 8 years ago

I tried to create a Bezier Curve creating all the points of the function and joining them with the connectTheDots model but the result is not great for the CNC because it split the curve in n line (in my case 100). I think that It may be approximated by arcs to reduce the number of cuts the machine must execute.. suggestions are welcome!

var makerjs = require('makerjs');
var fs = require('fs');

var bezier = function (t, p0, p1, p2, p3) {
    var cX = 3 * (p1.x - p0.x),
        bX = 3 * (p2.x - p1.x) - cX,
        aX = p3.x - p0.x - cX - bX;

    var cY = 3 * (p1.y - p0.y),
        bY = 3 * (p2.y - p1.y) - cY,
        aY = p3.y - p0.y - cY - bY;

    var x = (aX * Math.pow(t, 3)) + (bX * Math.pow(t, 2)) + (cX * t) + p0.x;
    var y = (aY * Math.pow(t, 3)) + (bY * Math.pow(t, 2)) + (cY * t) + p0.y;

    return {x: x, y: y};
};

(function () {
    var accuracy = 0.01, 
        p0 = {x: 10, y: 10},
        p1 = {x: 50, y: 100},
        p2 = {x: 150, y: 200},
        p3 = {x: 200, y: 75};

    var points = [];

    for (var i = 0; i < 1; i += accuracy) {
        var p = bezier(i, p0, p1, p2, p3);
        points.push([p.x, p.y]);
    }

    var bezierJS = new makerjs.models.ConnectTheDots(true, points);

    var svg = makerjs.exporter.toSVG(bezierJS);

    fs.writeFile('bezier.svg', svg);
})();
danmarshall commented 8 years ago

@gPrio Thanks for mentioning these. I would also another reason: for accuracy.

@tonillo Thanks for the code sample! It looks good on screen, but yes there are many segments. I would like to cut your sample on my laser cutter to see the results.

Can I ask everyone their use case for Bezier, to get a better understanding of your application? Is it purely aesthetic or to be employed in a mechanism?

danmarshall commented 8 years ago

Bezier branch created, with stubs for future work.

lollotek commented 8 years ago

I think the sample code need a fix. I found a thread with the same script for canvas, and a fiddle sample: http://jsfiddle.net/fQYsU/ Adapting the code for the playgound, I see a different result

var makerjs = require('makerjs');
var bezier = function (t, p0, p1, p2, p3) {
    var cX = 3 * (p1.x - p0.x),
        bX = 3 * (p2.x - p1.x) - cX,
        aX = p3.x - p0.x - cX - bX;
    var cY = 3 * (p1.y - p0.y),
        bY = 3 * (p2.y - p1.y) - cY,
        aY = p3.y - p0.y - cY - bY;
    var x = (aX * Math.pow(t, 3)) + (bX * Math.pow(t, 2)) + (cX * t) + p0.x;
    var y = (aY * Math.pow(t, 3)) + (bY * Math.pow(t, 2)) + (cY * t) + p0.y;
    return {x: x, y: y};
};
function bz () {
    var accuracy = 0.01, 
        p0 = {x: 10, y: 10},
        p1 = {x: 50, y: 100},
        p2 = {x: 150, y: 200},
        p3 = {x: 200, y: 75};

    var points = [];
    for (var i = 0; i < 1; i += accuracy) {
        var p = bezier(i, p0, p1, p2, p3);
        points.push([p.x, p.y]);
    }
    return new makerjs.models.ConnectTheDots(true, points);
};
module.exports = bz;

Probably, It's because the canvas have the oringin (0,0) on top left, and positive y go downside.

I also fork the jsfiddle script to create a quadratic Bezier, that works on cavas, but it's totally wrong on makerjs.

cavans: http://jsfiddle.net/05j1mb28/

makerjs:

var makerjs = require('makerjs');

var bezier = function(t, p0, p1, p2){
  var cX = 3 * (p1.x - p0.x),
      bX = 3 * (p2.x - p1.x) - cX

  var cY = 3 * (p0.y - p1.y),
      bY = 3 * (p1.y - p2.y) - cY

  var x =  (bX * Math.pow(t, 2)) + (cX * t) + p0.x;
  var y =  (bY * Math.pow(t, 2)) + (cY * t) + p0.y;

  return {x: x, y: y};
};

function bz() {
    var accuracy = 0.2; 
    var p0 = {x: 0, y: 0};
    var p1 = {x: 160, y: 0};
    var p2 = {x: 160, y: 160};

    var points = [];
    points.push([p0.x, p0.y]);
    points.push([p1.x, p1.y]);
    points.push([p2.x, p2.y]);

    for (var i = 0; i < 1; i += accuracy) {
        var p = bezier(i, p0, p1, p2);
        points.push([p.x, p.y]);
    }   

    return new makerjs.models.ConnectTheDots(true, points);

};

module.exports = bz;

I'm trying to make this because I want to print text. Using Opentypejs it's possible to extract paths from font letters, but after that, I need to convert these paths into makerjs, and the paths from opentype are quadratic Bezier and lines.

danmarshall commented 8 years ago

@lollotek I forked your fiddle to get the same result from both canvas and Maker.js: http://jsfiddle.net/Lztb65h4/5/

You are correct that the Y axis in Maker.js is opposite of SVG / Canvas. Maker.js follows traditional drafting coordinates, see http://microsoft.github.io/maker.js/docs/basic-drawing/#Points

It is simple to mirror on the Y axis: var flipY = makerjs.model.mirror(mymodel, false, true); http://microsoft.github.io/maker.js/docs/api/modules/makerjs.model.html#mirror

It looks like you were trying to use the actual Bezier control points for the Maker.js code: var points = []; points.push([p0.x, p0.y]); points.push([p1.x, p1.y]); points.push([p2.x, p2.y]);

But - there is no Bezier functionality in Maker.js (yet). I am working on an upcoming feature to add this. So the forked fiddle example works by drawing each point as you did with canvas.

Also, I'm intrigued by the use of Opentype.js - I will need to take a look at that! Text will be a great feature to add as well.

danmarshall commented 8 years ago

@lollotek also - slight mods to your code worked:

var makerjs = require('makerjs');

var bezier = function(t, p0, p1, p2){
  var cX = 3 * (p1.x - p0.x),
      bX = 3 * (p2.x - p1.x) - cX

  var cY = 3 * (p0.y - p1.y),
      bY = 3 * (p1.y - p2.y) - cY

  var x =  (bX * Math.pow(t, 2)) + (cX * t) + p0.x;
  var y =  (bY * Math.pow(t, 2)) + (cY * t) + p0.y;

  return {x: x, y: y};
};

function bz() {
    var accuracy = 0.02; //changed
    var p0 = {x: 0, y: 0};
    var p1 = {x: 160, y: 0};
    var p2 = {x: 160, y: 160};

    var points = [];
//    points.push([p0.x, p0.y]);
//    points.push([p1.x, p1.y]);
//    points.push([p2.x, p2.y]);

    for (var i = 0; i < 1; i += accuracy) {
        var p = bezier(i, p0, p1, p2);
        points.push([p.x, p.y]);
    }   

    return new makerjs.models.ConnectTheDots(false, points); //false = don't close the shape

};

module.exports = bz;
lollotek commented 8 years ago

@danmarshall Thanks for the feedle! I think the function for quadratic Bezier have some issue, because I expect an arc figure sending [[0,0],[160,0],[160,160]]. In my case I solved using an external library https://www.npmjs.com/package/bezier

The following code for nodejs generate svg of a single character from a specific font (remeber to set a valid ttf font into fonts folder):


var makerjs = require('makerjs');
var opentype = require('opentype.js');
var bezier = require('Bezier');

var fs = require('fs')

opentype.load('fonts/ZELDA.ttf', function(err, font) {
    if (err) {
        console.log('Could not load font: ' + err);
    } else {

        //some code to build line and circle
        var points = [];
        var bezierArray = [];
        var pathArray = [];

        var textPath = font.getPath("P", 0, 0, 72);
        var position = [0,0];

        var mymodel = [];
        for (var i = 0; i < textPath.commands.length; i++){
            var cmd = textPath.commands[i];
            if (cmd != undefined){
                switch (cmd.type){
                    case 'Q':
                        var points = [];
                        var x = [position[0],cmd.x1,cmd.x];
                        var y = [position[1],cmd.y1,cmd.y];
                        for (var t=0; t<1; t+=0.01){
                            points.push([bezier(x, t), bezier(y, t)]);
                        }
                        position.push = [cmd.x,cmd.y];
                        bezierArray.push(new makerjs.models.ConnectTheDots(false, points))
                        break;
                    case 'L':
                        var line = { 
                          type: 'line', 
                          origin: position, 
                          end: [cmd.x,cmd.y]
                        };
                        pathArray.push(line);
                        break;
                } 
                position = [cmd.x,cmd.y];
            }
        }

        var model = { paths: pathArray, models: bezierArray};
        var flipY = makerjs.model.mirror(model, false, true);
        var svg = makerjs.exporter.toSVG(flipY);
        fs.writeFile('test.svg', svg);
    }
});

I also found a very complete library for Bezier http://pomax.github.io/bezierjs/

danmarshall commented 8 years ago

@lollotek Right now I'm on vacation and don't have a computer, but just reviewing your code, could this be a fix?:

var x = [position[0],cmd.x,cmd.y];
var y = [position[1],cmd.x1,cmd.y1];
danmarshall commented 7 years ago

Hello all, I'm proud to say that Maker.js now supports Bezier curves. It has been many months in the making, involving a lot of research. Special thanks go to @Pomax for the excellent library Bezier.js which is now a dependency.

There is much to document, but here are the key pieces to know:

Demos: http://microsoft.github.io/maker.js/playground/?script=BezierCurve http://microsoft.github.io/maker.js/playground/?script=Ellipse (click on 'show paths' to see the arcs)

Please open a new issue for any bugs that you may find. Thanks! Dan

NickNaso commented 7 years ago

Thank you i wiil test this feautures very soon!

Pomax commented 7 years ago

Bezier curves in all the things =D

DanDaHahn commented 5 years ago

I've got this Issue: I want to display a dxf in the Viewer. Right now I'am writing a parser for it. Now I've got problems with Splines (BezierCurves). As I noticed, only certain curves (quadratic and cubic ) are supported which have 3 or 4 points. However, I also have Splines that have more than 4 points. Would you have an idea to implement it anyways? Best regards

Pomax commented 5 years ago

@DanDaHahn note that this issue got closed, so if you have a problem distinct from this one, you want to file that as a new issue instead.

DanDaHahn commented 5 years ago

@Pomax thanks for your reply. I thought that the participants maybe get a notification and maybe reopen this thread.

I'll open a new one.