Compare commits
2 Commits
pierremtb/
...
kurt-chick
Author | SHA1 | Date | |
---|---|---|---|
c5d103a166 | |||
c7b7b91129 |
@ -16,6 +16,7 @@ import {
|
||||
emptyExecState,
|
||||
ExecState,
|
||||
initPromise,
|
||||
KclValue,
|
||||
parse,
|
||||
PathToNode,
|
||||
Program,
|
||||
@ -33,6 +34,16 @@ import {
|
||||
ModelingCmdReq_type,
|
||||
} from '@kittycad/lib/dist/types/src/models'
|
||||
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 {
|
||||
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
|
||||
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
|
||||
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) {
|
||||
this.lastSuccessfulProgramMemory = execState.memory
|
||||
this.lastSuccessfulOperations = execState.operations
|
||||
@ -722,3 +875,379 @@ function setSelectionFilter(
|
||||
})
|
||||
.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 { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||
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 { EnvironmentRef } from '../wasm-lib/kcl/bindings/EnvironmentRef'
|
||||
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 {
|
||||
memory: ProgramMemory.fromRaw(raw.modLocal.memory),
|
||||
operations: raw.modLocal.operations,
|
||||
memory: ProgramMemory.fromRaw(execOutcome.memory),
|
||||
operations: execOutcome.operations,
|
||||
}
|
||||
}
|
||||
|
||||
@ -535,14 +535,14 @@ export const _executor = async (
|
||||
jsAppSettings = getAllCurrentSettings(lastSettingsSnapshot)
|
||||
}
|
||||
}
|
||||
const execState: RawExecState = await execute(
|
||||
const execOutcome: ExecOutcome = await execute(
|
||||
JSON.stringify(node),
|
||||
JSON.stringify(programMemoryOverride?.toRaw() || null),
|
||||
JSON.stringify({ settings: jsAppSettings }),
|
||||
engineCommandManager,
|
||||
fileSystemManager
|
||||
)
|
||||
return execStateFromRaw(execState)
|
||||
return execStateFromRust(execOutcome)
|
||||
} catch (e: any) {
|
||||
console.log(e)
|
||||
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
|
||||
|
@ -94,6 +94,18 @@ pub struct ModuleState {
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
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 {
|
||||
&self.mod_local.memory
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ pub async fn execute(
|
||||
// gloo-serialize crate instead.
|
||||
// DO NOT USE serde_wasm_bindgen::to_value(&exec_state).map_err(|e| e.to_string())
|
||||
// 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
|
||||
|
Reference in New Issue
Block a user