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:
parent
fbafcf6864
commit
3b4e1fd1a6
109
csg.js
109
csg.js
|
@ -100,6 +100,14 @@ CSG.fromPolygons = function(polygons) {
|
|||
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 = {
|
||||
toPolygons: function() {
|
||||
return this.polygons;
|
||||
|
@ -221,9 +229,7 @@ CSG.prototype = {
|
|||
},
|
||||
|
||||
mirrored: function(plane) {
|
||||
var newpolygons = this.polygons.map(function(p) { return p.mirrored(plane); } );
|
||||
return CSG.fromPolygons(newpolygons);
|
||||
// TODO: also mirror properties
|
||||
return this.transform(CSG.Matrix4x4.mirroring(plane));
|
||||
},
|
||||
|
||||
mirroredX: function() {
|
||||
|
@ -269,7 +275,7 @@ CSG.prototype = {
|
|||
},
|
||||
|
||||
toString: function() {
|
||||
var result = "";
|
||||
var result = "CSG solid:\n";
|
||||
this.polygons.map(function(p){ result += p.toString(); });
|
||||
return result;
|
||||
},
|
||||
|
@ -422,8 +428,15 @@ CSG.prototype = {
|
|||
return result;
|
||||
},
|
||||
|
||||
connectTo: function(myConnector, otherConnector, mirror, axisrotation) {
|
||||
var matrix = myConnector.getTransformationTo(otherConnector, mirror, axisrotation);
|
||||
// Connect a solid to another solid, such that two CSG.Connectors become connected
|
||||
// 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);
|
||||
},
|
||||
|
||||
|
@ -587,8 +600,8 @@ CSG.sphere = function(options) {
|
|||
}
|
||||
var result = CSG.fromPolygons(polygons);
|
||||
result.properties.sphere = new CSG.Properties();
|
||||
result.properties.sphere.center = new CSG.Vector3D(c);
|
||||
result.properties.sphere.facepoint = c.plus(xvector);
|
||||
result.properties.sphere.center = new CSG.Vector3D(center);
|
||||
result.properties.sphere.facepoint = center.plus(xvector);
|
||||
return result;
|
||||
};
|
||||
|
||||
|
@ -737,9 +750,9 @@ CSG.roundedCylinder = function(options) {
|
|||
var ray = zvector.unit();
|
||||
var axisX = xvector.unit();
|
||||
result.properties.roundedCylinder = new CSG.Properties();
|
||||
result.properties.cylinder.start = new CSG.Connector(p1, ray.negated(), axisX);
|
||||
result.properties.cylinder.end = new CSG.Connector(p2, ray, axisX);
|
||||
result.properties.cylinder.facepoint = p1.plus(xvector);
|
||||
result.properties.roundedCylinder.start = new CSG.Connector(p1, ray.negated(), axisX);
|
||||
result.properties.roundedCylinder.end = new CSG.Connector(p2, ray, axisX);
|
||||
result.properties.roundedCylinder.facepoint = p1.plus(xvector);
|
||||
return result;
|
||||
};
|
||||
|
||||
|
@ -991,6 +1004,12 @@ CSG.Vertex = function(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 = {
|
||||
// Return a vertex with all orientation-specific data (e.g. vertex normal) flipped. Called when the
|
||||
// orientation of a polygon is flipped.
|
||||
|
@ -1040,6 +1059,13 @@ CSG.Plane = function(normal, 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
|
||||
// point is on the plane.
|
||||
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 = {
|
||||
// check whether the polygon is convex (it should be, otherwise we will get unexpected results)
|
||||
checkIfConvex: function() {
|
||||
|
@ -1466,19 +1502,17 @@ CSG.Polygon.prototype = {
|
|||
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
|
||||
transform: function(matrix4x4) {
|
||||
var newvertices = this.vertices.map(function(v) { return v.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);
|
||||
},
|
||||
|
||||
|
@ -2155,6 +2189,21 @@ CSG.Matrix4x4.translation = function(v) {
|
|||
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:
|
||||
CSG.Matrix4x4.scaling = function(v) {
|
||||
// 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);
|
||||
};
|
||||
|
||||
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 = {
|
||||
normalized: function() {
|
||||
var axisvector = this.axisvector.unit();
|
||||
// make the normal vector truly normal:
|
||||
var n = this.normalvector.cross(axisvector).unit();
|
||||
// var normalvector = n.cross(axisvector);
|
||||
var normalvector = axisvector.cross(n);
|
||||
return new CSG.Connector(this.point, axisvector, normalvector);
|
||||
},
|
||||
|
@ -3396,9 +3437,15 @@ CSG.Connector.prototype = {
|
|||
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;
|
||||
axisrotation = axisrotation? Number(axisrotation):0;
|
||||
normalrotation = normalrotation? Number(normalrotation):0;
|
||||
var us = this.normalized();
|
||||
other = other.normalized();
|
||||
// shift to the origin:
|
||||
|
@ -3425,7 +3472,7 @@ CSG.Connector.prototype = {
|
|||
angle1 = normalsbasis.to2D(usAxesAligned.normalvector).angle();
|
||||
angle2 = normalsbasis.to2D(other.normalvector).angle();
|
||||
rotation = 180.0 * (angle2 - angle1) / Math.PI;
|
||||
rotation += axisrotation;
|
||||
rotation += normalrotation;
|
||||
transformation = transformation.multiply(normalsbasis.getProjectionMatrix());
|
||||
transformation = transformation.multiply(CSG.Matrix4x4.rotationZ(rotation));
|
||||
transformation = transformation.multiply(normalsbasis.getInverseProjectionMatrix());
|
||||
|
|
245
index.html
245
index.html
|
@ -32,130 +32,67 @@ textarea:focus {
|
|||
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; }
|
||||
|
||||
#needchrome {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script>
|
||||
|
||||
var gViewer=null;
|
||||
var gSolid=new CSG();
|
||||
var gSolidSource="";
|
||||
|
||||
function isChrome()
|
||||
{
|
||||
return (navigator.userAgent.search("Chrome") >= 0);
|
||||
}
|
||||
var gProcessor=null;
|
||||
|
||||
function onload()
|
||||
{
|
||||
var needchromediv = document.getElementById("needchrome");
|
||||
if(needchromediv)
|
||||
{
|
||||
if(!isChrome())
|
||||
{
|
||||
needchromediv.style.display="block";
|
||||
}
|
||||
}
|
||||
var containerelement = document.getElementById("viewer");
|
||||
gViewer = new OpenJsCad.Viewer(containerelement, 600, 600, 50);
|
||||
gProcessor = new OpenJsCad.Processor(document.getElementById("viewer"));
|
||||
updateSolid();
|
||||
}
|
||||
|
||||
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, '&').replace(/</g, '<').replace(/>/g, '>') + '</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";
|
||||
}
|
||||
}
|
||||
gProcessor.setJsCad(document.getElementById('code').value);
|
||||
}
|
||||
|
||||
function getStl()
|
||||
{
|
||||
updateSolid();
|
||||
var stl=gSolid.toStlString();
|
||||
var stlarea = document.getElementById('stloutput');
|
||||
stlarea.value=stl;
|
||||
stlarea.style.display = "inline";
|
||||
}
|
||||
|
||||
</script>
|
||||
<title>OpenJsCad</title>
|
||||
</head>
|
||||
<body onload="onload()">
|
||||
<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.
|
||||
<table>
|
||||
<tr>
|
||||
<td><div id="viewer" class="viewer" style="background-image:none;width:600px;height:600px;"></div></td>
|
||||
</tr>
|
||||
</table>
|
||||
Create an STL file for 3D printing using constructive solid modeling in Javascript.
|
||||
<div id="viewer"></div>
|
||||
<h2>Playground</h2>
|
||||
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
|
||||
and use the <a href="processfile.html"><b>OpenJsCad parser</b></a>.
|
||||
<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 sphere1 = CSG.sphere({center: [5, 5, 5], radius: 10, resolution: resolution });
|
||||
var sphere2 = sphere1.translate([12, 5, 0]);
|
||||
var sphere3 = CSG.sphere({center: [20, 0, 0], radius: 30, 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 sphere2 = sphere1.translate([12, 5, 0]);
|
||||
var sphere3 = CSG.sphere({center: [20, 0, 0], radius: 30, resolution: resolution });
|
||||
|
||||
var result = cube1;
|
||||
result = result.union(sphere1);
|
||||
result = result.subtract(sphere2);
|
||||
result = result.intersect(sphere3);
|
||||
return result;
|
||||
|
||||
</textarea>
|
||||
|
||||
<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>
|
||||
var result = cube1;
|
||||
result = result.union(sphere1);
|
||||
result = result.subtract(sphere2);
|
||||
result = result.intersect(sphere3);
|
||||
return result;
|
||||
}
|
||||
</textarea><br>
|
||||
<input type="submit" value="Update" onclick="updateSolid(); return false;">
|
||||
<br>
|
||||
<h1>About</h1>
|
||||
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>
|
||||
<code>var cube = CSG.cube(); return cube;</code> creates a cube with a radius of 1 and centered at the origin.
|
||||
The code should always end in a return statement, returning a CSG solid.
|
||||
<pre>function main() {
|
||||
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>
|
||||
To build your own modes, 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,
|
||||
ready to be printed on your 3d printer.
|
||||
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>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,
|
||||
|
@ -317,9 +254,129 @@ var csg = cube1.subtract(cube2);
|
|||
var rounded = csg.expand(0.2, 8);
|
||||
</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 "12 o'clock" 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>
|
||||
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:
|
||||
|
||||
<pre>
|
||||
|
|
274
openjscad.js
274
openjscad.js
|
@ -206,3 +206,277 @@ OpenJsCad.javaScriptToSolid = function(script) {
|
|||
}
|
||||
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();
|
||||
}
|
||||
},
|
||||
};
|
164
processfile.html
164
processfile.html
|
@ -8,9 +8,6 @@
|
|||
|
||||
body {
|
||||
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 {
|
||||
|
@ -25,9 +22,6 @@ pre, textarea {
|
|||
padding: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
textarea {
|
||||
height: 200px;
|
||||
}
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
@ -55,90 +49,27 @@ textarea:focus {
|
|||
}
|
||||
|
||||
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 {
|
||||
display: none;
|
||||
}
|
||||
canvas { cursor: move; }
|
||||
|
||||
</style>
|
||||
<script>
|
||||
|
||||
var gViewer=null;
|
||||
var gSolid=new CSG();
|
||||
|
||||
var gCurrentFile = null;
|
||||
|
||||
function isChrome()
|
||||
{
|
||||
return (navigator.userAgent.search("Chrome") >= 0);
|
||||
}
|
||||
var gProcessor=null;
|
||||
|
||||
function onload()
|
||||
{
|
||||
var needchromediv = document.getElementById("needchrome");
|
||||
if(needchromediv)
|
||||
{
|
||||
if(!isChrome())
|
||||
{
|
||||
needchromediv.style.display="block";
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
var containerelement = document.getElementById("viewer");
|
||||
gViewer = new OpenJsCad.Viewer(containerelement, 600, 600, 50);
|
||||
gProcessor = new OpenJsCad.Processor(document.getElementById("viewer"));
|
||||
setupDragDrop();
|
||||
} catch (e) {
|
||||
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, '&').replace(/</g, '<').replace(/>/g, '>') + '</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()
|
||||
{
|
||||
// Check for the various File API support.
|
||||
|
@ -206,8 +137,6 @@ function fileChanged()
|
|||
|
||||
function parseFile()
|
||||
{
|
||||
setErrorText("");
|
||||
gSolid=new CSG();
|
||||
if(gCurrentFile)
|
||||
{
|
||||
var reader = new FileReader();
|
||||
|
@ -215,78 +144,45 @@ function parseFile()
|
|||
var txt = evt.target.result;
|
||||
};
|
||||
reader.onloadend = function(evt) {
|
||||
if (evt.target.readyState == FileReader.DONE)
|
||||
try
|
||||
{
|
||||
var jscadscript = evt.target.result;
|
||||
if(jscadscript == "")
|
||||
if (evt.target.readyState == FileReader.DONE)
|
||||
{
|
||||
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.");
|
||||
gViewer.setCsg(gSolid);
|
||||
var jscadscript = evt.target.result;
|
||||
if(jscadscript == "")
|
||||
{
|
||||
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.");
|
||||
if(gProcessor) gProcessor.clearViewer();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(gProcessor)
|
||||
{
|
||||
gProcessor.setJsCad(jscadscript);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
parseJscad(jscadscript);
|
||||
throw new Error("Failed to read file");
|
||||
if(gProcessor) gProcessor.clearViewer();
|
||||
}
|
||||
}
|
||||
else
|
||||
catch(e)
|
||||
{
|
||||
alert("Failed to read file");
|
||||
gViewer.setCsg(gSolid);
|
||||
alert(e.toString());
|
||||
}
|
||||
};
|
||||
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>
|
||||
<title>OpenJsCad parser</title>
|
||||
<body onload="onload()">
|
||||
<h1>OpenJsCad parser</h1>
|
||||
<div id="needchrome">Please note: OpenJsCad currently only runs reliably on Google Chrome!</div>
|
||||
<div id="viewer" class="viewer" style="background-image:none;width:600px;height:600px;"></div>
|
||||
<div id="viewer"></div>
|
||||
<br>
|
||||
<div id="errdiv"></div>
|
||||
<div id="filedropzone">
|
||||
<div id="filedropzone_empty">Drop your .jscad file here</div>
|
||||
<div id="filedropzone_filled">
|
||||
|
@ -297,23 +193,23 @@ function getStl()
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="stloutput" readonly style="width: 80%; height: 100px; display: none;"></textarea><br>
|
||||
<br>
|
||||
<h2>Instructions:</h2>
|
||||
Create a new file in your favorite text editor. To get started enter the following text:
|
||||
<br>
|
||||
<pre>
|
||||
var cube = CSG.roundedCube({radius: 10, roundradius: 2, resolution: 16});
|
||||
var sphere = CSG.sphere({radius: 10, resolution: 16}).translate([5, 5, 5]);
|
||||
return cube.union(sphere);
|
||||
<pre>function main() {
|
||||
var cube = CSG.roundedCube({radius: 10, roundradius: 2, resolution: 16});
|
||||
var sphere = CSG.sphere({radius: 10, resolution: 16}).translate([5, 5, 5]);
|
||||
return cube.union(sphere);
|
||||
}
|
||||
</pre>
|
||||
Save this to a file on your desktop with .jscad extension. Then drag & drop the file from your desktop
|
||||
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
|
||||
to parse the changes, you do not need to drag & drop the file again.
|
||||
<br><br>
|
||||
When finished press Get STL to generate the STL file. Copy & paste the STL text to a file with
|
||||
.stl extension.
|
||||
When finished press Generate STL to generate the STL file. Then click Save STL and save it to
|
||||
a file with .stl extension.
|
||||
<br><br>
|
||||
For more information about OpenJsCad see the <a href="index.html">introduction</a>.
|
||||
</body>
|
||||
|
|
Loading…
Reference in a new issue