From 2e007ae2883158355974bccf838c64110b0a1fc7 Mon Sep 17 00:00:00 2001 From: Kurt Hutten IrevDev Date: Mon, 9 Jan 2023 08:52:48 +1100 Subject: [PATCH] Add sketch on extrude face functionality --- src/components/SketchLine.tsx | 1 - src/lang/abstractSyntaxTree.ts | 36 +++++ src/lang/artifact.test.ts | 135 ++++++++++++++++- src/lang/engine.tsx | 21 ++- src/lang/executor.ts | 255 +++++++++++++++++++++++---------- src/lang/sketch.ts | 84 ++++++++++- 6 files changed, 442 insertions(+), 90 deletions(-) diff --git a/src/components/SketchLine.tsx b/src/components/SketchLine.tsx index e1015ff82..37b7d1dba 100644 --- a/src/components/SketchLine.tsx +++ b/src/components/SketchLine.tsx @@ -84,7 +84,6 @@ function MovingSphere({ let theNewPoints: [number, number] = [x, y] const { modifiedAst } = changeArguments(ast, thePath, theNewPoints) updateAst(modifiedAst) - console.log('reset position') ref.current.position.set(...position) } setIsMouseDown(false) diff --git a/src/lang/abstractSyntaxTree.ts b/src/lang/abstractSyntaxTree.ts index 1cf39d626..d773d4eae 100644 --- a/src/lang/abstractSyntaxTree.ts +++ b/src/lang/abstractSyntaxTree.ts @@ -199,6 +199,23 @@ function makeArguments( expression, ]) } + if ( + argumentToken.token.type === 'brace' && + argumentToken.token.value === '{' + ) { + const { expression, lastIndex } = makeObjectExpression( + tokens, + argumentToken.index + ) + const nextCommarOrBraceTokenIndex = nextMeaningfulToken( + tokens, + lastIndex + ).index + return makeArguments(tokens, nextCommarOrBraceTokenIndex, [ + ...previousArgs, + expression, + ]) + } if (!isIdentifierOrLiteral) { const { expression, lastIndex } = makeBinaryExpression(tokens, index) return makeArguments(tokens, lastIndex, [...previousArgs, expression]) @@ -217,6 +234,25 @@ function makeArguments( value, ]) } + + if ( + argumentToken.token.type === 'word' && + nextBraceOrCommaToken.token.type === 'brace' && + nextBraceOrCommaToken.token.value === '(' + ) { + const { expression, lastIndex } = makeCallExpression( + tokens, + argumentToken.index + ) + const nextCommarOrBraceTokenIndex = nextMeaningfulToken( + tokens, + lastIndex + ).index + return makeArguments(tokens, nextCommarOrBraceTokenIndex, [ + ...previousArgs, + expression, + ]) + } if (argumentToken.token.type === 'word') { const identifier = makeIdentifier(tokens, argumentToken.index) return makeArguments(tokens, nextBraceOrCommaToken.index, [ diff --git a/src/lang/artifact.test.ts b/src/lang/artifact.test.ts index 904efd28f..606c7cffc 100644 --- a/src/lang/artifact.test.ts +++ b/src/lang/artifact.test.ts @@ -76,8 +76,13 @@ show(mySketch001)` value: [ { type: 'extrudePlane', - position: [0, 0, 0], - rotation: [0.3826834323650898, 0, 0, 0.9238795325112867], + position: [ + -0.5650000000000001, -2.602152954766495, -2.602152954766495, + ], + rotation: [ + 0.20394238048109659, 0.7817509623502217, -0.3238118510036805, + 0.4923604609001174, + ], __geoMeta: { geo: 'PlaneGeometry', sourceRange: [47, 66], @@ -97,6 +102,132 @@ show(mySketch001)` }, ]) }) + test('sketch extrude and sketch on one of the faces', () => { + const code = ` +sketch sk1 { + lineTo(-2.5, 0) + path p = lineTo(0, 10) + lineTo(2.5, 0) +} + |> rx(45, %) + |> translate([1,0,1], %) + |> ry(5, %) +const theExtrude = extrude(2, sk1) +const theTransf = getExtrudeWallTransform('p', theExtrude) +sketch sk2 { + lineTo(-2.5, 0) + path p = lineTo(0, 3) + lineTo(2.5, 0) +} + |> transform(theTransf, %) + |> extrude(2, %) + + +show(theExtrude, sk2)` + const programMemory = executor(abstractSyntaxTree(lexer(code))) + const geos = programMemory?.return?.map( + (a) => programMemory?.root?.[a.name] + ) + const artifactsWithoutGeos = removeGeo(geos as any) + expect(artifactsWithoutGeos).toEqual([ + { + type: 'extrudeGroup', + value: [ + { + type: 'extrudePlane', + position: [ + 0.14624915180581843, 3.5355339059327373, 4.540063765792454, + ], + rotation: [ + -0.24844095888221532, 0.7523143130765927, -0.2910733573455524, + -0.5362616571538269, + ], + __geoMeta: { + geo: 'PlaneGeometry', + sourceRange: [39, 56], + pathToNode: [], + }, + name: 'p', + }, + { + type: 'extrudePlane', + position: [ + 2.636735897035183, 3.5355339059327386, 4.322174408923308, + ], + rotation: [ + 0.22212685137378593, 0.7027132469491032, -0.3116187916437232, + 0.5997895323824204, + ], + __geoMeta: { + geo: 'PlaneGeometry', + sourceRange: [59, 73], + pathToNode: [], + }, + }, + ], + height: 2, + position: [1.083350440839404, 0, 0.9090389553440874], + rotation: [ + 0.38231920253318413, 0.04029905920751535, -0.01669241687462921, + 0.9230002039112792, + ], + __meta: [ + { + sourceRange: [138, 166], + pathToNode: [], + }, + ], + }, + { + type: 'extrudeGroup', + value: [ + { + type: 'extrudePlane', + position: [ + 0.43055783927228125, 5.453687003425103, 4.311246666755821, + ], + rotation: [ + 0.5307054034531232, -0.4972416536396126, 0.3641462373475848, + -0.5818075544860157, + ], + __geoMeta: { + geo: 'PlaneGeometry', + sourceRange: [264, 280], + pathToNode: [], + }, + name: 'p', + }, + { + type: 'extrudePlane', + position: [ + -0.3229447858093035, 3.7387011520000146, 2.6556327856208117, + ], + rotation: [ + 0.06000443169260189, 0.12863059446321826, 0.6408199244764428, + -0.7544557394170275, + ], + __geoMeta: { + geo: 'PlaneGeometry', + sourceRange: [283, 297], + pathToNode: [], + }, + }, + ], + height: 2, + position: [0.14624915180581843, 3.5355339059327373, 4.540063765792454], + rotation: [ + 0.24844095888221532, -0.7523143130765927, 0.2910733573455524, + -0.5362616571538269, + ], + __meta: [ + { + sourceRange: [334, 347], + pathToNode: [], + }, + ], + }, + ]) + }) }) function removeGeo(arts: (SketchGroup | ExtrudeGroup)[]): any { diff --git a/src/lang/engine.tsx b/src/lang/engine.tsx index ecf031fb8..54f06ffb0 100644 --- a/src/lang/engine.tsx +++ b/src/lang/engine.tsx @@ -3,7 +3,10 @@ import { SphereGeometry, BufferGeometry, PlaneGeometry, + Quaternion, + Euler, } from 'three' +import { Rotation, Position } from './executor' export function baseGeo({ from }: { from: [number, number, number] }) { const baseSphere = new SphereGeometry(0.25) @@ -99,9 +102,13 @@ export function extrudeGeo({ from: [number, number, number] to: [number, number, number] length: number -}): BufferGeometry { +}): { + geo: BufferGeometry + position: Position + rotation: Rotation +} { const { - // centre, + centre, Hypotenuse: Hypotenuse3d, ry, rz, @@ -116,5 +123,13 @@ export function extrudeGeo({ face.rotateZ(rz) face.translate(to[0], to[1], to[2]) - return face + const quat = new Quaternion() + const euler = new Euler(-Math.PI / 2, ry, rz * sign, 'XYZ') + quat.setFromEuler(euler) + + return { + geo: face, + position: [centre[0], centre[1], centre[2]], + rotation: quat.toArray() as Rotation, + } } diff --git a/src/lang/executor.ts b/src/lang/executor.ts index 706c3b79b..b428f70ee 100644 --- a/src/lang/executor.ts +++ b/src/lang/executor.ts @@ -6,6 +6,7 @@ import { ObjectExpression, MemberExpression, Identifier, + CallExpression, } from './abstractSyntaxTree' import { sketchFns } from './sketch' import { BufferGeometry } from 'three' @@ -71,6 +72,7 @@ interface ExtrudePlane { type: 'extrudePlane' position: Position rotation: Rotation + name?: string } export type ExtrudeSurface = GeoMeta & @@ -272,13 +274,19 @@ export const executor = ( __meta, } } else if (declaration.init.type === 'CallExpression') { + // TODO: use executeCallExpression here instead const functionName = declaration.init.callee.name const fnArgs = declaration.init.arguments.map((arg) => { if (arg.type === 'Literal') { return arg.value } else if (arg.type === 'Identifier') { return _programMemory.root[arg.name].value + } else if (arg.type === 'ObjectExpression') { + return executeObjectExpression(_programMemory, arg) } + throw new Error( + `Unexpected argument type ${arg.type} in function call` + ) }) if ( 'lineTo' === functionName || @@ -331,7 +339,10 @@ export const executor = ( sketchVal as any // todo memory redo ) _programMemory.root[variableName] = result as any // todo memory redo - } else if (functionName === 'translate') { + } else if ( + functionName === 'translate' || + functionName === 'transform' + ) { const sketch = declaration.init.arguments[1] if (sketch.type !== 'Identifier') throw new Error('rx must be called with an identifier') @@ -343,6 +354,22 @@ export const executor = ( sketchVal as any // todo memory redo ) _programMemory.root[variableName] = result as any // todo memory redo + } else if (functionName === 'getExtrudeWallTransform') { + const extrude = declaration.init.arguments[1] + if (extrude.type !== 'Identifier') + throw new Error('rx must be called with an identifier') + const sketchVal = _programMemory.root[extrude.name] + const value = sketchFns[functionName]( + _programMemory, + [declaration.start, declaration.end], + fnArgs[0], + sketchVal as any // todo memory redo + ) + _programMemory.root[variableName] = { + type: 'userVal', + value, + __meta, + } } else { _programMemory.root[variableName] = { type: 'userVal', @@ -472,85 +499,16 @@ function executePipeBody( [...previousResults, result] ) } else if (expression.type === 'CallExpression') { - const functionName = expression.callee.name - const fnArgs = expression.arguments.map((arg) => { - if (arg.type === 'Literal') { - return arg.value - } else if (arg.type === 'Identifier') { - return programMemory.root[arg.name] - } else if (arg.type === 'PipeSubstitution') { - return previousResults[expressionIndex - 1] - } else if (arg.type === 'ArrayExpression') { - return arg.elements.map((el) => { - if (el.type === 'Literal') { - return el.value - } else if (el.type === 'Identifier') { - return programMemory.root[el.name] - } else if (el.type === 'BinaryExpression') { - return getBinaryExpressionResult(el, programMemory) - } - throw new Error('Invalid argument type') - }) - } - throw new Error('Invalid argument type') - }) - if ( - 'rx' === functionName || - 'ry' === functionName || - 'rz' === functionName - ) { - const result = sketchFns[functionName]( - programMemory, - [expression.start, expression.end], - fnArgs[0], - fnArgs[1] - ) - return executePipeBody( - body, - programMemory, - previousPathToNode, - expressionIndex + 1, - [...previousResults, result] - ) - } - if (functionName === 'extrude') { - const result = sketchFns[functionName]( - programMemory, - 'yo', - [expression.start, expression.end], - fnArgs[0], - fnArgs[1] - ) - return executePipeBody( - body, - programMemory, - previousPathToNode, - expressionIndex + 1, - [...previousResults, result] - ) - } - if (functionName === 'translate') { - const result = sketchFns[functionName]( - programMemory, - [expression.start, expression.end], - fnArgs[0], - fnArgs[1] - ) - return executePipeBody( - body, - programMemory, - previousPathToNode, - expressionIndex + 1, - [...previousResults, result] - ) - } - const result = programMemory.root[functionName].value(...fnArgs) - return executePipeBody( - body, + return executeCallExpression( programMemory, + expression, previousPathToNode, - expressionIndex + 1, - [...previousResults, result] + { + isInPipe: true, + previousResults, + expressionIndex, + body, + } ) } else if (expression.type === 'SketchExpression') { const sketchBody = expression.body @@ -613,6 +571,19 @@ function executeObjectExpression( _programMemory, property.value ) + } else if (property.value.type === 'ArrayExpression') { + obj[property.key.name] = property.value.elements.map((el) => { + if (el.type === 'Literal') { + return el.value + } else if (el.type === 'Identifier') { + return _programMemory.root[el.name].value + } else if (el.type === 'BinaryExpression') { + return getBinaryExpressionResult(el, _programMemory) + } else if (el.type === 'ObjectExpression') { + return executeObjectExpression(_programMemory, el) + } + throw new Error('Invalid argument type') + }) } else { throw new Error( `Unexpected property type ${property.value.type} in object expression` @@ -626,3 +597,131 @@ function executeObjectExpression( }) return obj } + +function executeCallExpression( + programMemory: ProgramMemory, + expression: CallExpression, + previousPathToNode: PathToNode = [], + pipeInfo: { + isInPipe: boolean + previousResults: any[] + expressionIndex: number + body: PipeExpression['body'] + } = { + isInPipe: false, + previousResults: [], + expressionIndex: 0, + body: [], + } +) { + const { isInPipe, previousResults, expressionIndex, body } = pipeInfo + const functionName = expression.callee.name + const fnArgs = expression.arguments.map((arg) => { + if (arg.type === 'Literal') { + return arg.value + } else if (arg.type === 'Identifier') { + const temp = programMemory.root[arg.name] + return temp?.type === 'userVal' ? temp.value : temp + } else if (arg.type === 'PipeSubstitution') { + return previousResults[expressionIndex - 1] + } else if (arg.type === 'ArrayExpression') { + return arg.elements.map((el) => { + if (el.type === 'Literal') { + return el.value + } else if (el.type === 'Identifier') { + return programMemory.root[el.name] + } else if (el.type === 'BinaryExpression') { + return getBinaryExpressionResult(el, programMemory) + } + throw new Error('Invalid argument type') + }) + } else if (arg.type === 'CallExpression') { + const result: any = executeCallExpression( + programMemory, + arg, + previousPathToNode + ) + return result + } + throw new Error('Invalid argument type') + }) + if ('rx' === functionName || 'ry' === functionName || 'rz' === functionName) { + const result = sketchFns[functionName]( + programMemory, + [expression.start, expression.end], + fnArgs[0], + fnArgs[1] + ) + return isInPipe + ? executePipeBody( + body, + programMemory, + previousPathToNode, + expressionIndex + 1, + [...previousResults, result] + ) + : result + } + if (functionName === 'extrude') { + const result = sketchFns[functionName]( + programMemory, + 'yo', + [expression.start, expression.end], + fnArgs[0], + fnArgs[1] + ) + return isInPipe + ? executePipeBody( + body, + programMemory, + previousPathToNode, + expressionIndex + 1, + [...previousResults, result] + ) + : result + } + if (functionName === 'translate' || functionName === 'transform') { + const result = sketchFns[functionName]( + programMemory, + [expression.start, expression.end], + fnArgs[0], + fnArgs[1] + ) + return isInPipe + ? executePipeBody( + body, + programMemory, + previousPathToNode, + expressionIndex + 1, + [...previousResults, result] + ) + : result + } + if (functionName === 'getExtrudeWallTransform') { + const result = sketchFns[functionName]( + programMemory, + [expression.start, expression.end], + fnArgs[0], + fnArgs[1] + ) + return isInPipe + ? executePipeBody( + body, + programMemory, + previousPathToNode, + expressionIndex + 1, + [...previousResults, result] + ) + : result + } + const result = programMemory.root[functionName].value(...fnArgs) + return isInPipe + ? executePipeBody( + body, + programMemory, + previousPathToNode, + expressionIndex + 1, + [...previousResults, result] + ) + : result +} diff --git a/src/lang/sketch.ts b/src/lang/sketch.ts index c1b1fa1f5..8f19a051e 100644 --- a/src/lang/sketch.ts +++ b/src/lang/sketch.ts @@ -5,6 +5,8 @@ import { ExtrudeGroup, SourceRange, ExtrudeSurface, + Position, + Rotation, } from './executor' import { lineGeo, extrudeGeo } from './engine' import { Quaternion, Vector3 } from 'three' @@ -179,21 +181,40 @@ export const sketchFns = { from = lastPoint.to } const to = line.to - const geo = extrudeGeo({ + const { + geo, + position: facePosition, + rotation: faceRotation, + } = extrudeGeo({ from: [from[0], from[1], 0], to: [to[0], to[1], 0], length, }) - extrudeSurfaces.push({ + const groupQuaternion = new Quaternion(...rotation) + const currentWallQuat = new Quaternion(...faceRotation) + const unifiedQuit = new Quaternion().multiplyQuaternions( + currentWallQuat, + groupQuaternion.clone().invert() + ) + + const facePositionVector = new Vector3(...facePosition) + facePositionVector.applyQuaternion(groupQuaternion.clone()) + const unifiedPosition = new Vector3().addVectors( + facePositionVector, + new Vector3(...position) + ) + const surface: ExtrudeSurface = { type: 'extrudePlane', - position, // todo should come from extrudeGeo - rotation, // todo should come from extrudeGeo + position: unifiedPosition.toArray() as Position, + rotation: unifiedQuit.toArray() as Rotation, __geoMeta: { geo, sourceRange: line.__geoMeta.sourceRange, pathToNode: line.__geoMeta.pathToNode, }, - }) + } + line.name && (surface.name = line.name) + extrudeSurfaces.push(surface) } }) return { @@ -211,6 +232,8 @@ export const sketchFns = { } }, translate, + transform, + getExtrudeWallTransform, } function rotateOnAxis( @@ -258,7 +281,7 @@ function translate( const newPosition = oldPosition.add(new Vector3(...vec3)) return { ...sketch, - position: [newPosition.x, newPosition.y, newPosition.z], + position: newPosition.toArray(), __meta: [ ...sketch.__meta, { @@ -268,3 +291,52 @@ function translate( ], } } + +function transform( + programMemory: ProgramMemory, + sourceRange: SourceRange, + transformInfo: { + position: Position + quaternion: Rotation + }, + sketch: T +): T { + const quaternionToApply = new Quaternion(...transformInfo.quaternion) + const newQuaternion = new Quaternion(...sketch.rotation).multiply( + quaternionToApply.invert() + ) + + const oldPosition = new Vector3(...sketch.position) + const newPosition = oldPosition + .applyQuaternion(quaternionToApply) + .add(new Vector3(...transformInfo.position)) + return { + ...sketch, + position: newPosition.toArray(), + rotation: newQuaternion.toArray(), + __meta: [ + ...sketch.__meta, + { + sourceRange, + pathToNode: [], // TODO + }, + ], + } +} + +function getExtrudeWallTransform( + programMemory: ProgramMemory, + sourceRange: SourceRange, + pathName: string, + extrudeGroup: ExtrudeGroup +): { + position: Position + quaternion: Rotation +} { + const path = extrudeGroup.value.find((path) => path.name === pathName) + if (!path) throw new Error(`Could not find path with name ${pathName}`) + return { + position: path.position, + quaternion: path.rotation, + } +}