Compare commits
2 Commits
pierremtb/
...
kurt-chick
Author | SHA1 | Date | |
---|---|---|---|
c5d103a166 | |||
c7b7b91129 |
@ -16,6 +16,7 @@ import {
|
|||||||
emptyExecState,
|
emptyExecState,
|
||||||
ExecState,
|
ExecState,
|
||||||
initPromise,
|
initPromise,
|
||||||
|
KclValue,
|
||||||
parse,
|
parse,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
Program,
|
Program,
|
||||||
@ -33,6 +34,16 @@ import {
|
|||||||
ModelingCmdReq_type,
|
ModelingCmdReq_type,
|
||||||
} from '@kittycad/lib/dist/types/src/models'
|
} from '@kittycad/lib/dist/types/src/models'
|
||||||
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
||||||
|
import {
|
||||||
|
createCutList,
|
||||||
|
CutLumber,
|
||||||
|
LumberToCut,
|
||||||
|
Offcut,
|
||||||
|
STANDARD_TIMBER_LENGTH,
|
||||||
|
uuider,
|
||||||
|
} from './std/cutlist'
|
||||||
|
|
||||||
|
const LOCALSTORAGE_KEY = 'chickenCoop'
|
||||||
|
|
||||||
interface ExecuteArgs {
|
interface ExecuteArgs {
|
||||||
ast?: Node<Program>
|
ast?: Node<Program>
|
||||||
@ -370,6 +381,148 @@ export class KclManager {
|
|||||||
// Do not add the errors since the program was interrupted and the error is not a real KCL error
|
// Do not add the errors since the program was interrupted and the error is not a real KCL error
|
||||||
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
|
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
|
||||||
this.execState = execState
|
this.execState = execState
|
||||||
|
console.log('execState', execState)
|
||||||
|
const frame = execState.memory.get('aGroupOfStudForCreatingCutList')
|
||||||
|
if (frame) {
|
||||||
|
const _existingCutList = localStorage.getItem(LOCALSTORAGE_KEY)
|
||||||
|
const existingCutList: ReturnType<typeof createCutList>[] =
|
||||||
|
_existingCutList ? JSON.parse(_existingCutList) : []
|
||||||
|
console.log('existingCutList', existingCutList)
|
||||||
|
let idStart
|
||||||
|
if (existingCutList.length) {
|
||||||
|
const lastList = existingCutList[existingCutList.length - 1]
|
||||||
|
const lastId =
|
||||||
|
lastList.cutLumbers[lastList.cutLumbers.length - 1].timberLengthId
|
||||||
|
idStart = Number(lastId)
|
||||||
|
|
||||||
|
// sumerize the last cutlist
|
||||||
|
|
||||||
|
const masterCutLumbersByTimberLengthId: Record<string, CutLumber[]> = {}
|
||||||
|
existingCutList.forEach((cutList) => {
|
||||||
|
cutList.cutLumbers.forEach((cutLumber) => {
|
||||||
|
if (!masterCutLumbersByTimberLengthId[cutLumber.timberLengthId]) {
|
||||||
|
masterCutLumbersByTimberLengthId[cutLumber.timberLengthId] = []
|
||||||
|
}
|
||||||
|
masterCutLumbersByTimberLengthId[cutLumber.timberLengthId].push(
|
||||||
|
cutLumber
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
console.log(
|
||||||
|
'masterCutLumbersByTimberLengthId',
|
||||||
|
masterCutLumbersByTimberLengthId
|
||||||
|
)
|
||||||
|
let prettyPrint = ''
|
||||||
|
Object.entries(masterCutLumbersByTimberLengthId).forEach(
|
||||||
|
([key, value]) => {
|
||||||
|
// prettyPrint += `timberLengthId: ${key}`
|
||||||
|
value.forEach((cutLumber) => {
|
||||||
|
prettyPrint += `timberLengthId: ${key
|
||||||
|
.toString()
|
||||||
|
.padStart(2, ' ')}, cutLength: ${Math.round(
|
||||||
|
cutLumber.lengthBeforeAngles
|
||||||
|
)
|
||||||
|
.toString()
|
||||||
|
.padStart(5, ' ')}, id: ${cutLumber.id
|
||||||
|
.toString()
|
||||||
|
.padStart(2, ' ')}, ang1: ${Math.round(cutLumber.angle1)
|
||||||
|
.toString()
|
||||||
|
.padStart(3, ' ')}, ang2: ${Math.round(cutLumber.angle2)
|
||||||
|
.toString()
|
||||||
|
.padStart(3, ' ')}, angleOnWidth: ${cutLumber.angleRelevantWidth
|
||||||
|
.toString()
|
||||||
|
.padStart(3, ' ')}, depth: ${cutLumber.depth
|
||||||
|
.toString()
|
||||||
|
.padStart(2, ' ')}, name: ${cutLumber.name.padEnd(44, ' ')}\n`
|
||||||
|
})
|
||||||
|
// prettyPrint += `timberLengthId: ${key}`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
console.log('cutlist json')
|
||||||
|
console.log(JSON.stringify(existingCutList))
|
||||||
|
console.log(prettyPrint)
|
||||||
|
|
||||||
|
// cutLumbers.forEach((cutLumber) => {
|
||||||
|
// if (!cutLumbersByTimberLengthId[cutLumber.timberLengthId]) {
|
||||||
|
// cutLumbersByTimberLengthId[cutLumber.timberLengthId] = []
|
||||||
|
// }
|
||||||
|
// cutLumbersByTimberLengthId[cutLumber.timberLengthId].push(cutLumber)
|
||||||
|
// })
|
||||||
|
} else {
|
||||||
|
idStart = 0
|
||||||
|
}
|
||||||
|
console.log('idStart', idStart)
|
||||||
|
const aGroupOfStudForCreatingCutList = flatternKCLVal(frame) as {
|
||||||
|
angleRelevantWidth: number
|
||||||
|
endCut1: number
|
||||||
|
endCut2: number
|
||||||
|
lengthBeforeAngles: number
|
||||||
|
name: string
|
||||||
|
studType: (string | number)[]
|
||||||
|
}[]
|
||||||
|
console.log(
|
||||||
|
'aGroupOfStudForCreatingCutList',
|
||||||
|
aGroupOfStudForCreatingCutList
|
||||||
|
)
|
||||||
|
const uuid = uuider(idStart)
|
||||||
|
let initialOffcutList: Offcut[] = existingCutList.length
|
||||||
|
? existingCutList[existingCutList.length - 1].offcutList
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
length: STANDARD_TIMBER_LENGTH,
|
||||||
|
lastAngle: 0,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: uuid(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
/**
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 2000,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'a',
|
||||||
|
*/
|
||||||
|
const initialLumbersToCut: LumberToCut[] =
|
||||||
|
aGroupOfStudForCreatingCutList.map((stud, index) => {
|
||||||
|
return {
|
||||||
|
angleRelevantWidth: stud.angleRelevantWidth,
|
||||||
|
depth: stud.studType.find(
|
||||||
|
(item) =>
|
||||||
|
typeof item === 'number' && item !== stud.angleRelevantWidth
|
||||||
|
) as number,
|
||||||
|
lengthBeforeAngles: stud.lengthBeforeAngles,
|
||||||
|
angle1: stud.endCut1,
|
||||||
|
angle2: stud.endCut2,
|
||||||
|
name: stud.name,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// console.log('initialLumbersToCut', JSON.stringify(initialLumbersToCut))
|
||||||
|
const cutListResult = createCutList({
|
||||||
|
lumbersToCut: initialLumbersToCut,
|
||||||
|
offcutList: initialOffcutList,
|
||||||
|
uuid,
|
||||||
|
})
|
||||||
|
const newCutList = [...existingCutList, cutListResult]
|
||||||
|
// localStorage.setItem(LOCALSTORAGE_KEY, JSON.stringify(newCutList))
|
||||||
|
// console.log('cutListResultJSON', JSON.stringify(cutListResult))
|
||||||
|
console.log('cutListResult', cutListResult, 'newCutlistarr', newCutList)
|
||||||
|
} else {
|
||||||
|
console.log('no frame', execState)
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('frame', frame)
|
||||||
|
// if (Array.isArray(frame?.value)) {
|
||||||
|
// const cleanArray = frame?.value.map((value: any) => {
|
||||||
|
// const val = value.value
|
||||||
|
// const obj: any = {}
|
||||||
|
// Object.entries(val).forEach(([key, value2]: [any, any]) => {
|
||||||
|
// obj[key] = value2.value
|
||||||
|
// })
|
||||||
|
// return obj
|
||||||
|
// })
|
||||||
|
// console.log('cleanArray', cleanArray)
|
||||||
|
// }
|
||||||
if (!errors.length) {
|
if (!errors.length) {
|
||||||
this.lastSuccessfulProgramMemory = execState.memory
|
this.lastSuccessfulProgramMemory = execState.memory
|
||||||
this.lastSuccessfulOperations = execState.operations
|
this.lastSuccessfulOperations = execState.operations
|
||||||
@ -722,3 +875,379 @@ function setSelectionFilter(
|
|||||||
})
|
})
|
||||||
.catch(reportError)
|
.catch(reportError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function flatternKCLVal(val: KclValue): any {
|
||||||
|
if (val.type === 'Number' || val.type === 'String') {
|
||||||
|
return val.value
|
||||||
|
} else if (val.type === 'Array') {
|
||||||
|
return val.value.map(flatternKCLVal)
|
||||||
|
} else if (val.type === 'Object') {
|
||||||
|
const obj: any = {}
|
||||||
|
Object.entries(val.value).forEach(([key, value]: [any, any]) => {
|
||||||
|
obj[key] = flatternKCLVal(value)
|
||||||
|
})
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const yoTestFrontFrame = {
|
||||||
|
cutLumbers: [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1715,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorSupportUnderHeaderL',
|
||||||
|
id: 1,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1715,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorSupportUnderHeaderR',
|
||||||
|
id: 2,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1544.3684870912048,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'frontCornerStudL',
|
||||||
|
id: 3,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1544.3684870912048,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'frontCornerStudL',
|
||||||
|
id: 4,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1770.9253875090703,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'lDoorVertStud',
|
||||||
|
id: 5,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1770.9253875090703,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'rDoorVertStud',
|
||||||
|
id: 6,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1515,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsBetweenSideAndDoor',
|
||||||
|
id: 7,
|
||||||
|
timberLengthId: '3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1515,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsBetweenSideAndDoor',
|
||||||
|
id: 8,
|
||||||
|
timberLengthId: '3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 108.57232480920788,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsOverDoor',
|
||||||
|
id: 9,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 108.57232480920788,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsOverDoor',
|
||||||
|
id: 10,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 949.6869799080042,
|
||||||
|
angle1: 40,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'backPitchedStud',
|
||||||
|
id: 11,
|
||||||
|
timberLengthId: '3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 949.6869799080042,
|
||||||
|
angle1: 40,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'backPitchedStud',
|
||||||
|
id: 12,
|
||||||
|
timberLengthId: '3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 820,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorHeader1',
|
||||||
|
id: 13,
|
||||||
|
timberLengthId: '3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 820,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorHeader2',
|
||||||
|
id: 14,
|
||||||
|
timberLengthId: '4',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 375,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'footL',
|
||||||
|
id: 15,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 375,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'footR',
|
||||||
|
id: 16,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
offcutList: [
|
||||||
|
{
|
||||||
|
length: 308.7669656572259,
|
||||||
|
lastAngle: 0,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 420.0608402575016,
|
||||||
|
lastAngle: 0,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 115.40342621518221,
|
||||||
|
lastAngle: 0,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: '3',
|
||||||
|
},
|
||||||
|
{ length: 5177, lastAngle: 0, angleRelevantWidth: 90, timberLengthId: '4' },
|
||||||
|
],
|
||||||
|
lumbersToCut: [],
|
||||||
|
cutLumbersByTimberLengthId: {
|
||||||
|
'1': [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1715,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorSupportUnderHeaderL',
|
||||||
|
id: 1,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1715,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorSupportUnderHeaderR',
|
||||||
|
id: 2,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1544.3684870912048,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'frontCornerStudL',
|
||||||
|
id: 3,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 108.57232480920788,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsOverDoor',
|
||||||
|
id: 9,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 108.57232480920788,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsOverDoor',
|
||||||
|
id: 10,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 375,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'footL',
|
||||||
|
id: 15,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'2': [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1544.3684870912048,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'frontCornerStudL',
|
||||||
|
id: 4,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1770.9253875090703,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'lDoorVertStud',
|
||||||
|
id: 5,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1770.9253875090703,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'rDoorVertStud',
|
||||||
|
id: 6,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 375,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'footR',
|
||||||
|
id: 16,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'3': [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1515,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsBetweenSideAndDoor',
|
||||||
|
id: 7,
|
||||||
|
timberLengthId: '3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1515,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsBetweenSideAndDoor',
|
||||||
|
id: 8,
|
||||||
|
timberLengthId: '3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 949.6869799080042,
|
||||||
|
angle1: 40,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'backPitchedStud',
|
||||||
|
id: 11,
|
||||||
|
timberLengthId: '3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 949.6869799080042,
|
||||||
|
angle1: 40,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'backPitchedStud',
|
||||||
|
id: 12,
|
||||||
|
timberLengthId: '3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 820,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorHeader1',
|
||||||
|
id: 13,
|
||||||
|
timberLengthId: '3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'4': [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 820,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorHeader2',
|
||||||
|
id: 14,
|
||||||
|
timberLengthId: '4',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
;(window as any).removeChickenCoopCache = () => {
|
||||||
|
localStorage.removeItem(LOCALSTORAGE_KEY)
|
||||||
|
console.log('cache removed')
|
||||||
|
}
|
||||||
|
672
src/lang/std/cutlist.test.ts
Normal file
672
src/lang/std/cutlist.test.ts
Normal file
@ -0,0 +1,672 @@
|
|||||||
|
import {
|
||||||
|
createCutList,
|
||||||
|
LumberToCut,
|
||||||
|
Offcut,
|
||||||
|
STANDARD_TIMBER_LENGTH,
|
||||||
|
uuider,
|
||||||
|
} from './cutlist'
|
||||||
|
|
||||||
|
it('it should simple case where a second length is needed and it has to redo order to fit a shorter length towards the end on the first length of timber', () => {
|
||||||
|
const uuid = uuider()
|
||||||
|
let initialOffcutList: Offcut[] = [
|
||||||
|
{
|
||||||
|
length: STANDARD_TIMBER_LENGTH,
|
||||||
|
lastAngle: 0,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: uuid(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
let initialLumbersToCut: LumberToCut[] = [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 2000,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1500,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 45,
|
||||||
|
name: 'b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1000,
|
||||||
|
angle1: -50,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'c',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1500,
|
||||||
|
angle1: -49,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'd',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const { offcutList, lumbersToCut, cutLumbersByTimberLengthId } =
|
||||||
|
createCutList({
|
||||||
|
lumbersToCut: initialLumbersToCut,
|
||||||
|
offcutList: initialOffcutList,
|
||||||
|
uuid: uuid,
|
||||||
|
})
|
||||||
|
expect(lumbersToCut).toEqual([])
|
||||||
|
expect(cutLumbersByTimberLengthId).toEqual({
|
||||||
|
'1': [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 2000,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'a',
|
||||||
|
id: 1,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1500,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 45,
|
||||||
|
name: 'b',
|
||||||
|
id: 2,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1500,
|
||||||
|
angle1: -49,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'd',
|
||||||
|
id: 3,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'2': [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1000,
|
||||||
|
angle1: -50,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'c',
|
||||||
|
id: 4,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(offcutList).toEqual([
|
||||||
|
{
|
||||||
|
length: 886.22420266299,
|
||||||
|
lastAngle: 0,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 4888.0750051859395,
|
||||||
|
lastAngle: 50,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
it('backToBack trapazodal cuts with deep angles that should fit into a singleLength only because angles are preserved', () => {
|
||||||
|
const uuid = uuider()
|
||||||
|
let initialOffcutList: Offcut[] = [
|
||||||
|
{
|
||||||
|
length: STANDARD_TIMBER_LENGTH,
|
||||||
|
lastAngle: 0,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: uuid(),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const angleRelevantWidth = 90
|
||||||
|
const cutOverlap = Math.tan((67 * Math.PI) / 180) * angleRelevantWidth
|
||||||
|
|
||||||
|
// 25 is fudge factor to account for saw blade loss
|
||||||
|
// all three should fit with with a little bit of left over
|
||||||
|
const lengthOfEachCut = (STANDARD_TIMBER_LENGTH - 4 * cutOverlap - 45) / 3
|
||||||
|
let initialLumbersToCut: LumberToCut[] = [
|
||||||
|
{
|
||||||
|
angleRelevantWidth,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: lengthOfEachCut,
|
||||||
|
angle1: 67,
|
||||||
|
angle2: -67, // positive and negative angles is what makes them trapezoidal // but it shouldn't matter
|
||||||
|
name: 'a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: lengthOfEachCut,
|
||||||
|
angle1: 67,
|
||||||
|
angle2: -67, // positive and negative angles is what makes them trapezoidal // but it shouldn't matter
|
||||||
|
name: 'b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: lengthOfEachCut,
|
||||||
|
angle1: 67,
|
||||||
|
angle2: -67, // positive and negative angles is what makes them trapezoidal // but it shouldn't matter
|
||||||
|
name: 'c',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const { offcutList, lumbersToCut, cutLumbersByTimberLengthId } =
|
||||||
|
createCutList({
|
||||||
|
lumbersToCut: initialLumbersToCut,
|
||||||
|
offcutList: initialOffcutList,
|
||||||
|
uuid,
|
||||||
|
})
|
||||||
|
expect(lumbersToCut).toEqual([])
|
||||||
|
expect(cutLumbersByTimberLengthId).toEqual({
|
||||||
|
'1': [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1702.2977161011497,
|
||||||
|
angle1: 67,
|
||||||
|
angle2: -67,
|
||||||
|
name: 'a',
|
||||||
|
id: 1,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1702.2977161011497,
|
||||||
|
angle1: 67,
|
||||||
|
angle2: -67,
|
||||||
|
name: 'b',
|
||||||
|
id: 2,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1702.2977161011497,
|
||||||
|
angle1: 67,
|
||||||
|
angle2: -67,
|
||||||
|
name: 'c',
|
||||||
|
id: 3,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(offcutList).toHaveLength(1)
|
||||||
|
expect(offcutList).toEqual([
|
||||||
|
{
|
||||||
|
length: 21.966258012773956,
|
||||||
|
lastAngle: 67,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
it('backToBack parallelagram cuts with deep angles that should fit into a singleLength only because angles are preserved', () => {
|
||||||
|
let initialOffcutList: Offcut[] = [
|
||||||
|
{
|
||||||
|
length: STANDARD_TIMBER_LENGTH,
|
||||||
|
lastAngle: 0,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: '0',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const sortedDescOffcutById = initialOffcutList.sort(
|
||||||
|
(a, b) => Number(b.timberLengthId) - Number(a.timberLengthId)
|
||||||
|
)
|
||||||
|
const maxId = Number(sortedDescOffcutById[0].timberLengthId)
|
||||||
|
const uuid = uuider(maxId)
|
||||||
|
|
||||||
|
const angleRelevantWidth = 90
|
||||||
|
const cutOverlap = Math.tan((67 * Math.PI) / 180) * angleRelevantWidth
|
||||||
|
|
||||||
|
// 25 is fudge factor to account for saw blade loss
|
||||||
|
// all three should fit with with a little bit of left over
|
||||||
|
const lengthOfEachCut = (STANDARD_TIMBER_LENGTH - 4 * cutOverlap - 45) / 3
|
||||||
|
let initialLumbersToCut: LumberToCut[] = [
|
||||||
|
{
|
||||||
|
angleRelevantWidth,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: lengthOfEachCut,
|
||||||
|
angle1: 67,
|
||||||
|
angle2: 67,
|
||||||
|
name: 'a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: lengthOfEachCut,
|
||||||
|
angle1: 67,
|
||||||
|
angle2: 67,
|
||||||
|
name: 'b',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: lengthOfEachCut,
|
||||||
|
angle1: 67,
|
||||||
|
angle2: 67,
|
||||||
|
name: 'c',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const { offcutList, lumbersToCut, cutLumbersByTimberLengthId } =
|
||||||
|
createCutList({
|
||||||
|
lumbersToCut: initialLumbersToCut,
|
||||||
|
offcutList: initialOffcutList,
|
||||||
|
uuid: uuid,
|
||||||
|
})
|
||||||
|
expect(lumbersToCut).toEqual([])
|
||||||
|
expect(cutLumbersByTimberLengthId).toEqual({
|
||||||
|
'0': [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1702.2977161011497,
|
||||||
|
angle1: 67,
|
||||||
|
angle2: 67,
|
||||||
|
name: 'a',
|
||||||
|
id: 1,
|
||||||
|
timberLengthId: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1702.2977161011497,
|
||||||
|
angle1: 67,
|
||||||
|
angle2: 67,
|
||||||
|
name: 'b',
|
||||||
|
id: 2,
|
||||||
|
timberLengthId: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1702.2977161011497,
|
||||||
|
angle1: 67,
|
||||||
|
angle2: 67,
|
||||||
|
name: 'c',
|
||||||
|
timberLengthId: '0',
|
||||||
|
id: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(offcutList).toHaveLength(1)
|
||||||
|
expect(offcutList).toEqual([
|
||||||
|
{
|
||||||
|
length: 21.966258012773956,
|
||||||
|
lastAngle: 67,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: '0',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
})
|
||||||
|
it('Should work with cut list from an actual execution', () => {
|
||||||
|
let initialOffcutList: Offcut[] = [
|
||||||
|
{
|
||||||
|
length: STANDARD_TIMBER_LENGTH,
|
||||||
|
lastAngle: 0,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: '0',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const sortedDescOffcutById = initialOffcutList.sort(
|
||||||
|
(a, b) => Number(b.timberLengthId) - Number(a.timberLengthId)
|
||||||
|
)
|
||||||
|
const maxId = Number(sortedDescOffcutById[0].timberLengthId)
|
||||||
|
const uuid = uuider(maxId)
|
||||||
|
|
||||||
|
const angleRelevantWidth = 90
|
||||||
|
const cutOverlap = Math.tan((67 * Math.PI) / 180) * angleRelevantWidth
|
||||||
|
|
||||||
|
// 25 is fudge factor to account for saw blade loss
|
||||||
|
// all three should fit with with a little bit of left over
|
||||||
|
const lengthOfEachCut = (STANDARD_TIMBER_LENGTH - 4 * cutOverlap - 45) / 3
|
||||||
|
let initialLumbersToCut: LumberToCut[] = [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 949.6869799080042,
|
||||||
|
angle1: 40,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'backPitchedStud',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 949.6869799080042,
|
||||||
|
angle1: 40,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'backPitchedStud',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1544.3684870912048,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'frontCornerStudL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1544.3684870912048,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'frontCornerStudL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1770.9253875090703,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'lDoorVertStud',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1770.9253875090703,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'rDoorVertStud',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 375,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'footL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 375,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'footR',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1715,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorSupportUnderHeaderL',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1715,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorSupportUnderHeaderR',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 820,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorHeader1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 820,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorHeader2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1515,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsBetweenSideAndDoor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1515,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsBetweenSideAndDoor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 108.57232480920788,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsOverDoor',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 108.57232480920788,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsOverDoor',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const { offcutList, lumbersToCut, cutLumbersByTimberLengthId } =
|
||||||
|
createCutList({
|
||||||
|
lumbersToCut: initialLumbersToCut,
|
||||||
|
offcutList: initialOffcutList,
|
||||||
|
uuid: uuid,
|
||||||
|
})
|
||||||
|
expect(lumbersToCut).toEqual([])
|
||||||
|
expect(cutLumbersByTimberLengthId).toEqual({
|
||||||
|
'0': [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1715,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorSupportUnderHeaderL',
|
||||||
|
id: 1,
|
||||||
|
timberLengthId: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1715,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorSupportUnderHeaderR',
|
||||||
|
id: 2,
|
||||||
|
timberLengthId: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1544.3684870912048,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'frontCornerStudL',
|
||||||
|
id: 3,
|
||||||
|
timberLengthId: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 108.57232480920788,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsOverDoor',
|
||||||
|
id: 9,
|
||||||
|
timberLengthId: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 108.57232480920788,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsOverDoor',
|
||||||
|
id: 10,
|
||||||
|
timberLengthId: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 375,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'footL',
|
||||||
|
id: 15,
|
||||||
|
timberLengthId: '0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'1': [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 1544.3684870912048,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'frontCornerStudL',
|
||||||
|
id: 4,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1770.9253875090703,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'lDoorVertStud',
|
||||||
|
id: 5,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1770.9253875090703,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'rDoorVertStud',
|
||||||
|
id: 6,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 375,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'footR',
|
||||||
|
id: 16,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'2': [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1515,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsBetweenSideAndDoor',
|
||||||
|
id: 7,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 1515,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'vertStudsBetweenSideAndDoor',
|
||||||
|
id: 8,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 949.6869799080042,
|
||||||
|
angle1: 40,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'backPitchedStud',
|
||||||
|
id: 11,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 35,
|
||||||
|
depth: 90,
|
||||||
|
lengthBeforeAngles: 949.6869799080042,
|
||||||
|
angle1: 40,
|
||||||
|
angle2: 40,
|
||||||
|
name: 'backPitchedStud',
|
||||||
|
id: 12,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 820,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorHeader1',
|
||||||
|
id: 13,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'3': [
|
||||||
|
{
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
depth: 35,
|
||||||
|
lengthBeforeAngles: 820,
|
||||||
|
angle1: 0,
|
||||||
|
angle2: 0,
|
||||||
|
name: 'doorHeader2',
|
||||||
|
id: 14,
|
||||||
|
timberLengthId: '3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(offcutList).toHaveLength(4)
|
||||||
|
expect(offcutList).toEqual([
|
||||||
|
{
|
||||||
|
length: 308.7669656572259,
|
||||||
|
lastAngle: 0,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: '0',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 420.0608402575016,
|
||||||
|
lastAngle: 0,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
length: 115.40342621518221,
|
||||||
|
lastAngle: 0,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: '2',
|
||||||
|
},
|
||||||
|
{ length: 5177, lastAngle: 0, angleRelevantWidth: 90, timberLengthId: '3' },
|
||||||
|
])
|
||||||
|
})
|
256
src/lang/std/cutlist.ts
Normal file
256
src/lang/std/cutlist.ts
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
export const SAW_BLADE_THICKNESS = 3 // 3mm loss per cut from saw blade on 90 degree cuts
|
||||||
|
|
||||||
|
export function uuider(start = 0) {
|
||||||
|
let __uuid_cntr = start
|
||||||
|
return () => {
|
||||||
|
// but it's very fake
|
||||||
|
__uuid_cntr++
|
||||||
|
return String(__uuid_cntr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Offcut = {
|
||||||
|
length: number
|
||||||
|
lastAngle: number
|
||||||
|
angleRelevantWidth: number
|
||||||
|
timberLengthId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LumberToCut = {
|
||||||
|
angleRelevantWidth: number
|
||||||
|
depth: number
|
||||||
|
lengthBeforeAngles: number
|
||||||
|
angle1: number
|
||||||
|
angle2: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CutLumber = LumberToCut & {
|
||||||
|
id: number
|
||||||
|
timberLengthId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const STANDARD_TIMBER_LENGTH = 6_000
|
||||||
|
|
||||||
|
export function createCutList({
|
||||||
|
lumbersToCut,
|
||||||
|
offcutList,
|
||||||
|
uuid,
|
||||||
|
}: {
|
||||||
|
lumbersToCut: LumberToCut[]
|
||||||
|
offcutList: Offcut[]
|
||||||
|
uuid: () => string
|
||||||
|
}) {
|
||||||
|
const uniqueAngles = new Set<number>()
|
||||||
|
lumbersToCut.forEach((lumber) => {
|
||||||
|
uniqueAngles.add(lumber.angle1)
|
||||||
|
uniqueAngles.add(lumber.angle2)
|
||||||
|
})
|
||||||
|
let cutLumbers: CutLumber[] = []
|
||||||
|
|
||||||
|
const lumberGetter = (
|
||||||
|
lumbersToCut: LumberToCut[],
|
||||||
|
lastOffcut: Offcut
|
||||||
|
): {
|
||||||
|
lumber: LumberToCut
|
||||||
|
newLumbersToCut: LumberToCut[]
|
||||||
|
} => {
|
||||||
|
// let longestLumberOfAngle: LumberToCut | undefined
|
||||||
|
let longestLumberOfAngleIndex = -1
|
||||||
|
lumbersToCut.forEach((lumberToCut, index) => {
|
||||||
|
if (
|
||||||
|
(Math.abs(lumberToCut.angle1) === Math.abs(lastOffcut.lastAngle) ||
|
||||||
|
Math.abs(lumberToCut.angle2) === Math.abs(lastOffcut.lastAngle)) &&
|
||||||
|
lastOffcut.angleRelevantWidth === lumberToCut.angleRelevantWidth
|
||||||
|
) {
|
||||||
|
if (longestLumberOfAngleIndex === -1) {
|
||||||
|
longestLumberOfAngleIndex = index
|
||||||
|
} else {
|
||||||
|
const currentLongest = lumbersToCut[longestLumberOfAngleIndex]
|
||||||
|
if (
|
||||||
|
currentLongest.lengthBeforeAngles < lumberToCut.lengthBeforeAngles
|
||||||
|
) {
|
||||||
|
longestLumberOfAngleIndex = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (longestLumberOfAngleIndex !== -1) {
|
||||||
|
return {
|
||||||
|
lumber: lumbersToCut[longestLumberOfAngleIndex],
|
||||||
|
newLumbersToCut: lumbersToCut.filter(
|
||||||
|
(_, index) => index !== longestLumberOfAngleIndex
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// didn't find lumber with matching angle,
|
||||||
|
// so just get the longest lumber with whatever angle it happens to be
|
||||||
|
let longestLumberIndex = 0
|
||||||
|
lumbersToCut.forEach((lumberToCut, index) => {
|
||||||
|
if (
|
||||||
|
lumberToCut.lengthBeforeAngles >
|
||||||
|
lumbersToCut[longestLumberIndex].lengthBeforeAngles
|
||||||
|
) {
|
||||||
|
longestLumberIndex = index
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
lumber: lumbersToCut[longestLumberIndex],
|
||||||
|
newLumbersToCut: lumbersToCut.filter(
|
||||||
|
(_, index) => index !== longestLumberIndex
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cutLumberOutOfOffcuts = (
|
||||||
|
lumber: LumberToCut,
|
||||||
|
offcuts: Offcut[],
|
||||||
|
lastId: number
|
||||||
|
): {
|
||||||
|
cutLumber: CutLumber
|
||||||
|
newOffcuts: Offcut[]
|
||||||
|
} => {
|
||||||
|
const doCalcsForGivenOffcutAndCurrentLumber = (
|
||||||
|
offcut: Offcut,
|
||||||
|
lumber: LumberToCut
|
||||||
|
) => {
|
||||||
|
const getOverlap = (timberWidth: number, angleD: number) =>
|
||||||
|
Math.abs(Math.tan((Math.abs(angleD) * Math.PI) / 180) * timberWidth)
|
||||||
|
let angleThatMatchesClosestToOffcut = 0
|
||||||
|
let endCutAngle = 0
|
||||||
|
if (
|
||||||
|
Math.abs(Math.abs(offcut.lastAngle) - Math.abs(lumber.angle1)) <
|
||||||
|
Math.abs(Math.abs(offcut.lastAngle) - Math.abs(lumber.angle2))
|
||||||
|
) {
|
||||||
|
angleThatMatchesClosestToOffcut = Math.abs(lumber.angle1)
|
||||||
|
endCutAngle = Math.abs(lumber.angle2)
|
||||||
|
} else {
|
||||||
|
angleThatMatchesClosestToOffcut = Math.abs(lumber.angle2)
|
||||||
|
endCutAngle = Math.abs(lumber.angle1)
|
||||||
|
}
|
||||||
|
const offCutOverLapZon = getOverlap(
|
||||||
|
offcut.angleRelevantWidth,
|
||||||
|
offcut.lastAngle
|
||||||
|
)
|
||||||
|
const lumberOverLapZon = getOverlap(
|
||||||
|
lumber.angleRelevantWidth,
|
||||||
|
angleThatMatchesClosestToOffcut
|
||||||
|
)
|
||||||
|
let overLapZoneBuffer = 0
|
||||||
|
if (lumberOverLapZon - offCutOverLapZon > 0) {
|
||||||
|
overLapZoneBuffer = lumberOverLapZon - offCutOverLapZon
|
||||||
|
}
|
||||||
|
const extraNeededAtOtherEndOfCut = getOverlap(
|
||||||
|
lumber.angleRelevantWidth,
|
||||||
|
endCutAngle
|
||||||
|
)
|
||||||
|
const sawCutLoss = Math.abs(
|
||||||
|
SAW_BLADE_THICKNESS / Math.cos((Math.abs(endCutAngle) * Math.PI) / 180)
|
||||||
|
)
|
||||||
|
const minimumLengthNeeded =
|
||||||
|
lumber.lengthBeforeAngles +
|
||||||
|
overLapZoneBuffer +
|
||||||
|
extraNeededAtOtherEndOfCut
|
||||||
|
return {
|
||||||
|
minimumLengthNeeded,
|
||||||
|
lengthBeforeAngles: lumber.lengthBeforeAngles,
|
||||||
|
overLapZoneBuffer,
|
||||||
|
extraNeededAtOtherEndOfCut,
|
||||||
|
sawCutLoss,
|
||||||
|
endCutAngle: Math.abs(endCutAngle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const findIndexFn = (offcut: Offcut) => {
|
||||||
|
const { minimumLengthNeeded } = doCalcsForGivenOffcutAndCurrentLumber(
|
||||||
|
offcut,
|
||||||
|
lumber
|
||||||
|
)
|
||||||
|
if (offcut.length >= minimumLengthNeeded) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// none of the offcuts are big enough
|
||||||
|
// need to add a new full length of timber as an offcut and try again.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
let offCutToUseIndex = offcuts.findIndex(findIndexFn)
|
||||||
|
|
||||||
|
let newOffcuts = offcuts
|
||||||
|
if (offCutToUseIndex === -1) {
|
||||||
|
const lastOffcut = offcuts[offcuts.length - 1]
|
||||||
|
const newOffcut: Offcut = {
|
||||||
|
length: STANDARD_TIMBER_LENGTH,
|
||||||
|
lastAngle: 0,
|
||||||
|
angleRelevantWidth: 90,
|
||||||
|
timberLengthId: `${Number(lastOffcut.timberLengthId) + 1}`,
|
||||||
|
}
|
||||||
|
console.log('last and new offcut', offcuts[offcuts.length - 1], newOffcut)
|
||||||
|
newOffcuts = [...offcuts, newOffcut]
|
||||||
|
offCutToUseIndex = newOffcuts.length - 1
|
||||||
|
}
|
||||||
|
if (offCutToUseIndex === -1) {
|
||||||
|
console.warn('offCutToUseIndex === -1')
|
||||||
|
throw new Error('timber does not fit in any offcuts, even a fresh one')
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
lengthBeforeAngles,
|
||||||
|
overLapZoneBuffer,
|
||||||
|
extraNeededAtOtherEndOfCut,
|
||||||
|
sawCutLoss,
|
||||||
|
endCutAngle,
|
||||||
|
} = doCalcsForGivenOffcutAndCurrentLumber(
|
||||||
|
newOffcuts[offCutToUseIndex],
|
||||||
|
lumber
|
||||||
|
)
|
||||||
|
|
||||||
|
const updatedOffcut: Offcut = {
|
||||||
|
length:
|
||||||
|
newOffcuts[offCutToUseIndex].length -
|
||||||
|
lengthBeforeAngles -
|
||||||
|
overLapZoneBuffer -
|
||||||
|
extraNeededAtOtherEndOfCut -
|
||||||
|
sawCutLoss,
|
||||||
|
lastAngle: Math.abs(endCutAngle),
|
||||||
|
angleRelevantWidth: lumber.angleRelevantWidth,
|
||||||
|
timberLengthId: newOffcuts[offCutToUseIndex].timberLengthId,
|
||||||
|
}
|
||||||
|
newOffcuts[offCutToUseIndex] = updatedOffcut
|
||||||
|
const cutLumber: CutLumber = {
|
||||||
|
...lumber,
|
||||||
|
id: lastId + 1,
|
||||||
|
timberLengthId: newOffcuts[offCutToUseIndex].timberLengthId,
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
cutLumber,
|
||||||
|
newOffcuts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentId = 0
|
||||||
|
|
||||||
|
while (lumbersToCut.length > 0) {
|
||||||
|
const lastOffcut = offcutList[offcutList.length - 1]
|
||||||
|
const result = lumberGetter(lumbersToCut, lastOffcut)
|
||||||
|
lumbersToCut = result.newLumbersToCut
|
||||||
|
const lumber = result.lumber
|
||||||
|
const { cutLumber, newOffcuts } = cutLumberOutOfOffcuts(
|
||||||
|
lumber,
|
||||||
|
offcutList,
|
||||||
|
currentId
|
||||||
|
)
|
||||||
|
offcutList = newOffcuts
|
||||||
|
cutLumbers = [...cutLumbers, cutLumber]
|
||||||
|
currentId = cutLumber.id
|
||||||
|
}
|
||||||
|
// group cutLumbers by timberLengthId
|
||||||
|
const cutLumbersByTimberLengthId: Record<string, CutLumber[]> = {}
|
||||||
|
cutLumbers.forEach((cutLumber) => {
|
||||||
|
if (!cutLumbersByTimberLengthId[cutLumber.timberLengthId]) {
|
||||||
|
cutLumbersByTimberLengthId[cutLumber.timberLengthId] = []
|
||||||
|
}
|
||||||
|
cutLumbersByTimberLengthId[cutLumber.timberLengthId].push(cutLumber)
|
||||||
|
})
|
||||||
|
|
||||||
|
return { cutLumbers, offcutList, lumbersToCut, cutLumbersByTimberLengthId }
|
||||||
|
}
|
@ -35,7 +35,7 @@ import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
|||||||
import { DeepPartial } from 'lib/types'
|
import { DeepPartial } from 'lib/types'
|
||||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||||
import { Sketch } from '../wasm-lib/kcl/bindings/Sketch'
|
import { Sketch } from '../wasm-lib/kcl/bindings/Sketch'
|
||||||
import { ExecState as RawExecState } from '../wasm-lib/kcl/bindings/ExecState'
|
import { ExecOutcome } from 'wasm-lib/kcl/bindings/ExecOutcome'
|
||||||
import { ProgramMemory as RawProgramMemory } from '../wasm-lib/kcl/bindings/ProgramMemory'
|
import { ProgramMemory as RawProgramMemory } from '../wasm-lib/kcl/bindings/ProgramMemory'
|
||||||
import { EnvironmentRef } from '../wasm-lib/kcl/bindings/EnvironmentRef'
|
import { EnvironmentRef } from '../wasm-lib/kcl/bindings/EnvironmentRef'
|
||||||
import { Environment } from '../wasm-lib/kcl/bindings/Environment'
|
import { Environment } from '../wasm-lib/kcl/bindings/Environment'
|
||||||
@ -260,10 +260,10 @@ export function emptyExecState(): ExecState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function execStateFromRaw(raw: RawExecState): ExecState {
|
function execStateFromRust(execOutcome: ExecOutcome): ExecState {
|
||||||
return {
|
return {
|
||||||
memory: ProgramMemory.fromRaw(raw.modLocal.memory),
|
memory: ProgramMemory.fromRaw(execOutcome.memory),
|
||||||
operations: raw.modLocal.operations,
|
operations: execOutcome.operations,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,14 +535,14 @@ export const _executor = async (
|
|||||||
jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot)
|
jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const execState: RawExecState = await execute(
|
const execOutcome: ExecOutcome = await execute(
|
||||||
JSON.stringify(node),
|
JSON.stringify(node),
|
||||||
JSON.stringify(programMemoryOverride?.toRaw() || null),
|
JSON.stringify(programMemoryOverride?.toRaw() || null),
|
||||||
JSON.stringify({ settings: jsAppSettings }),
|
JSON.stringify({ settings: jsAppSettings }),
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
fileSystemManager
|
fileSystemManager
|
||||||
)
|
)
|
||||||
return execStateFromRaw(execState)
|
return execStateFromRust(execOutcome)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
|
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
|
||||||
|
@ -94,6 +94,18 @@ pub struct ModuleState {
|
|||||||
pub settings: MetaSettings,
|
pub settings: MetaSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Outcome from executing a program. This is used in WebAssembly/WASM.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ExecOutcome {
|
||||||
|
/// Program variable bindings of the top-level module.
|
||||||
|
pub memory: ProgramMemory,
|
||||||
|
/// Operations that have been performed in execution order, for display in
|
||||||
|
/// the Feature Tree.
|
||||||
|
pub operations: Vec<Operation>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for ExecState {
|
impl Default for ExecState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new()
|
Self::new()
|
||||||
@ -123,6 +135,25 @@ impl ExecState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert to an output for WebAssembly/WASM.
|
||||||
|
pub fn to_wasm_outcome(mut self) -> ExecOutcome {
|
||||||
|
// Clear closed-over program memory inside closures. This is
|
||||||
|
// destructive. It greatly reduces the amount of data crossing the WASM
|
||||||
|
// boundary that we won't ever use.
|
||||||
|
for env in self.mut_memory().environments.iter_mut() {
|
||||||
|
for (_, value) in env.bindings.iter_mut() {
|
||||||
|
if let KclValue::Function { memory, .. } = value {
|
||||||
|
*memory = Box::new(ProgramMemory::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ExecOutcome {
|
||||||
|
memory: self.mod_local.memory,
|
||||||
|
operations: self.mod_local.operations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn memory(&self) -> &ProgramMemory {
|
pub fn memory(&self) -> &ProgramMemory {
|
||||||
&self.mod_local.memory
|
&self.mod_local.memory
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ pub async fn execute(
|
|||||||
// gloo-serialize crate instead.
|
// gloo-serialize crate instead.
|
||||||
// DO NOT USE serde_wasm_bindgen::to_value(&exec_state).map_err(|e| e.to_string())
|
// DO NOT USE serde_wasm_bindgen::to_value(&exec_state).map_err(|e| e.to_string())
|
||||||
// it will break the frontend.
|
// it will break the frontend.
|
||||||
JsValue::from_serde(&exec_state).map_err(|e| e.to_string())
|
JsValue::from_serde(&exec_state.to_wasm_outcome()).map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
// wasm_bindgen wrapper for execute
|
// wasm_bindgen wrapper for execute
|
||||||
|
Reference in New Issue
Block a user