User editable parameters; debugging support; gears demo
This commit is contained in:
parent
1b89514039
commit
3544b92a1a
13
csg.js
13
csg.js
|
@ -144,6 +144,15 @@ CSG.prototype = {
|
|||
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
|
||||
// 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 = {
|
||||
// extend to a 3D vector by adding a z coordinate:
|
||||
toVector3D: function(z) {
|
||||
|
|
187
gearsdemo.html
Normal file
187
gearsdemo.html
Normal 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>
|
117
index.html
117
index.html
|
@ -35,6 +35,8 @@ textarea:focus {
|
|||
canvas { cursor: move; }
|
||||
|
||||
</style>
|
||||
<link rel="stylesheet" href="openjscad.css" type="text/css">
|
||||
|
||||
<script>
|
||||
|
||||
var gProcessor=null;
|
||||
|
@ -67,7 +69,7 @@ and use the <a href="processfile.html"><b>OpenJsCad parser</b></a>.
|
|||
<textarea id="code">
|
||||
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 sphere1 = CSG.sphere({center: [5, 5, 5], radius: 10, resolution: resolution });
|
||||
|
@ -83,7 +85,7 @@ function main()
|
|||
</textarea><br>
|
||||
<input type="submit" value="Update" onclick="updateSolid(); return false;">
|
||||
<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>,
|
||||
for 3D solid modeling.
|
||||
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
|
||||
<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.
|
||||
<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>
|
||||
Copyright (c) 2012 Joost Nieuwenhuijse.
|
||||
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>,
|
||||
<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>.
|
||||
<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>
|
||||
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
|
||||
|
@ -422,5 +436,94 @@ var extruded=shape2d.extrude({
|
|||
});
|
||||
</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>
|
||||
</html>
|
41
openjscad.css
Normal file
41
openjscad.css
Normal 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;
|
||||
}
|
420
openjscad.js
420
openjscad.js
|
@ -197,16 +197,6 @@ OpenJsCad.Viewer.csgToMesh = function(csg) {
|
|||
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 '/'
|
||||
// but does handle relative urls containing ../
|
||||
OpenJsCad.makeAbsoluteUrl = function(url, baseurl) {
|
||||
|
@ -246,10 +236,48 @@ OpenJsCad.makeAbsoluteUrl = function(url, baseurl) {
|
|||
OpenJsCad.isChrome = function()
|
||||
{
|
||||
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)
|
||||
OpenJsCad.javaScriptToSolidASync = function(script, callback) {
|
||||
OpenJsCad.javaScriptToSolidASync = function(script, mainParameters, callback) {
|
||||
var baselibraries = [
|
||||
"csg.js",
|
||||
"openjscad.js"
|
||||
|
@ -257,29 +285,38 @@ OpenJsCad.javaScriptToSolidASync = function(script, callback) {
|
|||
var baseurl = document.location + "";
|
||||
var workerscript = "";
|
||||
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_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 += "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 += " OpenJsCad.runMainInWorker("+JSON.stringify(mainParameters)+");";
|
||||
// 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";
|
||||
|
||||
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);
|
||||
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);
|
||||
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) {
|
||||
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");
|
||||
};
|
||||
|
||||
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) {
|
||||
var errormap = {
|
||||
1: NOT_FOUND_ERR,
|
||||
2: SECURITY_ERR,
|
||||
3: ABORT_ERR,
|
||||
4: NOT_READABLE_ERR,
|
||||
5: ENCODING_ERR,
|
||||
6: NO_MODIFICATION_ALLOWED_ERR,
|
||||
7: INVALID_STATE_ERR,
|
||||
8: SYNTAX_ERR,
|
||||
9: INVALID_MODIFICATION_ERR,
|
||||
10: QUOTA_EXCEEDED_ERR,
|
||||
11: TYPE_MISMATCH_ERR,
|
||||
12: PATH_EXISTS_ERR,
|
||||
1: 'NOT_FOUND_ERR',
|
||||
2: 'SECURITY_ERR',
|
||||
3: 'ABORT_ERR',
|
||||
4: 'NOT_READABLE_ERR',
|
||||
5: 'ENCODING_ERR',
|
||||
6: 'NO_MODIFICATION_ALLOWED_ERR',
|
||||
7: 'INVALID_STATE_ERR',
|
||||
8: 'SYNTAX_ERR',
|
||||
9: 'INVALID_MODIFICATION_ERR',
|
||||
10: 'QUOTA_EXCEEDED_ERR',
|
||||
11: 'TYPE_MISMATCH_ERR',
|
||||
12: 'PATH_EXISTS_ERR',
|
||||
};
|
||||
var errname;
|
||||
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 = {
|
||||
createElements: function() {
|
||||
while(this.containerdiv.children.length > 0)
|
||||
|
@ -390,9 +460,11 @@ OpenJsCad.Processor.prototype = {
|
|||
this.viewerdiv.innerHTML = e.toString();
|
||||
}
|
||||
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.style.width = this.viewerwidth + "px";
|
||||
this.statusdiv.className = "statusdiv";
|
||||
//this.statusdiv.style.width = this.viewerwidth + "px";
|
||||
this.statusspan = document.createElement("span");
|
||||
this.statusbuttons = document.createElement("div");
|
||||
this.statusbuttons.style.float = "right";
|
||||
|
@ -403,20 +475,36 @@ OpenJsCad.Processor.prototype = {
|
|||
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.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.containerdiv.appendChild(this.statusdiv);
|
||||
this.containerdiv.appendChild(this.errordiv);
|
||||
this.containerdiv.appendChild(this.parametersdiv);
|
||||
this.clearViewer();
|
||||
},
|
||||
|
||||
|
@ -447,11 +535,19 @@ OpenJsCad.Processor.prototype = {
|
|||
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";
|
||||
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) {
|
||||
this.errordiv.innerHTML = txt;
|
||||
this.errordiv.style.display = (txt == "")? "none":"block";
|
||||
this.hasError = (txt != "");
|
||||
this.errorpre.innerText = txt;
|
||||
this.enableItems();
|
||||
},
|
||||
|
||||
setDebugging: function(debugging) {
|
||||
this.debugging = debugging;
|
||||
},
|
||||
|
||||
// script: javascript code
|
||||
|
@ -461,12 +557,110 @@ OpenJsCad.Processor.prototype = {
|
|||
filename = filename.replace(/\.jscad$/i, "");
|
||||
this.abort();
|
||||
this.clearViewer();
|
||||
this.paramDefinitions = [];
|
||||
this.paramControls = [];
|
||||
this.script = null;
|
||||
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.statusspan.innerHTML = "Processing, please wait...";
|
||||
this.filename = filename;
|
||||
this.enableItems();
|
||||
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.worker = null;
|
||||
if(err)
|
||||
|
@ -484,8 +678,7 @@ OpenJsCad.Processor.prototype = {
|
|||
that.enableItems();
|
||||
if(that.onchange) that.onchange();
|
||||
});
|
||||
this.enableItems();
|
||||
if(this.onchange) this.onchange();
|
||||
}
|
||||
},
|
||||
|
||||
hasSolid: function() {
|
||||
|
@ -496,6 +689,7 @@ OpenJsCad.Processor.prototype = {
|
|||
return this.processing;
|
||||
},
|
||||
|
||||
/*
|
||||
clearStl1: function() {
|
||||
if(this.hasstl)
|
||||
{
|
||||
|
@ -519,6 +713,7 @@ OpenJsCad.Processor.prototype = {
|
|||
if(this.onchange) this.onchange();
|
||||
}
|
||||
},
|
||||
*/
|
||||
|
||||
clearStl: function() {
|
||||
if(this.hasstl)
|
||||
|
@ -543,14 +738,14 @@ OpenJsCad.Processor.prototype = {
|
|||
window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
|
||||
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)
|
||||
{
|
||||
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:
|
||||
var dirname = "OpenJsCadStlOutput_"+parseInt(Math.random()*1000000000, 10)+".stl";
|
||||
var dirname = "OpenJsCadStlOutput1_"+parseInt(Math.random()*1000000000, 10)+".stl";
|
||||
var filename = this.filename+".stl";
|
||||
var that = this;
|
||||
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;
|
||||
},
|
||||
|
||||
|
||||
};
|
|
@ -26,34 +26,12 @@ pre, textarea {
|
|||
textarea:focus {
|
||||
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; }
|
||||
|
||||
canvas { cursor: move; }
|
||||
|
||||
</style>
|
||||
|
||||
<link rel="stylesheet" href="openjscad.css" type="text/css">
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
|
@ -126,10 +104,10 @@ function fileChanged()
|
|||
document.getElementById("filedropzone_filled").style.display = "none";
|
||||
document.getElementById("filedropzone_empty").style.display = "block";
|
||||
}
|
||||
parseFile();
|
||||
parseFile(false);
|
||||
}
|
||||
|
||||
function parseFile()
|
||||
function parseFile(debugging)
|
||||
{
|
||||
if(gCurrentFile)
|
||||
{
|
||||
|
@ -158,6 +136,7 @@ function parseFile()
|
|||
{
|
||||
var filename = gCurrentFile.name;
|
||||
filename = filename.replace(/^.*\/([^\/]*)$/, "$1");
|
||||
gProcessor.setDebugging(debugging);
|
||||
gProcessor.setJsCad(jscadscript, filename);
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +163,8 @@ function parseFile()
|
|||
<span id="currentfile">dfghdfgh</span>
|
||||
<div id="filebuttons">
|
||||
<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>
|
||||
|
@ -207,5 +187,20 @@ When finished press Generate STL to generate the STL file. Then click Save STL a
|
|||
a file with .stl extension.
|
||||
<br><br>
|
||||
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>
|
||||
</html>
|
Loading…
Reference in a new issue