forked from M-Labs/web2019
feat(website: Adds solvespace
This commit is contained in:
parent
73d7842db0
commit
8155ff5e2c
|
@ -1,4 +1,21 @@
|
|||
+++
|
||||
title = "Smoltcp"
|
||||
title = "smoltcp"
|
||||
weight = 2
|
||||
+++
|
||||
|
||||
{% layoutsmall(title="smoltcp is a standalone, event-driven TCP/IP stack that is designed for bare-metal, real-time systems.") %}
|
||||
|
||||
Its design goals are simplicity and robustness. Its design anti-goals include complicated compile-time computations, such as macro or type tricks, even at cost of performance degradation.
|
||||
|
||||
|
||||
smoltcp does not need heap allocation at all, is <a href="https://docs.rs/smoltcp/" target="_blank" rel="noopener noreferrer">extensively documented</a>, and compiles on stable Rust 1.20 and later. It is used in embedded systems such as the ARTIQ core device and ionpak.
|
||||
|
||||
|
||||
smoltcp achieves <a href="https://github.com/m-labs/smoltcp#examplesbenchmarkrs" target="_blank" rel="noopener noreferrer">~Gbps</a> of throughput when tested against the Linux TCP stack in loopback mode.
|
||||
|
||||
|
||||
The source code is available <a href="https://github.com/m-labs/smoltcp" rel="noopener noreferrer">on GitHub</a>.
|
||||
|
||||
**Commercial support for smoltcp is available.** Email sales@m-l***s.hk.
|
||||
|
||||
{% end %}
|
|
@ -1,4 +1,227 @@
|
|||
+++
|
||||
title = "SolveSpace"
|
||||
weight = 1
|
||||
template = "page.html"
|
||||
|
||||
[extra]
|
||||
layoutcss = "col-12 col-md-10 mx-auto"
|
||||
+++
|
||||
|
||||
{% div() %}
|
||||
<script src="/js/hammer-2.0.8.js"></script>
|
||||
<script src="/js/three-r76.js"></script>
|
||||
<script src="/js/SolveSpaceControls.js"></script>
|
||||
{% end %}
|
||||
|
||||
|
||||
{% centerp(markdown=true) %}
|
||||
##### What is SolveSpace?
|
||||
{% end %}
|
||||
|
||||
<a href="http://solvespace.com/" target="_blank" rel="noopener noreferrer">SolveSpace</a> is a libre and open-source parametric computer-aided design application that uses a NURBS geometric kernel, allowing it to represent curved surfaces exactly. It was originally released by <a href="http://cq.cx/" target="_blank" rel="noopener noreferrer">Jonathan Westhues</a> under the GPLv3 license and is now further developed at M-Labs.
|
||||
|
||||
|
||||
{% centerp(markdown=true, css="mt-5") %}
|
||||
##### What can be done with SolveSpace?
|
||||
{% end %}
|
||||
|
||||
SolveSpace is primarily useful for mechanical design. At M-Labs, we use it to design vacuum chambers as well as custom fittings and fixtures. The models can then be exported as STEP or PDF with dimensions and sent to a machine shop for manufacturing.
|
||||
|
||||
|
||||
{% centerp(markdown=true, css="mt-5") %}
|
||||
**Fittings and adapters**
|
||||
{% end %}
|
||||
|
||||
Of course, most vacuum chambers would have some standard flanges. These can be easily modelled using a single sketch and a lathe operation:
|
||||
|
||||
{% layoutlr1() %}
|
||||
|
||||
<div class="col-12 d-flex flex-column flex-lg-row justify-content-center justify-content-md-between align-items-center">
|
||||
|
||||
<img class="img-fluid" src="/images/kf25-section.png" width="400" height="100%">
|
||||
|
||||
<div id="kf25"></div>
|
||||
<script src="/js/models/kf25.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var node = document.getElementById("kf25");
|
||||
var params = {width: 400, height: 300, scale: 10, offset: new THREE.Vector3(-8, 0, 0)};
|
||||
node.parentNode.replaceChild(solvespace(solvespace_model_kf25, params), node);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
|
||||
|
||||
{% centerp(markdown=true, css="mt-3") %}
|
||||
|
||||
{% end %}
|
||||
|
||||
(The illustration on the right is "live"; it can be panned, rotated and scaled.)
|
||||
|
||||
In a similar way, an NW160 viewport and an adapter with an NW160 flange, a KF40, two KF25 and two KF16 ports were designed:
|
||||
|
||||
{% layoutlr1() %}
|
||||
<div class="col-12 d-flex flex-column flex-lg-row justify-content-center justify-content-md-between align-items-center">
|
||||
|
||||
<div id="viewport"></div>
|
||||
<script src="/js/models/viewport.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var node = document.getElementById("viewport");
|
||||
var params = {width: 400, height: 300, scale: 3};
|
||||
node.parentNode.replaceChild(solvespace(solvespace_model_viewport, params), node);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div id="multiport"></div>
|
||||
<script src="/js/models/multiport.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var node = document.getElementById("multiport");
|
||||
var params = {width: 400, height: 300, scale: 3};
|
||||
node.parentNode.replaceChild(solvespace(solvespace_model_multiport, params), node);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
|
||||
|
||||
{% centerp(markdown=true, css="mt-5") %}
|
||||
**A vacuum chamber**
|
||||
{% end %}
|
||||
|
||||
The fittings above were designed to be used with a cylindrical vacuum chamber, 200mm long with two NW160 flanges, two KF40 and one KF25 ports:
|
||||
|
||||
{% layoutlr1() %}
|
||||
<div class="col-12 d-flex flex-column flex-lg-row justify-content-center align-items-center">
|
||||
|
||||
<div id="chamber"></div>
|
||||
<script src="/js/models/chamber.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var node = document.getElementById("chamber");
|
||||
var params = {width: 800, height: 600, scale: 3};
|
||||
node.parentNode.replaceChild(solvespace(solvespace_model_chamber, params), node);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
|
||||
|
||||
{% centerp(markdown=true, css="mt-5") %}
|
||||
**Cryocooler refilling fixture**
|
||||
{% end %}
|
||||
|
||||
To repair a broken Ricor K526S crycooler, it was necessary to disassemble it, which of course meant it was depressurized. To evacuate it and eventually refill with helium, a special fixture was designed, as the cryocooler's filling port is just a hole in the case with a set screw. Further, an adapter that allows to insert the cold head into a vacuum system via a KF25 port was also designed.
|
||||
|
||||
First, the cryocooler and its cold head were modelled to verify fits:
|
||||
|
||||
{% layoutlr1() %}
|
||||
<div class="col-12 d-flex flex-column flex-lg-row justify-content-center justify-content-md-between align-items-center mb-3">
|
||||
|
||||
<div id="k526s-body"></div>
|
||||
<script src="/js/models/k526s-body.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var node = document.getElementById("k526s-body");
|
||||
var params = {width: 400, height: 300, scale: 6, offset: new THREE.Vector3(-10, -15, 0)};
|
||||
node.parentNode.replaceChild(solvespace(solvespace_model_k526s_body, params), node);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div id="k526s-head"></div>
|
||||
<script src="/js/models/k526s-head.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var node = document.getElementById("k526s-head");
|
||||
var params = {width: 400, height: 300, scale: 8, offset: new THREE.Vector3(25, 0, 0)};
|
||||
node.parentNode.replaceChild(solvespace(solvespace_model_k526s_head, params), node);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
Then, a fixture was designed, shown below with a cutout (blue). It consists of a bracket, a piston case that screws into the bracket (the thread is not modelled), a piston with a retaining screw, a retaining washer, a Swagelok fitting attached to the orange port (not modelled), and a few compression gaskets (also not modelled). The bracket and the piston case hold the cryocooler The retaining washer prevents the piston from being ejected by high pressure helium and, conversely, together with another washer prevents the piston from being sucked in and blocking the gas flow during evacuation.
|
||||
|
||||
{% layoutlr1() %}
|
||||
<div class="col-12 d-flex flex-column flex-lg-row justify-content-center align-items-center mb-3">
|
||||
|
||||
<div id="k526s-fixture"></div>
|
||||
<script src="/js/models/k526s-fixture.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var node = document.getElementById("k526s-fixture");
|
||||
var params = {width: 800, height: 400, scale: 12};
|
||||
node.parentNode.replaceChild(solvespace(solvespace_model_k526s_fixture, params), node);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
The KF25 adapter for the cold head is a much simpler device:
|
||||
|
||||
{% layoutlr1() %}
|
||||
<div class="col-12 d-flex flex-column flex-lg-row justify-content-center justify-content-md-between align-items-center mb-5">
|
||||
|
||||
<div id="k526s-adapter"></div>
|
||||
<script src="/js/models/k526s-adapter.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var node = document.getElementById("k526s-adapter");
|
||||
var params = {width: 400, height: 300, scale: 8, offset: new THREE.Vector3(-8, 0, 0)};
|
||||
node.parentNode.replaceChild(solvespace(solvespace_model_k526s_adapter, params), node);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div id="k526s-adapter-assy"></div>
|
||||
<script src="/js/models/k526s-adapter-assy.js"></script>
|
||||
<script>
|
||||
(function () {
|
||||
var node = document.getElementById("k526s-adapter-assy");
|
||||
var params = {width: 400, height: 300, scale: 8};
|
||||
node.parentNode.replaceChild(solvespace(solvespace_model_k526s_adapter_assy, params), node);
|
||||
})();
|
||||
</script>
|
||||
|
||||
</div>
|
||||
{% end %}
|
||||
|
||||
|
||||
|
||||
{% centerp(markdown=true) %}
|
||||
##### Ongoing development by M-Labs
|
||||
{% end %}
|
||||
|
||||
|
||||
As originally released, SolveSpace was far ahead almost every other FLOSS CAD by virtue of its parametric nature, exact internal representation of curves and a codebase easy to work with. (The only other FLOSS parametric CAD that uses a NURBS representation, including NURBS booleans, is FreeCAD.) While it was already suitable for practical work, it had a much greater unrealized potential.
|
||||
|
||||
Thus, M-Labs has developed many additional features:
|
||||
|
||||
- Native Linux (GTK) and OS X ports;
|
||||
|
||||
- stippling as well as <a href="https://m-labs.hk/solvespace/images/hiddenline.gif" target="_blank" rel="noopener noreferrer">outline and hidden line</a> styling in preparation for export of shop drawings according to ISO or another standard;
|
||||
|
||||
- WebGL export using Three.js, which is how the interactive models on this page work;
|
||||
|
||||
- DXF export that preserves the ability to edit the drawing afterwards, by mapping parametric constraints to DXF dimensions and grouping the lines;
|
||||
|
||||
- DXF import that automatically infers (some) constraints, such as horizontal/vertical, point-coincident, linear and angular dimensions from DXF geometry and dimensions;
|
||||
|
||||
- internationalization;
|
||||
|
||||
- and many other minor ones.
|
||||
|
||||
|
||||
Currently, the focus of development is to improve SolveSpace's handling of complex assemblies with many similar parts by allowing to load a hierarchy of sketches instead of a single sketch and propagate the changes as they are made, and to derive many variants of geometry from a single sketch. For example, these changes would allow to use a single basic sketch to model framework made from varying lengths of 80/20 profile, whereas currently that would require a separate sketch for every size of cut.
|
||||
|
||||
**For commercial support and licensing**, please contact sales@m-l***s.hk.
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,490 @@
|
|||
window.devicePixelRatio = window.devicePixelRatio || 1;
|
||||
|
||||
SolvespaceCamera = function(renderWidth, renderHeight, scale, up, right, offset) {
|
||||
THREE.Camera.call(this);
|
||||
|
||||
this.type = 'SolvespaceCamera';
|
||||
|
||||
this.renderWidth = renderWidth;
|
||||
this.renderHeight = renderHeight;
|
||||
this.zoomScale = scale; /* Avoid namespace collision w/ THREE.Object.scale */
|
||||
this.up = up;
|
||||
this.right = right;
|
||||
this.offset = offset;
|
||||
this.depthBias = 0;
|
||||
|
||||
this.updateProjectionMatrix();
|
||||
};
|
||||
|
||||
SolvespaceCamera.prototype = Object.create(THREE.Camera.prototype);
|
||||
SolvespaceCamera.prototype.constructor = SolvespaceCamera;
|
||||
SolvespaceCamera.prototype.updateProjectionMatrix = function() {
|
||||
var temp = new THREE.Matrix4();
|
||||
var offset = new THREE.Matrix4().makeTranslation(this.offset.x, this.offset.y, this.offset.z);
|
||||
// Convert to right handed- do up cross right instead.
|
||||
var n = new THREE.Vector3().crossVectors(this.up, this.right);
|
||||
var rotate = new THREE.Matrix4().makeBasis(this.right, this.up, n);
|
||||
rotate.transpose();
|
||||
/* FIXME: At some point we ended up using row-major.
|
||||
THREE.js wants column major. Scale/depth correct unaffected b/c diagonal
|
||||
matrices remain the same when transposed. makeTranslation also makes
|
||||
a column-major matrix. */
|
||||
|
||||
/* TODO: If we want perspective, we need an additional matrix
|
||||
here which will modify w for perspective divide. */
|
||||
var scale = new THREE.Matrix4().makeScale(2 * this.zoomScale / this.renderWidth,
|
||||
2 * this.zoomScale / this.renderHeight, this.zoomScale / 30000.0);
|
||||
|
||||
temp.multiply(scale);
|
||||
temp.multiply(rotate);
|
||||
temp.multiply(offset);
|
||||
|
||||
this.projectionMatrix.copy(temp);
|
||||
};
|
||||
|
||||
SolvespaceCamera.prototype.NormalizeProjectionVectors = function() {
|
||||
/* After rotating, up and right may no longer be orthogonal.
|
||||
However, their cross product will produce the correct
|
||||
rotated plane, and we can recover an orthogonal basis. */
|
||||
var n = new THREE.Vector3().crossVectors(this.right, this.up);
|
||||
this.up = new THREE.Vector3().crossVectors(n, this.right);
|
||||
this.right.normalize();
|
||||
this.up.normalize();
|
||||
};
|
||||
|
||||
SolvespaceCamera.prototype.rotate = function(right, up) {
|
||||
var oldRight = new THREE.Vector3().copy(this.right).normalize();
|
||||
var oldUp = new THREE.Vector3().copy(this.up).normalize();
|
||||
this.up.applyAxisAngle(oldRight, up);
|
||||
this.right.applyAxisAngle(oldUp, right);
|
||||
this.NormalizeProjectionVectors();
|
||||
}
|
||||
|
||||
SolvespaceCamera.prototype.offsetProj = function(right, up) {
|
||||
var shift = new THREE.Vector3(right * this.right.x + up * this.up.x,
|
||||
right * this.right.y + up * this.up.y,
|
||||
right * this.right.z + up * this.up.z);
|
||||
this.offset.add(shift);
|
||||
}
|
||||
|
||||
/* Calculate the offset in terms of up and right projection vectors
|
||||
that will preserve the world coordinates of the current mouse position after
|
||||
the zoom. */
|
||||
SolvespaceCamera.prototype.zoomTo = function(x, y, delta) {
|
||||
// Get offset components in world coordinates, in terms of up/right.
|
||||
var projOffsetX = this.offset.dot(this.right);
|
||||
var projOffsetY = this.offset.dot(this.up);
|
||||
|
||||
/* Remove offset before scaling so, that mouse position changes
|
||||
proportionally to the model and independent of current offset. */
|
||||
var centerRightI = x/this.zoomScale - projOffsetX;
|
||||
var centerUpI = y/this.zoomScale - projOffsetY;
|
||||
var zoomFactor;
|
||||
|
||||
/* Zoom 20% every 100 delta. */
|
||||
if(delta < 0) {
|
||||
zoomFactor = (-delta * 0.002 + 1);
|
||||
}
|
||||
else if(delta > 0) {
|
||||
zoomFactor = (delta * (-1.0/600.0) + 1)
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
|
||||
this.zoomScale = this.zoomScale * zoomFactor;
|
||||
var centerRightF = x/this.zoomScale - projOffsetX;
|
||||
var centerUpF = y/this.zoomScale - projOffsetY;
|
||||
|
||||
this.offset.addScaledVector(this.right, centerRightF - centerRightI);
|
||||
this.offset.addScaledVector(this.up, centerUpF - centerUpI);
|
||||
}
|
||||
|
||||
|
||||
SolvespaceControls = function(object, domElement) {
|
||||
var _this = this;
|
||||
this.object = object;
|
||||
this.domElement = ( domElement !== undefined ) ? domElement : document;
|
||||
|
||||
var threePan = new Hammer.Pan({event : 'threepan', pointers : 3, enable : false});
|
||||
var panAfterTap = new Hammer.Pan({event : 'panaftertap', enable : false});
|
||||
|
||||
this.touchControls = new Hammer.Manager(domElement, {
|
||||
recognizers: [
|
||||
[Hammer.Pinch, { enable: true }],
|
||||
[Hammer.Pan],
|
||||
[Hammer.Tap],
|
||||
]
|
||||
});
|
||||
|
||||
this.touchControls.add(threePan);
|
||||
this.touchControls.add(panAfterTap);
|
||||
|
||||
var changeEvent = {
|
||||
type: 'change'
|
||||
};
|
||||
var startEvent = {
|
||||
type: 'start'
|
||||
};
|
||||
var endEvent = {
|
||||
type: 'end'
|
||||
};
|
||||
|
||||
var _changed = false;
|
||||
var _mouseMoved = false;
|
||||
//var _touchPoints = new Array();
|
||||
var _offsetPrev = new THREE.Vector2(0, 0);
|
||||
var _offsetCur = new THREE.Vector2(0, 0);
|
||||
var _rotatePrev = new THREE.Vector2(0, 0);
|
||||
var _rotateCur = new THREE.Vector2(0, 0);
|
||||
|
||||
// Used during touch events.
|
||||
var _rotateOrig = new THREE.Vector2(0, 0);
|
||||
var _offsetOrig = new THREE.Vector2(0, 0);
|
||||
var _prevScale = 1.0;
|
||||
|
||||
this.handleEvent = function(event) {
|
||||
if (typeof this[event.type] == 'function') {
|
||||
this[event.type](event);
|
||||
}
|
||||
}
|
||||
|
||||
function mousedown(event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
switch (event.button) {
|
||||
case 0:
|
||||
_rotateCur.set(event.screenX/window.devicePixelRatio, event.screenY/window.devicePixelRatio);
|
||||
_rotatePrev.copy(_rotateCur);
|
||||
document.addEventListener('mousemove', mousemove, false);
|
||||
document.addEventListener('mouseup', mouseup, false);
|
||||
break;
|
||||
case 2:
|
||||
_offsetCur.set(event.screenX/window.devicePixelRatio, event.screenY/window.devicePixelRatio);
|
||||
_offsetPrev.copy(_offsetCur);
|
||||
document.addEventListener('mousemove', mousemove, false);
|
||||
document.addEventListener('mouseup', mouseup, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function wheel( event ) {
|
||||
event.preventDefault();
|
||||
/* FIXME: Width and height might not be supported universally, but
|
||||
can be calculated? */
|
||||
var box = _this.domElement.getBoundingClientRect();
|
||||
object.zoomTo(event.clientX - box.width/2 - box.left,
|
||||
-(event.clientY - box.height/2 - box.top), event.deltaY);
|
||||
_changed = true;
|
||||
}
|
||||
|
||||
function mousemove(event) {
|
||||
switch (event.button) {
|
||||
case 0:
|
||||
_rotateCur.set(event.screenX/window.devicePixelRatio, event.screenY/window.devicePixelRatio);
|
||||
var diff = new THREE.Vector2().subVectors(_rotateCur, _rotatePrev)
|
||||
.multiplyScalar(1 / object.zoomScale);
|
||||
object.rotate(-0.3 * Math.PI / 180 * diff.x * object.zoomScale,
|
||||
-0.3 * Math.PI / 180 * diff.y * object.zoomScale);
|
||||
_changed = true;
|
||||
_rotatePrev.copy(_rotateCur);
|
||||
break;
|
||||
case 2:
|
||||
_mouseMoved = true;
|
||||
_offsetCur.set(event.screenX/window.devicePixelRatio, event.screenY/window.devicePixelRatio);
|
||||
var diff = new THREE.Vector2().subVectors(_offsetCur, _offsetPrev)
|
||||
.multiplyScalar(1 / object.zoomScale);
|
||||
object.offsetProj(diff.x, -diff.y);
|
||||
_changed = true;
|
||||
_offsetPrev.copy(_offsetCur)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function mouseup(event) {
|
||||
/* TODO: Opera mouse gestures will intercept this event, making it
|
||||
possible to have multiple mousedown events consecutively without
|
||||
a corresponding mouseup (so multiple viewports can be rotated/panned
|
||||
simultaneously). Disable mouse gestures for now. */
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
document.removeEventListener('mousemove', mousemove);
|
||||
document.removeEventListener('mouseup', mouseup);
|
||||
|
||||
_this.dispatchEvent(endEvent);
|
||||
}
|
||||
|
||||
function pan(event) {
|
||||
/* neWcur - prev does not necessarily equal (cur + diff) - prev.
|
||||
Floating point is not associative. */
|
||||
touchDiff = new THREE.Vector2(event.deltaX, event.deltaY);
|
||||
_rotateCur.addVectors(_rotateOrig, touchDiff);
|
||||
incDiff = new THREE.Vector2().subVectors(_rotateCur, _rotatePrev)
|
||||
.multiplyScalar(1 / object.zoomScale);
|
||||
object.rotate(-0.3 * Math.PI / 180 * incDiff.x * object.zoomScale,
|
||||
-0.3 * Math.PI / 180 * incDiff.y * object.zoomScale);
|
||||
_changed = true;
|
||||
_rotatePrev.copy(_rotateCur);
|
||||
}
|
||||
|
||||
function panstart(event) {
|
||||
/* TODO: Dynamically enable pan function? */
|
||||
_rotateOrig.copy(_rotateCur);
|
||||
}
|
||||
|
||||
function pinchstart(event) {
|
||||
_prevScale = event.scale;
|
||||
}
|
||||
|
||||
function pinch(event) {
|
||||
/* FIXME: Width and height might not be supported universally, but
|
||||
can be calculated? */
|
||||
var box = _this.domElement.getBoundingClientRect();
|
||||
|
||||
/* 16.6... pixels chosen heuristically... matches my touchpad. */
|
||||
if (event.scale < _prevScale) {
|
||||
object.zoomTo(event.center.x - box.width/2 - box.left,
|
||||
-(event.center.y - box.height/2 - box.top), 100/6.0);
|
||||
_changed = true;
|
||||
} else if (event.scale > _prevScale) {
|
||||
object.zoomTo(event.center.x - box.width/2 - box.left,
|
||||
-(event.center.y - box.height/2 - box.top), -100/6.0);
|
||||
_changed = true;
|
||||
}
|
||||
|
||||
_prevScale = event.scale;
|
||||
}
|
||||
|
||||
/* A tap will enable panning/disable rotate. */
|
||||
function tap(event) {
|
||||
panAfterTap.set({enable : true});
|
||||
_this.touchControls.get('pan').set({enable : false});
|
||||
}
|
||||
|
||||
function panaftertap(event) {
|
||||
touchDiff = new THREE.Vector2(event.deltaX, event.deltaY);
|
||||
_offsetCur.addVectors(_offsetOrig, touchDiff);
|
||||
incDiff = new THREE.Vector2().subVectors(_offsetCur, _offsetPrev)
|
||||
.multiplyScalar(1 / object.zoomScale);
|
||||
object.offsetProj(incDiff.x, -incDiff.y);
|
||||
_changed = true;
|
||||
_offsetPrev.copy(_offsetCur);
|
||||
}
|
||||
|
||||
function panaftertapstart(event) {
|
||||
_offsetOrig.copy(_offsetCur);
|
||||
}
|
||||
|
||||
function panaftertapend(event) {
|
||||
panAfterTap.set({enable : false});
|
||||
_this.touchControls.get('pan').set({enable : true});
|
||||
}
|
||||
|
||||
function contextmenu(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
this.update = function() {
|
||||
if (_changed) {
|
||||
_this.dispatchEvent(changeEvent);
|
||||
_changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.domElement.addEventListener('mousedown', mousedown, false);
|
||||
this.domElement.addEventListener('wheel', wheel, false);
|
||||
this.domElement.addEventListener('contextmenu', contextmenu, false);
|
||||
|
||||
/* Hammer.on wraps addEventListener */
|
||||
// Rotate
|
||||
this.touchControls.on('pan', pan);
|
||||
this.touchControls.on('panstart', panstart);
|
||||
|
||||
// Zoom
|
||||
this.touchControls.on('pinch', pinch);
|
||||
this.touchControls.on('pinchstart', pinchstart);
|
||||
|
||||
//Pan
|
||||
this.touchControls.on('tap', tap);
|
||||
this.touchControls.on('panaftertapstart', panaftertapstart);
|
||||
this.touchControls.on('panaftertap', panaftertap);
|
||||
this.touchControls.on('panaftertapend', panaftertapend);
|
||||
}
|
||||
|
||||
SolvespaceControls.prototype = Object.create(THREE.EventDispatcher.prototype);
|
||||
SolvespaceControls.prototype.constructor = SolvespaceControls;
|
||||
|
||||
|
||||
solvespace = function(obj, params) {
|
||||
var scene, edgeScene, camera, edgeCamera, renderer;
|
||||
var geometry, controls, material, mesh, edges;
|
||||
var width, height, scale, offset;
|
||||
var directionalLightArray = [];
|
||||
|
||||
if (typeof params === "undefined" || !("width" in params)) {
|
||||
width = window.innerWidth;
|
||||
} else {
|
||||
width = params.width;
|
||||
}
|
||||
|
||||
if (typeof params === "undefined" || !("height" in params)) {
|
||||
height = window.innerHeight;
|
||||
} else {
|
||||
height = params.height;
|
||||
}
|
||||
|
||||
if (typeof params === "undefined" || !("scale" in params)) {
|
||||
scale = 5;
|
||||
} else {
|
||||
scale = params.scale;
|
||||
}
|
||||
|
||||
if (typeof params === "undefined" || !("offset" in params)) {
|
||||
offset = new THREE.Vector3(0, 0, 0);
|
||||
} else {
|
||||
offset = params.offset;
|
||||
}
|
||||
|
||||
width *= window.devicePixelRatio;
|
||||
height *= window.devicePixelRatio;
|
||||
|
||||
domElement = init();
|
||||
render();
|
||||
return domElement;
|
||||
|
||||
|
||||
function init() {
|
||||
scene = new THREE.Scene();
|
||||
edgeScene = new THREE.Scene();
|
||||
|
||||
camera = new SolvespaceCamera(width/window.devicePixelRatio,
|
||||
height/window.devicePixelRatio, scale, new THREE.Vector3(0, 1, 0),
|
||||
new THREE.Vector3(0.5, 0, -0.5).normalize(), offset);
|
||||
|
||||
mesh = createMesh(obj);
|
||||
scene.add(mesh);
|
||||
edges = createEdges(obj);
|
||||
edgeScene.add(edges);
|
||||
|
||||
for (var i = 0; i < obj.lights.d.length; i++) {
|
||||
var lightColor = new THREE.Color(obj.lights.d[i].intensity,
|
||||
obj.lights.d[i].intensity, obj.lights.d[i].intensity);
|
||||
var directionalLight = new THREE.DirectionalLight(lightColor, 1);
|
||||
directionalLight.position.set(obj.lights.d[i].direction[0],
|
||||
obj.lights.d[i].direction[1], obj.lights.d[i].direction[2]);
|
||||
directionalLightArray.push(directionalLight);
|
||||
scene.add(directionalLight);
|
||||
}
|
||||
|
||||
var lightColor = new THREE.Color(obj.lights.a, obj.lights.a, obj.lights.a);
|
||||
var ambientLight = new THREE.AmbientLight(lightColor.getHex());
|
||||
scene.add(ambientLight);
|
||||
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true});
|
||||
renderer.setSize(width, height);
|
||||
renderer.autoClear = false;
|
||||
renderer.domElement.style = "width:"+width/window.devicePixelRatio+"px;height:"+height/window.devicePixelRatio+"px;";
|
||||
|
||||
controls = new SolvespaceControls(camera, renderer.domElement);
|
||||
controls.addEventListener("change", render);
|
||||
controls.addEventListener("change", lightUpdate);
|
||||
|
||||
animate();
|
||||
return renderer.domElement;
|
||||
}
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
controls.update();
|
||||
}
|
||||
|
||||
function render() {
|
||||
var context = renderer.getContext();
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.clear();
|
||||
|
||||
context.depthRange(0.1, 1);
|
||||
renderer.render(scene, camera);
|
||||
|
||||
context.depthRange(0.1-(2/60000.0), 1-(2/60000.0));
|
||||
renderer.render(edgeScene, camera);
|
||||
}
|
||||
|
||||
function lightUpdate() {
|
||||
var changeBasis = new THREE.Matrix4();
|
||||
|
||||
// The original light positions were in camera space.
|
||||
// Project them into standard space using camera's basis
|
||||
// vectors (up, target, and their cross product).
|
||||
n = new THREE.Vector3().crossVectors(camera.up, camera.right);
|
||||
changeBasis.makeBasis(camera.right, camera.up, n);
|
||||
|
||||
for (var i = 0; i < 2; i++) {
|
||||
var newLightPos = changeBasis.applyToVector3Array(
|
||||
[obj.lights.d[i].direction[0], obj.lights.d[i].direction[1],
|
||||
obj.lights.d[i].direction[2]]);
|
||||
directionalLightArray[i].position.set(newLightPos[0],
|
||||
newLightPos[1], newLightPos[2]);
|
||||
}
|
||||
}
|
||||
|
||||
function createMesh(meshObj) {
|
||||
var geometry = new THREE.Geometry();
|
||||
var materialIndex = 0;
|
||||
var materialList = [];
|
||||
var opacitiesSeen = {};
|
||||
|
||||
for (var i = 0; i < meshObj.points.length; i++) {
|
||||
geometry.vertices.push(new THREE.Vector3(meshObj.points[i][0],
|
||||
meshObj.points[i][1], meshObj.points[i][2]));
|
||||
}
|
||||
|
||||
for (var i = 0; i < meshObj.faces.length; i++) {
|
||||
var currOpacity = ((meshObj.colors[i] & 0xFF000000) >>> 24) / 255.0;
|
||||
if (opacitiesSeen[currOpacity] === undefined) {
|
||||
opacitiesSeen[currOpacity] = materialIndex;
|
||||
materialIndex++;
|
||||
materialList.push(new THREE.MeshLambertMaterial({
|
||||
vertexColors: THREE.FaceColors,
|
||||
opacity: currOpacity,
|
||||
transparent: true,
|
||||
side: THREE.DoubleSide
|
||||
}));
|
||||
}
|
||||
|
||||
geometry.faces.push(new THREE.Face3(meshObj.faces[i][0],
|
||||
meshObj.faces[i][1], meshObj.faces[i][2],
|
||||
[new THREE.Vector3(meshObj.normals[i][0][0],
|
||||
meshObj.normals[i][0][1], meshObj.normals[i][0][2]),
|
||||
new THREE.Vector3(meshObj.normals[i][1][0],
|
||||
meshObj.normals[i][1][1], meshObj.normals[i][1][2]),
|
||||
new THREE.Vector3(meshObj.normals[i][2][0],
|
||||
meshObj.normals[i][2][1], meshObj.normals[i][2][2])],
|
||||
new THREE.Color(meshObj.colors[i] & 0x00FFFFFF),
|
||||
opacitiesSeen[currOpacity]));
|
||||
}
|
||||
|
||||
geometry.computeBoundingSphere();
|
||||
return new THREE.Mesh(geometry, new THREE.MultiMaterial(materialList));
|
||||
}
|
||||
|
||||
function createEdges(meshObj) {
|
||||
var geometry = new THREE.Geometry();
|
||||
var material = new THREE.LineBasicMaterial();
|
||||
|
||||
for (var i = 0; i < meshObj.edges.length; i++) {
|
||||
geometry.vertices.push(new THREE.Vector3(meshObj.edges[i][0][0],
|
||||
meshObj.edges[i][0][1], meshObj.edges[i][0][2]),
|
||||
new THREE.Vector3(meshObj.edges[i][1][0],
|
||||
meshObj.edges[i][1][1], meshObj.edges[i][1][2]));
|
||||
}
|
||||
|
||||
geometry.computeBoundingSphere();
|
||||
return new THREE.LineSegments(geometry, material);
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -48,7 +48,7 @@
|
|||
|
||||
<div class="row pb-5">
|
||||
|
||||
<div class="col-12">
|
||||
<div class="{% if page and page.extra and page.extra.layoutcss %}{{ page.extra.layoutcss }}{% else %}col-12{% endif %}">
|
||||
|
||||
{% block main_content %}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<div class="text-center mt-3">
|
||||
{% if safe %}
|
||||
{{ body | safe}}
|
||||
<div class="text-center {% if css %}{{ css }}{% else %}mt-3{% endif %}">
|
||||
{% if markdown %}
|
||||
{{ body | markdown | safe}}
|
||||
{% elif safe %}
|
||||
{{ body | safe }}
|
||||
{% else %}
|
||||
{{ body }}
|
||||
{% endif %}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<div>
|
||||
{{ body | safe }}
|
||||
</div>
|
|
@ -0,0 +1,8 @@
|
|||
<div class="{% if customcss %}{{ customcss }}{% else %}row{% endif %}">
|
||||
|
||||
<div class="col-12 col-md-8 mx-auto">
|
||||
<h5 class="text-center">{{ title }}</h5>
|
||||
{{ body | markdown | safe }}
|
||||
</div>
|
||||
|
||||
</div>
|
Loading…
Reference in New Issue