joostn / OpenJsCad

3D solid CAD using only Javascript
313 stars 127 forks source link

Import SVG objects as as series of CAG objects #73

Closed z3dev closed 8 years ago

z3dev commented 8 years ago

This is the initial version of CAG.importSVG() which reads the SVG document and creates as series of CAG objects (source code). Internally, the SVG document is converted into a large control object which can have embedded objects. The control object is created in order to process the SVG document as part SVG standards, which is fairly complex. The control object is then converted into CAG objects, and transformations.

The function makes use of a SAX parser which is found at @isaacs / sax-ja This is a compact and complete SAX parser for XML, and should be considered for inclusion.

joostn commented 8 years ago

This is awesome! But I couldn't get it to work yet.. (in the browser)

I don't know how to get the module syntax to work in the browser, so I just stripped away the top and bottom of importSVG.

Added this to processfile.html:

And added it to openjscad.js: var baselibraries = [ "src/csg.js", "src/openjscad.js", "src/sax-js-master/lib/sax.js", "src/importSVG.js", ];

Here's my test file, just a quick drawing I made in inkscape:

function main()
{
var svgtxt = String.raw`<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   xmlns:dc="http://purl.org/dc/elements/1.1/"
   xmlns:cc="http://creativecommons.org/ns#"
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
   xmlns:svg="http://www.w3.org/2000/svg"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
   width="210mm"
   height="297mm"
   viewBox="0 0 744.09448819 1052.3622047"
   id="svg2"
   version="1.1"
   inkscape:version="0.91 r13725"
   sodipodi:docname="drawing.svg">
  <defs
     id="defs4" />
  <sodipodi:namedview
     id="base"
     pagecolor="#ffffff"
     bordercolor="#666666"
     borderopacity="1.0"
     inkscape:pageopacity="0.0"
     inkscape:pageshadow="2"
     inkscape:zoom="0.35"
     inkscape:cx="375"
     inkscape:cy="520"
     inkscape:document-units="px"
     inkscape:current-layer="layer1"
     showgrid="false"
     inkscape:window-width="2965"
     inkscape:window-height="1705"
     inkscape:window-x="99"
     inkscape:window-y="-8"
     inkscape:window-maximized="1" />
  <metadata
     id="metadata7">
    <rdf:RDF>
      <cc:Work
         rdf:about=">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label="Layer 1"
     inkscape:groupmode="layer"
     id="layer1">
    <path
       style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
       d="m 191.42857,509.50506 c -8.8535,5.84648 -20.05307,7.89695 -28.57143,14.28572 -3.76089,2.82067 -7.80684,12.55533 -8.57143,17.14285 -0.46971,2.81827 -1.06111,5.91864 0,8.57143 2.65322,6.63304 15.94608,9.86937 20,11.42857 7.59477,2.92107 14.90063,6.86646 22.85715,8.57143 5.58744,1.19731 11.42857,0 17.14285,0 0.95239,0 1.90477,0 2.85715,0 0.95238,0 2.1837,-0.67343 2.85714,0 0.0919,0.0919 0,7.8785 0,8.57143 0,1.90476 0,3.80952 0,5.71429 0,1.90476 0.46197,3.86639 0,5.71428 -0.5165,2.06601 -2.2721,3.66664 -2.85714,5.71429 -1.33411,4.66936 -1.80369,9.54514 -2.85715,14.28571 -0.85183,3.83326 -2.08704,7.57806 -2.85714,11.42857 -0.16111,0.80557 -0.18015,11.15835 0,11.42857 1.49422,2.24133 3.80953,3.80953 5.71429,5.71429 0.95238,0.95238 1.65246,2.2548 2.85714,2.85714 7.5213,3.76065 9.1274,2.85715 17.14286,2.85715 1.90476,0 3.90727,-0.60234 5.71428,0 2.02031,0.67343 3.73702,2.06623 5.71429,2.85714 2.79629,1.11851 5.87769,1.51027 8.57143,2.85714 11.58306,5.79153 -18.38396,7.63349 20,5.71429 30.06318,-1.50316 41.079,-4.10065 65.71428,-17.14286 7.61905,-4.7619 15.2381,-9.52381 22.85715,-14.28571 0,-0.95238 -0.67344,-2.18371 0,-2.85715 0.67343,-0.67343 2.1837,0.67344 2.85714,0 2.39964,-2.39964 -4.11137,-6.61983 -5.71429,-11.42857 -1.83194,-5.49582 -1.0252,-11.64703 -2.85714,-17.14286 -2.28184,-6.84551 -15.10926,-17.9664 -20,-22.85714 -3.80952,-3.80952 -7.28979,-7.97959 -11.42857,-11.42857 -1.636,-1.36333 -4.20844,-1.3513 -5.71429,-2.85714 -1.50584,-1.50585 -1.35129,-4.20844 -2.85714,-5.71429 -11.15067,-11.15067 -1.53442,0.66136 -8.57143,-2.85714 -1.20468,-0.60234 -1.55049,-2.53048 -2.85714,-2.85714 -3.69578,-0.92395 -7.61905,0 -11.42857,0 -7.61905,0 -15.2381,0 -22.85715,0 -0.95238,0 -1.90476,0 -2.85714,0 -0.95238,0 -2.18371,0.67343 -2.85714,0 -0.67344,-0.67344 0.23098,-1.9332 0,-2.85715 -3.15435,-12.61738 -5.44475,-6.52289 0,-22.85714 12.78625,-38.35875 -1.11179,11.35089 11.42857,-20 0.70741,-1.76853 -0.60234,-3.90727 0,-5.71429 0.42592,-1.27775 2.85714,-1.51027 2.85714,-2.85714 0,-0.95238 -1.90476,0 -2.85714,0 -3.80952,0 -7.61905,0 -11.42857,0 -8.57143,0 -17.14286,0 -25.71429,0 -0.95238,0 -2.1837,-0.67343 -2.85714,0 -0.67344,0.67344 0.85184,2.43123 0,2.85714 -1.70367,0.85184 -3.80952,0 -5.71429,0 -0.95238,0 -2.1837,-0.67343 -2.85714,0 -0.67343,0.67344 0.42592,2.00531 0,2.85715 -5.94537,11.89074 0.19725,-3.05439 -5.71428,2.85714 -0.67344,0.67344 0.85183,2.43123 0,2.85714 -1.70368,0.85184 -4.01062,-0.85183 -5.71429,0 -0.85184,0.42592 0.67344,2.18371 0,2.85715 -1.34687,1.34687 -4.36742,-1.34687 -5.71429,0 -0.67343,0.67343 0.85184,2.43122 0,2.85714 -1.70367,0.85183 -4.01061,-0.85184 -5.71428,0 -0.85184,0.42592 0.67343,2.18371 0,2.85714 -1.34687,1.34687 -4.36742,-1.34687 -5.71429,0 -0.67343,0.67344 0,1.90476 0,2.85714 -3.80952,-0.95238 -7.61904,-1.90476 -11.42857,-2.85714 z"
       id="path3336"
       inkscape:connector-curvature="0" />
  </g>
</svg>`;
  return CAG.parseSVG(svgtxt);
}

But this returns just an empty main() code.

joostn commented 8 years ago

Ok, I think I understand now, you intended this to be a standalone thing which converts svg to jscad code. The jscad code can then be pasted into a .jscad file. Right?

I think having a kind of inline import (as above) would be nice as well; String.raw`` can be used to include the file as a string.

In any case it seems to fail to parse my svg, any idea?

z3dev commented 8 years ago

"PATH" isn't supported yet. :( Does Inkscape only export PATH elements? What about simple shapes like circle, rectangle, etc? Or polygon shapes?

Correct. This would be another uploaded format in the "gui".

joostn commented 8 years ago

Actually supporting PATH should be fairly easy, I wrote CSG.Path2D.appendBezier() with SVG compatibility in mind. I think all PATH subcommands can be easily mapped to Path2D methods.

For me this would be the most useful application for SVG import: draw a smooth bezier shape in Inkscape; then include it inline in a jscad script so it can be further processed by extrusion etc.

z3dev commented 8 years ago

@joostn thanks

I will implement the SVG path in the next few weeks. I'll use CSG.Path2D.appendBezier() and other Path2D methods.

I'm still tweaking the code at this time as well. There were lots of little things wrong.

z3dev commented 8 years ago

I just checked in code for for SVG paths. I've attached a screen shot of one of the tests. I'm sure there's some bugs but this certainly looks promising.

@joostn The appendArc() and appendBezier() routine worked really nice. Awesome! 1

z3dev commented 8 years ago

FYI, I found issues with the sequence of transformations, i.e. scale(), rotate(), translate(). I'm sure that this is a common issue between 2D / 3D rendering programs, but is there a standard on how to apply these?

z3dev commented 8 years ago

Also, if you are looking for somewhere to get nice examples, then try the Kitchen Sink demo at www.fabricjs.com. That demo can produce SVG files which include all SVG shapes.

z3dev commented 8 years ago

Also, how to implement quadratic Bézier curves? Is this the same as the cubic Bézier curve, but the control point is shared between two points?

joostn commented 8 years ago

Nice work! It's a while ago I wrote the bezier functions, but I think if you have just a single control point between two points then appendBezier will create a quadratic curve. If you have two control points then it's a cubic Bézier.

z3dev commented 8 years ago

So, are you going to add color specifications to CAG? I think that CAG -> CSG would benefit as the setColor() would not be required after extruding the CAG into 3D space. Rendering of multiple CAG objects would also be nice.

z3dev commented 8 years ago

@joostn There's a little math issue with the conversion process. The functions are using floating point math in order to convert PX (user units) to MM. As you probably know already, this leads to some inaccuracy in the calculations, which cause the CSG / CAG functions to report issues, e.g. "Polygon is self intersecting!"

Here's an example. The first polygon is fine, but the second fails due to the extra precision in the floating point numbers.

Do you have a suggestion? I'm not sure that rounding/truncating to 4 decimal places will solve this.

  var cag201 = new CSG.Path2D([[12.9167,-52.7778]],false);
  cag201 = cag201.appendPoint([23.742,-77.7778]);
  cag201 = cag201.appendPoint([2.0914,-77.7778]);
  cag201 = cag201.appendPoint([12.9167,-52.7778]);
  cag201 = cag201.close();
  cag201 = cag201.innerToCAG();
  cag20 = cag20.union(cag201);
  var cag202 = new CSG.Path2D([[12.9167,-56.9445]],false);
  cag202 = cag202.appendPoint([21.0357,-75.6945]);
  cag202 = cag202.appendPoint([4.8039999999999985,-75.6945]);
  cag202 = cag202.appendPoint([12.922999999999998,-56.944500000000005]);
  cag202 = cag202.close();
  cag202 = cag202.innerToCAG();
  cag20 = cag20.union(cag202);
z3dev commented 8 years ago

Here's the original PATH from the SVG file, which has high precision numbers for X Y offsets.

<path d=" m 62.00000 190.00000 l 51.96152 90.00000 l -103.92304 0.00000 l 51.96152 -90.00000 z m 0.00000 15.00000 l 38.97114 67.50000 l -77.91228 0.00000 l 38.97114 -67.50000 z "/>

joostn commented 8 years ago

I just started implementing a CSG.fuzzyFactory to solve this (see source: this builds a database of coordinates and replaces values by existing values if they are close). But it doesn't work and now I'm looking closely at the above I don't really understand what's happening.

First of all, converting PX to MM is just a multiplication, right? Why do you get different result for the same multiplication (-56.9445 vs -56.944500000000005)? Also 12.9167 vs 12.9229 is a huge error which cannot be explained by math precision. So how did you get these values?

And in the above 201 you're adding the starting point [12.9167,-52.7778] at the end AND you are closing the CAG. You should not do that, just calling close() should be sufficient without adding the same point twice.

And I don't see how m 0.00000 15.00000 l 38.97114 67.50000 l -77.91228 0.00000 l 38.97114 -67.50000 z maps to cag202. I've plotted them and they look totally different?

z3dev commented 8 years ago

@joostn thanks I'll check the calculations being applied and determine why there's a substantial difference.

SVG starts from the upper left corner (0,0) and plots to the lower right. Therefore, the coordinates are converted in order to plot into the lower right of CSG coordinate space, i.e. X > X, Y > -Y). By doing this, the shapes and the orientation are maintained.

The best way to see the exact shape is by displaying the original SVG file. I've sent a mail with the original SVG file.

z3dev commented 8 years ago

The rounding issues were corrected by the latest commit.

There is one SVG shape that needs a final implementation; Ellipse. Any suggestions? I was thinking of using appendArc().

Also, this functionality needs a home. There are several other 'import' functions as well, including AMF, OBJ, STL, and GCode. I think these should be implemented as extensions to CSG and CAG objects in order to use CSG/CAG instance variables and namespace.

How about a new 'imports.js' file to hold these? Or separate files for each extension to CSG and CAG? For example, importSVG.js and exportSVG.js?

z3dev commented 8 years ago

I'm closing this pull request as there's another one that's more important. And this needs some more thought and input.