CSG construction must now be done in a main() function instead of in global scope; Added CSG properties and connectors; file processing is now done in the background (Web Workers); STL file is now downloaded instead of copied from a text area

This commit is contained in:
Joost Nieuwenhuijse 2012-02-13 15:29:37 +01:00
parent fbafcf6864
commit 3b4e1fd1a6
4 changed files with 536 additions and 262 deletions

109
csg.js
View file

@ -100,6 +100,14 @@ CSG.fromPolygons = function(polygons) {
return csg; return csg;
}; };
// create from an untyped object with identical property names:
CSG.fromObject = function(obj) {
var polygons = obj.polygons.map( function(p) {
return CSG.Polygon.fromObject(p);
});
return CSG.fromPolygons(polygons);
};
CSG.prototype = { CSG.prototype = {
toPolygons: function() { toPolygons: function() {
return this.polygons; return this.polygons;
@ -221,9 +229,7 @@ CSG.prototype = {
}, },
mirrored: function(plane) { mirrored: function(plane) {
var newpolygons = this.polygons.map(function(p) { return p.mirrored(plane); } ); return this.transform(CSG.Matrix4x4.mirroring(plane));
return CSG.fromPolygons(newpolygons);
// TODO: also mirror properties
}, },
mirroredX: function() { mirroredX: function() {
@ -269,7 +275,7 @@ CSG.prototype = {
}, },
toString: function() { toString: function() {
var result = ""; var result = "CSG solid:\n";
this.polygons.map(function(p){ result += p.toString(); }); this.polygons.map(function(p){ result += p.toString(); });
return result; return result;
}, },
@ -422,8 +428,15 @@ CSG.prototype = {
return result; return result;
}, },
connectTo: function(myConnector, otherConnector, mirror, axisrotation) { // Connect a solid to another solid, such that two CSG.Connectors become connected
var matrix = myConnector.getTransformationTo(otherConnector, mirror, axisrotation); // myConnector: a CSG.Connector of this solid
// otherConnector: a CSG.Connector to which myConnector should be connected
// mirror: false: the 'axis' vectors of the connectors should point in the same direction
// true: the 'axis' vectors of the connectors should point in opposite direction
// normalrotation: degrees of rotation between the 'normal' vectors of the two
// connectors
connectTo: function(myConnector, otherConnector, mirror, normalrotation) {
var matrix = myConnector.getTransformationTo(otherConnector, mirror, normalrotation);
return this.transform(matrix); return this.transform(matrix);
}, },
@ -587,8 +600,8 @@ CSG.sphere = function(options) {
} }
var result = CSG.fromPolygons(polygons); var result = CSG.fromPolygons(polygons);
result.properties.sphere = new CSG.Properties(); result.properties.sphere = new CSG.Properties();
result.properties.sphere.center = new CSG.Vector3D(c); result.properties.sphere.center = new CSG.Vector3D(center);
result.properties.sphere.facepoint = c.plus(xvector); result.properties.sphere.facepoint = center.plus(xvector);
return result; return result;
}; };
@ -737,9 +750,9 @@ CSG.roundedCylinder = function(options) {
var ray = zvector.unit(); var ray = zvector.unit();
var axisX = xvector.unit(); var axisX = xvector.unit();
result.properties.roundedCylinder = new CSG.Properties(); result.properties.roundedCylinder = new CSG.Properties();
result.properties.cylinder.start = new CSG.Connector(p1, ray.negated(), axisX); result.properties.roundedCylinder.start = new CSG.Connector(p1, ray.negated(), axisX);
result.properties.cylinder.end = new CSG.Connector(p2, ray, axisX); result.properties.roundedCylinder.end = new CSG.Connector(p2, ray, axisX);
result.properties.cylinder.facepoint = p1.plus(xvector); result.properties.roundedCylinder.facepoint = p1.plus(xvector);
return result; return result;
}; };
@ -991,6 +1004,12 @@ CSG.Vertex = function(pos) {
this.pos = pos; this.pos = pos;
}; };
// create from an untyped object with identical property names:
CSG.Vertex.fromObject = function(obj) {
var pos = new CSG.Vector3D(obj.pos);
return new CSG.Vertex(pos);
};
CSG.Vertex.prototype = { CSG.Vertex.prototype = {
// Return a vertex with all orientation-specific data (e.g. vertex normal) flipped. Called when the // Return a vertex with all orientation-specific data (e.g. vertex normal) flipped. Called when the
// orientation of a polygon is flipped. // orientation of a polygon is flipped.
@ -1040,6 +1059,13 @@ CSG.Plane = function(normal, w) {
this.w = w; this.w = w;
}; };
// create from an untyped object with identical property names:
CSG.Plane.fromObject = function(obj) {
var normal = new CSG.Vector3D(obj.normal);
var w = parseFloat(obj.w);
return new CSG.Plane(normal, w);
};
// `CSG.Plane.EPSILON` is the tolerance used by `splitPolygon()` to decide if a // `CSG.Plane.EPSILON` is the tolerance used by `splitPolygon()` to decide if a
// point is on the plane. // point is on the plane.
CSG.Plane.EPSILON = 1e-5; CSG.Plane.EPSILON = 1e-5;
@ -1343,6 +1369,16 @@ CSG.Polygon = function(vertices, shared, plane) {
} }
}; };
// create from an untyped object with identical property names:
CSG.Polygon.fromObject = function(obj) {
var vertices = obj.vertices.map(function(v) {
return CSG.Vertex.fromObject(v);
});
var shared = null;
var plane = CSG.Plane.fromObject(obj.plane);
return new CSG.Polygon(vertices, shared, plane);
};
CSG.Polygon.prototype = { CSG.Polygon.prototype = {
// check whether the polygon is convex (it should be, otherwise we will get unexpected results) // check whether the polygon is convex (it should be, otherwise we will get unexpected results)
checkIfConvex: function() { checkIfConvex: function() {
@ -1466,19 +1502,17 @@ CSG.Polygon.prototype = {
return new CSG.Polygon(newvertices, this.shared, newplane); return new CSG.Polygon(newvertices, this.shared, newplane);
}, },
mirrored: function(plane) {
var newvertices = this.vertices.map(function(v) {
var newpos = plane.mirrorPoint(v.pos);
return new CSG.Vertex(newpos);
});
newvertices.reverse();
return new CSG.Polygon(newvertices, this.shared);
},
// Affine transformation of polygon. Returns a new CSG.Polygon // Affine transformation of polygon. Returns a new CSG.Polygon
transform: function(matrix4x4) { transform: function(matrix4x4) {
var newvertices = this.vertices.map(function(v) { return v.transform(matrix4x4); } ); var newvertices = this.vertices.map(function(v) { return v.transform(matrix4x4); } );
var newplane = this.plane.transform(matrix4x4); var newplane = this.plane.transform(matrix4x4);
var scalefactor = matrix4x4.elements[0] * matrix4x4.elements[5] * matrix4x4.elements[10];
if(scalefactor < 0)
{
// the transformation includes mirroring. We need to reverse the vertex order
// in order to preserve the inside/outside orientation:
newvertices.reverse();
}
return new CSG.Polygon(newvertices, this.shared, newplane); return new CSG.Polygon(newvertices, this.shared, newplane);
}, },
@ -2155,6 +2189,21 @@ CSG.Matrix4x4.translation = function(v) {
return new CSG.Matrix4x4(els); return new CSG.Matrix4x4(els);
}; };
// Create an affine matrix for mirroring into an arbitrary plane:
CSG.Matrix4x4.mirroring = function(plane) {
var nx = plane.normal.x;
var ny = plane.normal.y;
var nz = plane.normal.z;
var w = plane.w;
var els = [
(1.0-2.0*nx*nx), (-2.0*ny*nx), (-2.0*nz*nx), 0,
(-2.0*nx*ny), (1.0-2.0*ny*ny), (-2.0*nz*ny), 0,
(-2.0*nx*nz), (-2.0*ny*nz), (1.0-2.0*nz*nz), 0,
(-2.0*nx*w), (-2.0*ny*w), (-2.0*nz*w), 1
];
return new CSG.Matrix4x4(els);
};
// Create an affine matrix for scaling: // Create an affine matrix for scaling:
CSG.Matrix4x4.scaling = function(v) { CSG.Matrix4x4.scaling = function(v) {
// parse as CSG.Vector3D, so we can pass an array or a CSG.Vector3D // parse as CSG.Vector3D, so we can pass an array or a CSG.Vector3D
@ -3372,19 +3421,11 @@ CSG.Connector = function(point, axisvector, normalvector) {
this.normalvector = new CSG.Vector3D(normalvector); this.normalvector = new CSG.Vector3D(normalvector);
}; };
CSG.Connector.test = function() {
var con1 = new CSG.Connector([1,2,5], [1,1,0], [1, -0.8, 0]);
var con2 = new CSG.Connector([-5,1,-1], [2,3,5], [0,0,1]);
var transform = con1.getTransformationTo(con2, false, 0);
var check = con1.transform(transform);
};
CSG.Connector.prototype = { CSG.Connector.prototype = {
normalized: function() { normalized: function() {
var axisvector = this.axisvector.unit(); var axisvector = this.axisvector.unit();
// make the normal vector truly normal: // make the normal vector truly normal:
var n = this.normalvector.cross(axisvector).unit(); var n = this.normalvector.cross(axisvector).unit();
// var normalvector = n.cross(axisvector);
var normalvector = axisvector.cross(n); var normalvector = axisvector.cross(n);
return new CSG.Connector(this.point, axisvector, normalvector); return new CSG.Connector(this.point, axisvector, normalvector);
}, },
@ -3396,9 +3437,15 @@ CSG.Connector.prototype = {
return new CSG.Connector(point, axisvector, normalvector); return new CSG.Connector(point, axisvector, normalvector);
}, },
getTransformationTo: function(other, mirror, axisrotation) { // Get the transformation matrix to connect this Connector to another connector
// other: a CSG.Connector to which this connector should be connected
// mirror: false: the 'axis' vectors of the connectors should point in the same direction
// true: the 'axis' vectors of the connectors should point in opposite direction
// normalrotation: degrees of rotation between the 'normal' vectors of the two
// connectors
getTransformationTo: function(other, mirror, normalrotation) {
mirror = mirror? true:false; mirror = mirror? true:false;
axisrotation = axisrotation? Number(axisrotation):0; normalrotation = normalrotation? Number(normalrotation):0;
var us = this.normalized(); var us = this.normalized();
other = other.normalized(); other = other.normalized();
// shift to the origin: // shift to the origin:
@ -3425,7 +3472,7 @@ CSG.Connector.prototype = {
angle1 = normalsbasis.to2D(usAxesAligned.normalvector).angle(); angle1 = normalsbasis.to2D(usAxesAligned.normalvector).angle();
angle2 = normalsbasis.to2D(other.normalvector).angle(); angle2 = normalsbasis.to2D(other.normalvector).angle();
rotation = 180.0 * (angle2 - angle1) / Math.PI; rotation = 180.0 * (angle2 - angle1) / Math.PI;
rotation += axisrotation; rotation += normalrotation;
transformation = transformation.multiply(normalsbasis.getProjectionMatrix()); transformation = transformation.multiply(normalsbasis.getProjectionMatrix());
transformation = transformation.multiply(CSG.Matrix4x4.rotationZ(rotation)); transformation = transformation.multiply(CSG.Matrix4x4.rotationZ(rotation));
transformation = transformation.multiply(normalsbasis.getInverseProjectionMatrix()); transformation = transformation.multiply(normalsbasis.getInverseProjectionMatrix());

View file

@ -32,130 +32,67 @@ textarea:focus {
outline: none; outline: none;
} }
//h1, h2 { font: bold 50px/50px 'Helvetica Neue', Helvetica, Arial; }
//h2 { font-size: 30px; margin: 10px 0 0 0; }
a { color: inherit; }
.viewer { width: 200px; height: 200px; background: #EEE url(images.png); }
#combined .viewer { width: 150px; height: 150px; }
table { border-collapse: collapse; margin: 0 auto; }
td { padding: 5px; text-align: center; }
td code { background: none; border: none; color: inherit; }
canvas { cursor: move; } canvas { cursor: move; }
#needchrome {
display: none;
}
</style> </style>
<script> <script>
var gProcessor=null;
var gViewer=null;
var gSolid=new CSG();
var gSolidSource="";
function isChrome()
{
return (navigator.userAgent.search("Chrome") >= 0);
}
function onload() function onload()
{ {
var needchromediv = document.getElementById("needchrome"); gProcessor = new OpenJsCad.Processor(document.getElementById("viewer"));
if(needchromediv)
{
if(!isChrome())
{
needchromediv.style.display="block";
}
}
var containerelement = document.getElementById("viewer");
gViewer = new OpenJsCad.Viewer(containerelement, 600, 600, 50);
updateSolid(); updateSolid();
} }
function updateSolid() function updateSolid()
{ {
if(gViewer.supported()) gProcessor.setJsCad(document.getElementById('code').value);
{
var code=document.getElementById('code').value;
if(code != gSolidSource)
{
gSolidSource=code;
var errtxt = "";
var csg = new CSG();
try {
csg = OpenJsCad.javaScriptToSolid(code);
} catch (e) {
errtxt = 'Error: <code>' + e.toString().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</code>';
}
gSolid = csg;
var errdiv=document.getElementById('error');
errdiv.innerHTML = errtxt;
errdiv.style.display = (errtxt == "")? "none":"block";
gViewer.setCsg(gSolid);
var stlarea = document.getElementById('stloutput');
stlarea.style.display = "none";
}
}
} }
function getStl()
{
updateSolid();
var stl=gSolid.toStlString();
var stlarea = document.getElementById('stloutput');
stlarea.value=stl;
stlarea.style.display = "inline";
}
</script> </script>
<title>OpenJsCad</title> <title>OpenJsCad</title>
</head> </head>
<body onload="onload()"> <body onload="onload()">
<h1>OpenJsCad</h1> <h1>OpenJsCad</h1>
<div id="needchrome">Please note: OpenJsCad currently only runs reliably on Google Chrome!</div> Create an STL file for 3D printing using constructive solid modeling in Javascript.
Create an STL file for 3D printing using constructive solid modeling in Javascript. <div id="viewer"></div>
<table>
<tr>
<td><div id="viewer" class="viewer" style="background-image:none;width:600px;height:600px;"></div></td>
</tr>
</table>
<h2>Playground</h2> <h2>Playground</h2>
Try it by entering some code below. Anything you enter will be lost as soon as this page is reloaded; Try it by entering some code below. Anything you enter will be lost as soon as this page is reloaded;
to build your own models you should instead store them in a .jscad file on your computer to build your own models you should instead store them in a .jscad file on your computer
and use the <a href="processfile.html"><b>OpenJsCad parser</b></a>. and use the <a href="processfile.html"><b>OpenJsCad parser</b></a>.
<br><br> <br><br>
<textarea id="code">var resolution = 16; // increase to get smoother corners (will get slow!) <textarea id="code">
function main()
{
var resolution = 16; // increase to get smoother corners (will get slow!)
var cube1 = CSG.roundedCube({center: [0,0,0], radius: [10,10,10], roundradius: 2, resolution: resolution}); var cube1 = CSG.roundedCube({center: [0,0,0], radius: [10,10,10], roundradius: 2, resolution: resolution});
var sphere1 = CSG.sphere({center: [5, 5, 5], radius: 10, resolution: resolution }); var sphere1 = CSG.sphere({center: [5, 5, 5], radius: 10, resolution: resolution });
var sphere2 = sphere1.translate([12, 5, 0]); var sphere2 = sphere1.translate([12, 5, 0]);
var sphere3 = CSG.sphere({center: [20, 0, 0], radius: 30, resolution: resolution }); var sphere3 = CSG.sphere({center: [20, 0, 0], radius: 30, resolution: resolution });
var result = cube1; var result = cube1;
result = result.union(sphere1); result = result.union(sphere1);
result = result.subtract(sphere2); result = result.subtract(sphere2);
result = result.intersect(sphere3); result = result.intersect(sphere3);
return result; return result;
}
</textarea> </textarea><br>
<input type="submit" value="Update" onclick="updateSolid(); return false;">
<p id="error"></p>
<br>
<input type="submit" value="Update Preview" onclick="updateSolid(); return false;">
<input type="submit" value="Get STL" onclick="getStl(); return false;"><br>
<textarea id="stloutput" readonly style="width: 80%; height: 100px; display: none;"></textarea>
<br> <br>
<h1>About</h1> <h1>About</h1>
This is intended to become a Javascript based alternative to <a href="http://www.openscad.org/">OpenSCAD</a>, This is intended to become a Javascript based alternative to <a href="http://www.openscad.org/">OpenSCAD</a>,
for 3D solid modeling. for 3D solid modeling.
CSG model is contructed using Javascript. For example:<br> CSG model is contructed using Javascript. For example:<br>
<code>var cube = CSG.cube(); return cube;</code> creates a cube with a radius of 1 and centered at the origin. <pre>function main() {
The code should always end in a return statement, returning a CSG solid. var cube = CSG.cube();
return cube;
}</pre>
creates a cube with a radius of 1 and centered at the origin.
The code should always contain a main() function, returning a CSG solid.
<br><br> <br><br>
To build your own modes, create a .jscad file with your javascript code and parse the file using the To build your own models, create a .jscad file with your javascript code and parse the file using the
<a href="processfile.html">OpenJsCad parser</a>. When finished you can generate an .stl file, <a href="processfile.html">OpenJsCad parser</a>. When finished, click on Generate STL and save the result
ready to be printed on your 3d printer. in an .stl file, ready to be printed on your 3d printer.
<h2>License</h2> <h2>License</h2>
Copyright (c) 2012 Joost Nieuwenhuijse. Copyright (c) 2012 Joost Nieuwenhuijse.
Uses CSG.js, <a href="https://github.com/evanw/csg.js">original</a> copyright (c) 2011 Evan Wallace, Uses CSG.js, <a href="https://github.com/evanw/csg.js">original</a> copyright (c) 2011 Evan Wallace,
@ -317,9 +254,129 @@ var csg = cube1.subtract(cube2);
var rounded = csg.expand(0.2, 8); var rounded = csg.expand(0.2, 8);
</pre> </pre>
<h2>Using Properties</h2>
The 'property' property of a solid can be used to store metdata for the object,
for example the coordinate of a specific point of interest of the solid. Whenever
the object is transformed (i.e. rotated, scaled or translated), the properties
are transformed with it. So the property will keep pointing to the same point
of interest even after several transformations have been applied to the solid.
<br><br>
Properties can have any type, but only the properties of classes supporting
a 'transform' method will actually be transformed. This includes CSG.Vector3D,
CSG.Plane and CSG.Connector. In particular CSG.Connector properties (see below)
can be very useful: these can
be used to attach a solid to another solid at a predetermined location regardless of the
current orientation.
<br><br>
It's even possible to include a CSG solid as a property of another solid. This could
be used for example
to define the cutout cylinders to create matching screw holes for an object. Those 'solid properties'
get the same transformations as the owning solid but they will not be visible in the result
of CSG operations such as union().
<br><br>
Other kind of properties (for
example, strings) will still be included in the properties of the transformed
solid, but the properties will not get any transformation when the owning solid is transformed. <br><br>
All primitive solids have some predefined properties, such as the center point
of a sphere (TODO: document).
<br><br>
The solid resulting from CSG operations (union(), subtract(), intersect()) will get
the merged properties of both source solids. If identically named properties exist, only
one of them will be kept.
<pre>
var cube = CSG.cube({radius: 1.0});
cube.properties.aCorner = new CSG.Vector3D([1, 1, 1]);
cube = cube.translate([5, 0, 0]);
cube = cube.scale(2);
// cube.properties.aCorner will now point to [12, 2, 2],
// which is still the same corner point
// Properties can be stored in arrays; all properties in the array
// will be transformed if the solid is transformed:
cube.properties.otherCorners = [
new CSG.Vector3D([-1, 1, 1]),
new CSG.Vector3D([-1, -1, 1])
];
// and we can create sub-property objects; these must be of the
// CSG.Properties class. All sub properties will be transformed with
// the solid:
cube.properties.myProperties = new CSG.Properties();
cube.properties.myProperties.someProperty = new CSG.Vector3D([-1, -1, -1]);
</pre>
<h2>Connectors</h2>
The CSG.Connector class is intended to facilitate
attaching two solids to each other at a predetermined
location and orientation.
For example suppose we have a CSG solid depicting a servo motor
and a solid of a servo arm: by defining a Connector property for each of them, we
can easily attach the servo arm to the servo motor at the correct position
(i.e. the motor shaft) and orientation (i.e. arm perpendicular to the shaft)
even if we don't know their current position and orientation
in 3D space.<br><br>
In other words Connector give us the freedom to rotate and translate objects at will without the need
to keep track of their positions and boundaries. And if a third party library exposes connectors for
its solids, the user of the library does not have to know the actual dimensions or
shapes, only the names of the connector properties.
<br><br>
A CSG.Connector consist of 3 properties:<br>
<b>point</b>: a CSG.Vector3D defining the connection point in 3D space<br>
<b>axis</b>: a CSG.Vector3D defining the direction vector of the connection
(in the case of the servo motor example it would point in the direction of the shaft)<br>
<b>normal</b>: a CSG.Vector3D direction vector somewhat perpendicular to axis; this
defines the &quot;12 o'clock&quot; orientation of the connection.
<br><br>
When connecting two connectors, the solid is transformed such that the <b>point</b>
properties will be identical, the <b>axis</b> properties will have the same direction
(or opposite direction if mirror == true), and the <b>normal</b>s match as much as possible.
<br><br>
Connectors can be connected by means of two methods:<br>
A CSG solid's <b>connectTo()</b> function transforms a solid such that two connectors
become connected.<br>
Alternatively we can use a connector's <b>getTransformationTo()</b> method to obtain
a transformation matrix which would connect the connectors. This can be used if we
need to apply the same transform to multiple solids.
<pre>
var cube1 = CSG.cube({radius: 10});
var cube2 = CSG.cube({radius: 4});
// define a connector on the center of one face of cube1
// The connector's axis points outwards and its normal points
// towards the positive z axis:
cube1.properties.myConnector = new CSG.Connector([10, 0, 0], [1, 0, 0], [0, 0, 1]);
// define a similar connector for cube 2:
cube2.properties.myConnector = new CSG.Connector([0, -4, 0], [0, -1, 0], [0, 0, 1]);
// do some random transformations on cube 1:
cube1 = cube1.rotateX(30);
cube1 = cube1.translate([3.1, 2, 0]);
// Now attach cube2 to cube 1:
cube2 = cube2.connectTo(
cube2.properties.myConnector,
cube1.properties.myConnector,
true, // mirror
0 // normalrotation
);
// Or alternatively:
var matrix = cube2.properties.myConnector.getTransformationTo(
cube1.properties.myConnector,
true, // mirror
0 // normalrotation
);
cube2 = cube2.transform(matrix);
var result = cube2.union(cube1);
</pre>
<h2>Determining the bounds of an object</h2> <h2>Determining the bounds of an object</h2>
The getBounds() function can be used to retrieve the bounding box of an object. The getBounds() function can be used to retrieve the bounding box of an object.
This can be useful if you want to align two objects to each other. getBounds() returns getBounds() returns
an array with two elements specifying the minimum x,y,z coordinate and the maximum x,y,z coordinate: an array with two elements specifying the minimum x,y,z coordinate and the maximum x,y,z coordinate:
<pre> <pre>

View file

@ -206,3 +206,277 @@ OpenJsCad.javaScriptToSolid = function(script) {
} }
return csg; return csg;
}; };
// this is a bit of a hack; doesn't properly supports urls that start with '/'
// but does handle relative urls containing ../
OpenJsCad.makeAbsoluteUrl = function(url, baseurl) {
if(!url.match(/^[a-z]+\:/i))
{
var basecomps = baseurl.split("/");
if(basecomps.length > 0)
{
basecomps.splice(basecomps.length - 1, 1);
}
var urlcomps = url.split("/");
var comps = basecomps.concat(urlcomps);
var comps2 = [];
comps.map(function(c) {
if(c == "..")
{
if(comps2.length > 0)
{
comps2.splice(comps2.length - 1, 1);
}
}
else
{
comps2.push(c);
}
});
url = "";
for(var i = 0; i < comps2.length; i++)
{
if(i > 0) url += "/";
url += comps2[i];
}
}
return url;
};
OpenJsCad.isChrome = function()
{
return (navigator.userAgent.search("Chrome") >= 0);
}
// callback: should be function(error, csg)
OpenJsCad.javaScriptToSolidASync = function(script, callback) {
var baselibraries = [
"csg.js",
"openjscad.js"
];
var baseurl = document.location + "";
var workerscript = "";
workerscript += script;
workerscript += "\n//// END OF USER SUPPLIED SCRIPT\n";
workerscript += "var _csg_libraries=" + JSON.stringify(baselibraries)+";\n";
workerscript += "var _csg_baseurl=" + JSON.stringify(baseurl)+";\n";
workerscript += "var _csg_makeAbsoluteURL=" + OpenJsCad.makeAbsoluteUrl.toString()+";\n";
workerscript += "if(typeof(libs) == 'function') _csg_libraries = _csg_libraries.concat(libs());\n";
workerscript += "_csg_libraries = _csg_libraries.map(function(l){return _csg_makeAbsoluteURL(l,_csg_baseurl);});\n";
//workerscript += "importScripts.call(null, _csg_libraries);\n";
workerscript += "_csg_libraries.map(function(l){importScripts(l)});\n";
workerscript += "self.addEventListener('message', function(e) {if(e.data && e.data.cmd == 'render'){";
workerscript += " if(typeof(main) != 'function') throw new Error('Your jscad file should contain a function main() which returns a CSG solid.');\n";
workerscript += " var csg = main(); self.postMessage({cmd: 'rendered', csg: csg});";
workerscript += "}},false);\n";
var blobURL = OpenJsCad.textToBlobUrl(workerscript);
if(!window.Worker) throw new Error("Your browser doesn't support Web Workers");
var worker = new Worker(blobURL);
worker.onmessage = function(e) {
if(e.data && e.data.cmd == 'rendered')
{
var csg = CSG.fromObject(e.data.csg);
callback(null, csg);
}
};
worker.onerror = function(e) {
var errtxt = "Error in line "+e.lineno+": "+e.message;
callback(errtxt, null);
};
worker.postMessage({
cmd: "render"
}); // Start the worker.
return worker;
};
OpenJsCad.textToBlobUrl = function(txt) {
var bb;
if(window.BlobBuilder) bb = new window.BlobBuilder()
else if(window.WebKitBlobBuilder) bb = new window.WebKitBlobBuilder()
else if(window.MozBlobBuilder) bb = new window.MozBlobBuilder()
else throw new Error("Your browser doesn't support BlobBuilder");
bb.append(txt);
var blob = bb.getBlob();
var blobURL;
if(window.URL) blobURL = window.URL.createObjectURL(blob)
else if(window.webkitURL) blobURL = window.webkitURL.createObjectURL(blob)
else throw new Error("Your browser doesn't support window.URL");
return blobURL;
};
OpenJsCad.revokeBlobUrl = function(url) {
if(window.URL) window.URL.revokeObjectURL(url)
else if(window.webkitURL) window.webkitURL.revokeObjectURL(url)
else throw new Error("Your browser doesn't support window.URL");
};
OpenJsCad.Processor = function(containerdiv, onchange) {
this.containerdiv = containerdiv;
this.onchange = onchange;
this.viewerdiv = null;
this.viewer = null;
this.viewerwidth = 800;
this.viewerheight = 600;
this.initialViewerDistance = 50;
this.processing = false;
this.solid = null;
this.validcsg = false;
this.hasstl = false;
this.worker = null;
this.createElements();
};
OpenJsCad.Processor.prototype = {
createElements: function() {
while(this.containerdiv.children.length > 0)
{
this.containerdiv.removeChild(0);
}
if(!OpenJsCad.isChrome() )
{
var div = document.createElement("div");
div.innerHTML = "Please note: OpenJsCad currently only runs reliably on Google Chrome!";
this.containerdiv.appendChild(div);
}
var viewerdiv = document.createElement("div");
viewerdiv.className = "viewer";
viewerdiv.style.width = this.viewerwidth + "px";
viewerdiv.style.height = this.viewerheight + "px";
viewerdiv.style.backgroundColor = "rgb(200,200,200)";
this.containerdiv.appendChild(viewerdiv);
this.viewerdiv = viewerdiv;
try
{
this.viewer = new OpenJsCad.Viewer(this.viewerdiv, this.viewerwidth, this.viewerheight, this.initialViewerDistance);
} catch (e) {
this.viewerdiv.innerHTML = e.toString();
}
this.errordiv = document.createElement("div");
this.errordiv.style.display = "none";
this.statusdiv = document.createElement("div");
this.statusdiv.style.width = this.viewerwidth + "px";
this.statusspan = document.createElement("span");
this.statusbuttons = document.createElement("div");
this.statusbuttons.style.float = "right";
this.statusdiv.appendChild(this.statusspan);
this.statusdiv.appendChild(this.statusbuttons);
this.abortbutton = document.createElement("button");
this.abortbutton.innerHTML = "Abort";
var that = this;
this.abortbutton.onclick = function(e) {
that.abort();
}
this.statusbuttons.appendChild(this.abortbutton);
this.generateStlButton = document.createElement("button");
this.generateStlButton.innerHTML = "Generate STL";
this.generateStlButton.onclick = function(e) {
that.generateStl();
}
this.statusbuttons.appendChild(this.generateStlButton);
this.downloadStlLink = document.createElement("a");
this.downloadStlLink.innerHTML = "Download STL";
this.statusbuttons.appendChild(this.downloadStlLink);
this.enableItems();
this.containerdiv.appendChild(this.statusdiv);
this.containerdiv.appendChild(this.errordiv);
this.clearViewer();
},
clearViewer: function() {
this.clearStl();
this.solid = new CSG();
if(this.viewer)
{
this.viewer.setCsg(this.solid);
}
this.validcsg = false;
this.enableItems();
},
abort: function() {
if(this.processing)
{
//todo: abort
this.processing=false;
this.statusspan.innerHTML = "Aborted.";
this.worker.terminate();
this.enableItems();
if(this.onchange) this.onchange();
}
},
enableItems: function() {
this.abortbutton.style.display = this.processing? "inline":"none";
this.generateStlButton.style.display = ((!this.hasstl)&&(this.validcsg))? "inline":"none";
this.downloadStlLink.style.display = this.hasstl? "inline":"none";
},
setError: function(txt) {
this.errordiv.innerHTML = txt;
this.errordiv.style.display = (txt == "")? "none":"block";
},
setJsCad: function(script) {
this.abort();
this.clearViewer();
this.setError("");
this.processing = true;
this.statusspan.innerHTML = "Processing, please wait...";
var that = this;
this.worker = OpenJsCad.javaScriptToSolidASync(script, function(err, csg) {
that.processing = false;
that.worker = null;
if(err)
{
that.setError(err);
that.statusspan.innerHTML = "Error.";
}
else
{
that.solid = csg;
if(that.viewer) that.viewer.setCsg(csg);
that.validcsg = true;
that.statusspan.innerHTML = "Ready.";
}
that.enableItems();
if(that.onchange) that.onchange();
});
this.enableItems();
if(this.onchange) this.onchange();
},
hasSolid: function() {
return this.validcsg;
},
isProcessing: function() {
return this.processing;
},
clearStl: function() {
if(this.hasstl)
{
this.hasstl = false;
OpenJsCad.revokeBlobUrl(this.stlBlobUrl);
this.stlBlobUrl = null;
this.enableItems();
if(this.onchange) this.onchange();
}
},
generateStl: function() {
this.clearStl();
if(this.validcsg)
{
var stltxt = this.solid.toStlString();
this.stlBlobUrl = OpenJsCad.textToBlobUrl(stltxt);
this.hasstl = true;
this.downloadStlLink.href = this.stlBlobUrl;
this.enableItems();
if(this.onchange) this.onchange();
}
},
};

View file

@ -8,9 +8,6 @@
body { body {
font: 14px/20px 'Helvetica Neue Light', HelveticaNeue-Light, 'Helvetica Neue', Helvetica, Arial, sans-serif; font: 14px/20px 'Helvetica Neue Light', HelveticaNeue-Light, 'Helvetica Neue', Helvetica, Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 10px;
} }
pre, code, textarea { pre, code, textarea {
@ -25,9 +22,6 @@ pre, textarea {
padding: 10px; padding: 10px;
width: 100%; width: 100%;
} }
textarea {
height: 200px;
}
textarea:focus { textarea:focus {
outline: none; outline: none;
} }
@ -55,90 +49,27 @@ textarea:focus {
} }
a { color: inherit; } a { color: inherit; }
.viewer { width: 200px; height: 200px; background: #EEE url(images.png); }
#combined .viewer { width: 150px; height: 150px; }
table { border-collapse: collapse; margin: 0 auto; }
td { padding: 5px; text-align: center; }
td code { background: none; border: none; color: inherit; }
canvas { cursor: move; }
#errdiv {
border: 2px solid red;
display: none;
margin: 5px;
}
#needchrome { canvas { cursor: move; }
display: none;
}
</style> </style>
<script> <script>
var gViewer=null;
var gSolid=new CSG();
var gCurrentFile = null; var gCurrentFile = null;
function isChrome() var gProcessor=null;
{
return (navigator.userAgent.search("Chrome") >= 0);
}
function onload() function onload()
{ {
var needchromediv = document.getElementById("needchrome");
if(needchromediv)
{
if(!isChrome())
{
needchromediv.style.display="block";
}
}
try try
{ {
var containerelement = document.getElementById("viewer"); gProcessor = new OpenJsCad.Processor(document.getElementById("viewer"));
gViewer = new OpenJsCad.Viewer(containerelement, 600, 600, 50);
setupDragDrop(); setupDragDrop();
} catch (e) { } catch (e) {
alert(e.toString()); alert(e.toString());
} }
} }
function updateSolid()
{
if(gViewer.supported())
{
var code=document.getElementById('code').value;
if(code != gSolidSource)
{
gSolidSource=code;
var errtxt = "";
var csg = new CSG();
try {
csg = OpenJsCad.javaScriptToSolid(code);
} catch (e) {
errtxt = 'Error: <code>' + e.toString().replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</code>';
}
gSolid = csg;
var errdiv=document.getElementById('error');
errdiv.innerHTML = errtxt;
errdiv.style.display = (errtxt == "")? "none":"block";
gViewer.setCsg(gSolid);
var stlarea = document.getElementById('stloutput');
stlarea.style.display = "none";
}
}
}
function getStl()
{
updateSolid();
var stl=gSolid.toStlString();
var stlarea = document.getElementById('stloutput');
stlarea.value=stl;
stlarea.style.display = "inline";
}
function setupDragDrop() function setupDragDrop()
{ {
// Check for the various File API support. // Check for the various File API support.
@ -206,8 +137,6 @@ function fileChanged()
function parseFile() function parseFile()
{ {
setErrorText("");
gSolid=new CSG();
if(gCurrentFile) if(gCurrentFile)
{ {
var reader = new FileReader(); var reader = new FileReader();
@ -215,78 +144,45 @@ function parseFile()
var txt = evt.target.result; var txt = evt.target.result;
}; };
reader.onloadend = function(evt) { reader.onloadend = function(evt) {
try
{
if (evt.target.readyState == FileReader.DONE) if (evt.target.readyState == FileReader.DONE)
{ {
var jscadscript = evt.target.result; var jscadscript = evt.target.result;
if(jscadscript == "") if(jscadscript == "")
{ {
alert("Could not read file. This may be due to security restrictions: in Google Chrome the File API only works if this html page is on a web server. Accessing this page from your hard drive does not work."); throw new Error("Could not read file. This may be due to security restrictions: in Google Chrome the File API only works if this html page is on a web server. Accessing this page from your hard drive does not work.");
gViewer.setCsg(gSolid); if(gProcessor) gProcessor.clearViewer();
} }
else else
{ {
parseJscad(jscadscript); if(gProcessor)
{
gProcessor.setJsCad(jscadscript);
}
} }
} }
else else
{ {
alert("Failed to read file"); throw new Error("Failed to read file");
gViewer.setCsg(gSolid); if(gProcessor) gProcessor.clearViewer();
}
}
catch(e)
{
alert(e.toString());
} }
}; };
reader.readAsText(gCurrentFile, "UTF-8"); reader.readAsText(gCurrentFile, "UTF-8");
} }
} }
function parseJscad(script)
{
try
{
var csg = OpenJsCad.javaScriptToSolid(script);
gSolid = csg;
gViewer.setCsg(gSolid);
setErrorText("");
}
catch(e)
{
gSolid = new CSG();
gViewer.setCsg(gSolid);
setErrorText(e.toString());
}
enableItems();
var stlarea = document.getElementById('stloutput');
stlarea.style.display = "none";
}
function enableItems()
{
var hassolid = (gSolid.polygons.length > 0);
document.getElementById('getstlbutton').style.display = hassolid? "inline":"none";
}
function setErrorText(errtxt)
{
var errdiv = document.getElementById("errdiv");
errdiv.style.display = (errtxt == "")? "none":"block";
errdiv.innerHTML = errtxt;
}
function getStl()
{
var stl=gSolid.toStlString();
var stlarea = document.getElementById('stloutput');
stlarea.value=stl;
stlarea.style.display = "inline";
}
</script> </script>
<title>OpenJsCad parser</title> <title>OpenJsCad parser</title>
<body onload="onload()"> <body onload="onload()">
<h1>OpenJsCad parser</h1> <h1>OpenJsCad parser</h1>
<div id="needchrome">Please note: OpenJsCad currently only runs reliably on Google Chrome!</div> <div id="viewer"></div>
<div id="viewer" class="viewer" style="background-image:none;width:600px;height:600px;"></div>
<br> <br>
<div id="errdiv"></div>
<div id="filedropzone"> <div id="filedropzone">
<div id="filedropzone_empty">Drop your .jscad file here</div> <div id="filedropzone_empty">Drop your .jscad file here</div>
<div id="filedropzone_filled"> <div id="filedropzone_filled">
@ -297,23 +193,23 @@ function getStl()
</div> </div>
</div> </div>
</div> </div>
<textarea id="stloutput" readonly style="width: 80%; height: 100px; display: none;"></textarea><br>
<br> <br>
<h2>Instructions:</h2> <h2>Instructions:</h2>
Create a new file in your favorite text editor. To get started enter the following text: Create a new file in your favorite text editor. To get started enter the following text:
<br> <br>
<pre> <pre>function main() {
var cube = CSG.roundedCube({radius: 10, roundradius: 2, resolution: 16}); var cube = CSG.roundedCube({radius: 10, roundradius: 2, resolution: 16});
var sphere = CSG.sphere({radius: 10, resolution: 16}).translate([5, 5, 5]); var sphere = CSG.sphere({radius: 10, resolution: 16}).translate([5, 5, 5]);
return cube.union(sphere); return cube.union(sphere);
}
</pre> </pre>
Save this to a file on your desktop with .jscad extension. Then drag &amp; drop the file from your desktop Save this to a file on your desktop with .jscad extension. Then drag &amp; drop the file from your desktop
to the box above (marked with 'drop your .jscad file here).<br><br> to the box above (marked with 'drop your .jscad file here).<br><br>
The 3d model should now appear. You can continue to make changes to your .jscad file. Just press Reload The 3d model should now appear. You can continue to make changes to your .jscad file. Just press Reload
to parse the changes, you do not need to drag &amp; drop the file again. to parse the changes, you do not need to drag &amp; drop the file again.
<br><br> <br><br>
When finished press Get STL to generate the STL file. Copy &amp; paste the STL text to a file with When finished press Generate STL to generate the STL file. Then click Save STL and save it to
.stl extension. a file with .stl extension.
<br><br> <br><br>
For more information about OpenJsCad see the <a href="index.html">introduction</a>. For more information about OpenJsCad see the <a href="index.html">introduction</a>.
</body> </body>