User editable parameters; debugging support; gears demo

This commit is contained in:
Joost Nieuwenhuijse 2012-02-14 18:04:17 +01:00
parent 1b89514039
commit 3544b92a1a
6 changed files with 749 additions and 108 deletions

13
csg.js
View file

@ -144,6 +144,15 @@ CSG.prototype = {
return result; return result;
}, },
// Like union, but when we know that the two solids are not intersecting
// Do not use if you are not completely sure that the solids do not intersect!
unionForNonIntersecting: function(csg) {
var newpolygons = this.polygons.concat(csg.polygons);
var result = CSG.fromPolygons(newpolygons);
result.properties = this.properties._merge(csg.properties);
return result;
},
// Return a new CSG solid representing space in this solid but not in the // Return a new CSG solid representing space in this solid but not in the
// solid `csg`. Neither this solid nor the solid `csg` are modified. // solid `csg`. Neither this solid nor the solid `csg` are modified.
// //
@ -2258,6 +2267,10 @@ CSG.Vector2D = function(x, y) {
} }
}; };
CSG.Vector2D.fromAngle = function(radians) {
return new CSG.Vector2D(Math.cos(radians), Math.sin(radians));
};
CSG.Vector2D.prototype = { CSG.Vector2D.prototype = {
// extend to a 3D vector by adding a z coordinate: // extend to a 3D vector by adding a z coordinate:
toVector3D: function(z) { toVector3D: function(z) {

187
gearsdemo.html Normal file
View file

@ -0,0 +1,187 @@
<!DOCTYPE html>
<html><head>
<script src="lightgl.js"></script>
<script src="csg.js"></script>
<script src="openjscad.js"></script>
<style>
body {
font: 14px/20px 'Helvetica Neue Light', HelveticaNeue-Light, 'Helvetica Neue', Helvetica, Arial, sans-serif;
max-width: 820px;
margin: 0 auto;
padding: 10px;
}
pre, code, textarea {
font: 12px/20px Monaco, monospace;
border: 1px solid #CCC;
border-radius: 3px;
background: #F9F9F9;
padding: 0 3px;
color: #555;
}
pre, textarea {
padding: 10px;
width: 100%;
}
textarea {
height: 200px;
}
textarea:focus {
outline: none;
}
canvas { cursor: move; }
</style>
<link rel="stylesheet" href="openjscad.css" type="text/css">
<script>
var gProcessor=null;
// Show all exceptions to the user:
OpenJsCad.AlertUserOfUncaughtExceptions();
function onload()
{
gProcessor = new OpenJsCad.Processor(document.getElementById("viewer"));
updateSolid();
}
function updateSolid()
{
gProcessor.setJsCad(document.getElementById('code').value);
}
</script>
<title>OpenJsCad demo: involute gears</title>
</head>
<body onload="onload()">
<h1>OpenJsCad demo: involute gears</h1>
<div id="viewer"></div>
<h2>Source code</h2>
Below is the OpenJsCad script for this demo. To build your own models, create a .jscad script
and use the <a href="processfile.html"><b>OpenJsCad parser</b></a>. For more information see the
<a href="index.html">OpenJsCad documentation</a>.
<br><br>
<textarea id="code">
// Here we define the user editable parameters:
function getParameterDefinitions() {
return [
{ name: 'numTeeth', caption: 'Number of teeth:', type: 'int', default: 10 },
{ name: 'circularPitch', caption: 'Circular pitch:', type: 'float', default: 5 },
{ name: 'pressureAngle', caption: 'Pressure angle:', type: 'float', default: 20 },
{ name: 'clearance', caption: 'Clearance:', type: 'float', default: 0 },
{ name: 'thickness', caption: 'Thickness:', type: 'float', default: 5 },
{ name: 'centerholeradius', caption: 'Radius of center hole (0 for no hole):', type: 'float', default: 2 },
];
}
// Main entry point; here we construct our solid:
function main(params)
{
var gear = involuteGear(
params.numTeeth,
params.circularPitch,
params.pressureAngle,
params.clearance,
params.thickness
);
if(params.centerholeradius > 0)
{
var centerhole = CSG.cylinder({start: [0,0,-params.thickness], end: [0,0,params.thickness], radius: params.centerholeradius, resolution: 16});
gear = gear.subtract(centerhole);
}
return gear;
}
/*
For gear terminology see:
http://www.astronomiainumbria.org/advanced_internet_files/meccanica/easyweb.easynet.co.uk/_chrish/geardata.htm
Algorithm based on:
http://www.cartertools.com/involute.html
circularPitch: The distance between adjacent teeth measured at the pitch circle
*/
function involuteGear(numTeeth, circularPitch, pressureAngle, clearance, thickness)
{
// default values:
if(arguments.length < 3) pressureAngle = 20;
if(arguments.length < 4) clearance = 0;
if(arguments.length < 4) thickness = 1;
var addendum = circularPitch / Math.PI;
var dedendum = addendum + clearance;
// radiuses of the 4 circles:
var pitchRadius = numTeeth * circularPitch / (2 * Math.PI);
var baseRadius = pitchRadius * Math.cos(Math.PI * pressureAngle / 180);
var outerRadius = pitchRadius + addendum;
var rootRadius = pitchRadius - dedendum;
var maxtanlength = Math.sqrt(outerRadius*outerRadius - baseRadius*baseRadius);
var maxangle = maxtanlength / baseRadius;
var tl_at_pitchcircle = Math.sqrt(pitchRadius*pitchRadius - baseRadius*baseRadius);
var angle_at_pitchcircle = tl_at_pitchcircle / baseRadius;
var diffangle = angle_at_pitchcircle - Math.atan(angle_at_pitchcircle);
var angularToothWidthAtBase = Math.PI / numTeeth + 2*diffangle;
// build a single 2d tooth in the 'points' array:
var resolution = 5;
var points = [new CSG.Vector2D(0,0)];
for(var i = 0; i <= resolution; i++)
{
// first side of the tooth:
var angle = maxangle * i / resolution;
var tanlength = angle * baseRadius;
var radvector = CSG.Vector2D.fromAngle(angle);
var tanvector = radvector.normal();
var p = radvector.times(baseRadius).plus(tanvector.times(tanlength));
points[i+1] = p;
// opposite side of the tooth:
radvector = CSG.Vector2D.fromAngle(angularToothWidthAtBase - angle);
tanvector = radvector.normal().negated();
p = radvector.times(baseRadius).plus(tanvector.times(tanlength));
points[2 * resolution + 2 - i] = p;
}
// create the polygon and extrude into 3D:
var tooth3d = new CSG.Polygon2D(points).extrude({offset: [0, 0, thickness]});
var allteeth = new CSG();
for(var i = 0; i < numTeeth; i++)
{
var angle = i*360/numTeeth;
var rotatedtooth = tooth3d.rotateZ(angle);
allteeth = allteeth.unionForNonIntersecting(rotatedtooth);
}
// build the root circle:
points = [];
var toothAngle = 2 * Math.PI / numTeeth;
var toothCenterAngle = 0.5 * angularToothWidthAtBase;
for(var i = 0; i < numTeeth; i++)
{
var angle = toothCenterAngle + i * toothAngle;
var p = CSG.Vector2D.fromAngle(angle).times(rootRadius);
points.push(p);
}
// create the polygon and extrude into 3D:
var rootcircle = new CSG.Polygon2D(points).extrude({offset: [0, 0, thickness]});
var result = rootcircle.union(allteeth);
// center at origin:
result = result.translate([0, 0, -thickness/2]);
return result;
}
</textarea><br>
<input type="submit" value="Update" onclick="updateSolid(); return false;">
<br><br>
</body>
</html>

View file

@ -35,6 +35,8 @@ textarea:focus {
canvas { cursor: move; } canvas { cursor: move; }
</style> </style>
<link rel="stylesheet" href="openjscad.css" type="text/css">
<script> <script>
var gProcessor=null; var gProcessor=null;
@ -67,7 +69,7 @@ and use the <a href="processfile.html"><b>OpenJsCad parser</b></a>.
<textarea id="code"> <textarea id="code">
function main() function main()
{ {
var resolution = 16; // increase to get smoother corners (will get slow!) var resolution = 24; // 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 });
@ -83,7 +85,7 @@ function main()
</textarea><br> </textarea><br>
<input type="submit" value="Update" onclick="updateSolid(); return false;"> <input type="submit" value="Update" onclick="updateSolid(); return false;">
<br> <br>
<h1>About</h1> <h2>About</h2>
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>
@ -97,6 +99,23 @@ The code should always contain a main() function, returning a CSG solid.
To build your own models, 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, click on Generate STL and save the result <a href="processfile.html">OpenJsCad parser</a>. When finished, click on Generate STL and save the result
in an .stl file, ready to be printed on your 3d printer. in an .stl file, ready to be printed on your 3d printer.
<h2>Why use OpenJsCad?</h2>
Some of the benefits:
<ul>
<li>Runs in your browser, no need to install any software.</li>
<li>You can create parametric models with user editable parameters: parameters can be changed in the browser window,
without the need to edit the source script. See the <a href="gearsdemo.html">Gears demo</a> for example!</li>
<li>JavaScript is an extremely flexible language, supporting dynamic arrays and object oriented programming.</li>
<li>Solids are stored in variables. This allows for example conditional cloning of objects, something which is nearly impossible in OpenSCAD.</li>
<li>Properties and Connectors (see below) make it very easy to attach objects to each other at predetermined
points, even if you don't know the actual orientation or size.</li>
</ul>
<h2>Viewer navigation</h2>
Click and drag to rotate the model around the origin.<br>
Shift+Drag moves the model around.<br>
Alt+drag zooms (by changing the distance between camera and model).
<h2>Demos</h2>
Try the <a href="gearsdemo.html">Gears demo</a>!
<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,
@ -109,11 +128,6 @@ know how to modify it as well.<br><br>
To contribute go to <a href="https://github.com/joostn/csg.js">CSG.js at GitHub</a>, To contribute go to <a href="https://github.com/joostn/csg.js">CSG.js at GitHub</a>,
<a href="http://help.github.com/fork-a-repo/">create your own fork</a> and <a href="http://help.github.com/fork-a-repo/">create your own fork</a> and
<a href="http://help.github.com/send-pull-requests/">send me a pull request</a>. <a href="http://help.github.com/send-pull-requests/">send me a pull request</a>.
<h2>Viewer navigation</h2>
Click and drag to rotate the model around the origin.<br>
Shift+Drag moves the model around.<br>
Alt+drag zooms (by changing the distance between camera and model).
<h2>Primitive solids</h2> <h2>Primitive solids</h2>
Currently the following solids are supported. The parameters are passed in an object; most Currently the following solids are supported. The parameters are passed in an object; most
parameters are optional. 3D vectors can be passed in an array. If a scalar is passed parameters are optional. 3D vectors can be passed in an array. If a scalar is passed
@ -422,5 +436,94 @@ var extruded=shape2d.extrude({
}); });
</pre> </pre>
<h2>Interactive parametric models</h2>
It is possible to make certain parameters
editable in the browser. This allows users not familiar with JavaScript to create customized STL files.
<br><br>
To do so, add a function getParameterDefinitions() to your .jscad source. This function should return
an array with parameter definitions. Currently 4 parameters types are supported: float, int, text and choice.
The user edited values of the parameters will be supplied as an object parameter to the main() function of your .jscad file.
<br><br>
A float, int or text parameter is created by including the following object in the array returned by getParameterDefinitions():
<pre>{
name: 'width',
type: 'float', // or 'text' or 'int'
default: 1.23, // optional, sets the initial value
caption: 'Width of the thingy:', // optional, displayed left of the input field
// if omitted, the 'name' is displayed (i.e. 'width')
}</pre>
A 'choice' parameter is created using the following object:
<pre>{
name: 'shape',
type: 'choice',
values: ["TRI", "SQU", "CIR"], // these are the values that will be supplied to your script
captions: ["Triangle", "Square", "Circle"], // optional, these values are shown in the listbox
// if omitted, the items in the 'values' array are used
caption: 'Shape:', // optional, displayed left of the input field
default: "SQU", // optional, default selected value
// if omitted, the first item is selected by default
}</pre>
To use the values add an argument to your main() function. This argument will be supplied an object
with the user edited parameter values:
<pre>
function main(params)
{
// custom error checking:
if(params.width <= 0) throw new Error("Width should be positive!");
if(params.shape == "TRI")
{
// do something
}
}
</pre>
A complete example:
<pre>
function getParameterDefinitions() {
return [
{
name: 'width',
type: 'float',
default: 10,
caption: "Width of the cube:",
},
{
name: 'height',
type: 'float',
default: 14,
caption: "Height of the cube:",
},
{
name: 'depth',
type: 'float',
default: 7,
caption: "Depth of the cube:",
},
{
name: 'rounded',
type: 'choice',
caption: 'Round the corners?',
values: [0, 1],
captions: ["No thanks", "Yes please"],
default: 1,
},
];
}
function main(params) {
var result;
if(params.rounded == 1)
{
result = CSG.roundedCube({radius: [params.width, params.height, params.depth], roundradius: 2, resolution: 32});
}
else
{
result = CSG.cube({radius: [params.width, params.height, params.depth]});
}
return result;
}
</pre>
</body> </body>
</html> </html>

41
openjscad.css Normal file
View file

@ -0,0 +1,41 @@
#filedropzone {
border: 2px dashed #bbb;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
padding: 15px;
color: black;
}
#filedropzone_empty {
text-align: center;
color: #bbb;
}
#filedropzone_filled {
color: black;
display: none;
}
#filebuttons {
float: right;
}
canvas { cursor: move; }
.parametersdiv {
border: 1px solid rgb(200,200,200);
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
padding: 10px;
}
.parametersdiv .header {
font-weight: bold;
}
.statusdiv {
width: 800px;
margin-bottom: 10px;
}

View file

@ -197,16 +197,6 @@ OpenJsCad.Viewer.csgToMesh = function(csg) {
return mesh; return mesh;
}; };
// parse javascript into solid:
OpenJsCad.javaScriptToSolid = function(script) {
var csg = new Function(script)();
if( (typeof(csg) != "object") || (!('polygons' in csg)))
{
throw new Error("Your javascript code should return a CSG object. Try for example: return CSG.cube();");
}
return csg;
};
// this is a bit of a hack; doesn't properly supports urls that start with '/' // this is a bit of a hack; doesn't properly supports urls that start with '/'
// but does handle relative urls containing ../ // but does handle relative urls containing ../
OpenJsCad.makeAbsoluteUrl = function(url, baseurl) { OpenJsCad.makeAbsoluteUrl = function(url, baseurl) {
@ -246,10 +236,48 @@ OpenJsCad.makeAbsoluteUrl = function(url, baseurl) {
OpenJsCad.isChrome = function() OpenJsCad.isChrome = function()
{ {
return (navigator.userAgent.search("Chrome") >= 0); return (navigator.userAgent.search("Chrome") >= 0);
} };
// This is called from within the web worker. Execute the main() function of the supplied script
// and post a message to the calling thread when finished
OpenJsCad.runMainInWorker = function(mainParameters)
{
try
{
if(typeof(main) != 'function') throw new Error('Your jscad file should contain a function main() which returns a CSG solid.');
var csg = main(mainParameters);
if( (typeof(csg) != "object") || (!(csg instanceof CSG)) )
{
throw new Error("Your main() function should return a CSG solid.");
}
self.postMessage({cmd: 'rendered', csg: csg});
}
catch(e)
{
var errtxt = e.stack;
self.postMessage({cmd: 'error', err: errtxt});
}
};
OpenJsCad.javaScriptToSolidSync = function(script, mainParameters, callback) {
var workerscript = "";
workerscript += script;
workerscript += "\n\n\n\n\n\n\n/* -------------------------------------------------------------------------\n";
workerscript += "OpenJsCad debugging\n\nAssuming you are running Chrome:\nF10 steps over an instruction\nF11 steps into an instruction\n";
workerscript += "F8 continues running\nPress the (||) button at the bottom to enable pausing whenever an error occurs\n";
workerscript += "Click on a line number to set or clear a breakpoint\n";
workerscript += "For more information see: http://code.google.com/chrome/devtools/docs/overview.html\n\n";
workerscript += "------------------------------------------------------------------------- */\n";
workerscript += "\n\n// Now press F11 twice to enter your main() function:\n\n";
workerscript += "debugger;\n";
workerscript += "return main("+JSON.stringify(mainParameters)+");";
var f = new Function(workerscript);
var csg = f();
return csg;
};
// callback: should be function(error, csg) // callback: should be function(error, csg)
OpenJsCad.javaScriptToSolidASync = function(script, callback) { OpenJsCad.javaScriptToSolidASync = function(script, mainParameters, callback) {
var baselibraries = [ var baselibraries = [
"csg.js", "csg.js",
"openjscad.js" "openjscad.js"
@ -257,29 +285,38 @@ OpenJsCad.javaScriptToSolidASync = function(script, callback) {
var baseurl = document.location + ""; var baseurl = document.location + "";
var workerscript = ""; var workerscript = "";
workerscript += script; workerscript += script;
workerscript += "\n//// END OF USER SUPPLIED SCRIPT\n"; workerscript += "\n\n\n\n//// The following code is added by OpenJsCad:\n";
workerscript += "var _csg_libraries=" + JSON.stringify(baselibraries)+";\n"; workerscript += "var _csg_libraries=" + JSON.stringify(baselibraries)+";\n";
workerscript += "var _csg_baseurl=" + JSON.stringify(baseurl)+";\n"; workerscript += "var _csg_baseurl=" + JSON.stringify(baseurl)+";\n";
workerscript += "var _csg_makeAbsoluteURL=" + OpenJsCad.makeAbsoluteUrl.toString()+";\n"; workerscript += "var _csg_makeAbsoluteURL=" + OpenJsCad.makeAbsoluteUrl.toString()+";\n";
workerscript += "if(typeof(libs) == 'function') _csg_libraries = _csg_libraries.concat(libs());\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 += "_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 += "_csg_libraries.map(function(l){importScripts(l)});\n";
workerscript += "self.addEventListener('message', function(e) {if(e.data && e.data.cmd == 'render'){"; 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 += " OpenJsCad.runMainInWorker("+JSON.stringify(mainParameters)+");";
workerscript += " var csg = main(); self.postMessage({cmd: 'rendered', csg: csg});"; // 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; try {csg = main("+JSON.stringify(mainParameters)+"); self.postMessage({cmd: 'rendered', csg: csg});}";
// workerscript += " catch(e) {var errtxt = e.stack; self.postMessage({cmd: 'error', err: errtxt});}";
workerscript += "}},false);\n"; workerscript += "}},false);\n";
var blobURL = OpenJsCad.textToBlobUrl(workerscript); var blobURL = OpenJsCad.textToBlobUrl(workerscript);
if(!window.Worker) throw new Error("Your browser doesn't support Web Workers"); if(!window.Worker) throw new Error("Your browser doesn't support Web Workers. Please try the Chrome browser instead.");
var worker = new Worker(blobURL); var worker = new Worker(blobURL);
worker.onmessage = function(e) { worker.onmessage = function(e) {
if(e.data && e.data.cmd == 'rendered') if(e.data)
{
if(e.data.cmd == 'rendered')
{ {
var csg = CSG.fromObject(e.data.csg); var csg = CSG.fromObject(e.data.csg);
callback(null, csg); callback(null, csg);
} }
else if(e.data.cmd == "error")
{
// var errtxt = "Error in line "+e.data.err.lineno+": "+e.data.err.message;
callback(e.data.err, null);
}
}
}; };
worker.onerror = function(e) { worker.onerror = function(e) {
var errtxt = "Error in line "+e.lineno+": "+e.message; var errtxt = "Error in line "+e.lineno+": "+e.message;
@ -313,36 +350,20 @@ OpenJsCad.revokeBlobUrl = function(url) {
else throw new Error("Your browser doesn't support window.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.FileSystemApiErrorHandler = function(fileError, operation) { OpenJsCad.FileSystemApiErrorHandler = function(fileError, operation) {
var errormap = { var errormap = {
1: NOT_FOUND_ERR, 1: 'NOT_FOUND_ERR',
2: SECURITY_ERR, 2: 'SECURITY_ERR',
3: ABORT_ERR, 3: 'ABORT_ERR',
4: NOT_READABLE_ERR, 4: 'NOT_READABLE_ERR',
5: ENCODING_ERR, 5: 'ENCODING_ERR',
6: NO_MODIFICATION_ALLOWED_ERR, 6: 'NO_MODIFICATION_ALLOWED_ERR',
7: INVALID_STATE_ERR, 7: 'INVALID_STATE_ERR',
8: SYNTAX_ERR, 8: 'SYNTAX_ERR',
9: INVALID_MODIFICATION_ERR, 9: 'INVALID_MODIFICATION_ERR',
10: QUOTA_EXCEEDED_ERR, 10: 'QUOTA_EXCEEDED_ERR',
11: TYPE_MISMATCH_ERR, 11: 'TYPE_MISMATCH_ERR',
12: PATH_EXISTS_ERR, 12: 'PATH_EXISTS_ERR',
}; };
var errname; var errname;
if(fileError.code in errormap) if(fileError.code in errormap)
@ -364,6 +385,55 @@ OpenJsCad.AlertUserOfUncaughtExceptions = function() {
}; };
}; };
// parse the jscad script to get the parameter definitions
OpenJsCad.getParamDefinitions = function(script) {
var scriptisvalid = true;
try
{
// first try to execute the script itself
// this will catch any syntax errors
var f = new Function(script);
f();
}
catch(e) {
scriptisvalid = false;
}
var params = [];
if(scriptisvalid)
{
var script1 = "if(typeof(getParameterDefinitions) == 'function') {return getParameterDefinitions();} else {return [];} ";
script1 += script;
var f = new Function(script1);
params = f();
if( (typeof(params) != "object") || (typeof(params.length) != "number") )
{
throw new Error("The getParameterDefinitions() function should return an array with the parameter definitions");
}
}
return params;
};
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.paramDefinitions = [];
this.paramControls = [];
this.script = null;
this.hasError = false;
this.debugging = false;
this.createElements();
};
OpenJsCad.Processor.prototype = { OpenJsCad.Processor.prototype = {
createElements: function() { createElements: function() {
while(this.containerdiv.children.length > 0) while(this.containerdiv.children.length > 0)
@ -390,9 +460,11 @@ OpenJsCad.Processor.prototype = {
this.viewerdiv.innerHTML = e.toString(); this.viewerdiv.innerHTML = e.toString();
} }
this.errordiv = document.createElement("div"); this.errordiv = document.createElement("div");
this.errordiv.style.display = "none"; this.errorpre = document.createElement("pre");
this.errordiv.appendChild(this.errorpre);
this.statusdiv = document.createElement("div"); this.statusdiv = document.createElement("div");
this.statusdiv.style.width = this.viewerwidth + "px"; this.statusdiv.className = "statusdiv";
//this.statusdiv.style.width = this.viewerwidth + "px";
this.statusspan = document.createElement("span"); this.statusspan = document.createElement("span");
this.statusbuttons = document.createElement("div"); this.statusbuttons = document.createElement("div");
this.statusbuttons.style.float = "right"; this.statusbuttons.style.float = "right";
@ -403,20 +475,36 @@ OpenJsCad.Processor.prototype = {
var that = this; var that = this;
this.abortbutton.onclick = function(e) { this.abortbutton.onclick = function(e) {
that.abort(); that.abort();
} };
this.statusbuttons.appendChild(this.abortbutton); this.statusbuttons.appendChild(this.abortbutton);
this.generateStlButton = document.createElement("button"); this.generateStlButton = document.createElement("button");
this.generateStlButton.innerHTML = "Generate STL"; this.generateStlButton.innerHTML = "Generate STL";
this.generateStlButton.onclick = function(e) { this.generateStlButton.onclick = function(e) {
that.generateStl(); that.generateStl();
} };
this.statusbuttons.appendChild(this.generateStlButton); this.statusbuttons.appendChild(this.generateStlButton);
this.downloadStlLink = document.createElement("a"); this.downloadStlLink = document.createElement("a");
this.downloadStlLink.innerHTML = "Download STL"; this.downloadStlLink.innerHTML = "Download STL";
this.statusbuttons.appendChild(this.downloadStlLink); this.statusbuttons.appendChild(this.downloadStlLink);
this.parametersdiv = document.createElement("div");
this.parametersdiv.className = "parametersdiv";
var headerdiv = document.createElement("div");
headerdiv.innerText = "Parameters:";
headerdiv.className = "header";
this.parametersdiv.appendChild(headerdiv);
this.parameterstable = document.createElement("table");
this.parameterstable.className = "parameterstable";
this.parametersdiv.appendChild(this.parameterstable);
var parseParametersButton = document.createElement("button");
parseParametersButton.innerHTML = "Update";
parseParametersButton.onclick = function(e) {
that.rebuildSolid();
};
this.parametersdiv.appendChild(parseParametersButton);
this.enableItems(); this.enableItems();
this.containerdiv.appendChild(this.statusdiv); this.containerdiv.appendChild(this.statusdiv);
this.containerdiv.appendChild(this.errordiv); this.containerdiv.appendChild(this.errordiv);
this.containerdiv.appendChild(this.parametersdiv);
this.clearViewer(); this.clearViewer();
}, },
@ -447,11 +535,19 @@ OpenJsCad.Processor.prototype = {
this.abortbutton.style.display = this.processing? "inline":"none"; this.abortbutton.style.display = this.processing? "inline":"none";
this.generateStlButton.style.display = ((!this.hasstl)&&(this.validcsg))? "inline":"none"; this.generateStlButton.style.display = ((!this.hasstl)&&(this.validcsg))? "inline":"none";
this.downloadStlLink.style.display = this.hasstl? "inline":"none"; this.downloadStlLink.style.display = this.hasstl? "inline":"none";
this.parametersdiv.style.display = (this.paramControls.length > 0)? "block":"none";
this.errordiv.style.display = this.hasError? "block":"none";
this.statusdiv.style.display = this.hasError? "none":"block";
}, },
setError: function(txt) { setError: function(txt) {
this.errordiv.innerHTML = txt; this.hasError = (txt != "");
this.errordiv.style.display = (txt == "")? "none":"block"; this.errorpre.innerText = txt;
this.enableItems();
},
setDebugging: function(debugging) {
this.debugging = debugging;
}, },
// script: javascript code // script: javascript code
@ -461,12 +557,110 @@ OpenJsCad.Processor.prototype = {
filename = filename.replace(/\.jscad$/i, ""); filename = filename.replace(/\.jscad$/i, "");
this.abort(); this.abort();
this.clearViewer(); this.clearViewer();
this.paramDefinitions = [];
this.paramControls = [];
this.script = null;
this.setError(""); this.setError("");
var scripthaserrors = false;
try
{
this.paramDefinitions = OpenJsCad.getParamDefinitions(script);
this.createParamControls();
}
catch(e)
{
this.setError(e.toString());
this.statusspan.innerHTML = "Error.";
scripthaserrors = true;
}
if(!scripthaserrors)
{
this.script = script;
this.filename = filename;
this.rebuildSolid();
}
else
{
this.enableItems();
if(this.onchange) this.onchange();
}
},
getParamValues: function()
{
var paramValues = {};
for(var i = 0; i < this.paramDefinitions.length; i++)
{
var paramdef = this.paramDefinitions[i];
var type = "text";
if('type' in paramdef)
{
type = paramdef.type;
}
var control = this.paramControls[i];
var value;
if( (type == "text") || (type == "float") || (type == "int") )
{
value = control.value;
if( (type == "float") || (type == "int") )
{
var isnumber = !isNaN(parseFloat(value)) && isFinite(value);
if(!isnumber)
{
throw new Error("Not a number: "+value);
}
if(type == "int")
{
value = parseInt(value);
}
else
{
value = parseFloat(value);
}
}
}
else if(type == "choice")
{
value = control.options[control.selectedIndex].value;
}
paramValues[paramdef.name] = value;
}
return paramValues;
},
rebuildSolid: function()
{
this.abort();
this.setError("");
this.clearViewer();
this.processing = true; this.processing = true;
this.statusspan.innerHTML = "Processing, please wait..."; this.statusspan.innerHTML = "Processing, please wait...";
this.filename = filename; this.enableItems();
var that = this; var that = this;
this.worker = OpenJsCad.javaScriptToSolidASync(script, function(err, csg) { var paramValues = this.getParamValues();
if(this.debugging)
{
try
{
var csg = OpenJsCad.javaScriptToSolidSync(this.script, paramValues);
that.processing = false;
that.solid = csg;
if(that.viewer) that.viewer.setCsg(csg);
that.validcsg = true;
that.statusspan.innerHTML = "Ready.";
}
catch(e)
{
that.processing = false;
that.setError(e.toString());
that.statusspan.innerHTML = "Error.";
}
that.enableItems();
if(that.onchange) that.onchange();
}
else
{
this.worker = OpenJsCad.javaScriptToSolidASync(this.script, paramValues, function(err, csg) {
that.processing = false; that.processing = false;
that.worker = null; that.worker = null;
if(err) if(err)
@ -484,8 +678,7 @@ OpenJsCad.Processor.prototype = {
that.enableItems(); that.enableItems();
if(that.onchange) that.onchange(); if(that.onchange) that.onchange();
}); });
this.enableItems(); }
if(this.onchange) this.onchange();
}, },
hasSolid: function() { hasSolid: function() {
@ -496,6 +689,7 @@ OpenJsCad.Processor.prototype = {
return this.processing; return this.processing;
}, },
/*
clearStl1: function() { clearStl1: function() {
if(this.hasstl) if(this.hasstl)
{ {
@ -519,6 +713,7 @@ OpenJsCad.Processor.prototype = {
if(this.onchange) this.onchange(); if(this.onchange) this.onchange();
} }
}, },
*/
clearStl: function() { clearStl: function() {
if(this.hasstl) if(this.hasstl)
@ -543,14 +738,14 @@ OpenJsCad.Processor.prototype = {
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder; window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
if(!window.requestFileSystem) if(!window.requestFileSystem)
{ {
throw new Error("Your browser does not support the HTML5 FileSystem API"); throw new Error("Your browser does not support the HTML5 FileSystem API. Please try the Chrome browser instead.");
} }
if(!window.BlobBuilder) if(!window.BlobBuilder)
{ {
throw new Error("Your browser does not support the HTML5 BlobBuilder API"); throw new Error("Your browser does not support the HTML5 BlobBuilder API. Please try the Chrome browser instead.");
} }
// create a random directory name: // create a random directory name:
var dirname = "OpenJsCadStlOutput_"+parseInt(Math.random()*1000000000, 10)+".stl"; var dirname = "OpenJsCadStlOutput1_"+parseInt(Math.random()*1000000000, 10)+".stl";
var filename = this.filename+".stl"; var filename = this.filename+".stl";
var that = this; var that = this;
window.requestFileSystem(TEMPORARY, 20*1024*1024, function(fs){ window.requestFileSystem(TEMPORARY, 20*1024*1024, function(fs){
@ -586,5 +781,112 @@ OpenJsCad.Processor.prototype = {
} }
}, },
createParamControls: function() {
this.parameterstable.innerHTML = "";
this.paramControls = [];
var paramControls = [];
var tablerows = [];
for(var i = 0; i < this.paramDefinitions.length; i++)
{
var errorprefix = "Error in parameter definition #"+(i+1)+": ";
var paramdef = this.paramDefinitions[i];
if(!('name' in paramdef))
{
throw new Error(errorprefix + "Should include a 'name' parameter");
}
var type = "text";
if('type' in paramdef)
{
type = paramdef.type;
}
if( (type !== "text") && (type !== "int") && (type !== "float") && (type !== "choice") )
{
throw new Error(errorprefix + "Unknown parameter type '"+type+"'");
}
var control;
if( (type == "text") || (type == "int") || (type == "float") )
{
control = document.createElement("input");
control.type = "text";
if('default' in paramdef)
{
control.value = paramdef.default;
}
else
{
if( (type == "int") || (type == "float") )
{
control.value = "0";
}
else
{
control.value = "";
}
}
}
else if(type == "choice")
{
if(!('values' in paramdef))
{
throw new Error(errorprefix + "Should include a 'values' parameter");
}
control = document.createElement("select");
var values = paramdef.values;
var captions;
if('captions' in paramdef)
{
captions = paramdef.captions;
if(captions.length != values.length)
{
throw new Error(errorprefix + "'captions' and 'values' should have the same number of items");
}
}
else
{
captions = values;
}
var selectedindex = 0;
for(var valueindex = 0; valueindex < values.length; valueindex++)
{
var option = document.createElement("option");
option.value = values[valueindex];
option.text = captions[valueindex];
control.add(option);
if('default' in paramdef)
{
if(paramdef.default == values[valueindex])
{
selectedindex = valueindex;
}
}
}
if(values.length > 0)
{
control.selectedIndex = selectedindex;
}
}
paramControls.push(control);
var tr = document.createElement("tr");
var td = document.createElement("td");
var label = paramdef.name + ":";
if('caption' in paramdef)
{
label = paramdef.caption;
}
td.innerHTML = label;
tr.appendChild(td);
td = document.createElement("td");
td.appendChild(control);
tr.appendChild(td);
tablerows.push(tr);
}
var that = this;
tablerows.map(function(tr){
that.parameterstable.appendChild(tr);
});
this.paramControls = paramControls;
},
}; };

View file

@ -4,7 +4,7 @@
<script src="lightgl.js"></script> <script src="lightgl.js"></script>
<script src="csg.js"></script> <script src="csg.js"></script>
<script src="openjscad.js"></script> <script src="openjscad.js"></script>
<style> <style>
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;
@ -26,34 +26,12 @@ pre, textarea {
textarea:focus { textarea:focus {
outline: none; outline: none;
} }
#filedropzone {
border: 2px dashed #bbb;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
padding: 15px;
color: black;
}
#filedropzone_empty {
text-align: center;
color: #bbb;
}
#filedropzone_filled {
color: black;
display: none;
}
#filebuttons {
float: right;
}
a { color: inherit; } a { color: inherit; }
</style>
canvas { cursor: move; } <link rel="stylesheet" href="openjscad.css" type="text/css">
</style>
<script> <script>
@ -126,10 +104,10 @@ function fileChanged()
document.getElementById("filedropzone_filled").style.display = "none"; document.getElementById("filedropzone_filled").style.display = "none";
document.getElementById("filedropzone_empty").style.display = "block"; document.getElementById("filedropzone_empty").style.display = "block";
} }
parseFile(); parseFile(false);
} }
function parseFile() function parseFile(debugging)
{ {
if(gCurrentFile) if(gCurrentFile)
{ {
@ -158,6 +136,7 @@ function parseFile()
{ {
var filename = gCurrentFile.name; var filename = gCurrentFile.name;
filename = filename.replace(/^.*\/([^\/]*)$/, "$1"); filename = filename.replace(/^.*\/([^\/]*)$/, "$1");
gProcessor.setDebugging(debugging);
gProcessor.setJsCad(jscadscript, filename); gProcessor.setJsCad(jscadscript, filename);
} }
} }
@ -184,7 +163,8 @@ function parseFile()
<span id="currentfile">dfghdfgh</span> <span id="currentfile">dfghdfgh</span>
<div id="filebuttons"> <div id="filebuttons">
<button id="getstlbutton" style="display:none" onclick="getStl();">Get STL</button> <button id="getstlbutton" style="display:none" onclick="getStl();">Get STL</button>
<button onclick="parseFile();">Reload</button> <button onclick="parseFile(false);">Reload</button>
<button onclick="parseFile(true);">Debug (see below)</button>
</div> </div>
</div> </div>
</div> </div>
@ -207,5 +187,20 @@ When finished press Generate STL to generate the STL file. Then click Save STL a
a file with .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>.
<h2>Debugging</h2>
By default your .jscad file is parsed in a separate thread (in a Web Worker). This allows long running
scripts to be executed while the web browser stays responsive. The web browser's debugger does not
have access to scripts running in web workers however. To allow debugging you can use the Debug button above to execute
your jscad code in the main thread instead of in a web worker.
<br><br>
To debug your code in Google Chrome: open the Developer Tools by typing Ctrl+Shift+I (or Command+Option+I on mac).
Then press the Debug button above. The debugger will stop just before executing your main() function.
<br><br>
For more information about debugging in Chrome see
<a href="http://code.google.com/chrome/devtools/docs/overview.html" target="_blank">Chrome Developer Tools: Overview</a>
<br><br>
</body> </body>
</html> </html>