src/js/molecules/nest.js
import Atom from '../prototypes/atom'
import GlobalVariables from '../globalvariables.js'
import saveAs from '../lib/FileSaver.js'
import * as svgNest from '../lib/svgnest.js'
/**
* This class creates the nest atom which lets you download a nested .svg file.
*/
export default class Nest extends Atom {
/**
* The constructor function.
* @param {object} values An array of values passed in which will be assigned to the class as this.x
*/
constructor(values){
super(values)
/**
* This atom's name
* @type {string}
*/
this.name = 'Nest'
/**
* This atom's type
* @type {string}
*/
this.atomType = 'Nest'
/**
* This atom's value. Contains the value of the input geometry, not the stl
* @type {string}
*/
this.value = null
/**
* This atom's height as drawn on the screen
*/
this.height = 0
this.addIO('input', 'geometry', this, 'geometry', null)
this.addIO('input', 'spacing', this, 'number', 0.3)
this.addIO('input', 'curveTolerance', this, 'number', 0.3)
this.setValues(values)
/**
* Number of times nesting has been tried
*/
this.iterations = 0
/**
* Boolean to determine wether nesting process is ongoing
*/
this.isworking = false
/**
* Length and width inputs for svg placement
*/
this.material = {"width": 121,"length":243}
}
/**
* Draw the svg atom which has a SVG icon.
*/
draw() {
super.draw("rect")
let pixelsRadius = GlobalVariables.widthToPixels(this.radius)
this.height = pixelsRadius * 1.5
GlobalVariables.c.beginPath()
GlobalVariables.c.fillStyle = '#484848'
GlobalVariables.c.font = `${pixelsRadius/1.2}px Work Sans Bold`
GlobalVariables.c.fillText('SVG', GlobalVariables.widthToPixels(this.x- this.radius/1.3), GlobalVariables.heightToPixels(this.y)+this.height/6)
GlobalVariables.c.fill()
GlobalVariables.c.closePath()
}
/**
* Re excecutes configure function for nesting if values are updated by user
*/
setValue(){
this.setConfig()
}
/**
* Set the value to be the input geometry, then call super updateValue()
*/
updateValue(){
try{
const values = [this.findIOValue('geometry')]
this.basicThreadValueProcessing(values, "outline")
}catch(err){this.setAlert(err)}
//Saves new config values for nesting (Stops any nesting in progress)
this.setConfig()
}
/**
* Create buttons to start nest, to download the .svg file and checkbox "Part in Part".
*/
updateSidebar(){
const list = super.updateSidebar()
this.createEditableValueListItem(list,this.material,'width', 'Width of Material', true, () => this.setConfig())
this.createEditableValueListItem(list,this.material,'length','Length of Material', true, () => this.setConfig())
//this.createEditableValueListItem(list,this.BOMitem,'costUSD', 'Price', true, () => this.updateValue())
this.createCheckbox(list,"Part in Part", false, ()=>{this.setConfig()})
this.createButton(list, this, "Start Nest", ()=>{this.svgToNest()})
//remember to disable until svg is nested
this.createButton(list, this, "Download SVG", ()=>{this.downloadSvg()})
var svgButton = document.getElementById("DownloadSVG-button")
svgButton.disabled = true
svgButton.classList.add("disabled")
}
/**
* Update values for config(). Called when the values on sidebar have been edited.
*/
setConfig() {
// config = [distance, curve tolerance,rotations, population size, mutation rate, use holes, concave]
const configKeys = ["spacing","curveTolerance"]
var c = {"useholes":false,"exploreConcave":false,"rotations":4,"mutationRate":10,"populationSize":10}
for(var i=0; i<configKeys.length; i++){
var key = configKeys[i]
c[key] = this.findIOValue(key)
}
var check1 = document.getElementById("Part in Part")
if(check1 !== null){
if (check1.checked){
c[check1] = true
}
else{
c[check1] = false
}
}
window.SvgNest.config(c)
var svgButton = document.getElementById("DownloadSVG-button")
if (svgButton !== null){
svgButton.disabled = true
svgButton.classList.add("disabled")
}
// new configs will invalidate current nest
if(this.isworking){
this.stopnest()
}
return false
}
/**
* Turns geometry values into svg then starts nest and returns nested SVG
*/
svgToNest(){
//turn into svg
const values = [this.findIOValue('geometry')]
const computeValue = async (values, key) => {
try{
return await GlobalVariables.ask({values: values, op: key})
}
catch(err){
this.setAlert(err)
}
}
var unestedSVG
computeValue(values, "svg").then(result => {
if (result != -1 ){
var decoder = new TextDecoder('utf8')
unestedSVG = decoder.decode(result)
return unestedSVG
}else{
this.setAlert("Unable to compute")
}
}).then(result =>{
try{
var svg = SvgNest.parsesvg(result)
var display = document.getElementById('select')
{
var wholeSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg")
// Copy relevant scaling info
wholeSVG.setAttribute('width',this.material["width"])
wholeSVG.setAttribute('height',this.material["length"])
wholeSVG.setAttribute('viewBox',svg.getAttribute('viewBox'))
var rect = document.createElementNS(wholeSVG.namespaceURI,'rect')
rect.setAttribute('x', wholeSVG.viewBox.baseVal.x)
rect.setAttribute('y', wholeSVG.viewBox.baseVal.x)
rect.setAttribute('width', "100%")
rect.setAttribute('height', "100%")
rect.setAttribute('class', 'fullRect')
wholeSVG.appendChild(rect)
}
display.innerHTML = ''
display.appendChild(wholeSVG) // As a default bin in background
display.appendChild(svg)
}
catch(e){
console.warn(e)
//message.innerHTML = e;
//message.className = 'error animated bounce';
return
}
//hideSplash();
//message.className = 'active animated bounce';
//start.className = 'button start disabled';
//attachSvgListeners(svg);
//set the bin to wholeSVG
this.attachSvgListeners(wholeSVG)
})
}
/**
* Defines bin which will be used to place parts for nested svg
*/
attachSvgListeners(svg){
// auto set bin to be whole svg
for(var i=0; i<svg.childNodes.length; i++){
var node = svg.childNodes[i]
if(node.nodeType == 1){
node.setAttribute("class", "active")
//if(display.className == 'disabled'){
// return;
//}
var currentbin = document.querySelector('#select .active')
if(currentbin){
var className = currentbin.getAttribute('class').replace('active', '').trim()
if(!className)
currentbin.removeAttribute('class')
else
currentbin.setAttribute('class', className)
}
SvgNest.setbin(node)
node.setAttribute('class',(node.getAttribute('class') ? node.getAttribute('class')+' ' : '') + 'active')
//start.className = 'button start animated bounce';
//message.className = '';
this.startnest()
}
}
}
/**
* Starts nesting, replaces html svg for nested svg
*/
async startnest(){
// Once started, don't allow this anymore
SvgNest.start(this.progress,this.renderSvg)
//remember to change label so nest can stop
//startlabel.innerHTML = 'Stop Nest';
//start.className = 'button spinner';
//configbutton.className = 'button config disabled';
//config.className = '';
var svg = document.querySelector('#select svg')
if(svg){
svg.removeAttribute('style')
}
this.isworking = true
}
/**
* Stops nest and changes flag to not working
*/
stopnest(){
SvgNest.stop()
//startlabel.innerHTML = 'Start Nest'
//start.className = 'button start'
//configbutton.className = 'button config'
this.isworking = false
}
/**
* NOT WORKING Defines percentage of nesting and estimates time and iterations
progress(percent, prevpercent = 0){
var transition = percent > prevpercent //? '; transition: width 0.1s' : ''
//document.getElementById('info_progress').setAttribute('style','width: '+Math.round(percent*100)+'% ' + transition)
//document.getElementById('info').setAttribute('style','display: block')
prevpercent = percent
var now = new Date().getTime()
if(startTime && now){
var diff = now-startTime
// show a time estimate for long-running placements
var estimate = (diff/percent)*(1-percent)
//document.getElementById('info_time').innerHTML = millisecondsToStr(estimate)+' remaining'
console.log(estimate)
if(diff > 5000 && percent < 0.3 && percent > 0.02 && estimate > 10000){
document.getElementById('info_time').setAttribute('style','display: block')
}
}
if(percent > 0.95 || percent < 0.02){
document.getElementById('info_time').setAttribute('style','display: none')
}
if(percent < 0.02){
startTime = new Date().getTime()
}
}
*/
/**
* NOT WORKING Defines HTML bit and defines svg list
*/
renderSvg(svglist, efficiency, placed, total){
//this.iterations++;
//document.getElementById('info_iterations').innerHTML = iterations;
//Enable download button if all parts have been placed
if (placed == total){
var svgButton = document.getElementById("DownloadSVG-button")
svgButton.disabled = false
svgButton.classList.remove("disabled")
}
if(!svglist || svglist.length == 0){
return
}
var bins = document.getElementById('bins')
bins.innerHTML = ''
for(var i=0; i<svglist.length; i++){
if(svglist.length > 2){
svglist[i].setAttribute('class','grid')
}
bins.appendChild(svglist[i])
}
if(efficiency || efficiency === 0){
// document.getElementById('info_efficiency').innerHTML = Math.round(efficiency*100);
}
//document.getElementById('info_placed').innerHTML = placed+'/'+total;
//document.getElementById('info_placement').setAttribute('style','display: block');
//display.setAttribute('style','display: none');
//download.className = 'button download animated bounce';
}
/**
* The function which is called when you press the download button.
* Stops nest in progress and downloads file to computer. Only enabled if svg has been nested at least once
*/
downloadSvg(){
//if it is still trying iterations stop nest
this.stopnest()
var bins = document.getElementById('bins')
if(bins.children.length == 0){
console.warn('No SVG to export')
return
}
var svg
var display = document.getElementById('select')
svg = display.querySelector('svg')
if(!svg){
svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
}
svg = svg.cloneNode(false)
// maintain stroke, fill etc of input
if(SvgNest.style){
svg.appendChild(SvgNest.style)
}
var binHeight = parseInt(bins.children[0].getAttribute('height'))
for(var i=0; i<bins.children.length; i++){
var b = bins.children[i]
var group = document.createElementNS('http://www.w3.org/2000/svg', 'g')
group.setAttribute('fill', 'none')
group.setAttribute('stroke-width', '.1')
group.setAttribute('fill', 'none')
group.setAttribute('transform', 'translate(0 '+binHeight*1.1*i+')')
for(var j=0; j<b.children.length; j++){
group.appendChild(b.children[j].cloneNode(true))
}
svg.appendChild(group)
}
var output
if(typeof XMLSerializer != 'undefined'){
output = (new XMLSerializer()).serializeToString(svg)
}
else{
output = svg.outerHTML
}
var blob = new Blob([output], {type: "image/svg+xml;charset=utf-8"})
saveAs(blob, "SVGnest-output.svg")
}
}