Skip to content
Snippets Groups Projects
Commit e992799c authored by Phil Hughes's avatar Phil Hughes Committed by Jacob Schatz
Browse files

STL file viewer

parent 49bdd8d6
No related branches found
No related tags found
No related merge requests found
import * as THREE from 'three/build/three.module';
import STLLoaderClass from 'three-stl-loader';
import OrbitControlsClass from 'three-orbit-controls';
import MeshObject from './mesh_object';
const STLLoader = STLLoaderClass(THREE);
const OrbitControls = OrbitControlsClass(THREE);
export default class Renderer {
constructor(container) {
this.renderWrapper = this.render.bind(this);
this.objects = [];
this.container = container;
this.width = this.container.offsetWidth;
this.height = 500;
this.loader = new STLLoader();
this.fov = 45;
this.camera = new THREE.PerspectiveCamera(
this.fov,
this.width / this.height,
1,
1000,
);
this.scene = new THREE.Scene();
this.scene.add(this.camera);
// Setup the viewer
this.setupRenderer();
this.setupGrid();
this.setupLight();
// Setup OrbitControls
this.controls = new OrbitControls(
this.camera,
this.renderer.domElement,
);
this.controls.minDistance = 5;
this.controls.maxDistance = 30;
this.controls.enableKeys = false;
this.loadFile();
}
setupRenderer() {
this.renderer = new THREE.WebGLRenderer({
antialias: true,
});
this.renderer.setClearColor(0xFFFFFF);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(
this.width,
this.height,
);
}
setupLight() {
// Point light illuminates the object
const pointLight = new THREE.PointLight(
0xFFFFFF,
2,
0,
);
pointLight.castShadow = true;
this.camera.add(pointLight);
// Ambient light illuminates the scene
const ambientLight = new THREE.AmbientLight(
0xFFFFFF,
1,
);
this.scene.add(ambientLight);
}
setupGrid() {
this.grid = new THREE.GridHelper(
20,
20,
0x000000,
0x000000,
);
this.scene.add(this.grid);
}
loadFile() {
this.loader.load(this.container.dataset.endpoint, (geo) => {
const obj = new MeshObject(geo);
this.objects.push(obj);
this.scene.add(obj);
this.start();
this.setDefaultCameraPosition();
});
}
start() {
// Empty the container first
this.container.innerHTML = '';
// Add to DOM
this.container.appendChild(this.renderer.domElement);
// Make controls visible
this.container.parentNode.classList.remove('is-stl-loading');
this.render();
}
render() {
this.renderer.render(
this.scene,
this.camera,
);
requestAnimationFrame(this.renderWrapper);
}
changeObjectMaterials(type) {
this.objects.forEach((obj) => {
obj.changeMaterial(type);
});
}
setDefaultCameraPosition() {
const obj = this.objects[0];
const radius = (obj.geometry.boundingSphere.radius / 1.5);
const dist = radius / (Math.sin((this.fov * (Math.PI / 180)) / 2));
this.camera.position.set(
0,
dist + 1,
dist,
);
this.camera.lookAt(this.grid);
this.controls.update();
}
}
import {
Matrix4,
MeshLambertMaterial,
Mesh,
} from 'three/build/three.module';
const defaultColor = 0xE24329;
const materials = {
default: new MeshLambertMaterial({
color: defaultColor,
}),
wireframe: new MeshLambertMaterial({
color: defaultColor,
wireframe: true,
}),
};
export default class MeshObject extends Mesh {
constructor(geo) {
super(
geo,
materials.default,
);
this.geometry.computeBoundingSphere();
this.rotation.set(-Math.PI / 2, 0, 0);
if (this.geometry.boundingSphere.radius > 4) {
const scale = 4 / this.geometry.boundingSphere.radius;
this.geometry.applyMatrix(
new Matrix4().makeScale(
scale,
scale,
scale,
),
);
this.geometry.computeBoundingSphere();
this.position.x = -this.geometry.boundingSphere.center.x;
this.position.z = this.geometry.boundingSphere.center.y;
}
}
changeMaterial(type) {
this.material = materials[type];
}
}
import Renderer from './3d_viewer';
document.addEventListener('DOMContentLoaded', () => {
const viewer = new Renderer(document.getElementById('js-stl-viewer'));
[].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => {
el.addEventListener('click', (e) => {
const target = e.target;
e.preventDefault();
document.querySelector('.js-material-changer.active').classList.remove('active');
target.classList.add('active');
target.blur();
viewer.changeObjectMaterials(target.dataset.type);
});
});
});
Loading
Loading
@@ -275,3 +275,9 @@ span.idiff {
}
}
}
.is-stl-loading {
.stl-controls {
display: none;
}
}
Loading
Loading
@@ -58,6 +58,10 @@ class Blob < SimpleDelegator
binary? && extname.downcase.delete('.') == 'sketch'
end
 
def stl?
extname.downcase.delete('.') == 'stl'
end
def size_within_svg_limits?
size <= MAXIMUM_SVG_SIZE
end
Loading
Loading
@@ -81,6 +85,8 @@ class Blob < SimpleDelegator
'notebook'
elsif sketch?
'sketch'
elsif stl?
'stl'
elsif text?
'text'
else
Loading
Loading
- content_for :page_specific_javascripts do
= page_specific_javascript_bundle_tag('stl_viewer')
.file-content.is-stl-loading
.text-center#js-stl-viewer{ data: { endpoint: namespace_project_raw_path(@project.namespace, @project, @id) } }
= icon('spinner spin 2x', class: 'prepend-top-default append-bottom-default', 'aria-hidden' => 'true', 'aria-label' => 'Loading')
.text-center.prepend-top-default.append-bottom-default.stl-controls
.btn-group
%button.btn.btn-default.btn-sm.js-material-changer{ data: { type: 'wireframe' } }
Wireframe
%button.btn.btn-default.btn-sm.active.js-material-changer{ data: { type: 'default' } }
Solid
Loading
Loading
@@ -42,6 +42,7 @@ var config = {
profile: './profile/profile_bundle.js',
protected_branches: './protected_branches/protected_branches_bundle.js',
snippet: './snippet/snippet_bundle.js',
stl_viewer: './blob/stl_viewer.js',
terminal: './terminal/terminal_bundle.js',
u2f: ['vendor/u2f'],
users: './users/users_bundle.js',
Loading
Loading
Loading
Loading
@@ -36,6 +36,9 @@
"raw-loader": "^0.5.1",
"select2": "3.5.2-browserify",
"stats-webpack-plugin": "^0.4.3",
"three": "^0.84.0",
"three-orbit-controls": "^82.1.0",
"three-stl-loader": "^1.0.4",
"timeago.js": "^2.0.5",
"underscore": "^1.8.3",
"visibilityjs": "^1.2.4",
Loading
Loading
import {
BoxGeometry,
} from 'three/build/three.module';
import MeshObject from '~/blob/3d_viewer/mesh_object';
describe('Mesh object', () => {
it('defaults to non-wireframe material', () => {
const object = new MeshObject(
new BoxGeometry(10, 10, 10),
);
expect(object.material.wireframe).toBeFalsy();
});
it('changes to wirefame material', () => {
const object = new MeshObject(
new BoxGeometry(10, 10, 10),
);
object.changeMaterial('wireframe');
expect(object.material.wireframe).toBeTruthy();
});
it('scales object down', () => {
const object = new MeshObject(
new BoxGeometry(10, 10, 10),
);
const radius = object.geometry.boundingSphere.radius;
expect(radius).not.toBeGreaterThan(4);
});
it('does not scale object down', () => {
const object = new MeshObject(
new BoxGeometry(1, 1, 1),
);
const radius = object.geometry.boundingSphere.radius;
expect(radius).toBeLessThan(1);
});
});
Loading
Loading
@@ -111,6 +111,20 @@ describe Blob do
end
end
 
describe '#stl?' do
it 'is falsey with image extension' do
git_blob = Gitlab::Git::Blob.new(name: 'file.png')
expect(described_class.decorate(git_blob)).not_to be_stl
end
it 'is truthy with STL extension' do
git_blob = Gitlab::Git::Blob.new(name: 'file.stl')
expect(described_class.decorate(git_blob)).to be_stl
end
end
describe '#to_partial_path' do
let(:project) { double(lfs_enabled?: true) }
 
Loading
Loading
@@ -122,7 +136,8 @@ describe Blob do
lfs_pointer?: false,
svg?: false,
text?: false,
binary?: false
binary?: false,
stl?: false
)
 
described_class.decorate(double).tap do |blob|
Loading
Loading
@@ -175,6 +190,11 @@ describe Blob do
blob = stubbed_blob(text?: true, sketch?: true, binary?: true)
expect(blob.to_partial_path(project)).to eq 'sketch'
end
it 'handles STLs' do
blob = stubbed_blob(text?: true, stl?: true)
expect(blob.to_partial_path(project)).to eq 'stl'
end
end
 
describe '#size_within_svg_limits?' do
Loading
Loading
Loading
Loading
@@ -4305,6 +4305,18 @@ text-table@~0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
 
three-orbit-controls@^82.1.0:
version "82.1.0"
resolved "https://registry.yarnpkg.com/three-orbit-controls/-/three-orbit-controls-82.1.0.tgz#11a7f33d0a20ecec98f098b37780f6537374fab4"
three-stl-loader@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/three-stl-loader/-/three-stl-loader-1.0.4.tgz#6b3319a31e3b910aab1883d19b00c81a663c3e03"
three@^0.84.0:
version "0.84.0"
resolved "https://registry.yarnpkg.com/three/-/three-0.84.0.tgz#95be85a55a0fa002aa625ed559130957dcffd918"
throttleit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c"
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment