Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…

// ----------------------------------------------------------------------------- | |
// Variables | |
// ----------------------------------------------------------------------------- | |
//window.localStorage.clear(); | |
var settings = new SLAcer.Settings({ | |
file: { | |
panel: { | |
collapsed: false, | |
position : 0 | |
} | |
}, | |
slicer: { | |
layers: { | |
height: 100 // μm | |
}, | |
light: { | |
on : 1000, | |
off: 500 | |
}, | |
zip: true, | |
svg: false, | |
png: true, | |
speed: false, | |
speedDelay: 10, // ms | |
panel: { | |
collapsed: false, | |
position : 1 | |
}, | |
lifting: { | |
speed : 50, // mm/min | |
height: 3, // mm | |
} | |
}, | |
mesh: { | |
panel: { | |
collapsed: false, | |
position : 2 | |
} | |
}, | |
transform: { | |
panel: { | |
collapsed: false, | |
position : 3 | |
}, | |
mirror: false | |
}, | |
buildVolume: { | |
size : { x: 100, y: 100, z: 100 }, // mm | |
unit : 'mm', // mm or in | |
color : 0xcccccc, | |
opacity : 0.1, | |
panel : { | |
collapsed: false, | |
position : 4 | |
} | |
}, | |
resin: { | |
density : 1.1, // g/cm3 | |
price : 50, // $ | |
panel : { | |
collapsed: false, | |
position : 5 | |
} | |
}, | |
screen: { | |
width : window.screen.width, | |
height : window.screen.height, | |
diagonal : { size: 22, unit: 'in' }, | |
panel : { | |
collapsed: false, | |
position : 6 | |
} | |
}, | |
colors: { | |
mesh : '#eb0984', | |
slice: '#88ee11', | |
panel: { | |
collapsed: false, | |
position : 7 | |
} | |
}, | |
viewer3d: { | |
color: 0xffffff | |
} | |
}); | |
// ----------------------------------------------------------------------------- | |
// Error handler | |
// ----------------------------------------------------------------------------- | |
function errorHandler(error) { | |
console.error(error); | |
} | |
// ----------------------------------------------------------------------------- | |
// Slicer | |
// ----------------------------------------------------------------------------- | |
var slicer = new SLAcer.Slicer(); | |
var shapes, slices; | |
function removeShapes() { | |
if (shapes && shapes.length) { | |
for (var i = 0, il = shapes.length; i < il; i++) { | |
viewer3d.removeObject(shapes[i]); | |
} | |
} | |
} | |
function removeSlices() { | |
if (slices && slices.length) { | |
for (var i = 0, il = slices.length; i < il; i++) { | |
viewer2d.removeObject(slices[i]); | |
} | |
} | |
sliceImage('none'); | |
} | |
function hexToDec(hex) { | |
return parseInt(hex.toString().replace('#', ''), 16); | |
} | |
function sliceImage(dataURL) { | |
settings.set('slice.dataURL', dataURL || 'none'); | |
} | |
function getSlice(layerNumber) { | |
// remove old shapes | |
removeSlices(); | |
removeShapes(); | |
// ... | |
$slicerLayerValue.html(layerNumber); | |
if (layerNumber < 1) { | |
viewer2d.render(); | |
viewer3d.render(); | |
return; | |
} | |
if (transformations.update) { | |
throw 'transformations not applyed...'; | |
} | |
// get position | |
layerHeight = settings.get('slicer.layers.height') / 1000; | |
zPosition = layerNumber * layerHeight; | |
// get faces | |
var faces = slicer.getFaces(zPosition); | |
//console.log('layer number:', layerNumber); | |
//console.log('z position :', zPosition); | |
// get new shapes list | |
shapes = faces.meshes; | |
zPosition -= viewer3d.buildVolume.size.z / 2; | |
// slices | |
slices = []; | |
var slice, shape; | |
var sliceColor = hexToDec(settings.get('colors.slice')); | |
// add new shapes | |
for (var i = 0, il = shapes.length; i < il; i++) { | |
shape = shapes[i]; | |
slice = shape.clone(); | |
slice.material = slice.material.clone(); | |
slice.material.color.setHex(0xffffff); | |
viewer2d.addObject(slice); | |
slices.push(slice); | |
shape.material.color.setHex(sliceColor); | |
shape.material.depthTest = false; | |
shape.position.z = zPosition; | |
viewer3d.scene.add(shape); | |
} | |
// render 3D view | |
viewer3d.render(); | |
// render 2D view | |
viewer2d.screenshot(function(dataURL) { | |
sliceImage(dataURL); | |
if (PNGExport) { | |
var fileName = layerNumber + '.png'; | |
var imgData = dataURL.substr(dataURL.indexOf(',') + 1); | |
zipFolder.file(fileName, imgData, { base64: true }); | |
} | |
}); | |
// SVG export | |
if (SVGExport) { | |
// offset for positive coords | |
var xOffset = Math.abs(slicer.mesh.geometry.boundingBox.min.x); | |
var yOffset = Math.abs(slicer.mesh.geometry.boundingBox.min.y); | |
function SVGPath(actions, dir) { | |
var path = []; | |
var area = 0; | |
var a, b, c, x, y; | |
if (dir != 'ccw') { | |
dir = 'cw'; | |
} | |
actions.push({ action: 'lineTo', args: actions[0].args }); | |
for (var i = 0, il = actions.length; i < il; i++) { | |
a = actions[i]; | |
if (i > 0) { | |
b = actions[i - 1]; | |
area += a.args[0] * b.args[1]; | |
area -= b.args[0] * a.args[1]; | |
} | |
c = a.action == 'moveTo' ? 'M' : 'L'; | |
x = (a.args[0] + xOffset); | |
y = (a.args[1] + yOffset); | |
path.push(c + x + ' ' + y); | |
} | |
if (dir == 'cw' && area < 0 || dir == 'ccw' && area > 0) { | |
path = path.reverse(); | |
path[0] = path[0].replace(/^L/, 'M'); | |
path[actions.length - 1] = path[actions.length - 1].replace(/^M/, 'L'); | |
} | |
return path.join(' ') + ' Z '; | |
} | |
// svg start | |
var size = slicer.mesh.getSize(); | |
var svg = '<svg version="1.1" width="' + size.x + '" height="' + size.y + '" baseProfile="full" xmlns="http://www.w3.org/2000/svg">\n'; | |
// svg background | |
svg += '<rect x="0" y="0" width="100%" height="100%" style="fill:black; stroke:none"/>\n'; | |
// fix coordinate system (rotate 180°) | |
svg += '<g transform="translate(0, ' + size.y + ') scale(1, -1)">\n'; | |
// draw main paths | |
var actions; | |
for (var i = 0, il = faces.shapes.length; i < il; i++) { | |
actions = faces.shapes[i].actions; | |
// path start | |
svg += '<path d="'; | |
// main paths | |
svg += SVGPath(actions, 'cw'); | |
// holes paths | |
var holes = faces.shapes[i].holes; | |
for (var j = 0, jl = holes.length; j < jl; j++) { | |
svg += SVGPath(holes[j].actions, 'ccw'); | |
} | |
// path end | |
svg += '" style="fill:white; stroke:none" />\n'; | |
} | |
// svg end | |
svg += '</g>'; | |
svg += '</svg>'; | |
// add svg file to zip | |
zipFolder.file(layerNumber + '.svg', svg); | |
} | |
} | |
// ----------------------------------------------------------------------------- | |
// UI | |
// ----------------------------------------------------------------------------- | |
// Main container | |
var $main = $('#main'); | |
// Viewer 3D | |
var $viewer3d = $('#viewer3d'); | |
var viewer3d = new SLAcer.Viewer3D({ | |
color : settings.get('viewer3d.color'), | |
buildVolume: settings.get('buildVolume'), | |
target : $viewer3d[0] | |
}); | |
// Triangulation algorithm | |
//THREE.Triangulation.setTimer(true); | |
THREE.Triangulation.setLibrary('earcut'); | |
//THREE.Triangulation.setLibrary('libtess'); | |
//THREE.Triangulation.setLibrary('poly2tri'); | |
// Viewer 2D | |
var viewer2dWin = null; | |
var $openViewer2D = $('#open-viewer-2d'); | |
var viewer2d = new SLAcer.Viewer2D({ | |
target : null, // off-screen | |
color : 0x000000, | |
buildPlate : { | |
size : settings.get('buildVolume.size'), | |
unit : settings.get('buildVolume.unit'), | |
color : 0x000000, | |
opacity: 0 // hide build plate | |
}, | |
size: settings.get('screen') | |
}); | |
$openViewer2D.click(function(e) { | |
if (viewer2dWin == null || viewer2dWin.closed) { | |
var screen = settings.get('screen'); | |
var size = 'width=' + screen.width + ', height=' + screen.height; | |
var opts = 'menubar=0, toolbar=0, location=0, directories=0, personalbar=0, status=0, resizable=1, dependent=0' | |
viewer2dWin = window.open('viewer2d.html', 'SLAcerViewer2D', size + ', ' + opts); | |
$(viewer2dWin).on('beforeunload', function(e) { | |
viewer2dWin = null; | |
}) | |
.load(function(e) { | |
getSlice($sliderInput.slider('getValue')); | |
}); | |
} | |
else { | |
viewer2dWin.focus(); | |
} | |
return false; | |
}); | |
// Slider | |
var $sliderInput = $('#slider input'); | |
$sliderInput.slider({ reversed : true }).on('change', function(e) { | |
getSlice(e.value.newValue); | |
}); | |
var $sliderElement = $('#slider .slider'); | |
var $sliderMaxValue = $('#slider .max'); | |
function updateSliderUI() { | |
var layersHeight = settings.get('slicer.layers.height') / 1000; | |
var layersNumber = Math.floor(slicer.mesh.getSize().z / layersHeight); | |
$sliderInput.slider('setAttribute', 'max', layersNumber); | |
$sliderMaxValue.html(layersNumber); | |
$slicerLayersValue.html(layersNumber); | |
} | |
// Sidebar | |
var $sidebar = $('#sidebar'); | |
var $panels = $sidebar.find('.panel'); | |
$sidebar.sortable({ | |
axis : 'y', | |
handle : '.panel-heading', | |
cancel : '.panel-toggle', | |
placeholder: 'panel-placeholder', forcePlaceholderSize: true, | |
// update panel position | |
stop: function(e, ui) { | |
$sidebar.find('.panel').each(function(i, element) { | |
settings.set(_.camelCase(element.id) + '.panel.position', i); | |
}); | |
} | |
}); | |
// Sort panels | |
var panels = []; | |
var panel; | |
_.forEach(settings.settings, function(item, namespace) { | |
if (item && item.panel) { | |
panels[item.panel.position] = $('#' + _.kebabCase(namespace)); | |
} | |
}); | |
for (var i in panels) { | |
$sidebar.append(panels[i]); | |
} | |
// Init panel | |
function initPanel(name) { | |
var id = _.kebabCase(name); | |
var name = _.camelCase(name); | |
var $body = $('#' + id + '-body'); | |
$body.on('hidden.bs.collapse', function () { | |
settings.set(name + '.panel.collapsed', true); | |
}); | |
$body.on('shown.bs.collapse', function () { | |
settings.set(name + '.panel.collapsed', false); | |
}); | |
if (settings.get(name + '.panel.collapsed')) { | |
$body.collapse('hide'); | |
} | |
return $body; | |
} | |
// Unit converter | |
function parseUnit(value, unit) { | |
return parseFloat(unit == 'in' ? (value / 25.4) : (value * 25.4)); | |
} | |
// File panel | |
var $fileBody = initPanel('file'); | |
var $fileInput = $fileBody.find('#file-input'); | |
var loadedFile = null; | |
$fileInput.on('change', function(e) { | |
resetTransformValues(); | |
loadedFile = e.target.files[0]; | |
loader.loadFile(loadedFile); | |
}); | |
// Mesh panel | |
var $meshBody = initPanel('mesh'); | |
var $meshFaces = $meshBody.find('#mesh-faces'); | |
var $meshVolume = $meshBody.find('#mesh-volume'); | |
var $meshWeight = $meshBody.find('#mesh-weight'); | |
var $meshCost = $meshBody.find('#mesh-cost'); | |
var $meshSizeX = $meshBody.find('#mesh-size-x'); | |
var $meshSizeY = $meshBody.find('#mesh-size-y'); | |
var $meshSizeZ = $meshBody.find('#mesh-size-z'); | |
var $meshSizeUnit = $meshBody.find('.mesh-size-unit'); | |
function updateMeshInfoUI() { | |
var mesh = slicer.mesh; | |
var size = mesh.getSize(); | |
var unit = settings.get('buildVolume.unit'); | |
updateSliderUI(); | |
$meshSizeUnit.html(unit); | |
if (unit == 'in') { | |
size.x = parseUnit(size.x, 'in'); | |
size.y = parseUnit(size.y, 'in'); | |
size.z = parseUnit(size.z, 'in'); | |
} | |
$meshSizeX.html(size.x.toFixed(2)); | |
$meshSizeY.html(size.y.toFixed(2)); | |
$meshSizeZ.html(size.z.toFixed(2)); | |
var volume = parseInt(mesh.getVolume() / 1000); // cm3/ml | |
var weight = (volume * settings.get('resin.density')).toFixed(2); // g | |
var cost = volume * settings.get('resin.price') / 1000; | |
$meshFaces.html(mesh.geometry.faces.length); | |
$meshVolume.html(volume); | |
$meshWeight.html(weight); | |
$meshCost.html(cost); | |
} | |
// Slicer panel | |
var $slicerBody = initPanel('slicer'); | |
var $slicerLayerHeight = $slicerBody.find('#slicer-layers-height'); | |
var $slicerLayersValue = $slicerBody.find('#slicer-layers-value'); | |
var $slicerLayerValue = $slicerBody.find('#slicer-layer-value'); | |
var $slicerLightOff = $slicerBody.find('#slicer-light-off'); | |
var $slicerLightOn = $slicerBody.find('#slicer-light-on'); | |
var $slicerLiftingSpeed = $slicerBody.find('#slicer-lifting-speed'); | |
var $slicerLiftingHeight = $slicerBody.find('#slicer-lifting-height'); | |
var $slicerExportPNG = $slicerBody.find('#slicer-image-extension-png'); | |
var $slicerExportSVG = $slicerBody.find('#slicer-image-extension-svg'); | |
var $slicerSpeedYes = $slicerBody.find('#slicer-speed-yes'); | |
var $slicerSpeedNo = $slicerBody.find('#slicer-speed-no'); | |
var $slicerSpeedDelay = $slicerBody.find('#slicer-speed-delay'); | |
var $slicerMakeZipYes = $slicerBody.find('#slicer-make-zip-yes'); | |
var $slicerMakeZipNo = $slicerBody.find('#slicer-make-zip-no'); | |
var $sliceButton = $sidebar.find('#slice-button'); | |
var $abortButton = $sidebar.find('#abort-button'); | |
var $zipButton = $sidebar.find('#zip-button'); | |
function updateSlicerUI() { | |
var slicer = settings.get('slicer'); | |
$slicerSpeedDelay.val(slicer.speedDelay); | |
$slicerLayerHeight.val(slicer.layers.height); | |
$slicerLightOff.val(slicer.light.off); | |
$slicerLightOn.val(slicer.light.on); | |
$slicerLiftingSpeed.val(slicer.lifting.speed); | |
$slicerLiftingHeight.val(slicer.lifting.height); | |
} | |
function updateSlicerSettings() { | |
settings.set('slicer.layers.height', $slicerLayerHeight.val()); | |
settings.set('slicer.light.off', $slicerLightOff.val()); | |
settings.set('slicer.light.on', $slicerLightOn.val()); | |
settings.set('slicer.lifting.speed', $slicerLiftingSpeed.val()); | |
settings.set('slicer.lifting.height', $slicerLiftingHeight.val()); | |
settings.set('slicer.png', $slicerExportPNG[0].checked); | |
settings.set('slicer.svg', $slicerExportSVG[0].checked); | |
settings.set('slicer.zip', $slicerMakeZipYes[0].checked); | |
settings.set('slicer.speed', $slicerSpeedYes[0].checked); | |
settings.set('slicer.speedDelay', $slicerSpeedDelay.val()); | |
getSlice($sliderInput.slider('getValue')); | |
updateSliderUI(); | |
} | |
var sliceInterval; | |
var expectedSliceInterval; | |
var currentSliceNumber; | |
var slicesNumber; | |
var zipFile; | |
var zipFolder; | |
var SVGExport; | |
var PNGExport; | |
var layerHeight; | |
var zPosition; | |
var exposureTime; | |
var liftingSpeed; | |
var liftingHeight; | |
var liftingTime; | |
var estimatedTime; | |
function slice() { | |
currentSliceNumber++; | |
if (currentSliceNumber > slicesNumber) { | |
return endSlicing(); | |
} | |
getSlice(currentSliceNumber); | |
$sliderInput.slider('setValue', currentSliceNumber); | |
var time = Date.now(); | |
var diff = time - expectedSliceInterval; | |
!settings.get('slicer.speed') && viewer2dWin && setTimeout(function() { | |
sliceImage('none'); | |
}, settings.get('slicer.light.on')); | |
expectedSliceInterval += sliceInterval; | |
setTimeout(slice, Math.max(0, sliceInterval - diff)); | |
} | |
function endSlicing() { | |
sliceImage('none'); | |
$sidebar.find('input, button').prop('disabled', false); | |
$sliderInput.slider('enable'); | |
$abortButton.addClass('hidden'); | |
$sliceButton.removeClass('hidden'); | |
$zipButton.prop('disabled', !zipFile); | |
} | |
function startSlicing() { | |
var times = settings.get('slicer.light'); | |
if (settings.get('slicer.speed')) { | |
sliceInterval = parseInt(settings.get('slicer.speedDelay')); | |
} | |
else { | |
sliceInterval = parseInt(times.on) + parseInt(times.off); | |
} | |
expectedSliceInterval = Date.now() + sliceInterval; | |
slicesNumber = parseInt($slicerLayersValue.html()); | |
currentSliceNumber = 0; | |
zipFile = null; | |
zipFolder = null; | |
SVGExport = null; | |
PNGExport = null; | |
if (settings.get('slicer.zip')) { | |
zipFile = new JSZip(); | |
zipFolder = zipFile.folder('slices'); | |
zipFile.file("README.txt", 'Generated by SLAcer.js\r\nhttp://lautr3k.github.io/SLAcer.js/\r\n'); | |
zipFile.file("slacer.json", JSON.stringify({ | |
imageExtension: settings.get('slicer.png') ? 'png' : 'svg', | |
imageDirectory: 'slices', | |
screenWidth : settings.get('screen.width'), | |
screenHeight : settings.get('screen.height'), | |
screenSize : settings.get('screen.diagonal.size'), | |
screenUnit : settings.get('screen.diagonal.unit'), | |
layersNumber : slicesNumber, | |
layersHeight : settings.get('slicer.layers.height') / 1000, // mm | |
exposureTime : parseInt(settings.get('slicer.light.on')), // ms | |
liftingSpeed : parseInt(settings.get('slicer.lifting.speed')), // mm/min | |
liftingHeight : parseInt(settings.get('slicer.lifting.height')) // mm | |
}, null, 2)); | |
SVGExport = settings.get('slicer.svg'); | |
PNGExport = settings.get('slicer.png'); | |
} | |
slicesNumber && slice(); | |
} | |
$zipButton.on('click', function(e) { | |
if (zipFile) { | |
var name = 'SLAcer'; | |
if (loadedFile && loadedFile.name) { | |
name = loadedFile.name; | |
} | |
saveAs(zipFile.generate({type: 'blob'}), name + '.zip'); | |
} | |
}); | |
$sliceButton.on('click', function(e) { | |
$sidebar.find('input, button').prop('disabled', true); | |
$('.panel-heading button').prop('disabled', false); | |
$openViewer2D.prop('disabled', false); | |
$sliderInput.slider('disable'); | |
$abortButton.prop('disabled', false); | |
$abortButton.removeClass('hidden'); | |
$sliceButton.addClass('hidden'); | |
startSlicing(); | |
}); | |
$abortButton.on('click', function(e) { | |
currentSliceNumber = slicesNumber + 1; | |
endSlicing(); | |
}); | |
$('#slicer-image-extension-' + (settings.get('slicer.png') ? 'png' : 'svg')).prop('checked', true); | |
$('#slicer-make-zip-' + (settings.get('slicer.zip') ? 'yes' : 'no')).prop('checked', true); | |
$('#slicer-speed-' + (settings.get('slicer.speed') ? 'yes' : 'no')).prop('checked', true); | |
$('#slicer input').on('input, change', updateSlicerSettings); | |
updateSlicerUI(); | |
// Build volume panel | |
var $buildVolumeBody = initPanel('buildVolume'); | |
var $buildVolumeX = $buildVolumeBody.find('#build-volume-x'); | |
var $buildVolumeY = $buildVolumeBody.find('#build-volume-y'); | |
var $buildVolumeZ = $buildVolumeBody.find('#build-volume-z'); | |
function updateBuildVolumeUI() { | |
var buildVolume = settings.get('buildVolume'); | |
$buildVolumeX.val(buildVolume.size.x); | |
$buildVolumeY.val(buildVolume.size.y); | |
$buildVolumeZ.val(buildVolume.size.z); | |
updateBuildVolumeSizeStep(); | |
} | |
function updateBuildVolumeSizeStep() { | |
var step = (settings.get('buildVolume.unit') == 'in') ? 0.01 : 1; | |
$buildVolumeX.prop('step', step); | |
$buildVolumeY.prop('step', step); | |
$buildVolumeZ.prop('step', step); | |
} | |
function updateBuildVolumeSettings() { | |
var unit = $('#build-volume input[type=radio]:checked').val(); | |
if (unit != settings.get('buildVolume.unit')) { | |
var size = settings.get('buildVolume.size'); | |
$buildVolumeX.val(parseUnit(size.x, unit)); | |
$buildVolumeY.val(parseUnit(size.y, unit)); | |
$buildVolumeZ.val(parseUnit(size.z, unit)); | |
} | |
settings.set('buildVolume', { | |
size: { | |
x: $buildVolumeX.val(), | |
y: $buildVolumeY.val(), | |
z: $buildVolumeZ.val() | |
}, | |
unit: unit | |
}); | |
viewer3d.setBuildVolume(settings.get('buildVolume')); | |
slicer.mesh && viewer3d.dropObject(slicer.mesh); | |
viewer3d.render(); | |
size && updateMeshInfoUI(); | |
updateBuildVolumeSizeStep(); | |
getSlice($sliderInput.slider('getValue')); | |
} | |
$('#build-volume-unit-' + settings.get('buildVolume.unit')).prop('checked', true); | |
$('#build-volume input[type=radio]').on('change', updateBuildVolumeSettings); | |
$('#build-volume input').on('input', updateBuildVolumeSettings); | |
updateBuildVolumeUI(); | |
// Resin panel | |
var $resinBody = initPanel('resin'); | |
var $resinPrice = $resinBody.find('#resin-price'); | |
var $resinDensity = $resinBody.find('#resin-density'); | |
function updateResinUI() { | |
var resin = settings.get('resin'); | |
$resinDensity.val(resin.density); | |
$resinPrice.val(resin.price); | |
} | |
function updateResinSettings() { | |
settings.set('resin.price' , $resinPrice.val()); | |
settings.set('resin.density', $resinDensity.val()); | |
updateMeshInfoUI(); | |
} | |
$('#resin input').on('input', updateResinSettings); | |
updateResinUI(); | |
// Screen | |
var $screenBody = initPanel('screen'); | |
var $screenWidth = $screenBody.find('#screen-width'); | |
var $screenHeight = $screenBody.find('#screen-height'); | |
var $screenDiagonalSize = $screenBody.find('#screen-diagonal-size'); | |
var $screenDotPitch = $screenBody.find('#screen-dot-pitch'); | |
function updateScreenUI() { | |
var screen = settings.get('screen'); | |
$screenWidth.val(screen.width); | |
$screenHeight.val(screen.height); | |
$screenDiagonalSize.val(screen.diagonal.size); | |
$screenDotPitch.html(viewer2d.dotPitch.toFixed(2)); | |
viewer2d.setScreenResolution(screen); | |
} | |
function updateScreenSettings() { | |
var unit = $('#screen input[type=radio]:checked').val(); | |
if (unit != settings.get('screen.diagonal.unit')) { | |
$screenDiagonalSize.val( | |
parseUnit(settings.get('screen.diagonal.size'), unit) | |
); | |
} | |
settings.set('screen', { | |
width : $screenWidth.val(), | |
height : $screenHeight.val(), | |
diagonal: { | |
size: $screenDiagonalSize.val(), | |
unit: unit | |
} | |
}); | |
viewer2d.setScreenResolution(settings.get('screen')); | |
$screenDotPitch.html(viewer2d.dotPitch.toFixed(2)); | |
getSlice($sliderInput.slider('getValue')); | |
} | |
$('#screen-diagonal-unit-' + settings.get('screen.diagonal.unit')).prop('checked', true); | |
$('#screen input[type=radio]').on('change', updateScreenSettings); | |
$('#screen input').on('input', updateScreenSettings); | |
updateScreenUI(); | |
// Colors | |
var $colorsBody = initPanel('colors'); | |
var $meshColor = $colorsBody.find('#mesh-color'); | |
var $sliceColor = $colorsBody.find('#slice-color'); | |
function updateColorsUI() { | |
var colors = settings.get('colors'); | |
$meshColor.val(colors.mesh); | |
$sliceColor.val(colors.slice); | |
} | |
updateColorsUI(); | |
$meshColor.colorpicker({ format: 'hex' }); | |
$sliceColor.colorpicker({ format: 'hex' }); | |
$meshColor.colorpicker().on('changeColor.colorpicker', function(e) { | |
if (slicer.mesh && slicer.mesh.material) { | |
var hexString = e.color.toHex(); | |
var hexInteger = hexToDec(hexString); | |
settings.set('colors.mesh', hexString); | |
slicer.mesh.material.color.setHex(hexInteger); | |
viewer3d.render(); | |
} | |
}); | |
$sliceColor.colorpicker().on('changeColor.colorpicker', function(e) { | |
if (shapes && shapes.length) { | |
var hexString = e.color.toHex(); | |
var hexInteger = hexToDec(hexString); | |
settings.set('colors.slice', hexString); | |
for (var i = 0, il = shapes.length; i < il; i++) { | |
shapes[i].material.color.setHex(hexInteger); | |
} | |
viewer3d.render(); | |
} | |
}); | |
// Alert | |
var $alertPanel = $('#alert'); | |
var $alertMessage = $alertPanel.find('.message'); | |
// Transform | |
var $transformBody = initPanel('transform'); | |
var $transformAction = $transformBody.find('#transform-action'); | |
var $transformUniform = $transformBody.find('#transform-uniform'); | |
var $transformX = $transformBody.find('#transform-x'); | |
var $transformY = $transformBody.find('#transform-y'); | |
var $transformZ = $transformBody.find('#transform-z'); | |
var $transformButtons = $transformBody.find('button'); | |
var $transformMirrorYes = $transformBody.find('#transform-mirror-yes'); | |
var $transformMirrorNo = $transformBody.find('#transform-mirror-no'); | |
var transformAction, transformations; | |
function resetTransformValues() { | |
transformAction = 'scale'; | |
transformations = { | |
scale : { x:1, y:1 , z:1 }, | |
rotate : { x:0, y:0 , z:0 }, | |
translate: { x:0, y:0 , z:0 } | |
}; | |
updateTransformAction(); | |
} | |
function updateTransformAction() { | |
transformAction = $transformAction.val(); | |
var axis = transformations[transformAction]; | |
var min, max, step; | |
if (transformAction == 'scale') { | |
min = 0.01; | |
max = 999; | |
step = 0.01; | |
} | |
else if (transformAction == 'rotate') { | |
min = 0; | |
max = 360; | |
step = 1; | |
} | |
else { | |
min = -9999; | |
max = 9999; | |
step = 1; | |
} | |
$transformUniform.toggleClass('hidden', transformAction != 'scale'); | |
$transformZ.parent().toggleClass('hidden', transformAction == 'translate'); | |
$transformX.prop('min', min); | |
$transformY.prop('min', min); | |
$transformZ.prop('min', min); | |
$transformX.prop('max', max); | |
$transformY.prop('max', max); | |
$transformZ.prop('max', max); | |
$transformX.prop('step', step); | |
$transformY.prop('step', step); | |
$transformZ.prop('step', step); | |
$transformX.val(axis.x); | |
$transformY.val(axis.y); | |
$transformZ.val(axis.z); | |
} | |
function updateTransformValues() { | |
var current = transformations[transformAction]; | |
var uniform = $('#transform-uniform input[type=radio]:checked').val() == 'yes'; | |
var input = { | |
x: parseFloat($transformX.val()), | |
y: parseFloat($transformY.val()), | |
z: parseFloat($transformZ.val()) | |
}; | |
input.x = isNaN(input.x) ? current.x : input.x; | |
input.y = isNaN(input.y) ? current.y : input.y; | |
input.z = isNaN(input.z) ? current.z : input.z; | |
$transformX.val(input.x); | |
$transformY.val(input.y); | |
$transformZ.val(input.z); | |
if (transformAction == 'scale') { | |
if (uniform) { | |
if (input.x != current.x) { | |
var ratio = current.x / input.x; | |
input.y = (current.y / ratio).toFixed(2); | |
input.z = (current.z / ratio).toFixed(2); | |
$transformY.val(input.y); | |
$transformZ.val(input.z); | |
} | |
else if (input.y != current.y) { | |
var ratio = current.y / input.y; | |
input.x = (current.x / ratio).toFixed(2); | |
input.z = (current.z / ratio).toFixed(2); | |
$transformX.val(input.x); | |
$transformZ.val(input.z); | |
} | |
else if (input.z != current.z) { | |
var ratio = current.z / input.z; | |
input.x = (current.x / ratio).toFixed(2); | |
input.y = (current.y / ratio).toFixed(2); | |
$transformX.val(input.x); | |
$transformY.val(input.y); | |
} | |
} | |
input.x <= 0 && (input.x = 1); | |
input.y <= 0 && (input.y = 1); | |
input.z <= 0 && (input.z = 1); | |
slicer.mesh.geometry.scale( | |
input.x / current.x, | |
input.y / current.y, | |
input.z / current.z | |
); | |
} | |
else if (transformAction == 'rotate') { | |
var deg = Math.PI / 180; | |
var offsets = { | |
x: input.x - current.x, | |
y: input.y - current.y, | |
z: input.z - current.z | |
}; | |
slicer.mesh.geometry.rotateX(offsets.x * deg); | |
slicer.mesh.geometry.rotateY(offsets.y * deg); | |
slicer.mesh.geometry.rotateZ(offsets.z * deg); | |
} | |
else { | |
var offsets = { | |
x: input.x - current.x, | |
y: input.y - current.y | |
}; | |
slicer.mesh.geometry.translate(offsets.x, offsets.y, 0); | |
} | |
current.x = input.x; | |
current.y = input.y; | |
current.z = input.z; | |
if (transformAction != 'translate') { | |
loadGeometry(slicer.mesh.geometry, false); | |
// stay at current position | |
var current = transformations['translate']; | |
slicer.mesh.geometry.translate(current.x, current.y, current.z); | |
} | |
getSlice($sliderInput.slider('getValue')); | |
} | |
$transformButtons.on('click', function(e) { | |
var $this = $(this); | |
var axis = $this.data('axis'); | |
var action = $this.data('action'); | |
var value = transformations[transformAction][axis]; | |
var $target = $transformBody.find('#transform-' + axis); | |
$target.val(value + (action == '+' ? 1 : -1)); | |
updateTransformValues(); | |
}); | |
function flipGeometry() { | |
settings.set('transform.mirror', $transformMirrorYes[0].checked); | |
loadGeometry(slicer.mesh.geometry, true); | |
getSlice($sliderInput.slider('getValue')); | |
} | |
$transformMirrorYes.on('change', flipGeometry); | |
$transformMirrorNo.on('change', flipGeometry); | |
$('#transform-mirror-' + (settings.get('transform.mirror') ? 'yes' : 'no')).prop('checked', true); | |
$('#transform select').on('change', updateTransformAction); | |
$('#transform input').on('change', updateTransformValues); | |
resetTransformValues(); | |
// UI resize | |
function resizeUI() { | |
var width = $main.width(); | |
var height = $main.height(); | |
$sliderElement.height(height - 80); | |
viewer3d.setSize({ width : width, height: height }); | |
viewer3d.render(); | |
} | |
$(window).resize(resizeUI); | |
resizeUI(); | |
// ----------------------------------------------------------------------------- | |
// STL loader | |
// ----------------------------------------------------------------------------- | |
// Loader instance | |
var loader = new MeshesJS.STLLoader($main[0]); // drop target | |
// Load an geometry | |
function loadGeometry(geometry, mirror) { | |
try { | |
// remove old mesh and plane | |
slicer.mesh && viewer3d.removeObject(slicer.mesh); | |
// remove old shapes | |
removeShapes(); | |
// flip geometry | |
if (mirror) { | |
geometry.applyMatrix(new THREE.Matrix4().makeScale(-1, 1, 1)); | |
} | |
// load new mesh in slicer | |
slicer.loadMesh(new SLAcer.Mesh(geometry, new THREE.MeshPhongMaterial({ | |
color: hexToDec(settings.get('colors.mesh')), side: THREE.DoubleSide | |
}))); | |
// add new mesh and render view | |
viewer3d.addObject(slicer.mesh); | |
viewer3d.render(); | |
// update mesh info | |
updateMeshInfoUI(); | |
// get first slice | |
//getSlice(1); | |
} | |
catch(e) { | |
errorHandler(e); | |
} | |
} | |
// Haaaaaaaaaaaaaaaaaa!!!! | |
function ultraMegaDirtyFix() { | |
$transformBody.find('#transform-x').val(1.1); | |
$transformBody.find('#transform-y').val(1.1); | |
$transformBody.find('#transform-z').val(1.1); | |
updateTransformValues(); | |
$transformBody.find('#transform-x').val(1); | |
$transformBody.find('#transform-y').val(1); | |
$transformBody.find('#transform-z').val(1); | |
updateTransformValues(); | |
} | |
// On Geometry loaded | |
loader.onGeometry = function(geometry) { | |
resetTransformValues(); | |
loadGeometry(geometry, settings.get('transform.mirror')); | |
ultraMegaDirtyFix(); | |
}; | |
// On loading error | |
loader.onError = errorHandler; | |
// ----------------------------------------------------------------------------- | |
// load example | |
// ----------------------------------------------------------------------------- | |
// example STL file | |
//var stl = 'stl/Octocat-v2.stl'; | |
var stl = 'stl/StressTest.stl'; | |
//var stl = 'stl/SLAcer.stl'; | |
// File url | |
// var url = 'http://' + window.location.hostname + window.location.pathname + stl; | |
var url = window.location.href + stl; | |
// Create http request object | |
var xmlhttp = new XMLHttpRequest(); | |
// Get the file contents | |
xmlhttp.open("GET", url); | |
xmlhttp.onreadystatechange = function() { | |
if (xmlhttp.readyState == XMLHttpRequest.DONE) { | |
if(xmlhttp.status == 200){ | |
loader.loadString(xmlhttp.responseText); | |
}else{ | |
errorHandler('xmlhttp: ' + xmlhttp.statusText); | |
} | |
} | |
} | |
xmlhttp.send(); |