2012-03-17 20:42:33 +01:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
<head>
|
2012-03-17 22:59:19 +01:00
|
|
|
<script src="../lightgl.js"></script>
|
|
|
|
<script src="../csg.js"></script>
|
|
|
|
<script src="../openjscad.js"></script>
|
|
|
|
<script src="../jquery.js"></script>
|
|
|
|
<script src="../coffee-script.js"></script>
|
|
|
|
<script src="../Math.uuid.js"></script>
|
2012-03-17 20:42:33 +01:00
|
|
|
<style>
|
|
|
|
body {
|
|
|
|
font: 14px/20px 'Helvetica Neue Light', HelveticaNeue-Light, 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
|
|
}
|
2012-03-17 22:59:19 +01:00
|
|
|
pre, code, textarea, canvas {
|
2012-03-17 20:42:33 +01:00
|
|
|
font: 12px/20px Monaco, monospace;
|
|
|
|
border: 1px solid #CCC;
|
|
|
|
border-radius: 3px;
|
|
|
|
background: #F9F9F9;
|
2012-03-17 22:59:19 +01:00
|
|
|
padding: 0;
|
|
|
|
margin: 0;
|
2012-03-17 20:42:33 +01:00
|
|
|
color: #555;
|
|
|
|
}
|
|
|
|
pre {
|
2012-03-17 22:59:19 +01:00
|
|
|
padding: 10px;
|
2012-03-17 20:42:33 +01:00
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
textarea:focus {
|
|
|
|
outline: none;
|
|
|
|
}
|
|
|
|
canvas {
|
|
|
|
cursor: move;
|
|
|
|
}
|
2012-03-17 22:59:19 +01:00
|
|
|
header {
|
|
|
|
padding: 0;
|
|
|
|
margin: 0;
|
|
|
|
position: fixed;
|
|
|
|
top: 0;
|
|
|
|
right: 0;
|
|
|
|
left: 0;
|
|
|
|
border-bottom: 1px solid black;
|
|
|
|
background: #bbb;
|
2012-03-18 09:53:05 +01:00
|
|
|
height: 1.9em;
|
2012-03-17 22:59:19 +01:00
|
|
|
}
|
|
|
|
header > * {
|
2012-03-17 20:42:33 +01:00
|
|
|
display: inline-block;
|
|
|
|
}
|
2012-03-17 22:59:19 +01:00
|
|
|
body {
|
2012-03-18 09:53:05 +01:00
|
|
|
margin: 2em 0 0 0;
|
2012-03-17 22:59:19 +01:00
|
|
|
padding: 0;
|
|
|
|
}
|
2012-03-17 20:42:33 +01:00
|
|
|
</style>
|
2012-03-18 09:53:05 +01:00
|
|
|
<link rel="stylesheet" href="../openjscad.css" type="text/css">
|
2012-03-17 22:59:19 +01:00
|
|
|
<script type="text/coffeescript">
|
2012-03-18 09:53:05 +01:00
|
|
|
window.DelayedCall = (fun, delay = 250) ->
|
|
|
|
self = (args...) ->
|
|
|
|
self.stop()
|
|
|
|
self.fire(args...)
|
|
|
|
self.call = fun
|
|
|
|
self.delay = delay
|
|
|
|
self.fire = (args...) ->
|
|
|
|
self.stop()
|
|
|
|
self.to = setTimeout (-> self.call( args...)), self.delay
|
|
|
|
this
|
|
|
|
self.stop = ->
|
|
|
|
clearTimeout self.to
|
|
|
|
this
|
|
|
|
self
|
|
|
|
|
|
|
|
CSG.loadFile = (fn) ->
|
|
|
|
[fn, rev, lang, code] = openjscadUI.loadFile()
|
|
|
|
eval openjscadUI.prepareCode( lang, code)
|
|
|
|
|
|
|
|
class UI
|
|
|
|
constructor: ->
|
|
|
|
self = this
|
|
|
|
# Show all exceptions to the user:
|
|
|
|
OpenJsCad.AlertUserOfUncaughtExceptions()
|
|
|
|
@gProcessor = null
|
|
|
|
@revision = null
|
|
|
|
@$code = $ '#code'
|
|
|
|
@$lang = $ '#lang'
|
|
|
|
@$viewer = $ '#viewer'
|
|
|
|
@$body = $ 'body'
|
|
|
|
|
|
|
|
[fn, rev, lang, code] = @loadFile @currentFilename()
|
|
|
|
@$lang.val lang
|
|
|
|
@$code.
|
|
|
|
on( 'keyup.openjscadui', -> self.edit_history()).
|
|
|
|
val code
|
|
|
|
@windowResized = new DelayedCall -> self.resizeCode()
|
|
|
|
$(window).on 'resize.openjscadui', @windowResized
|
|
|
|
|
|
|
|
@gProcessor = new OpenJsCad.Processor @$viewer[0]
|
|
|
|
@updateSolid()
|
|
|
|
@resizeCode()
|
|
|
|
|
|
|
|
destroy: ->
|
|
|
|
@$code.off '.openjscadui'
|
|
|
|
$(window).off '.openjscadui'
|
|
|
|
|
|
|
|
prepareCode: (lang, code) ->
|
|
|
|
code = CoffeeScript.compile code, bare: 'on' if 'CoffeeScript' == lang
|
|
|
|
console.group 'code'
|
|
|
|
console.log code
|
|
|
|
console.groupEnd()
|
|
|
|
code
|
|
|
|
getCode: -> @$code.val()
|
|
|
|
getLang: -> @$lang.val()
|
|
|
|
getJsCode: -> @prepareCode @getLang(), @getCode()
|
|
|
|
updateSolid: -> @gProcessor.setJsCad @getJsCode()
|
|
|
|
currentFilename: -> /\/([^\/]*?)$/.exec(window.location.pathname)[1]
|
|
|
|
|
|
|
|
loadFile: (fn) ->
|
|
|
|
rev = localStorage[fn]
|
|
|
|
JSON.parse localStorage[rev]
|
|
|
|
|
|
|
|
writeFile: (fn, lang, code) ->
|
|
|
|
rev = Math.uuid()
|
|
|
|
localStorage[rev] = JSON.stringify [fn, @revision, @getLang(), @getCode()]
|
|
|
|
localStorage[fn] = @revision = rev
|
|
|
|
|
|
|
|
# Store the file-history (for undo) in the browser-storage:
|
|
|
|
edit_history: (event) ->
|
|
|
|
fn = @currentFilename()
|
|
|
|
code = @getCode()
|
|
|
|
lang = @getLang()
|
|
|
|
[_,_, oldlang, oldcode] = @loadFile fn
|
|
|
|
@writeFile fn, lang, code unless oldcode == code and oldlang == lang
|
|
|
|
true
|
|
|
|
|
|
|
|
resizeCode: ->
|
|
|
|
@$code.css
|
|
|
|
width: "#{@$body.innerWidth() - @$viewer.outerWidth() - @$code.outerWidth() + @$code.width()}px"
|
|
|
|
height: '600px'
|
|
|
|
|
|
|
|
zoom: (i) ->
|
|
|
|
factor = -1
|
|
|
|
viewer = @gProcessor.viewer
|
|
|
|
viewer.viewpointZ *= Math.pow 2, factor * i
|
|
|
|
viewer.onDraw()
|
2012-03-17 20:42:33 +01:00
|
|
|
|
2012-03-17 22:59:19 +01:00
|
|
|
# http://pallieter.org/Projects/insertTab/
|
|
|
|
window.insertTab = (o, e) ->
|
|
|
|
kC = e.keyCode ? e.charCode ? e.which
|
|
|
|
if kC == 9 && !e.shiftKey && !e.ctrlKey && !e.altKey
|
|
|
|
oS = o.scrollTop
|
|
|
|
if o.setSelectionRange
|
|
|
|
sS = o.selectionStart
|
|
|
|
sE = o.selectionEnd
|
|
|
|
o.value = "#{o.value.substring 0, sS}\t#{o.value.substr sE}"
|
|
|
|
o.setSelectionRange sS + 1, sS + 1
|
|
|
|
o.focus()
|
|
|
|
else if o.createTextRange
|
|
|
|
document.selection.createRange().text = "\t"
|
|
|
|
e.returnValue = false
|
|
|
|
o.scrollTop = oS
|
|
|
|
if e.preventDefault
|
|
|
|
e.preventDefault()
|
|
|
|
true
|
2012-03-17 20:42:33 +01:00
|
|
|
|
2012-03-17 22:59:19 +01:00
|
|
|
$ ->
|
2012-03-18 09:53:05 +01:00
|
|
|
window.openjscadUI = new UI
|
|
|
|
openjscadUI.$code.on 'keydown', (e)-> insertTab this, e
|
2012-03-17 20:42:33 +01:00
|
|
|
</script>
|
2012-03-18 09:53:05 +01:00
|
|
|
<title>OpenJsCad</title>
|
2012-03-17 20:42:33 +01:00
|
|
|
</head>
|
|
|
|
|
2012-03-17 22:59:19 +01:00
|
|
|
<body>
|
2012-03-17 20:42:33 +01:00
|
|
|
<header>
|
2012-03-18 09:53:05 +01:00
|
|
|
<button onclick="openjscadUI.updateSolid();return false;">Update</button>
|
|
|
|
<span style="float:right">
|
|
|
|
<button onclick="openjscadUI.zoom(1);return false;">+</button>
|
|
|
|
<button onclick="openjscadUI.zoom(-1);return false;">-</button>
|
|
|
|
</span>
|
2012-03-17 20:42:33 +01:00
|
|
|
</header>
|
|
|
|
|
|
|
|
<div id="viewer" style="float:right;"></div>
|
|
|
|
|
|
|
|
<div id="editor">
|
2012-03-17 22:59:19 +01:00
|
|
|
<textarea id="code" name="code"></textarea>
|
2012-03-17 20:42:33 +01:00
|
|
|
<br>
|
|
|
|
<select id="lang" name="lang">
|
|
|
|
<option>JavaScript</option>
|
|
|
|
<option selected>CoffeeScript</option>
|
|
|
|
</select>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div id="help" style="display:none">
|
|
|
|
<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
|
|
|
|
for a parameter which expects a 3D vector, it is used for the x, y and z value.
|
|
|
|
In other words: <code>radius: 1</code> will give <code>radius: [1,1,1]</code>.
|
|
|
|
<br><br>
|
|
|
|
All rounded solids have a 'resolution' parameter which controls tesselation. If resolution
|
|
|
|
is set to 8, then 8 polygons per 360 degree of revolution are used. Beware that rendering
|
|
|
|
time will increase dramatically when increasing the resolution. For a sphere the number of polygons
|
|
|
|
increases quadratically with the resolution used.
|
|
|
|
<br><br>
|
|
|
|
<pre>
|
|
|
|
// a cube:
|
|
|
|
var cube = CSG.cube({
|
|
|
|
center: [0, 0, 0],
|
|
|
|
radius: [1, 1, 1]
|
|
|
|
});
|
|
|
|
|
|
|
|
// a sphere:
|
|
|
|
var sphere = CSG.sphere({
|
|
|
|
center: [0, 0, 0],
|
|
|
|
radius: 2, // must be scalar
|
|
|
|
resolution: 32
|
|
|
|
});
|
|
|
|
|
|
|
|
// a cylinder:
|
|
|
|
var cylinder = CSG.cylinder({
|
|
|
|
start: [0, -1, 0],
|
|
|
|
end: [0, 1, 0],
|
|
|
|
radius: 1,
|
|
|
|
resolution: 16
|
|
|
|
});
|
|
|
|
|
|
|
|
// like a cylinder, but with spherical endpoints:
|
|
|
|
var roundedCylinder = CSG.roundedCylinder({
|
|
|
|
start: [0, -1, 0],
|
|
|
|
end: [0, 1, 0],
|
|
|
|
radius: 1,
|
|
|
|
resolution: 16
|
|
|
|
});
|
|
|
|
|
|
|
|
// a rounded cube:
|
|
|
|
var cube = CSG.roundedCube({
|
|
|
|
center: [0, 0, 0],
|
|
|
|
radius: 1,
|
|
|
|
roundradius: 0.2,
|
|
|
|
resolution: 8,
|
|
|
|
});
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<h2>CSG operations</h2>
|
|
|
|
The 3 standard CSG operations are supported. All CSG operations return a new solid; the source solids
|
|
|
|
are not modified:
|
|
|
|
<pre>
|
|
|
|
var csg1 = cube.union(sphere);
|
|
|
|
var csg2 = cube.intersect(sphere);
|
|
|
|
var csg3 = cube.subtract(sphere);
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<h2>Transformations</h2>
|
|
|
|
Solids can be translated, scaled and rotated. Multiple transforms can be combined into a single matrix transform.
|
|
|
|
For <a href="#math">matrix and vector math see below</a>.
|
|
|
|
<pre>
|
|
|
|
var cube = CSG.cube();
|
|
|
|
|
|
|
|
// translation:
|
|
|
|
var cube2 = cube.translate([1, 2, 3]);
|
|
|
|
|
|
|
|
// scaling:
|
|
|
|
var largecube = cube.scale(2.0);
|
|
|
|
var stretchedcube = cube.scale([1.5, 1, 0.5]);
|
|
|
|
|
|
|
|
// rotation:
|
|
|
|
var rotated1 = cube.rotateX(-45); // rotate around the X axis
|
|
|
|
var rotated2 = cube.rotateY(90); // rotate around the Y axis
|
|
|
|
var rotated3 = cube.rotateZ(20); // rotate around the Z axis
|
|
|
|
|
|
|
|
// combine multiple transforms into a single matrix transform:
|
|
|
|
var m = new CSG.Matrix4x4();
|
|
|
|
m = m.multiply(CSG.Matrix4x4.rotationX(40));
|
|
|
|
m = m.multiply(CSG.Matrix4x4.rotationZ(40));
|
|
|
|
m = m.multiply(CSG.Matrix4x4.translation([-.5, 0, 0]));
|
|
|
|
m = m.multiply(CSG.Matrix4x4.scaling([1.1, 1.2, 1.3]));
|
|
|
|
|
|
|
|
// and apply the transform:
|
|
|
|
var cube3 = cube.transform(m);
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<h2>Mirroring</h2>
|
|
|
|
Solids can be mirrored in any plane in 3D space. For <a href="#math">plane math see below</a>.
|
|
|
|
<pre>
|
|
|
|
var cube = CSG.cube().translate([1,0,0]);
|
|
|
|
|
|
|
|
var cube2 = cube.mirroredX(); // mirrored in the x=0 plane
|
|
|
|
var cube3 = cube.mirroredY(); // mirrored in the y=0 plane
|
|
|
|
var cube4 = cube.mirroredZ(); // mirrored in the z=0 plane
|
|
|
|
|
|
|
|
// create a plane by specifying 3 points:
|
|
|
|
var plane = CSG.Plane.fromPoints([5,0,0], [5, 1, 0], [3, 1, 7]);
|
|
|
|
|
|
|
|
// and mirror in that plane:
|
|
|
|
var cube5 = cube.mirrored(plane);
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<h2>Cutting by a plane</h2>
|
|
|
|
A solid can be cut by a plane; only the part on the back side is kept:
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
var cube = CSG.cube({radius: 10});
|
|
|
|
|
|
|
|
// create a plane by specifying 3 points:
|
|
|
|
var plane1 = CSG.Plane.fromPoints([5,0,0], [7, 1, 0], [3, 1, 7]);
|
|
|
|
|
|
|
|
// or by specifying a normal and a point on the plane:
|
|
|
|
var plane2 = CSG.Plane.fromNormalAndPoint([3, 1, 2], [5, 0, 0]);
|
|
|
|
|
|
|
|
// and cut by the plane:
|
|
|
|
var part1 = cube.cutByPlane(plane2);
|
|
|
|
|
|
|
|
// or if we need the other half of the cube:
|
|
|
|
var part2 = cube.cutByPlane(plane2.flipped());
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
|
|
|
|
<h2>Expansion and contraction</h2>
|
|
|
|
Expansion can be seen
|
|
|
|
as the 3D convolution of an object with a sphere. Contraction is the reverse: the area outside the solid
|
|
|
|
is expanded, and this is then subtracted from the solid.
|
|
|
|
<br><br>
|
|
|
|
Expansion and contraction are very powerful ways to get an object with nice smooth corners. For example
|
|
|
|
a rounded cube can be created by expanding a normal cube.
|
|
|
|
<br><br>
|
|
|
|
Note that these are expensive operations: spheroids are created around every corner and edge in the original
|
|
|
|
object, so the number of polygons quickly increases. Expansion and contraction therefore are only practical for simple
|
|
|
|
non-curved objects.
|
|
|
|
<br><br>
|
|
|
|
expand() and contract() take two parameters: the first is the radius of expansion or contraction; the second
|
|
|
|
parameter is optional and specififies the resolution (number of polygons on spherical surfaces, per 360 degree revolution).
|
|
|
|
<pre>
|
|
|
|
var cube1 = CSG.cube({radius: 1.0});
|
|
|
|
var cube2 = CSG.cube({radius: 1.0}).translate([-0.3, -0.3, -0.3]);
|
|
|
|
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 metadata 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>
|
|
|
|
For an example see the <a href="servodemo.html">Servo motor</a> demo.
|
|
|
|
|
|
|
|
<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>
|
|
|
|
For a more complete example see the <a href="servodemo.html">Servo motor</a> demo.
|
|
|
|
|
|
|
|
<h2>Determining the bounds of an object</h2>
|
|
|
|
The getBounds() function can be used to retrieve the bounding box of an object.
|
|
|
|
getBounds() returns
|
|
|
|
an array with two elements specifying the minimum x,y,z coordinate and the maximum x,y,z coordinate:
|
|
|
|
|
|
|
|
<pre>
|
|
|
|
var cube1 = CSG.cube({radius: 10});
|
|
|
|
var cube2 = CSG.cube({radius: 5});
|
|
|
|
|
|
|
|
// get the right bound of cube1 and the left bound of cube2:
|
|
|
|
var deltax = cube1.getBounds()[1].x - cube2.getBounds()[0].x;
|
|
|
|
|
|
|
|
// align cube2 so it touches cube1:
|
|
|
|
cube2 = cube2.translate([deltax, 0, 0]);
|
|
|
|
|
|
|
|
return cube1.union(cube2);
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<h2>2D shapes</h2>
|
|
|
|
Two dimensional shapes can be defined through the Polygon2D class. Currently this requires the polygon to be convex
|
|
|
|
(i.e. all corners less than 180 degrees). Shapes can be transformed (rotation, translation, scaling).
|
|
|
|
To actually use the shape it needs to be extruded into a 3D CSG object through the extrude() function. extrude()
|
|
|
|
places the 2D solid onto the z=0 plane, and extrudes in the specified direction. Extrusion can be done with an optional
|
|
|
|
twist. This rotates the solid around the z axis (and not necessariy around the extrusion axis) during extrusion.
|
|
|
|
The total degrees of rotation is specified in the twistangle parameter, and twiststeps determine the number of steps
|
|
|
|
between the bottom and top surface.
|
|
|
|
<pre>
|
|
|
|
// Create a shape; argument is an array of 2D coordinates
|
|
|
|
// The shape must be convex, can be specified in clockwise or in counterclockwise direction
|
|
|
|
var shape2d=new CSG.Polygon2D([[0,0], [5,0], [3,5], [0,5]]);
|
|
|
|
|
|
|
|
// Do some transformations:
|
|
|
|
shape2d=shape2d.translate([-2, -2]);
|
|
|
|
shape2d=shape2d.rotate(20);
|
|
|
|
shape2d=shape2d.scale([0.2, 0.2]);
|
|
|
|
|
|
|
|
// And extrude. This creates a CSG solid:
|
|
|
|
var extruded=shape2d.extrude({
|
|
|
|
offset: [0.5, 0, 2], // direction for extrusion
|
|
|
|
twistangle: 180, // top surface is rotated 180 degrees
|
|
|
|
twiststeps: 100 // create 100 slices
|
|
|
|
});
|
|
|
|
</pre>
|
|
|
|
For an example of 2D shapes see the <a href="hookdemo.html">Parametric S hook</a> demo.
|
|
|
|
|
|
|
|
<h2>2D Paths</h2>
|
|
|
|
A path is simply a series of points, connected by lines. A path can be open or closed (an additional line
|
|
|
|
is drawn between the first and last point). 2D paths are supported through the CSG.Path2D class.
|
|
|
|
<br><br>
|
|
|
|
Paths can be contructed either by giving a series of 2D coordinates, or through the CSG.Path2D.arc() function,
|
|
|
|
which creates a circular curved path. Paths can be concatenated, the result is a new path.
|
|
|
|
<br><br>
|
|
|
|
Creating a 3D solid is currently supported by the rectangularExtrude() function. This creates a 3D shape
|
|
|
|
by following the path with a 2D rectangle (upright, perpendicular to the path direction).
|
|
|
|
<pre>
|
|
|
|
var path = new CSG.Path2D([[10,10], [-10,10]], /* closed = */ false);
|
|
|
|
var anotherpath = new CSG.Path2D([[-10,-10]]);
|
|
|
|
path = path.concat(anotherpath);
|
|
|
|
path = path.appendPoint([10,-10]);
|
|
|
|
path = path.close(); // close the path
|
|
|
|
|
|
|
|
// of course we could simply have done:
|
|
|
|
// var path = new CSG.Path2D([[10,10], [-10,10], [-10,-10], [10,-10]], /* closed = */ true);
|
|
|
|
|
|
|
|
// We can make arcs and circles:
|
|
|
|
var curvedpath = CSG.Path2D.arc({
|
|
|
|
center: [0,0,0],
|
|
|
|
radius: 10,
|
|
|
|
startangle: 0,
|
|
|
|
endangle: 180,
|
|
|
|
resolution: 16,
|
|
|
|
});
|
|
|
|
|
|
|
|
// Extrude the path by following it with a rectangle (upright, perpendicular to the path direction)
|
|
|
|
// Returns a CSG solid
|
|
|
|
// width: width of the extrusion, in the z=0 plane
|
|
|
|
// height: height of the extrusion in the z direction
|
|
|
|
// resolution: number of segments per 360 degrees for the curve in a corner
|
|
|
|
// roundEnds: if true, the ends of the polygon will be rounded, otherwise they will be flat
|
|
|
|
var csg = path.rectangularExtrude(3, 4, 16, true);
|
|
|
|
return csg;
|
|
|
|
</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. Copy/paste it into the Playground at the top of this page to see how it works:
|
|
|
|
<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>
|
|
|
|
Or see the <a href="gearsdemo.html">Gears demo</a> for another example of interactive parameters.
|
|
|
|
|
|
|
|
<h2>Miscellaneous</h2>
|
|
|
|
Solids can be given a color using the setColor(r, g, b) function. Beware: this returns a new solid,
|
|
|
|
the original solid is not modified! Faces of the solid will keep their original color when doing
|
|
|
|
CSG operations (union() etc). Colors values range between 0.0 and 1.0 (not 255).
|
|
|
|
<pre>
|
|
|
|
var cube1 = CSG.cube({radius: 10});
|
|
|
|
cube1 = cube1.setColor(0.5, 0, 0);
|
|
|
|
|
|
|
|
var cube2 = CSG.cube({radius: 10});
|
|
|
|
cube2 = cube2.setColor(0, 0.5, 0);
|
|
|
|
cube2 = cube2.translate([5,1,4]);
|
|
|
|
|
|
|
|
var result = cube1.subtract(cube2);
|
|
|
|
// the resulting solid will have faces with 2 different colors
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
<h2><a name="math"></a>2D and 3D Math</h2>
|
|
|
|
There are utility classes for many 2D and 3D operations. Below is a quick summary, for details
|
|
|
|
view the source of csg.js:
|
|
|
|
<pre>
|
|
|
|
// --------- Vector3D ---------------------
|
|
|
|
var vec1 = new CSG.Vector3D(1,2,3); // 3 arguments
|
|
|
|
var vec2 = new CSG.Vector3D( [1,2,3] ); // 1 array argument
|
|
|
|
var vec3 = new CSG.Vector3D(vec2); // cloning a vector
|
|
|
|
// get the values as: vec1.x, vec.y, vec1.z
|
|
|
|
// vector math. All operations return a new vector, the original is unmodified!
|
|
|
|
// vectors cannot be modified. Instead you should create a new vector.
|
|
|
|
vec.negated()
|
|
|
|
vec.abs()
|
|
|
|
vec.plus(othervector)
|
|
|
|
vec.minus(othervector)
|
|
|
|
vec.times(3.0)
|
|
|
|
vec.dividedBy(-5)
|
|
|
|
vec.dot(othervector)
|
|
|
|
vec.lerp(othervector, t) // linear interpolation (0 <= t <= 1)
|
|
|
|
vec.length()
|
|
|
|
vec.lengthSquared() // == vec.length()^2
|
|
|
|
vec.unit()
|
|
|
|
vec.cross(othervector) // cross product: returns a vector perpendicular to both
|
|
|
|
vec.distanceTo(othervector)
|
|
|
|
vec.distanceToSquared(othervector) // == vec.distanceTo(othervector)^2
|
|
|
|
vec.equals(othervector)
|
|
|
|
vec.multiply4x4(matrix4x4) // right multiply by a 4x4 matrix
|
|
|
|
vec.min(othervector) // returns a new vector with the minimum x,y and z values
|
|
|
|
vec.max(othervector) // returns a new vector with the maximum x,y and z values
|
|
|
|
|
|
|
|
// --------- Vector2D ---------------------
|
|
|
|
var vec1 = new CSG.Vector2D(1,2); // 2 arguments
|
|
|
|
var vec2 = new CSG.Vector2D( [1,2] ); // 1 array argument
|
|
|
|
var vec3 = new CSG.Vector2D(vec2); // cloning a vector
|
|
|
|
// vector math. All operations return a new vector, the original is unmodified!
|
|
|
|
vec.negated()
|
|
|
|
vec.abs()
|
|
|
|
vec.plus(othervector)
|
|
|
|
vec.minus(othervector)
|
|
|
|
vec.times(3.0)
|
|
|
|
vec.dividedBy(-5)
|
|
|
|
vec.dot(othervector)
|
|
|
|
vec.lerp(othervector, t) // linear interpolation (0 <= t <= 1)
|
|
|
|
vec.length()
|
|
|
|
vec.unit()
|
|
|
|
vec.normal() // returns a 90 degree clockwise rotated vector
|
|
|
|
vec.distanceTo(othervector)
|
|
|
|
vec.equals(othervector)
|
|
|
|
vec.multiply4x4(matrix4x4) // right multiply by a 4x4 matrix
|
|
|
|
vec.toVector3D(z) // convert to a vector3D by adding a z coordinate
|
|
|
|
vec.angleDegrees() // returns the angle of the vector: [1,0] = 0 degrees, [0, 1] = 90 degrees, etc
|
|
|
|
vec.angleRadians() // ditto in radians
|
|
|
|
var vec = CSG.Vector2D.fromAngleDegrees(degrees); // returns a vector at the specified angle
|
|
|
|
var vec = CSG.Vector2D.fromAngleRadians(radians); // returns a vector at the specified angle
|
|
|
|
|
|
|
|
// --------- Matrix4x4 ---------------------
|
|
|
|
var m1 = new CSG.Matrix4x4(); // unity matrix
|
|
|
|
var m2 = new CSG.Matrix4x4( [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] );
|
|
|
|
// elements are passed in row order
|
|
|
|
var result = m1.plus(m2);
|
|
|
|
var result = m1.minus(m2);
|
|
|
|
var result = m1.multiply(m2);
|
|
|
|
// matrix vector multiplication (vectors are padded with zeroes to get a 4x1 vector):
|
|
|
|
var vec3d = m1.rightMultiply1x3Vector(vec3d); // matrix * vector
|
|
|
|
var vec3d = m1.leftMultiply1x3Vector(vec3d); // vector * matrix
|
|
|
|
var vec2d = m1.rightMultiply1x2Vector(vec2d); // matrix * vector
|
|
|
|
var vec2d = m1.leftMultiply1x2Vector(vec2d); // vector * matrix
|
|
|
|
// common transformation matrices:
|
|
|
|
var m = CSG.Matrix4x4.rotationX(degrees); // matrix for rotation about X axis
|
|
|
|
var m = CSG.Matrix4x4.rotationY(degrees); // matrix for rotation about Y axis
|
|
|
|
var m = CSG.Matrix4x4.rotationZ(degrees); // matrix for rotation about Z axis
|
|
|
|
var m = CSG.Matrix4x4.rotation(rotationCenter, rotationAxis, degrees); // rotation about arbitrary point and axis
|
|
|
|
var m = CSG.Matrix4x4.translation(vec3d); // translation
|
|
|
|
var m = CSG.Matrix4x4.scaling(vec3d); // scale
|
|
|
|
var m = CSG.Matrix4x4.mirroring(plane); // mirroring in a plane; the argument must be a CSG.Plane
|
|
|
|
// matrix transformations can be concatenated:
|
|
|
|
var transform = CSG.Matrix4x4.rotationX(20).multiply(CSG.Matrix4x4.rotationY(30));
|
|
|
|
// Use a CSG solid's transform() method to apply the transformation to a CSG solid
|
|
|
|
|
|
|
|
// ------------ Plane --------------------------
|
|
|
|
// a 3D plane is represented by a normal vector (should have unit length) and a distance from the origin w
|
|
|
|
// the plane passes through normal.times(w)
|
|
|
|
var plane1 = new CSG.Plane(normal, w);
|
|
|
|
// Or we can construct a plane from 3 points:
|
|
|
|
var plane2 = CSG.Plane.fromPoints(p1, p2, p3);
|
|
|
|
// Or from a normal vector and 1 point:
|
|
|
|
var plane3 = CSG.Plane.fromNormalAndPoint(normal, point);
|
|
|
|
// Flip a plane (front side becomes back side):
|
|
|
|
var plane4 = plane3.flipped();
|
|
|
|
// Apply transformations (rotation, scaling, translation):
|
|
|
|
var transformed = plane3.transformed(matrix4x4); // argument is a CSG.Matrix4x4
|
|
|
|
// Intersection of plane and 3d line:
|
|
|
|
var point = plane3.intersectWithLine(line); // argument is CSG.Line3D, returns a CSG.Vector3D
|
|
|
|
// Intersection of 2 planes:
|
|
|
|
var line = plane3.intersectWithPlane(plane); // argument is another CSG.Plane, returns a CSG.Line3D
|
|
|
|
// Distance to point:
|
|
|
|
var w = signedDistanceToPoint(point); // argument is CSG.Vector3D, returns a float (positive
|
|
|
|
// if in front of plane, negative if in back)
|
|
|
|
|
|
|
|
// ------------ Line3D --------------------------
|
|
|
|
// A line in 3d space is represented by a point and a direction vector.
|
|
|
|
// Direction should be a unit vector. Point can be any point on the line:
|
|
|
|
var line = new CSG.Line3D(point, direction); // argumenst are CSG.Vector3D
|
|
|
|
// or by giving two points:
|
|
|
|
var line = CSG.Line3D.fromPoints(p1, p2); // argumenst are CSG.Vector3D
|
|
|
|
var point = intersectWithPlane(plane); // == plane.intersectWithLine(this)
|
|
|
|
var line2 = line.reverse(); // same line but reverse direction
|
|
|
|
var line2 = line.transform(matrix4x4); // for rotation, scaling, etc
|
|
|
|
var p = line.closestPointOnLine(point); // project point onto the line
|
|
|
|
var d = line.distanceToPoint(point);
|
|
|
|
</pre>
|
|
|
|
|
|
|
|
</div> <!-- help -->
|
|
|
|
</body>
|
|
|
|
</html>
|