Add sketch on extrude face functionality

This commit is contained in:
Kurt Hutten IrevDev
2023-01-09 08:52:48 +11:00
parent 9ad6b946c0
commit 2e007ae288
6 changed files with 442 additions and 90 deletions

View File

@ -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)

View File

@ -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, [

View File

@ -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 {

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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<T extends SketchGroup | ExtrudeGroup>(
@ -258,7 +281,7 @@ function translate<T extends SketchGroup | ExtrudeGroup>(
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<T extends SketchGroup | ExtrudeGroup>(
],
}
}
function transform<T extends SketchGroup | ExtrudeGroup>(
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,
}
}