functional sketch working (#26)
* functional sketch working With old sketch block still there * get all version of lines working with add line and update line * remove old ui state types * some clean up * rename some things * add todo for multi cursor * shorten useStore repitition * small type improvement * big overhaul to group sketch function and they ast modifying helpers together * unneeded tweak * ruthlessly rip out sketch logic * clean up path keyword * getting sketch on face working again with all the new sketch line types * add a bunch of tests and re-arrage file structure
This commit is contained in:
1
.github/workflows/test.yml
vendored
1
.github/workflows/test.yml
vendored
@ -14,3 +14,4 @@ jobs:
|
||||
node-version: '18.x'
|
||||
- run: yarn install
|
||||
- run: yarn test:nowatch
|
||||
- run: yarn test:cov
|
||||
|
@ -29,8 +29,9 @@
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"test:nowatch": "react-scripts test --watchAll=false",
|
||||
"test:cov": "react-scripts test --watchAll=false --coverage=true",
|
||||
"eject": "react-scripts eject",
|
||||
"fmt": "prettier --write ./src/**.{ts,tsx} && prettier --write ./src/**/*.{ts,tsx}"
|
||||
"fmt": "prettier --write ./src/**.{ts,tsx} && prettier --write ./src/**/*.{ts,tsx} && prettier --write ./src/lang/**/*.{ts,tsx}"
|
||||
},
|
||||
"jest": {
|
||||
"transformIgnorePatterns": [
|
||||
|
@ -77,6 +77,8 @@ function App() {
|
||||
setEditorView(viewUpdate.view)
|
||||
}
|
||||
const range = viewUpdate.state.selection.ranges[0]
|
||||
// console.log(viewUpdate.state.selection.ranges)
|
||||
// TODO allow multiple cursors so that we can do constrain style features
|
||||
const isNoChange =
|
||||
range.from === selectionRange[0] && range.to === selectionRange[1]
|
||||
if (isNoChange) return
|
||||
|
@ -1,17 +1,18 @@
|
||||
import { useStore } from './useStore'
|
||||
import { useStore, toolTips } from './useStore'
|
||||
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
||||
import { getNodePathFromSourceRange } from './lang/abstractSyntaxTree'
|
||||
|
||||
export const Toolbar = () => {
|
||||
const { setGuiMode, guiMode, selectionRange, ast, updateAst } = useStore(
|
||||
({ guiMode, setGuiMode, selectionRange, ast, updateAst }) => ({
|
||||
guiMode,
|
||||
setGuiMode,
|
||||
selectionRange,
|
||||
ast,
|
||||
updateAst,
|
||||
})
|
||||
)
|
||||
const { setGuiMode, guiMode, selectionRange, ast, updateAst, programMemory } =
|
||||
useStore((s) => ({
|
||||
guiMode: s.guiMode,
|
||||
setGuiMode: s.setGuiMode,
|
||||
selectionRange: s.selectionRange,
|
||||
ast: s.ast,
|
||||
updateAst: s.updateAst,
|
||||
programMemory: s.programMemory,
|
||||
}))
|
||||
|
||||
return (
|
||||
<div>
|
||||
{guiMode.mode === 'default' && (
|
||||
@ -24,7 +25,7 @@ export const Toolbar = () => {
|
||||
}}
|
||||
className="border m-1 px-1 rounded"
|
||||
>
|
||||
Start sketch
|
||||
Start Sketch
|
||||
</button>
|
||||
)}
|
||||
{guiMode.mode === 'canEditExtrude' && (
|
||||
@ -32,7 +33,11 @@ export const Toolbar = () => {
|
||||
onClick={() => {
|
||||
if (!ast) return
|
||||
const pathToNode = getNodePathFromSourceRange(ast, selectionRange)
|
||||
const { modifiedAst } = sketchOnExtrudedFace(ast, pathToNode)
|
||||
const { modifiedAst } = sketchOnExtrudedFace(
|
||||
ast,
|
||||
pathToNode,
|
||||
programMemory
|
||||
)
|
||||
updateAst(modifiedAst)
|
||||
}}
|
||||
className="border m-1 px-1 rounded"
|
||||
@ -41,7 +46,7 @@ export const Toolbar = () => {
|
||||
</button>
|
||||
)}
|
||||
{(guiMode.mode === 'canEditSketch' || false) && (
|
||||
/*guiMode.mode === 'canEditExtrude'*/ <button
|
||||
<button
|
||||
onClick={() => {
|
||||
setGuiMode({
|
||||
mode: 'sketch',
|
||||
@ -49,11 +54,12 @@ export const Toolbar = () => {
|
||||
pathToNode: guiMode.pathToNode,
|
||||
rotation: guiMode.rotation,
|
||||
position: guiMode.position,
|
||||
isTooltip: true,
|
||||
})
|
||||
}}
|
||||
className="border m-1 px-1 rounded"
|
||||
>
|
||||
EditSketch
|
||||
Edit Sketch
|
||||
</button>
|
||||
)}
|
||||
{guiMode.mode === 'canEditSketch' && (
|
||||
@ -98,24 +104,29 @@ export const Toolbar = () => {
|
||||
Exit sketch
|
||||
</button>
|
||||
)}
|
||||
{guiMode.mode === 'sketch' &&
|
||||
(guiMode.sketchMode === 'points' ||
|
||||
guiMode.sketchMode === 'sketchEdit') && (
|
||||
{toolTips.map((sketchFnName) => {
|
||||
if (guiMode.mode !== 'sketch' || !('isTooltip' in guiMode)) return null
|
||||
return (
|
||||
<button
|
||||
key={sketchFnName}
|
||||
className={`border m-1 px-1 rounded ${
|
||||
guiMode.sketchMode === 'points' && 'bg-gray-400'
|
||||
guiMode.sketchMode === sketchFnName && 'bg-gray-400'
|
||||
}`}
|
||||
onClick={() =>
|
||||
setGuiMode({
|
||||
...guiMode,
|
||||
sketchMode:
|
||||
guiMode.sketchMode === 'points' ? 'sketchEdit' : 'points',
|
||||
guiMode.sketchMode === sketchFnName
|
||||
? 'sketchEdit'
|
||||
: sketchFnName,
|
||||
})
|
||||
}
|
||||
>
|
||||
LineTo{guiMode.sketchMode === 'points' && '✅'}
|
||||
{sketchFnName}
|
||||
{guiMode.sketchMode === sketchFnName && '✅'}
|
||||
</button>
|
||||
)}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -69,6 +69,7 @@ export const BasePlanes = () => {
|
||||
rotation: quaternion.toArray() as [number, number, number, number],
|
||||
position: [0, 0, 0],
|
||||
pathToNode,
|
||||
isTooltip: true,
|
||||
})
|
||||
|
||||
updateAst(modifiedAst)
|
||||
|
@ -11,17 +11,15 @@ const myFn = (a) => {
|
||||
}
|
||||
const otherVar = myFn(5)
|
||||
|
||||
sketch theExtrude {
|
||||
lineTo(-2.4, myVar)
|
||||
lineTo(-0.76, otherVar)
|
||||
}
|
||||
const theExtrude = startSketchAt([0, 0])
|
||||
|> lineTo([-2.4, myVar], %)
|
||||
|> lineTo([-0.76, otherVar], %)
|
||||
|> extrude(4, %)
|
||||
|
||||
sketch theSketch {
|
||||
lineTo(-3.35, 0.17)
|
||||
lineTo(0.98, 5.16)
|
||||
lineTo(2.15, 4.32)
|
||||
}
|
||||
const theSketch = startSketchAt([0, 0])
|
||||
|> lineTo([-3.35, 0.17], %)
|
||||
|> lineTo([0.98, 5.16], %)
|
||||
|> lineTo([2.15, 4.32], %)
|
||||
|> rx(90, %)
|
||||
show(theExtrude, theSketch)`
|
||||
const tokens = lexer(code)
|
||||
|
@ -3,8 +3,9 @@ import {
|
||||
getNodePathFromSourceRange,
|
||||
getNodeFromPath,
|
||||
CallExpression,
|
||||
ArrayExpression,
|
||||
} from '../lang/abstractSyntaxTree'
|
||||
import { changeArguments } from '../lang/modifyAst'
|
||||
import { changeSketchArguments } from '../lang/std/sketch'
|
||||
import {
|
||||
ExtrudeGroup,
|
||||
ExtrudeSurface,
|
||||
@ -17,14 +18,10 @@ import {
|
||||
} from '../lang/executor'
|
||||
import { BufferGeometry } from 'three'
|
||||
import { useStore } from '../useStore'
|
||||
import { isOverlapping } from '../lib/utils'
|
||||
import { isOverlap } from '../lib/utils'
|
||||
import { Vector3, DoubleSide, Quaternion } from 'three'
|
||||
import { useSetCursor } from '../hooks/useSetCursor'
|
||||
|
||||
const roundOff = (num: number, places: number): number => {
|
||||
const x = Math.pow(10, places)
|
||||
return Math.round(num * x) / x
|
||||
}
|
||||
import { roundOff } from '../lib/utils'
|
||||
|
||||
function MovingSphere({
|
||||
geo,
|
||||
@ -32,12 +29,14 @@ function MovingSphere({
|
||||
editorCursor,
|
||||
rotation,
|
||||
position,
|
||||
from,
|
||||
}: {
|
||||
geo: BufferGeometry
|
||||
sourceRange: [number, number]
|
||||
editorCursor: boolean
|
||||
rotation: Rotation
|
||||
position: Position
|
||||
from: [number, number]
|
||||
}) {
|
||||
const ref = useRef<BufferGeometry | undefined>() as any
|
||||
const detectionPlaneRef = useRef<BufferGeometry | undefined>() as any
|
||||
@ -46,11 +45,13 @@ function MovingSphere({
|
||||
const [hovered, setHover] = useState(false)
|
||||
const [isMouseDown, setIsMouseDown] = useState(false)
|
||||
|
||||
const { setHighlightRange, guiMode, ast, updateAst } = useStore((s) => ({
|
||||
const { setHighlightRange, guiMode, ast, updateAst, programMemory } =
|
||||
useStore((s) => ({
|
||||
setHighlightRange: s.setHighlightRange,
|
||||
guiMode: s.guiMode,
|
||||
ast: s.ast,
|
||||
updateAst: s.updateAst,
|
||||
programMemory: s.programMemory,
|
||||
}))
|
||||
const { originalXY } = useMemo(() => {
|
||||
if (ast) {
|
||||
@ -59,7 +60,10 @@ function MovingSphere({
|
||||
ast,
|
||||
thePath
|
||||
)
|
||||
const [xArg, yArg] = callExpression?.arguments || []
|
||||
const [xArg, yArg] =
|
||||
guiMode.mode === 'sketch'
|
||||
? callExpression?.arguments || []
|
||||
: (callExpression?.arguments?.[0] as ArrayExpression)?.elements || []
|
||||
const x = xArg?.type === 'Literal' ? xArg.value : -1
|
||||
const y = yArg?.type === 'Literal' ? yArg.value : -1
|
||||
return {
|
||||
@ -87,7 +91,15 @@ function MovingSphere({
|
||||
yo.sub(new Vector3(...position).applyQuaternion(inverseQuaternion))
|
||||
let [x, y] = [roundOff(yo.x, 2), roundOff(yo.y, 2)]
|
||||
let theNewPoints: [number, number] = [x, y]
|
||||
const { modifiedAst } = changeArguments(ast, thePath, theNewPoints)
|
||||
const { modifiedAst } = changeSketchArguments(
|
||||
ast,
|
||||
programMemory,
|
||||
sourceRange,
|
||||
theNewPoints,
|
||||
guiMode,
|
||||
from
|
||||
)
|
||||
|
||||
updateAst(modifiedAst)
|
||||
ref.current.position.set(...position)
|
||||
}
|
||||
@ -285,7 +297,7 @@ function WallRender({
|
||||
|
||||
const [editorCursor, setEditorCursor] = useState(false)
|
||||
useEffect(() => {
|
||||
const shouldHighlight = isOverlapping(
|
||||
const shouldHighlight = isOverlap(
|
||||
geoInfo.__geoMeta.sourceRange,
|
||||
selectionRange
|
||||
)
|
||||
@ -340,7 +352,7 @@ function PathRender({
|
||||
}))
|
||||
const [editorCursor, setEditorCursor] = useState(false)
|
||||
useEffect(() => {
|
||||
const shouldHighlight = isOverlapping(
|
||||
const shouldHighlight = isOverlap(
|
||||
geoInfo.__geoMeta.sourceRange,
|
||||
selectionRange
|
||||
)
|
||||
@ -366,6 +378,7 @@ function PathRender({
|
||||
<MovingSphere
|
||||
key={i}
|
||||
geo={meta.geo}
|
||||
from={geoInfo.from}
|
||||
sourceRange={geoInfo.__geoMeta.sourceRange}
|
||||
editorCursor={forceHighlight || editorCursor}
|
||||
rotation={rotation}
|
||||
@ -424,9 +437,9 @@ function LineRender({
|
||||
)
|
||||
}
|
||||
|
||||
type Boop = ExtrudeGroup | SketchGroup
|
||||
type Artifact = ExtrudeGroup | SketchGroup
|
||||
|
||||
function useSetAppModeFromCursorLocation(artifacts: Boop[]) {
|
||||
function useSetAppModeFromCursorLocation(artifacts: Artifact[]) {
|
||||
const { selectionRange, guiMode, setGuiMode, ast } = useStore(
|
||||
({ selectionRange, guiMode, setGuiMode, ast }) => ({
|
||||
selectionRange,
|
||||
@ -438,7 +451,7 @@ function useSetAppModeFromCursorLocation(artifacts: Boop[]) {
|
||||
useEffect(() => {
|
||||
const artifactsWithinCursorRange: (
|
||||
| {
|
||||
parentType: Boop['type']
|
||||
parentType: Artifact['type']
|
||||
isParent: true
|
||||
pathToNode: PathToNode
|
||||
sourceRange: SourceRange
|
||||
@ -446,25 +459,29 @@ function useSetAppModeFromCursorLocation(artifacts: Boop[]) {
|
||||
position: Position
|
||||
}
|
||||
| {
|
||||
parentType: Boop['type']
|
||||
parentType: Artifact['type']
|
||||
isParent: false
|
||||
pathToNode: PathToNode
|
||||
sourceRange: SourceRange
|
||||
rotation: Rotation
|
||||
position: Position
|
||||
}
|
||||
)[] = []
|
||||
artifacts?.forEach((artifact) => {
|
||||
artifact.value.forEach((geo) => {
|
||||
if (isOverlapping(geo.__geoMeta.sourceRange, selectionRange)) {
|
||||
if (isOverlap(geo.__geoMeta.sourceRange, selectionRange)) {
|
||||
artifactsWithinCursorRange.push({
|
||||
parentType: artifact.type,
|
||||
isParent: false,
|
||||
pathToNode: geo.__geoMeta.pathToNode,
|
||||
sourceRange: geo.__geoMeta.sourceRange,
|
||||
rotation: artifact.rotation,
|
||||
position: artifact.position,
|
||||
})
|
||||
}
|
||||
})
|
||||
artifact.__meta.forEach((meta) => {
|
||||
if (isOverlapping(meta.sourceRange, selectionRange)) {
|
||||
if (isOverlap(meta.sourceRange, selectionRange)) {
|
||||
artifactsWithinCursorRange.push({
|
||||
parentType: artifact.type,
|
||||
isParent: true,
|
||||
@ -477,35 +494,39 @@ function useSetAppModeFromCursorLocation(artifacts: Boop[]) {
|
||||
})
|
||||
})
|
||||
const parentArtifacts = artifactsWithinCursorRange.filter((a) => a.isParent)
|
||||
if (parentArtifacts.length > 1) {
|
||||
console.log('multiple parents, might be an issue?', parentArtifacts)
|
||||
}
|
||||
const hasSketchArtifact = artifactsWithinCursorRange.filter(
|
||||
({ parentType }) => parentType === 'sketchGroup'
|
||||
)
|
||||
const hasExtrudeArtifact = artifactsWithinCursorRange.filter(
|
||||
({ parentType }) => parentType === 'extrudeGroup'
|
||||
)
|
||||
const artifact = parentArtifacts[0]
|
||||
const shouldHighlight = !!artifact
|
||||
const shouldHighlight = !!artifact || hasSketchArtifact.length
|
||||
if (
|
||||
shouldHighlight &&
|
||||
(guiMode.mode === 'default' || guiMode.mode === 'canEditSketch') &&
|
||||
ast &&
|
||||
artifact.parentType === 'sketchGroup' &&
|
||||
artifact.isParent
|
||||
hasSketchArtifact.length
|
||||
) {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, artifact.sourceRange)
|
||||
const { rotation, position } = artifact
|
||||
const pathToNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
hasSketchArtifact[0].sourceRange
|
||||
)
|
||||
const { rotation, position } = hasSketchArtifact[0]
|
||||
setGuiMode({ mode: 'canEditSketch', pathToNode, rotation, position })
|
||||
} else if (
|
||||
shouldHighlight &&
|
||||
(guiMode.mode === 'default' || guiMode.mode === 'canEditSketch') &&
|
||||
ast &&
|
||||
artifact.parentType === 'extrudeGroup' &&
|
||||
artifact.isParent
|
||||
hasExtrudeArtifact.length &&
|
||||
(guiMode.mode === 'default' || guiMode.mode === 'canEditExtrude') &&
|
||||
ast
|
||||
) {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, artifact.sourceRange)
|
||||
const { rotation, position } = artifact
|
||||
const pathToNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
hasExtrudeArtifact[0].sourceRange
|
||||
)
|
||||
const { rotation, position } = hasExtrudeArtifact[0]
|
||||
setGuiMode({ mode: 'canEditExtrude', pathToNode, rotation, position })
|
||||
} else if (
|
||||
!shouldHighlight &&
|
||||
(guiMode.mode === 'canEditSketch' || guiMode.mode === 'canEditExtrude')
|
||||
// (artifact.parentType === 'extrudeGroup' || artifact.type === 'extrudeGroup')
|
||||
(guiMode.mode === 'canEditExtrude' || guiMode.mode === 'canEditSketch')
|
||||
) {
|
||||
setGuiMode({ mode: 'default' })
|
||||
}
|
||||
|
@ -1,20 +1,20 @@
|
||||
import { useStore } from '../useStore'
|
||||
import { DoubleSide, Vector3, Quaternion } from 'three'
|
||||
import { Program } from '../lang/abstractSyntaxTree'
|
||||
import { addLine } from '../lang/modifyAst'
|
||||
import { toolTipModification } from '../lang/std/sketch'
|
||||
import { roundOff } from '../lib/utils'
|
||||
|
||||
export const SketchPlane = () => {
|
||||
const { ast, guiMode, updateAst } = useStore(
|
||||
({ guiMode, ast, updateAst }) => ({
|
||||
guiMode,
|
||||
ast,
|
||||
updateAst,
|
||||
})
|
||||
)
|
||||
const { ast, guiMode, updateAst, programMemory } = useStore((s) => ({
|
||||
guiMode: s.guiMode,
|
||||
ast: s.ast,
|
||||
updateAst: s.updateAst,
|
||||
programMemory: s.programMemory,
|
||||
}))
|
||||
if (guiMode.mode !== 'sketch') {
|
||||
return null
|
||||
}
|
||||
if (guiMode.sketchMode !== 'points' && guiMode.sketchMode !== 'sketchEdit') {
|
||||
if (!(guiMode.sketchMode === 'lineTo') && !('isTooltip' in guiMode)) {
|
||||
return null
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ export const SketchPlane = () => {
|
||||
position={position}
|
||||
name={sketchGridName}
|
||||
onClick={(e) => {
|
||||
if (guiMode.sketchMode !== 'points') {
|
||||
if (!('isTooltip' in guiMode)) {
|
||||
return
|
||||
}
|
||||
const sketchGridIntersection = e.intersections.find(
|
||||
@ -65,10 +65,11 @@ export const SketchPlane = () => {
|
||||
nonCodeMeta: {},
|
||||
}
|
||||
const addLinePoint: [number, number] = [point.x, point.y]
|
||||
const { modifiedAst } = addLine(
|
||||
const { modifiedAst } = toolTipModification(
|
||||
_ast,
|
||||
guiMode.pathToNode,
|
||||
addLinePoint
|
||||
programMemory,
|
||||
addLinePoint,
|
||||
guiMode
|
||||
)
|
||||
updateAst(modifiedAst)
|
||||
}}
|
||||
@ -91,10 +92,6 @@ export const SketchPlane = () => {
|
||||
}
|
||||
|
||||
function roundy({ x, y, z }: any) {
|
||||
const roundOff = (num: number, places: number): number => {
|
||||
const x = Math.pow(10, places)
|
||||
return Math.round(num * x) / x
|
||||
}
|
||||
return {
|
||||
x: roundOff(x, 2),
|
||||
y: roundOff(y, 2),
|
||||
|
@ -462,198 +462,6 @@ const myVar = funcN(1, 2)`
|
||||
})
|
||||
})
|
||||
|
||||
describe('structures specific to this lang', () => {
|
||||
test('sketch', () => {
|
||||
let code = `sketch mySketch {
|
||||
path myPath = lineTo(0,1)
|
||||
lineTo(1,1)
|
||||
path rightPath = lineTo(1,0)
|
||||
close()
|
||||
}
|
||||
`
|
||||
const tokens = lexer(code)
|
||||
const { body } = abstractSyntaxTree(tokens)
|
||||
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
|
||||
expect(body).toEqual([
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
start: 0,
|
||||
end: 102,
|
||||
kind: 'sketch',
|
||||
declarations: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
start: 7,
|
||||
end: 102,
|
||||
id: {
|
||||
type: 'Identifier',
|
||||
start: 7,
|
||||
end: 15,
|
||||
name: 'mySketch',
|
||||
},
|
||||
init: {
|
||||
type: 'SketchExpression',
|
||||
start: 16,
|
||||
end: 102,
|
||||
body: {
|
||||
type: 'BlockStatement',
|
||||
start: 16,
|
||||
end: 102,
|
||||
body: [
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
start: 20,
|
||||
end: 45,
|
||||
kind: 'path',
|
||||
declarations: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
start: 25,
|
||||
end: 45,
|
||||
id: {
|
||||
type: 'Identifier',
|
||||
start: 25,
|
||||
end: 31,
|
||||
name: 'myPath',
|
||||
},
|
||||
init: {
|
||||
type: 'CallExpression',
|
||||
start: 34,
|
||||
end: 45,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
start: 34,
|
||||
end: 40,
|
||||
name: 'lineTo',
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 41,
|
||||
end: 42,
|
||||
value: 0,
|
||||
raw: '0',
|
||||
},
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 43,
|
||||
end: 44,
|
||||
value: 1,
|
||||
raw: '1',
|
||||
},
|
||||
],
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'ExpressionStatement',
|
||||
start: 48,
|
||||
end: 59,
|
||||
expression: {
|
||||
type: 'CallExpression',
|
||||
start: 48,
|
||||
end: 59,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
start: 48,
|
||||
end: 54,
|
||||
name: 'lineTo',
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 55,
|
||||
end: 56,
|
||||
value: 1,
|
||||
raw: '1',
|
||||
},
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 57,
|
||||
end: 58,
|
||||
value: 1,
|
||||
raw: '1',
|
||||
},
|
||||
],
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
start: 62,
|
||||
end: 90,
|
||||
kind: 'path',
|
||||
declarations: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
start: 67,
|
||||
end: 90,
|
||||
id: {
|
||||
type: 'Identifier',
|
||||
start: 67,
|
||||
end: 76,
|
||||
name: 'rightPath',
|
||||
},
|
||||
init: {
|
||||
type: 'CallExpression',
|
||||
start: 79,
|
||||
end: 90,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
start: 79,
|
||||
end: 85,
|
||||
name: 'lineTo',
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 86,
|
||||
end: 87,
|
||||
value: 1,
|
||||
raw: '1',
|
||||
},
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 88,
|
||||
end: 89,
|
||||
value: 0,
|
||||
raw: '0',
|
||||
},
|
||||
],
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'ExpressionStatement',
|
||||
start: 93,
|
||||
end: 100,
|
||||
expression: {
|
||||
type: 'CallExpression',
|
||||
start: 93,
|
||||
end: 100,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
start: 93,
|
||||
end: 98,
|
||||
name: 'close',
|
||||
},
|
||||
arguments: [],
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
describe('testing hasPipeOperator', () => {
|
||||
test('hasPipeOperator is true', () => {
|
||||
let code = `sketch mySketch {
|
||||
@ -739,186 +547,230 @@ const yo = myFunc(9()
|
||||
|
||||
describe('testing pipe operator special', () => {
|
||||
test('pipe operator with sketch', () => {
|
||||
let code = `sketch mySketch {
|
||||
lineTo(2, 3)
|
||||
path myPath = lineTo(0, 1)
|
||||
lineTo(1,1)
|
||||
let code = `const mySketch = startSketchAt([0, 0])
|
||||
|> lineTo([2, 3], %)
|
||||
|> lineTo({ to: [0, 1], tag: "myPath" }, %)
|
||||
|> lineTo([1, 1], %)
|
||||
} |> rx(45, %)
|
||||
`
|
||||
const tokens = lexer(code)
|
||||
const { body } = abstractSyntaxTree(tokens)
|
||||
delete (body[0] as any).declarations[0].init.nonCodeMeta
|
||||
delete (body[0] as any).declarations[0].init.body[0].body.nonCodeMeta
|
||||
expect(body).toEqual([
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
start: 0,
|
||||
end: 90,
|
||||
kind: 'sketch',
|
||||
end: 145,
|
||||
kind: 'const',
|
||||
declarations: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
start: 7,
|
||||
end: 90,
|
||||
id: {
|
||||
type: 'Identifier',
|
||||
start: 7,
|
||||
end: 15,
|
||||
name: 'mySketch',
|
||||
},
|
||||
start: 6,
|
||||
end: 145,
|
||||
id: { type: 'Identifier', start: 6, end: 14, name: 'mySketch' },
|
||||
init: {
|
||||
type: 'PipeExpression',
|
||||
start: 16,
|
||||
end: 90,
|
||||
start: 15,
|
||||
end: 145,
|
||||
body: [
|
||||
{
|
||||
type: 'SketchExpression',
|
||||
start: 16,
|
||||
end: 77,
|
||||
body: {
|
||||
type: 'BlockStatement',
|
||||
start: 16,
|
||||
end: 77,
|
||||
body: [
|
||||
{
|
||||
type: 'ExpressionStatement',
|
||||
start: 20,
|
||||
end: 32,
|
||||
expression: {
|
||||
type: 'CallExpression',
|
||||
start: 20,
|
||||
end: 32,
|
||||
start: 17,
|
||||
end: 38,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
start: 20,
|
||||
end: 26,
|
||||
name: 'lineTo',
|
||||
start: 17,
|
||||
end: 30,
|
||||
name: 'startSketchAt',
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 27,
|
||||
end: 28,
|
||||
value: 2,
|
||||
raw: '2',
|
||||
},
|
||||
type: 'ArrayExpression',
|
||||
start: 31,
|
||||
end: 37,
|
||||
elements: [
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 30,
|
||||
end: 31,
|
||||
value: 3,
|
||||
raw: '3',
|
||||
},
|
||||
],
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
start: 35,
|
||||
end: 61,
|
||||
kind: 'path',
|
||||
declarations: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
start: 40,
|
||||
end: 61,
|
||||
id: {
|
||||
type: 'Identifier',
|
||||
start: 40,
|
||||
end: 46,
|
||||
name: 'myPath',
|
||||
},
|
||||
init: {
|
||||
type: 'CallExpression',
|
||||
start: 49,
|
||||
end: 61,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
start: 49,
|
||||
end: 55,
|
||||
name: 'lineTo',
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 56,
|
||||
end: 57,
|
||||
start: 32,
|
||||
end: 33,
|
||||
value: 0,
|
||||
raw: '0',
|
||||
},
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 59,
|
||||
end: 60,
|
||||
value: 1,
|
||||
raw: '1',
|
||||
start: 35,
|
||||
end: 36,
|
||||
value: 0,
|
||||
raw: '0',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'ExpressionStatement',
|
||||
start: 64,
|
||||
end: 75,
|
||||
expression: {
|
||||
type: 'CallExpression',
|
||||
start: 64,
|
||||
end: 75,
|
||||
start: 44,
|
||||
end: 61,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
start: 64,
|
||||
end: 70,
|
||||
start: 44,
|
||||
end: 50,
|
||||
name: 'lineTo',
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: 'ArrayExpression',
|
||||
start: 51,
|
||||
end: 57,
|
||||
elements: [
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 71,
|
||||
end: 72,
|
||||
value: 1,
|
||||
raw: '1',
|
||||
start: 52,
|
||||
end: 53,
|
||||
value: 2,
|
||||
raw: '2',
|
||||
},
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 73,
|
||||
end: 74,
|
||||
value: 1,
|
||||
raw: '1',
|
||||
start: 55,
|
||||
end: 56,
|
||||
value: 3,
|
||||
raw: '3',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: 'PipeSubstitution', start: 59, end: 60 },
|
||||
],
|
||||
optional: false,
|
||||
},
|
||||
{
|
||||
type: 'CallExpression',
|
||||
start: 67,
|
||||
end: 107,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
start: 67,
|
||||
end: 73,
|
||||
name: 'lineTo',
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: 'ObjectExpression',
|
||||
start: 74,
|
||||
end: 103,
|
||||
properties: [
|
||||
{
|
||||
type: 'ObjectProperty',
|
||||
start: 76,
|
||||
end: 86,
|
||||
key: {
|
||||
type: 'Identifier',
|
||||
start: 76,
|
||||
end: 78,
|
||||
name: 'to',
|
||||
},
|
||||
value: {
|
||||
type: 'ArrayExpression',
|
||||
start: 80,
|
||||
end: 86,
|
||||
elements: [
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 81,
|
||||
end: 82,
|
||||
value: 0,
|
||||
raw: '0',
|
||||
},
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 84,
|
||||
end: 85,
|
||||
value: 1,
|
||||
raw: '1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'ObjectProperty',
|
||||
start: 88,
|
||||
end: 101,
|
||||
key: {
|
||||
type: 'Identifier',
|
||||
start: 88,
|
||||
end: 91,
|
||||
name: 'tag',
|
||||
},
|
||||
value: {
|
||||
type: 'Literal',
|
||||
start: 93,
|
||||
end: 101,
|
||||
value: 'myPath',
|
||||
raw: '"myPath"',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: 'PipeSubstitution', start: 105, end: 106 },
|
||||
],
|
||||
optional: false,
|
||||
},
|
||||
{
|
||||
type: 'CallExpression',
|
||||
start: 81,
|
||||
end: 90,
|
||||
start: 113,
|
||||
end: 130,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
start: 81,
|
||||
end: 83,
|
||||
start: 113,
|
||||
end: 119,
|
||||
name: 'lineTo',
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: 'ArrayExpression',
|
||||
start: 120,
|
||||
end: 126,
|
||||
elements: [
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 121,
|
||||
end: 122,
|
||||
value: 1,
|
||||
raw: '1',
|
||||
},
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 124,
|
||||
end: 125,
|
||||
value: 1,
|
||||
raw: '1',
|
||||
},
|
||||
],
|
||||
},
|
||||
{ type: 'PipeSubstitution', start: 128, end: 129 },
|
||||
],
|
||||
optional: false,
|
||||
},
|
||||
{
|
||||
type: 'CallExpression',
|
||||
start: 136,
|
||||
end: 145,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
start: 136,
|
||||
end: 138,
|
||||
name: 'rx',
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: 'Literal',
|
||||
start: 84,
|
||||
end: 86,
|
||||
start: 139,
|
||||
end: 141,
|
||||
value: 45,
|
||||
raw: '45',
|
||||
},
|
||||
{
|
||||
type: 'PipeSubstitution',
|
||||
start: 88,
|
||||
end: 89,
|
||||
},
|
||||
{ type: 'PipeSubstitution', start: 143, end: 144 },
|
||||
],
|
||||
optional: false,
|
||||
},
|
||||
@ -1841,47 +1693,42 @@ const key = 'c'`
|
||||
expect(nonCodeMeta2[0].start).not.toBe(nonCodeMetaInstance.start)
|
||||
})
|
||||
it('comments nested within a block statement', () => {
|
||||
const code = `sketch mySketch {
|
||||
path myPath = lineTo(0,1)
|
||||
lineTo(1,1) /* this is
|
||||
const code = `const mySketch = startSketchAt([0,0])
|
||||
|> lineTo({ to: [0, 1], tag: 'myPath' }, %)
|
||||
|> lineTo([1, 1], %) /* this is
|
||||
a comment
|
||||
spanning a few lines */
|
||||
path rightPath = lineTo(1,0)
|
||||
close()
|
||||
}
|
||||
`
|
||||
|> lineTo({ to: [1,0], tag: "rightPath" }, %)
|
||||
|> close(%)
|
||||
`
|
||||
|
||||
const { body } = abstractSyntaxTree(lexer(code))
|
||||
const indexOfSecondLineToExpression = 1 // 0 index so `path myPath = lineTo(0,1)` is 0
|
||||
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.body
|
||||
.nonCodeMeta
|
||||
const indexOfSecondLineToExpression = 2
|
||||
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
|
||||
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
|
||||
type: 'NoneCodeNode',
|
||||
start: 67,
|
||||
end: 133,
|
||||
start: 106,
|
||||
end: 168,
|
||||
value:
|
||||
' /* this is \n a comment \n spanning a few lines */\n ',
|
||||
})
|
||||
})
|
||||
it('comments in a pipe expression', () => {
|
||||
const code = [
|
||||
'sketch mySk1 {',
|
||||
' lineTo(1, 1)',
|
||||
' path myPath = lineTo(0, 1)',
|
||||
' lineTo(1, 1)',
|
||||
'}',
|
||||
'const mySk1 = startSketchAt([0, 0])',
|
||||
' |> lineTo([1, 1], %)',
|
||||
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
|
||||
' |> lineTo([1, 1], %)',
|
||||
'// a comment',
|
||||
' |> rx(90, %)',
|
||||
].join('\n')
|
||||
|
||||
const { body } = abstractSyntaxTree(lexer(code))
|
||||
const bing = abstractSyntaxTree(lexer(code))
|
||||
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
|
||||
expect(1).toBe(1)
|
||||
expect(sketchNonCodeMeta[0]).toEqual({
|
||||
expect(sketchNonCodeMeta[3]).toEqual({
|
||||
type: 'NoneCodeNode',
|
||||
start: 75,
|
||||
end: 91,
|
||||
start: 125,
|
||||
end: 141,
|
||||
value: '\n// a comment\n ',
|
||||
})
|
||||
})
|
||||
|
@ -17,7 +17,6 @@ type syntaxType =
|
||||
| 'ObjectExpression'
|
||||
| 'ObjectProperty'
|
||||
| 'FunctionExpression'
|
||||
| 'SketchExpression'
|
||||
| 'PipeExpression'
|
||||
| 'PipeSubstitution'
|
||||
| 'Literal'
|
||||
@ -321,14 +320,14 @@ function makeArguments(
|
||||
export interface VariableDeclaration extends GeneralStatement {
|
||||
type: 'VariableDeclaration'
|
||||
declarations: VariableDeclarator[]
|
||||
kind: 'const' | 'unknown' | 'fn' | 'sketch' | 'path' //| "solid" | "surface" | "face"
|
||||
kind: 'const' | 'unknown' | 'fn' //| "solid" | "surface" | "face"
|
||||
}
|
||||
|
||||
function makeVariableDeclaration(
|
||||
tokens: Token[],
|
||||
index: number
|
||||
): { declaration: VariableDeclaration; lastIndex: number } {
|
||||
// token index should point to a declaration keyword i.e. const, fn, sketch, path
|
||||
// token index should point to a declaration keyword i.e. const, fn
|
||||
const currentToken = tokens[index]
|
||||
const declarationStartToken = nextMeaningfulToken(tokens, index)
|
||||
const { declarations, lastIndex } = makeVariableDeclarators(
|
||||
@ -345,10 +344,6 @@ function makeVariableDeclaration(
|
||||
? 'const'
|
||||
: currentToken.value === 'fn'
|
||||
? 'fn'
|
||||
: currentToken.value === 'sketch'
|
||||
? 'sketch'
|
||||
: currentToken.value === 'path'
|
||||
? 'path'
|
||||
: 'unknown',
|
||||
declarations,
|
||||
},
|
||||
@ -362,7 +357,6 @@ export type Value =
|
||||
| BinaryExpression
|
||||
| FunctionExpression
|
||||
| CallExpression
|
||||
| SketchExpression
|
||||
| PipeExpression
|
||||
| PipeSubstitution
|
||||
| ArrayExpression
|
||||
@ -486,13 +480,6 @@ function makeVariableDeclarators(
|
||||
)
|
||||
init = expression
|
||||
lastIndex = pipeLastIndex
|
||||
} else if (
|
||||
declarationToken.token.type === 'word' &&
|
||||
declarationToken.token.value === 'sketch'
|
||||
) {
|
||||
const sketchExp = makeSketchExpression(tokens, assignmentToken.index)
|
||||
init = sketchExp.expression
|
||||
lastIndex = sketchExp.lastIndex
|
||||
} else {
|
||||
const { value, lastIndex: valueLastIndex } = makeValue(
|
||||
tokens,
|
||||
@ -544,7 +531,7 @@ function makeIdentifier(token: Token[], index: number): Identifier {
|
||||
}
|
||||
}
|
||||
|
||||
interface PipeSubstitution extends GeneralStatement {
|
||||
export interface PipeSubstitution extends GeneralStatement {
|
||||
type: 'PipeSubstitution'
|
||||
}
|
||||
|
||||
@ -841,30 +828,6 @@ function makeBinaryExpression(
|
||||
}
|
||||
}
|
||||
|
||||
export interface SketchExpression extends GeneralStatement {
|
||||
type: 'SketchExpression'
|
||||
body: BlockStatement
|
||||
}
|
||||
|
||||
function makeSketchExpression(
|
||||
tokens: Token[],
|
||||
index: number
|
||||
): { expression: SketchExpression; lastIndex: number } {
|
||||
const currentToken = tokens[index]
|
||||
const { block, lastIndex: bodyLastIndex } = makeBlockStatement(tokens, index)
|
||||
const endToken = tokens[bodyLastIndex]
|
||||
|
||||
return {
|
||||
expression: {
|
||||
type: 'SketchExpression',
|
||||
start: currentToken.start,
|
||||
end: endToken.end,
|
||||
body: block,
|
||||
},
|
||||
lastIndex: bodyLastIndex,
|
||||
}
|
||||
}
|
||||
|
||||
export interface PipeExpression extends GeneralStatement {
|
||||
type: 'PipeExpression'
|
||||
body: Value[]
|
||||
@ -909,10 +872,6 @@ function makePipeBody(
|
||||
const val = makeValue(tokens, expressionStart.index)
|
||||
value = val.value
|
||||
lastIndex = val.lastIndex
|
||||
} else if (currentToken.type === 'brace' && currentToken.value === '{') {
|
||||
const sketch = makeSketchExpression(tokens, index)
|
||||
value = sketch.expression
|
||||
lastIndex = sketch.lastIndex
|
||||
} else {
|
||||
throw new Error('Expected a previous PipeValue if statement to match')
|
||||
}
|
||||
@ -1125,10 +1084,7 @@ function makeBody(
|
||||
|
||||
if (
|
||||
token.type === 'word' &&
|
||||
(token.value === 'const' ||
|
||||
token.value === 'fn' ||
|
||||
token.value === 'sketch' ||
|
||||
token.value === 'path')
|
||||
(token.value === 'const' || token.value === 'fn')
|
||||
) {
|
||||
const { declaration, lastIndex } = makeVariableDeclaration(
|
||||
tokens,
|
||||
@ -1218,10 +1174,7 @@ export function findNextDeclarationKeyword(
|
||||
}
|
||||
if (
|
||||
nextToken.token.type === 'word' &&
|
||||
(nextToken.token.value === 'const' ||
|
||||
nextToken.token.value === 'fn' ||
|
||||
nextToken.token.value === 'sketch' ||
|
||||
nextToken.token.value === 'path')
|
||||
(nextToken.token.value === 'const' || nextToken.token.value === 'fn')
|
||||
) {
|
||||
return nextToken
|
||||
}
|
||||
@ -1287,7 +1240,7 @@ export function hasPipeOperator(
|
||||
_limitIndex = -1
|
||||
): ReturnType<typeof nextMeaningfulToken> | false {
|
||||
// this probably still needs some work
|
||||
// should be called on expression statuments (i.e "lineTo" for lineTo(10, 10)) or "{" for sketch declarations
|
||||
// should be called on expression statuments (i.e "lineTo" for lineTo(10, 10))
|
||||
let limitIndex = _limitIndex
|
||||
if (limitIndex === -1) {
|
||||
const callExpressionEnd = isCallExpression(tokens, index)
|
||||
@ -1584,34 +1537,13 @@ export function getNodePathFromSourceRange(
|
||||
const init = declaration.init
|
||||
if (init.start <= start && init.end >= end) {
|
||||
path.push('init')
|
||||
if (init.type === 'SketchExpression') {
|
||||
const body = init.body
|
||||
if (body.start <= start && body.end >= end) {
|
||||
path.push('body')
|
||||
if (body.type === 'BlockStatement') {
|
||||
path = getNodePathFromSourceRange(body, sourceRange, path)
|
||||
}
|
||||
}
|
||||
} else if (init.type === 'PipeExpression') {
|
||||
if (init.type === 'PipeExpression') {
|
||||
const body = init.body
|
||||
for (let pipeIndex = 0; pipeIndex < body.length; pipeIndex++) {
|
||||
const pipe = body[pipeIndex]
|
||||
if (pipe.start <= start && pipe.end >= end) {
|
||||
path.push('body')
|
||||
path.push(pipeIndex)
|
||||
if (pipe.type === 'SketchExpression') {
|
||||
const body = pipe.body
|
||||
if (body.start <= start && body.end >= end) {
|
||||
path.push('body')
|
||||
if (body.type === 'BlockStatement') {
|
||||
path = getNodePathFromSourceRange(
|
||||
body,
|
||||
sourceRange,
|
||||
path
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (init.type === 'CallExpression') {
|
||||
|
@ -5,10 +5,9 @@ import { executor, SketchGroup, ExtrudeGroup } from './executor'
|
||||
describe('testing artifacts', () => {
|
||||
test('sketch artifacts', () => {
|
||||
const code = `
|
||||
sketch mySketch001 {
|
||||
lineTo(-1.59, -1.54)
|
||||
lineTo(0.46, -5.82)
|
||||
}
|
||||
const mySketch001 = startSketchAt([0, 0])
|
||||
|> lineTo([-1.59, -1.54], %)
|
||||
|> lineTo([0.46, -5.82], %)
|
||||
|> rx(45, %)
|
||||
show(mySketch001)`
|
||||
const programMemory = executor(abstractSyntaxTree(lexer(code)))
|
||||
@ -19,13 +18,14 @@ show(mySketch001)`
|
||||
expect(artifactsWithoutGeos).toEqual([
|
||||
{
|
||||
type: 'sketchGroup',
|
||||
start: [0, 0],
|
||||
value: [
|
||||
{
|
||||
type: 'toPoint',
|
||||
to: [-1.59, -1.54],
|
||||
from: [0, 0],
|
||||
__geoMeta: {
|
||||
sourceRange: [24, 44],
|
||||
sourceRange: [48, 73],
|
||||
pathToNode: [],
|
||||
geos: ['line', 'lineEnd'],
|
||||
},
|
||||
@ -35,7 +35,7 @@ show(mySketch001)`
|
||||
to: [0.46, -5.82],
|
||||
from: [-1.59, -1.54],
|
||||
__geoMeta: {
|
||||
sourceRange: [47, 66],
|
||||
sourceRange: [79, 103],
|
||||
pathToNode: [],
|
||||
geos: ['line', 'lineEnd'],
|
||||
},
|
||||
@ -44,24 +44,17 @@ show(mySketch001)`
|
||||
position: [0, 0, 0],
|
||||
rotation: [0.3826834323650898, 0, 0, 0.9238795325112867],
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [20, 68],
|
||||
pathToNode: ['body', 0, 'declarations', 0, 'init', 0],
|
||||
},
|
||||
{
|
||||
sourceRange: [74, 83],
|
||||
pathToNode: [],
|
||||
},
|
||||
{ sourceRange: [21, 42], pathToNode: [] },
|
||||
{ sourceRange: [109, 118], pathToNode: [] },
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
test('extrude artifacts', () => {
|
||||
const code = `
|
||||
sketch mySketch001 {
|
||||
lineTo(-1.59, -1.54)
|
||||
lineTo(0.46, -5.82)
|
||||
}
|
||||
const mySketch001 = startSketchAt([0, 0])
|
||||
|> lineTo([-1.59, -1.54], %)
|
||||
|> lineTo([0.46, -5.82], %)
|
||||
|> rx(45, %)
|
||||
|> extrude(2, %)
|
||||
show(mySketch001)`
|
||||
@ -83,7 +76,7 @@ show(mySketch001)`
|
||||
],
|
||||
__geoMeta: {
|
||||
geo: 'PlaneGeometry',
|
||||
sourceRange: [24, 44],
|
||||
sourceRange: [48, 73],
|
||||
pathToNode: [],
|
||||
},
|
||||
},
|
||||
@ -98,7 +91,7 @@ show(mySketch001)`
|
||||
],
|
||||
__geoMeta: {
|
||||
geo: 'PlaneGeometry',
|
||||
sourceRange: [47, 66],
|
||||
sourceRange: [79, 103],
|
||||
pathToNode: [],
|
||||
},
|
||||
},
|
||||
@ -107,35 +100,27 @@ show(mySketch001)`
|
||||
position: [0, 0, 0],
|
||||
rotation: [0.3826834323650898, 0, 0, 0.9238795325112867],
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [89, 102],
|
||||
pathToNode: [],
|
||||
},
|
||||
{
|
||||
sourceRange: [20, 68],
|
||||
pathToNode: ['body', 0, 'declarations', 0, 'init', 0],
|
||||
},
|
||||
{ sourceRange: [124, 137], pathToNode: [] },
|
||||
{ sourceRange: [21, 42], pathToNode: [] },
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
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)
|
||||
}
|
||||
const sk1 = startSketchAt([0, 0])
|
||||
|> lineTo([-2.5, 0], %)
|
||||
|> lineTo({ to: [0, 10], tag: "p" }, %)
|
||||
|> 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)
|
||||
}
|
||||
const sk2 = startSketchAt([0, 0])
|
||||
|> lineTo([-2.5, 0], %)
|
||||
|> lineTo({ to: [0, 3], tag: "p" }, %)
|
||||
|> lineTo([2.5, 0], %)
|
||||
|> transform(theTransf, %)
|
||||
|> extrude(2, %)
|
||||
|
||||
@ -159,7 +144,7 @@ show(theExtrude, sk2)`
|
||||
],
|
||||
__geoMeta: {
|
||||
geo: 'PlaneGeometry',
|
||||
sourceRange: [16, 31],
|
||||
sourceRange: [40, 60],
|
||||
pathToNode: [],
|
||||
},
|
||||
},
|
||||
@ -174,7 +159,7 @@ show(theExtrude, sk2)`
|
||||
],
|
||||
__geoMeta: {
|
||||
geo: 'PlaneGeometry',
|
||||
sourceRange: [39, 56],
|
||||
sourceRange: [66, 102],
|
||||
pathToNode: [],
|
||||
},
|
||||
name: 'p',
|
||||
@ -190,7 +175,7 @@ show(theExtrude, sk2)`
|
||||
],
|
||||
__geoMeta: {
|
||||
geo: 'PlaneGeometry',
|
||||
sourceRange: [59, 73],
|
||||
sourceRange: [108, 127],
|
||||
pathToNode: [],
|
||||
},
|
||||
},
|
||||
@ -202,14 +187,8 @@ show(theExtrude, sk2)`
|
||||
0.9230002039112792,
|
||||
],
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [138, 166],
|
||||
pathToNode: [],
|
||||
},
|
||||
{
|
||||
sourceRange: [12, 75],
|
||||
pathToNode: ['body', 0, 'declarations', 0, 'init', 0],
|
||||
},
|
||||
{ sourceRange: [190, 218], pathToNode: [] },
|
||||
{ sourceRange: [13, 34], pathToNode: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
@ -226,7 +205,7 @@ show(theExtrude, sk2)`
|
||||
],
|
||||
__geoMeta: {
|
||||
geo: 'PlaneGeometry',
|
||||
sourceRange: [241, 256],
|
||||
sourceRange: [317, 337],
|
||||
pathToNode: [],
|
||||
},
|
||||
},
|
||||
@ -241,7 +220,7 @@ show(theExtrude, sk2)`
|
||||
],
|
||||
__geoMeta: {
|
||||
geo: 'PlaneGeometry',
|
||||
sourceRange: [264, 280],
|
||||
sourceRange: [343, 378],
|
||||
pathToNode: [],
|
||||
},
|
||||
name: 'p',
|
||||
@ -257,7 +236,7 @@ show(theExtrude, sk2)`
|
||||
],
|
||||
__geoMeta: {
|
||||
geo: 'PlaneGeometry',
|
||||
sourceRange: [283, 297],
|
||||
sourceRange: [384, 403],
|
||||
pathToNode: [],
|
||||
},
|
||||
},
|
||||
@ -269,14 +248,8 @@ show(theExtrude, sk2)`
|
||||
-0.5362616571538269,
|
||||
],
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [334, 347],
|
||||
pathToNode: [],
|
||||
},
|
||||
{
|
||||
sourceRange: [237, 299],
|
||||
pathToNode: ['body', 3, 'declarations', 0, 'init', 0],
|
||||
},
|
||||
{ sourceRange: [438, 451], pathToNode: [] },
|
||||
{ sourceRange: [290, 311], pathToNode: [] },
|
||||
],
|
||||
},
|
||||
])
|
||||
|
@ -62,12 +62,11 @@ log(5, myVar)`
|
||||
expect(root.magicNum.value).toBe(69)
|
||||
})
|
||||
it('sketch declaration', () => {
|
||||
let code = `sketch mySketch {
|
||||
path myPath = lineTo(0,2)
|
||||
lineTo(2,3)
|
||||
path rightPath = lineTo(5,-1)
|
||||
close()
|
||||
}
|
||||
let code = `const mySketch = startSketchAt([0,0])
|
||||
|> lineTo({to: [0,2], tag: "myPath"}, %)
|
||||
|> lineTo([2,3], %)
|
||||
|> lineTo({ to: [5,-1], tag: "rightPath" }, %)
|
||||
// |> close(%)
|
||||
show(mySketch)
|
||||
`
|
||||
const { root, return: _return } = exe(code)
|
||||
@ -77,9 +76,9 @@ show(mySketch)
|
||||
{
|
||||
type: 'toPoint',
|
||||
to: [0, 2],
|
||||
from: [5, -1],
|
||||
from: [0, 0],
|
||||
__geoMeta: {
|
||||
sourceRange: [25, 45],
|
||||
sourceRange: [43, 80],
|
||||
pathToNode: [],
|
||||
geos: ['line', 'lineEnd'],
|
||||
},
|
||||
@ -90,7 +89,7 @@ show(mySketch)
|
||||
to: [2, 3],
|
||||
from: [0, 2],
|
||||
__geoMeta: {
|
||||
sourceRange: [48, 59],
|
||||
sourceRange: [86, 102],
|
||||
pathToNode: [],
|
||||
geos: ['line', 'lineEnd'],
|
||||
},
|
||||
@ -100,29 +99,19 @@ show(mySketch)
|
||||
to: [5, -1],
|
||||
from: [2, 3],
|
||||
__geoMeta: {
|
||||
sourceRange: [67, 91],
|
||||
sourceRange: [108, 151],
|
||||
pathToNode: [],
|
||||
geos: ['line', 'lineEnd'],
|
||||
},
|
||||
name: 'rightPath',
|
||||
},
|
||||
{
|
||||
type: 'toPoint',
|
||||
from: [5, -1],
|
||||
to: [0, 2],
|
||||
__geoMeta: {
|
||||
sourceRange: [94, 101],
|
||||
pathToNode: [],
|
||||
geos: ['line', 'lineEnd'],
|
||||
},
|
||||
},
|
||||
])
|
||||
// expect(root.mySketch.sketch[0]).toEqual(root.mySketch.sketch[4].firstPath)
|
||||
expect(_return).toEqual([
|
||||
{
|
||||
type: 'Identifier',
|
||||
start: 109,
|
||||
end: 117,
|
||||
start: 174,
|
||||
end: 182,
|
||||
name: 'mySketch',
|
||||
},
|
||||
])
|
||||
@ -139,13 +128,11 @@ show(mySketch)
|
||||
|
||||
it('rotated sketch', () => {
|
||||
const code = [
|
||||
'sketch mySk1 {',
|
||||
' lineTo(1,1)',
|
||||
' path myPath = lineTo(0, 1)',
|
||||
' lineTo(1,1)',
|
||||
'}',
|
||||
'const mySk1 = startSketchAt([0,0])',
|
||||
' |> lineTo([1,1], %)',
|
||||
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
|
||||
' |> lineTo([1, 1], %)',
|
||||
'const rotated = rx(90, mySk1)',
|
||||
// 'show(mySk1)',
|
||||
].join('\n')
|
||||
const { root } = exe(code)
|
||||
expect(root.mySk1.value).toHaveLength(3)
|
||||
@ -166,23 +153,24 @@ show(mySketch)
|
||||
|
||||
it('execute pipe sketch into call expression', () => {
|
||||
const code = [
|
||||
'sketch mySk1 {',
|
||||
' lineTo(1,1)',
|
||||
' path myPath = lineTo(0, 1)',
|
||||
' lineTo(1,1)',
|
||||
'} |> rx(90, %)',
|
||||
'const mySk1 = startSketchAt([0,0])',
|
||||
' |> lineTo([1,1], %)',
|
||||
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
|
||||
' |> lineTo([1,1], %)',
|
||||
' |> rx(90, %)',
|
||||
].join('\n')
|
||||
const { root } = exe(code)
|
||||
const striptVersion = removeGeoFromSketch(root.mySk1 as SketchGroup)
|
||||
expect(striptVersion).toEqual({
|
||||
type: 'sketchGroup',
|
||||
start: [0, 0],
|
||||
value: [
|
||||
{
|
||||
type: 'toPoint',
|
||||
to: [1, 1],
|
||||
from: [0, 0],
|
||||
__geoMeta: {
|
||||
sourceRange: [17, 28],
|
||||
sourceRange: [40, 56],
|
||||
pathToNode: [],
|
||||
geos: ['line', 'lineEnd'],
|
||||
},
|
||||
@ -192,7 +180,7 @@ show(mySketch)
|
||||
to: [0, 1],
|
||||
from: [1, 1],
|
||||
__geoMeta: {
|
||||
sourceRange: [36, 57],
|
||||
sourceRange: [62, 100],
|
||||
pathToNode: [],
|
||||
geos: ['line', 'lineEnd'],
|
||||
},
|
||||
@ -203,7 +191,7 @@ show(mySketch)
|
||||
to: [1, 1],
|
||||
from: [0, 1],
|
||||
__geoMeta: {
|
||||
sourceRange: [60, 71],
|
||||
sourceRange: [106, 122],
|
||||
pathToNode: [],
|
||||
geos: ['line', 'lineEnd'],
|
||||
},
|
||||
@ -212,14 +200,8 @@ show(mySketch)
|
||||
position: [0, 0, 0],
|
||||
rotation: [0.7071067811865475, 0, 0, 0.7071067811865476],
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [13, 73],
|
||||
pathToNode: ['body', 0, 'declarations', 0, 'init', 0],
|
||||
},
|
||||
{
|
||||
sourceRange: [77, 86],
|
||||
pathToNode: [],
|
||||
},
|
||||
{ sourceRange: [14, 34], pathToNode: [] },
|
||||
{ sourceRange: [128, 137], pathToNode: [] },
|
||||
],
|
||||
})
|
||||
})
|
||||
|
@ -7,13 +7,10 @@ import {
|
||||
MemberExpression,
|
||||
Identifier,
|
||||
CallExpression,
|
||||
ArrayExpression,
|
||||
} from './abstractSyntaxTree'
|
||||
import {
|
||||
sketchFns,
|
||||
internalFns,
|
||||
InternalFnNames,
|
||||
SketchFnNames,
|
||||
} from './sketch'
|
||||
import { InternalFnNames } from './std/stdTypes'
|
||||
import { internalFns } from './std/std'
|
||||
import { BufferGeometry } from 'three'
|
||||
|
||||
export type SourceRange = [number, number]
|
||||
@ -68,6 +65,7 @@ export type Path = ToPoint | HorizontalLineTo | AngledLineTo
|
||||
export interface SketchGroup {
|
||||
type: 'sketchGroup'
|
||||
value: Path[]
|
||||
start?: Path['from']
|
||||
position: Position
|
||||
rotation: Rotation
|
||||
__meta: Metadata[]
|
||||
@ -220,25 +218,6 @@ export const executor = (
|
||||
value: executeObjectExpression(_programMemory, declaration.init),
|
||||
__meta,
|
||||
}
|
||||
} else if (declaration.init.type === 'SketchExpression') {
|
||||
const sketchInit = declaration.init
|
||||
const fnMemory: ProgramMemory = {
|
||||
root: {
|
||||
..._programMemory.root,
|
||||
},
|
||||
_sketch: [],
|
||||
}
|
||||
let { _sketch } = executor(sketchInit.body, fnMemory, {
|
||||
bodyType: 'sketch',
|
||||
})
|
||||
const newSketch: SketchGroup = {
|
||||
type: 'sketchGroup',
|
||||
value: _sketch || [],
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1], //x,y,z,w
|
||||
__meta,
|
||||
}
|
||||
_programMemory.root[variableName] = newSketch
|
||||
} else if (declaration.init.type === 'FunctionExpression') {
|
||||
const fnInit = declaration.init
|
||||
|
||||
@ -287,33 +266,14 @@ export const executor = (
|
||||
return _programMemory.root[arg.name].value
|
||||
} else if (arg.type === 'ObjectExpression') {
|
||||
return executeObjectExpression(_programMemory, arg)
|
||||
} else if (arg.type === 'ArrayExpression') {
|
||||
return executeArrayExpression(_programMemory, arg)
|
||||
}
|
||||
throw new Error(
|
||||
`Unexpected argument type ${arg.type} in function call`
|
||||
)
|
||||
})
|
||||
if (functionName in sketchFns) {
|
||||
const sketchFnName = functionName as SketchFnNames
|
||||
if (options.bodyType !== 'sketch') {
|
||||
throw new Error(
|
||||
`Cannot call ${functionName} outside of a sketch declaration`
|
||||
)
|
||||
}
|
||||
const result = sketchFns[sketchFnName](
|
||||
{
|
||||
programMemory: _programMemory,
|
||||
name: variableName,
|
||||
sourceRange: [declaration.start, declaration.end],
|
||||
},
|
||||
...fnArgs
|
||||
)
|
||||
_programMemory._sketch = result.programMemory._sketch
|
||||
_programMemory.root[variableName] = {
|
||||
type: 'userVal',
|
||||
value: result.currentPath,
|
||||
__meta,
|
||||
}
|
||||
} else if (functionName in internalFns) {
|
||||
if (functionName in internalFns) {
|
||||
const result = executeCallExpression(
|
||||
_programMemory,
|
||||
declaration.init,
|
||||
@ -362,22 +322,7 @@ export const executor = (
|
||||
return _programMemory.root[arg.name].value
|
||||
}
|
||||
})
|
||||
if (functionName in sketchFns) {
|
||||
if (options.bodyType !== 'sketch') {
|
||||
throw new Error(
|
||||
`Cannot call ${functionName} outside of a sketch declaration`
|
||||
)
|
||||
}
|
||||
const sketchFnName = functionName as SketchFnNames
|
||||
const result = sketchFns[sketchFnName](
|
||||
{
|
||||
programMemory: _programMemory,
|
||||
sourceRange: [statement.start, statement.end],
|
||||
},
|
||||
...args
|
||||
)
|
||||
_programMemory._sketch = [...(result.programMemory._sketch || [])]
|
||||
} else if ('show' === functionName) {
|
||||
if ('show' === functionName) {
|
||||
if (options.bodyType !== 'root') {
|
||||
throw new Error(`Cannot call ${functionName} outside of a root`)
|
||||
}
|
||||
@ -482,36 +427,6 @@ function executePipeBody(
|
||||
body,
|
||||
}
|
||||
)
|
||||
} else if (expression.type === 'SketchExpression') {
|
||||
const sketchBody = expression.body
|
||||
const fnMemory: ProgramMemory = {
|
||||
root: {
|
||||
...programMemory.root,
|
||||
},
|
||||
_sketch: [],
|
||||
}
|
||||
let { _sketch } = executor(sketchBody, fnMemory, {
|
||||
bodyType: 'sketch',
|
||||
})
|
||||
const newSketch: SketchGroup = {
|
||||
type: 'sketchGroup',
|
||||
value: _sketch || [],
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0, 1], //x,y,z,w
|
||||
__meta: [
|
||||
{
|
||||
sourceRange: [expression.start, expression.end],
|
||||
pathToNode: [...previousPathToNode, expressionIndex],
|
||||
},
|
||||
],
|
||||
}
|
||||
return executePipeBody(
|
||||
body,
|
||||
programMemory,
|
||||
previousPathToNode,
|
||||
expressionIndex + 1,
|
||||
[...previousResults, newSketch]
|
||||
)
|
||||
}
|
||||
|
||||
throw new Error('Invalid pipe expression')
|
||||
@ -544,18 +459,8 @@ function executeObjectExpression(
|
||||
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')
|
||||
})
|
||||
const result = executeArrayExpression(_programMemory, property.value)
|
||||
obj[property.key.name] = result
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected property type ${property.value.type} in object expression`
|
||||
@ -570,6 +475,24 @@ function executeObjectExpression(
|
||||
return obj
|
||||
}
|
||||
|
||||
function executeArrayExpression(
|
||||
_programMemory: ProgramMemory,
|
||||
arrExp: ArrayExpression
|
||||
) {
|
||||
return arrExp.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')
|
||||
})
|
||||
}
|
||||
|
||||
function executeCallExpression(
|
||||
programMemory: ProgramMemory,
|
||||
expression: CallExpression,
|
||||
@ -604,16 +527,7 @@ function executeCallExpression(
|
||||
} 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')
|
||||
})
|
||||
return executeArrayExpression(programMemory, arg)
|
||||
} else if (arg.type === 'CallExpression') {
|
||||
const result: any = executeCallExpression(
|
||||
programMemory,
|
||||
@ -621,21 +535,12 @@ function executeCallExpression(
|
||||
previousPathToNode
|
||||
)
|
||||
return result
|
||||
} else if (arg.type === 'ObjectExpression') {
|
||||
return executeObjectExpression(programMemory, arg)
|
||||
}
|
||||
throw new Error('Invalid argument type')
|
||||
throw new Error('Invalid argument type in function call')
|
||||
})
|
||||
if (
|
||||
functionName in internalFns &&
|
||||
[
|
||||
'rx',
|
||||
'ry',
|
||||
'rz',
|
||||
'translate',
|
||||
'transform',
|
||||
'extrude',
|
||||
'getExtrudeWallTransform',
|
||||
].includes(functionName)
|
||||
) {
|
||||
if (functionName in internalFns) {
|
||||
const fnNameWithSketchOrExtrude = functionName as InternalFnNames
|
||||
const result = internalFns[fnNameWithSketchOrExtrude](
|
||||
{
|
||||
@ -655,25 +560,6 @@ function executeCallExpression(
|
||||
)
|
||||
: result
|
||||
}
|
||||
if (functionName in sketchFns) {
|
||||
const sketchFnName = functionName as SketchFnNames
|
||||
const result = sketchFns[sketchFnName](
|
||||
{
|
||||
programMemory,
|
||||
sourceRange: sourceRangeOverride || [expression.start, expression.end],
|
||||
},
|
||||
...fnArgs
|
||||
)
|
||||
return isInPipe
|
||||
? executePipeBody(
|
||||
body,
|
||||
programMemory,
|
||||
previousPathToNode,
|
||||
expressionIndex + 1,
|
||||
[...previousResults, result]
|
||||
)
|
||||
: result
|
||||
}
|
||||
const result = programMemory.root[functionName].value(...fnArgs)
|
||||
return isInPipe
|
||||
? executePipeBody(
|
||||
|
@ -5,14 +5,13 @@ import { abstractSyntaxTree, getNodeFromPath } from './abstractSyntaxTree'
|
||||
describe('testing getNodePathFromSourceRange', () => {
|
||||
it('test it gets the right path for a `lineTo` CallExpression within a SketchExpression', () => {
|
||||
const code = `
|
||||
const myVar = 5
|
||||
sketch sk3 {
|
||||
lineTo(1, 2)
|
||||
path yo = lineTo(3, 4)
|
||||
close()
|
||||
}
|
||||
`
|
||||
const subStr = 'lineTo(3, 4)'
|
||||
const myVar = 5
|
||||
const sk3 = startSketchAt([0, 0])
|
||||
|> lineTo([1, 2], %)
|
||||
|> lineTo({ to: [3, 4], tag: 'yo' }, %)
|
||||
|> close(%)
|
||||
`
|
||||
const subStr = "lineTo({ to: [3, 4], tag: 'yo' }, %)"
|
||||
const lineToSubstringIndex = code.indexOf(subStr)
|
||||
const sourceRange: [number, number] = [
|
||||
lineToSubstringIndex,
|
||||
|
113
src/lang/modifyAst.test.ts
Normal file
113
src/lang/modifyAst.test.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import {
|
||||
createLiteral,
|
||||
createIdentifier,
|
||||
createCallExpression,
|
||||
createObjectExpression,
|
||||
createArrayExpression,
|
||||
createPipeSubstitution,
|
||||
createVariableDeclaration,
|
||||
createPipeExpression,
|
||||
findUniqueName,
|
||||
addSketchTo,
|
||||
} from './modifyAst'
|
||||
import { recast } from './recast'
|
||||
|
||||
describe('Testing createLiteral', () => {
|
||||
it('should create a literal', () => {
|
||||
const result = createLiteral(5)
|
||||
expect(result.type).toBe('Literal')
|
||||
expect(result.value).toBe(5)
|
||||
})
|
||||
})
|
||||
describe('Testing createIdentifier', () => {
|
||||
it('should create an identifier', () => {
|
||||
const result = createIdentifier('myVar')
|
||||
expect(result.type).toBe('Identifier')
|
||||
expect(result.name).toBe('myVar')
|
||||
})
|
||||
})
|
||||
describe('Testing createCallExpression', () => {
|
||||
it('should create a call expression', () => {
|
||||
const result = createCallExpression('myFunc', [createLiteral(5)])
|
||||
expect(result.type).toBe('CallExpression')
|
||||
expect(result.callee.type).toBe('Identifier')
|
||||
expect(result.callee.name).toBe('myFunc')
|
||||
expect(result.arguments[0].type).toBe('Literal')
|
||||
expect((result.arguments[0] as any).value).toBe(5)
|
||||
})
|
||||
})
|
||||
describe('Testing createObjectExpression', () => {
|
||||
it('should create an object expression', () => {
|
||||
const result = createObjectExpression({
|
||||
myProp: createLiteral(5),
|
||||
})
|
||||
expect(result.type).toBe('ObjectExpression')
|
||||
expect(result.properties[0].type).toBe('ObjectProperty')
|
||||
expect(result.properties[0].key.name).toBe('myProp')
|
||||
expect(result.properties[0].value.type).toBe('Literal')
|
||||
expect((result.properties[0].value as any).value).toBe(5)
|
||||
})
|
||||
})
|
||||
describe('Testing createArrayExpression', () => {
|
||||
it('should create an array expression', () => {
|
||||
const result = createArrayExpression([createLiteral(5)])
|
||||
expect(result.type).toBe('ArrayExpression')
|
||||
expect(result.elements[0].type).toBe('Literal')
|
||||
expect((result.elements[0] as any).value).toBe(5)
|
||||
})
|
||||
})
|
||||
describe('Testing createPipeSubstitution', () => {
|
||||
it('should create a pipe substitution', () => {
|
||||
const result = createPipeSubstitution()
|
||||
expect(result.type).toBe('PipeSubstitution')
|
||||
})
|
||||
})
|
||||
describe('Testing createVariableDeclaration', () => {
|
||||
it('should create a variable declaration', () => {
|
||||
const result = createVariableDeclaration('myVar', createLiteral(5))
|
||||
expect(result.type).toBe('VariableDeclaration')
|
||||
expect(result.declarations[0].type).toBe('VariableDeclarator')
|
||||
expect(result.declarations[0].id.type).toBe('Identifier')
|
||||
expect(result.declarations[0].id.name).toBe('myVar')
|
||||
expect(result.declarations[0].init.type).toBe('Literal')
|
||||
expect((result.declarations[0].init as any).value).toBe(5)
|
||||
})
|
||||
})
|
||||
describe('Testing createPipeExpression', () => {
|
||||
it('should create a pipe expression', () => {
|
||||
const result = createPipeExpression([createLiteral(5)])
|
||||
expect(result.type).toBe('PipeExpression')
|
||||
expect(result.body[0].type).toBe('Literal')
|
||||
expect((result.body[0] as any).value).toBe(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Testing findUniqueName', () => {
|
||||
it('should find a unique name', () => {
|
||||
const result = findUniqueName(
|
||||
'yo01 yo02 yo03 yo04 yo05 yo06 yo07 yo08 yo09',
|
||||
'yo',
|
||||
2
|
||||
)
|
||||
expect(result).toBe('yo10')
|
||||
})
|
||||
})
|
||||
describe('Testing addSketchTo', () => {
|
||||
it('should add a sketch to a program', () => {
|
||||
const result = addSketchTo(
|
||||
{
|
||||
type: 'Program',
|
||||
body: [],
|
||||
start: 0,
|
||||
end: 0,
|
||||
nonCodeMeta: {},
|
||||
},
|
||||
'yz'
|
||||
)
|
||||
const str = recast(result.modifiedAst)
|
||||
expect(str).toBe(`const part001 = startSketchAt([0, 0])
|
||||
|> ry(90, %)
|
||||
|> lineTo([1, 1], %)
|
||||
show(part001)`)
|
||||
})
|
||||
})
|
@ -1,16 +1,20 @@
|
||||
import {
|
||||
Program,
|
||||
BlockStatement,
|
||||
SketchExpression,
|
||||
CallExpression,
|
||||
PipeExpression,
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
ExpressionStatement,
|
||||
Value,
|
||||
getNodeFromPath,
|
||||
VariableDeclarator,
|
||||
Literal,
|
||||
PipeSubstitution,
|
||||
Identifier,
|
||||
ArrayExpression,
|
||||
ObjectExpression,
|
||||
} from './abstractSyntaxTree'
|
||||
import { PathToNode } from './executor'
|
||||
import { PathToNode, ProgramMemory } from './executor'
|
||||
import { addTagForSketchOnFace } from './std/sketch'
|
||||
|
||||
export function addSketchTo(
|
||||
node: Program,
|
||||
@ -20,73 +24,37 @@ export function addSketchTo(
|
||||
const _node = { ...node }
|
||||
const dumbyStartend = { start: 0, end: 0 }
|
||||
const _name = name || findUniqueName(node, 'part')
|
||||
const sketchBody: BlockStatement = {
|
||||
type: 'BlockStatement',
|
||||
...dumbyStartend,
|
||||
body: [],
|
||||
nonCodeMeta: {},
|
||||
}
|
||||
const sketch: SketchExpression = {
|
||||
type: 'SketchExpression',
|
||||
...dumbyStartend,
|
||||
body: sketchBody,
|
||||
}
|
||||
|
||||
const rotate: CallExpression = {
|
||||
type: 'CallExpression',
|
||||
...dumbyStartend,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
...dumbyStartend,
|
||||
name: axis === 'xz' ? 'rx' : 'ry',
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: 'Literal',
|
||||
...dumbyStartend,
|
||||
value: axis === 'yz' ? 90 : 90,
|
||||
raw: axis === 'yz' ? '90' : '90',
|
||||
},
|
||||
{
|
||||
type: 'PipeSubstitution',
|
||||
...dumbyStartend,
|
||||
},
|
||||
],
|
||||
optional: false,
|
||||
}
|
||||
const startSketchAt = createCallExpression('startSketchAt', [
|
||||
createArrayExpression([createLiteral(0), createLiteral(0)]),
|
||||
])
|
||||
const rotate = createCallExpression(axis === 'xz' ? 'rx' : 'ry', [
|
||||
createLiteral(90),
|
||||
createPipeSubstitution(),
|
||||
])
|
||||
const initialLineTo = createCallExpression('lineTo', [
|
||||
createArrayExpression([createLiteral(1), createLiteral(1)]),
|
||||
createPipeSubstitution(),
|
||||
])
|
||||
|
||||
const pipChain: PipeExpression = {
|
||||
type: 'PipeExpression',
|
||||
nonCodeMeta: {},
|
||||
...dumbyStartend,
|
||||
body: [sketch, rotate],
|
||||
}
|
||||
const pipeBody =
|
||||
axis !== 'xy'
|
||||
? [startSketchAt, rotate, initialLineTo]
|
||||
: [startSketchAt, initialLineTo]
|
||||
|
||||
const variableDeclaration = createVariableDeclaration(
|
||||
_name,
|
||||
createPipeExpression(pipeBody)
|
||||
)
|
||||
|
||||
const sketchVariableDeclaration: VariableDeclaration = {
|
||||
type: 'VariableDeclaration',
|
||||
...dumbyStartend,
|
||||
kind: 'sketch',
|
||||
declarations: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
...dumbyStartend,
|
||||
id: {
|
||||
type: 'Identifier',
|
||||
...dumbyStartend,
|
||||
name: _name,
|
||||
},
|
||||
init: axis === 'xy' ? sketch : pipChain,
|
||||
},
|
||||
],
|
||||
}
|
||||
const showCallIndex = getShowIndex(_node)
|
||||
let sketchIndex = showCallIndex
|
||||
if (showCallIndex === -1) {
|
||||
_node.body = [...node.body, sketchVariableDeclaration]
|
||||
_node.body = [...node.body, variableDeclaration]
|
||||
sketchIndex = _node.body.length - 1
|
||||
} else {
|
||||
const newBody = [...node.body]
|
||||
newBody.splice(showCallIndex, 0, sketchVariableDeclaration)
|
||||
newBody.splice(showCallIndex, 0, variableDeclaration)
|
||||
_node.body = newBody
|
||||
}
|
||||
let pathToNode: (string | number)[] = [
|
||||
@ -107,7 +75,7 @@ export function addSketchTo(
|
||||
}
|
||||
}
|
||||
|
||||
function findUniqueName(
|
||||
export function findUniqueName(
|
||||
ast: Program | string,
|
||||
name: string,
|
||||
pad = 3,
|
||||
@ -197,93 +165,58 @@ function getShowIndex(node: Program): number {
|
||||
)
|
||||
}
|
||||
|
||||
export function addLine(
|
||||
node: Program,
|
||||
pathToNode: (string | number)[],
|
||||
to: [number, number]
|
||||
): { modifiedAst: Program; pathToNode: (string | number)[] } {
|
||||
const _node = { ...node }
|
||||
const dumbyStartend = { start: 0, end: 0 }
|
||||
const { node: sketchExpression } = getNodeFromPath<SketchExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'SketchExpression'
|
||||
)
|
||||
const line: ExpressionStatement = {
|
||||
type: 'ExpressionStatement',
|
||||
...dumbyStartend,
|
||||
expression: {
|
||||
type: 'CallExpression',
|
||||
...dumbyStartend,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
...dumbyStartend,
|
||||
name: 'lineTo',
|
||||
},
|
||||
optional: false,
|
||||
arguments: [
|
||||
{
|
||||
type: 'Literal',
|
||||
...dumbyStartend,
|
||||
value: to[0],
|
||||
raw: `${to[0]}`,
|
||||
},
|
||||
{
|
||||
type: 'Literal',
|
||||
...dumbyStartend,
|
||||
value: to[1],
|
||||
raw: `${to[1]}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
export function mutateArrExp(
|
||||
node: Value,
|
||||
updateWith: ArrayExpression
|
||||
): boolean {
|
||||
if (node.type === 'ArrayExpression') {
|
||||
node.elements.forEach((element, i) => {
|
||||
if (element.type === 'Literal') {
|
||||
node.elements[i] = updateWith.elements[i]
|
||||
}
|
||||
const newBody = [...sketchExpression.body.body, line]
|
||||
sketchExpression.body.body = newBody
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode,
|
||||
})
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function changeArguments(
|
||||
node: Program,
|
||||
pathToNode: (string | number)[],
|
||||
args: [number, number]
|
||||
): { modifiedAst: Program; pathToNode: (string | number)[] } {
|
||||
const _node = { ...node }
|
||||
const dumbyStartend = { start: 0, end: 0 }
|
||||
// const thePath = getNodePathFromSourceRange(_node, sourceRange)
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const newXArg: CallExpression['arguments'][number] =
|
||||
callExpression.arguments[0].type === 'Literal'
|
||||
? {
|
||||
type: 'Literal',
|
||||
...dumbyStartend,
|
||||
value: args[0],
|
||||
raw: `${args[0]}`,
|
||||
export function mutateObjExpProp(
|
||||
node: Value,
|
||||
updateWith: Literal | ArrayExpression,
|
||||
key: string
|
||||
): boolean {
|
||||
if (node.type === 'ObjectExpression') {
|
||||
const keyIndex = node.properties.findIndex((a) => a.key.name === key)
|
||||
if (keyIndex !== -1) {
|
||||
if (
|
||||
updateWith.type === 'Literal' &&
|
||||
node.properties[keyIndex].value.type === 'Literal'
|
||||
) {
|
||||
node.properties[keyIndex].value = updateWith
|
||||
return true
|
||||
} else if (
|
||||
node.properties[keyIndex].value.type === 'ArrayExpression' &&
|
||||
updateWith.type === 'ArrayExpression'
|
||||
) {
|
||||
const arrExp = node.properties[keyIndex].value as ArrayExpression
|
||||
arrExp.elements.forEach((element, i) => {
|
||||
if (element.type === 'Literal') {
|
||||
arrExp.elements[i] = updateWith.elements[i]
|
||||
}
|
||||
: {
|
||||
...callExpression.arguments[0],
|
||||
})
|
||||
}
|
||||
const newYArg: CallExpression['arguments'][number] =
|
||||
callExpression.arguments[1].type === 'Literal'
|
||||
? {
|
||||
type: 'Literal',
|
||||
...dumbyStartend,
|
||||
value: args[1],
|
||||
raw: `${args[1]}`,
|
||||
return true
|
||||
} else {
|
||||
node.properties.push({
|
||||
type: 'ObjectProperty',
|
||||
key: createIdentifier(key),
|
||||
value: updateWith,
|
||||
start: 0,
|
||||
end: 0,
|
||||
})
|
||||
}
|
||||
: {
|
||||
...callExpression.arguments[1],
|
||||
}
|
||||
callExpression.arguments = [newXArg, newYArg]
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode,
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export function extrudeSketch(
|
||||
@ -297,10 +230,10 @@ export function extrudeSketch(
|
||||
} {
|
||||
const _node = { ...node }
|
||||
const dumbyStartend = { start: 0, end: 0 }
|
||||
const { node: sketchExpression } = getNodeFromPath<SketchExpression>(
|
||||
const { node: sketchExpression } = getNodeFromPath(
|
||||
_node,
|
||||
pathToNode,
|
||||
'SketchExpression'
|
||||
'SketchExpression' // TODO fix this #25
|
||||
)
|
||||
|
||||
// determine if sketchExpression is in a pipeExpression or not
|
||||
@ -324,17 +257,9 @@ export function extrudeSketch(
|
||||
},
|
||||
optional: false,
|
||||
arguments: [
|
||||
{
|
||||
type: 'Literal',
|
||||
...dumbyStartend,
|
||||
value: 4,
|
||||
raw: '4',
|
||||
},
|
||||
createLiteral(4),
|
||||
shouldPipe
|
||||
? {
|
||||
type: 'PipeSubstitution',
|
||||
...dumbyStartend,
|
||||
}
|
||||
? createPipeSubstitution()
|
||||
: {
|
||||
type: 'Identifier',
|
||||
...dumbyStartend,
|
||||
@ -354,7 +279,7 @@ export function extrudeSketch(
|
||||
type: 'PipeExpression',
|
||||
nonCodeMeta: {},
|
||||
...dumbyStartend,
|
||||
body: [sketchExpression, extrudeCall],
|
||||
body: [sketchExpression as any, extrudeCall], // TODO fix this #25
|
||||
}
|
||||
|
||||
variableDeclorator.init = pipeChain
|
||||
@ -411,137 +336,61 @@ export function extrudeSketch(
|
||||
|
||||
export function sketchOnExtrudedFace(
|
||||
node: Program,
|
||||
pathToNode: (string | number)[]
|
||||
pathToNode: (string | number)[],
|
||||
programMemory: ProgramMemory
|
||||
): { modifiedAst: Program; pathToNode: (string | number)[] } {
|
||||
const _node = { ...node }
|
||||
const dumbyStartend = { start: 0, end: 0 }
|
||||
let _node = { ...node }
|
||||
const newSketchName = findUniqueName(node, 'part')
|
||||
const oldSketchName = getNodeFromPath<VariableDeclarator>(
|
||||
const { node: oldSketchNode, path: pathToOldSketch } =
|
||||
getNodeFromPath<VariableDeclarator>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'VariableDeclarator',
|
||||
true
|
||||
).node.id.name
|
||||
const { node: expression } = getNodeFromPath<
|
||||
VariableDeclarator | CallExpression
|
||||
>(_node, pathToNode, 'CallExpression')
|
||||
|
||||
const pathName =
|
||||
expression.type === 'VariableDeclarator'
|
||||
? expression.id.name
|
||||
: findUniqueName(node, 'path', 2)
|
||||
|
||||
if (expression.type === 'CallExpression') {
|
||||
const { node: block } = getNodeFromPath<BlockStatement>(
|
||||
)
|
||||
const oldSketchName = oldSketchNode.id.name
|
||||
const { node: expression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'BlockStatement'
|
||||
'CallExpression'
|
||||
)
|
||||
const expressionIndex = getLastIndex(pathToNode)
|
||||
if (expression.callee.name !== 'lineTo')
|
||||
throw new Error('expected a lineTo call')
|
||||
const newExpression: VariableDeclaration = {
|
||||
type: 'VariableDeclaration',
|
||||
...dumbyStartend,
|
||||
declarations: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
...dumbyStartend,
|
||||
id: {
|
||||
type: 'Identifier',
|
||||
...dumbyStartend,
|
||||
name: pathName,
|
||||
},
|
||||
init: expression,
|
||||
},
|
||||
],
|
||||
kind: 'path',
|
||||
}
|
||||
|
||||
block.body.splice(expressionIndex, 1, newExpression)
|
||||
}
|
||||
const { modifiedAst, tag } = addTagForSketchOnFace(
|
||||
{
|
||||
previousProgramMemory: programMemory,
|
||||
pathToNode,
|
||||
node: _node,
|
||||
},
|
||||
expression.callee.name
|
||||
)
|
||||
_node = modifiedAst
|
||||
|
||||
// create pipe expression with a sketch block piped into a transform function
|
||||
const sketchPipe: PipeExpression = {
|
||||
type: 'PipeExpression',
|
||||
nonCodeMeta: {},
|
||||
...dumbyStartend,
|
||||
body: [
|
||||
{
|
||||
type: 'SketchExpression',
|
||||
...dumbyStartend,
|
||||
body: {
|
||||
type: 'BlockStatement',
|
||||
...dumbyStartend,
|
||||
body: [],
|
||||
nonCodeMeta: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'CallExpression',
|
||||
...dumbyStartend,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
...dumbyStartend,
|
||||
name: 'transform',
|
||||
},
|
||||
optional: false,
|
||||
arguments: [
|
||||
{
|
||||
type: 'CallExpression',
|
||||
...dumbyStartend,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
...dumbyStartend,
|
||||
name: 'getExtrudeWallTransform',
|
||||
},
|
||||
optional: false,
|
||||
arguments: [
|
||||
{
|
||||
type: 'Literal',
|
||||
...dumbyStartend,
|
||||
value: pathName,
|
||||
raw: `'${pathName}'`,
|
||||
},
|
||||
{
|
||||
type: 'Identifier',
|
||||
...dumbyStartend,
|
||||
name: oldSketchName,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'PipeSubstitution',
|
||||
...dumbyStartend,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
const variableDec: VariableDeclaration = {
|
||||
type: 'VariableDeclaration',
|
||||
...dumbyStartend,
|
||||
declarations: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
...dumbyStartend,
|
||||
id: {
|
||||
type: 'Identifier',
|
||||
...dumbyStartend,
|
||||
name: newSketchName,
|
||||
},
|
||||
init: sketchPipe,
|
||||
},
|
||||
],
|
||||
kind: 'sketch',
|
||||
}
|
||||
|
||||
const showIndex = getShowIndex(_node)
|
||||
_node.body.splice(showIndex, 0, variableDec)
|
||||
const newSketch = createVariableDeclaration(
|
||||
newSketchName,
|
||||
createPipeExpression([
|
||||
createCallExpression('startSketchAt', [
|
||||
createArrayExpression([createLiteral(0), createLiteral(0)]),
|
||||
]),
|
||||
createCallExpression('lineTo', [
|
||||
createArrayExpression([createLiteral(1), createLiteral(1)]),
|
||||
createPipeSubstitution(),
|
||||
]),
|
||||
createCallExpression('transform', [
|
||||
createCallExpression('getExtrudeWallTransform', [
|
||||
createLiteral(tag),
|
||||
createIdentifier(oldSketchName),
|
||||
]),
|
||||
createPipeSubstitution(),
|
||||
]),
|
||||
]),
|
||||
'const'
|
||||
)
|
||||
const expressionIndex = getLastIndex(pathToOldSketch)
|
||||
_node.body.splice(expressionIndex + 1, 0, newSketch)
|
||||
|
||||
return {
|
||||
modifiedAst: addToShow(_node, newSketchName),
|
||||
pathToNode,
|
||||
pathToNode: [...pathToNode.slice(0, -1), expressionIndex],
|
||||
}
|
||||
}
|
||||
|
||||
@ -552,3 +401,111 @@ const getLastIndex = (pathToNode: PathToNode): number => {
|
||||
}
|
||||
return getLastIndex(pathToNode.slice(0, -1))
|
||||
}
|
||||
|
||||
export function createLiteral(value: string | number): Literal {
|
||||
return {
|
||||
type: 'Literal',
|
||||
start: 0,
|
||||
end: 0,
|
||||
value,
|
||||
raw: `${value}`,
|
||||
}
|
||||
}
|
||||
|
||||
export function createIdentifier(name: string): Identifier {
|
||||
return {
|
||||
type: 'Identifier',
|
||||
start: 0,
|
||||
end: 0,
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
export function createPipeSubstitution(): PipeSubstitution {
|
||||
return {
|
||||
type: 'PipeSubstitution',
|
||||
start: 0,
|
||||
end: 0,
|
||||
}
|
||||
}
|
||||
|
||||
export function createCallExpression(
|
||||
name: string,
|
||||
args: CallExpression['arguments']
|
||||
): CallExpression {
|
||||
return {
|
||||
type: 'CallExpression',
|
||||
start: 0,
|
||||
end: 0,
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
start: 0,
|
||||
end: 0,
|
||||
name,
|
||||
},
|
||||
optional: false,
|
||||
arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
export function createArrayExpression(
|
||||
elements: ArrayExpression['elements']
|
||||
): ArrayExpression {
|
||||
return {
|
||||
type: 'ArrayExpression',
|
||||
start: 0,
|
||||
end: 0,
|
||||
elements,
|
||||
}
|
||||
}
|
||||
|
||||
export function createPipeExpression(
|
||||
body: PipeExpression['body']
|
||||
): PipeExpression {
|
||||
return {
|
||||
type: 'PipeExpression',
|
||||
start: 0,
|
||||
end: 0,
|
||||
body,
|
||||
nonCodeMeta: {},
|
||||
}
|
||||
}
|
||||
|
||||
export function createVariableDeclaration(
|
||||
varName: string,
|
||||
init: VariableDeclarator['init'],
|
||||
kind: VariableDeclaration['kind'] = 'const'
|
||||
): VariableDeclaration {
|
||||
return {
|
||||
type: 'VariableDeclaration',
|
||||
start: 0,
|
||||
end: 0,
|
||||
declarations: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
start: 0,
|
||||
end: 0,
|
||||
id: createIdentifier(varName),
|
||||
init,
|
||||
},
|
||||
],
|
||||
kind,
|
||||
}
|
||||
}
|
||||
|
||||
export function createObjectExpression(properties: {
|
||||
[key: string]: Value
|
||||
}): ObjectExpression {
|
||||
return {
|
||||
type: 'ObjectExpression',
|
||||
start: 0,
|
||||
end: 0,
|
||||
properties: Object.entries(properties).map(([key, value]) => ({
|
||||
type: 'ObjectProperty',
|
||||
start: 0,
|
||||
end: 0,
|
||||
key: createIdentifier(key),
|
||||
value,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
@ -62,12 +62,12 @@ log(5, myVar)`
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
it('sketch declaration', () => {
|
||||
let code = `sketch mySketch {
|
||||
path myPath = lineTo(0, 1)
|
||||
lineTo(1, 1)
|
||||
path rightPath = lineTo(1, 0)
|
||||
close()
|
||||
}
|
||||
let code = `const mySketch = startSketchAt([0, 0])
|
||||
|> lineTo({ to: [0, 1], tag: "myPath" }, %)
|
||||
|> lineTo([1, 1], %)
|
||||
|> lineTo({ to: [1, 0], tag: "rightPath" }, %)
|
||||
|> close(%)
|
||||
|
||||
show(mySketch)
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
@ -76,11 +76,10 @@ show(mySketch)
|
||||
})
|
||||
it('sketch piped into callExpression', () => {
|
||||
const code = [
|
||||
'sketch mySk1 {',
|
||||
' lineTo(1, 1)',
|
||||
' path myPath = lineTo(0, 1)',
|
||||
' lineTo(1, 1)',
|
||||
'}',
|
||||
'const mySk1 = startSketchAt([0, 0])',
|
||||
' |> lineTo([1, 1], %)',
|
||||
' |> lineTo({ to: [0, 1], tag: "myTag" }, %)',
|
||||
' |> lineTo([1, 1], %)',
|
||||
' |> rx(90, %)',
|
||||
].join('\n')
|
||||
const { ast } = code2ast(code)
|
||||
@ -226,24 +225,6 @@ const myFn = () => {
|
||||
|
||||
const key = 'c'
|
||||
// this is also a comment
|
||||
}`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
it('comments in a sketch block', () => {
|
||||
const code = `
|
||||
sketch mySketch { /* comment at start */
|
||||
// comment at start more
|
||||
path myPath = lineTo(0, 1) /* comment here with
|
||||
some whitespace below */
|
||||
|
||||
|
||||
lineTo(1, 1)
|
||||
/* comment before declaration*/path rightPath = lineTo(1, 0)
|
||||
close()
|
||||
// comment at end
|
||||
|
||||
}`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
@ -251,11 +232,10 @@ sketch mySketch { /* comment at start */
|
||||
})
|
||||
it('comments in a pipe expression', () => {
|
||||
const code = [
|
||||
'sketch mySk1 {',
|
||||
' lineTo(1, 1)',
|
||||
' path myPath = lineTo(0, 1)',
|
||||
' lineTo(1, 1)',
|
||||
'}',
|
||||
'const mySk1 = startSketchAt([0, 0])',
|
||||
' |> lineTo([1, 1], %)',
|
||||
' |> lineTo({ to: [0, 1], tag: "myTag" }, %)',
|
||||
' |> lineTo([1, 1], %)',
|
||||
' // a comment',
|
||||
' |> rx(90, %)',
|
||||
].join('\n')
|
||||
@ -267,14 +247,13 @@ sketch mySketch { /* comment at start */
|
||||
const code = `
|
||||
/* comment at start */
|
||||
|
||||
sketch mySk1 {
|
||||
lineTo(1, 1)
|
||||
const mySk1 = startSketchAt([0, 0])
|
||||
|> lineTo([1, 1], %)
|
||||
// comment here
|
||||
path myPath = lineTo(0, 1)
|
||||
lineTo(1, 1) /* and
|
||||
|> lineTo({ to: [0, 1], tag: 'myTag' }, %)
|
||||
|> lineTo([1, 1], %) /* and
|
||||
here
|
||||
*/
|
||||
}
|
||||
// a comment between pipe expression statements
|
||||
|> rx(90, %)
|
||||
// and another with just white space between others below
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
CallExpression,
|
||||
Value,
|
||||
FunctionExpression,
|
||||
SketchExpression,
|
||||
ArrayExpression,
|
||||
ObjectExpression,
|
||||
MemberExpression,
|
||||
@ -36,17 +35,9 @@ export function recast(
|
||||
} else if (statement.type === 'VariableDeclaration') {
|
||||
return statement.declarations
|
||||
.map((declaration) => {
|
||||
const isSketchOrFirstPipeExpressionIsSketch =
|
||||
declaration.init.type === 'SketchExpression' ||
|
||||
(declaration.init.type === 'PipeExpression' &&
|
||||
declaration.init.body[0].type === 'SketchExpression')
|
||||
|
||||
const assignmentString = isSketchOrFirstPipeExpressionIsSketch
|
||||
? ' '
|
||||
: ' = '
|
||||
return `${statement.kind} ${
|
||||
declaration.id.name
|
||||
}${assignmentString}${recastValue(declaration.init)}`
|
||||
return `${statement.kind} ${declaration.id.name} = ${recastValue(
|
||||
declaration.init
|
||||
)}`
|
||||
})
|
||||
.join('')
|
||||
} else if (statement.type === 'ReturnStatement') {
|
||||
@ -193,15 +184,6 @@ function recastFunction(expression: FunctionExpression): string {
|
||||
.join(', ')}) => {${recast(expression.body, '', '', true)}}`
|
||||
}
|
||||
|
||||
function recastSketchExpression(
|
||||
expression: SketchExpression,
|
||||
indentation: string
|
||||
): string {
|
||||
return `{${
|
||||
recast(expression.body, '', indentation + ' ', true) || '\n \n'
|
||||
}}`
|
||||
}
|
||||
|
||||
function recastMemberExpression(
|
||||
expression: MemberExpression,
|
||||
indentation: string
|
||||
@ -236,8 +218,6 @@ function recastValue(node: Value, indentation = ''): string {
|
||||
return recastCallExpression(node)
|
||||
} else if (node.type === 'Identifier') {
|
||||
return node.name
|
||||
} else if (node.type === 'SketchExpression') {
|
||||
return recastSketchExpression(node, indentation)
|
||||
} else if (node.type === 'PipeExpression') {
|
||||
return recastPipeExpression(node)
|
||||
}
|
||||
|
@ -1,335 +0,0 @@
|
||||
import {
|
||||
ProgramMemory,
|
||||
Path,
|
||||
SketchGroup,
|
||||
ExtrudeGroup,
|
||||
SourceRange,
|
||||
ExtrudeSurface,
|
||||
Position,
|
||||
Rotation,
|
||||
} from './executor'
|
||||
import { lineGeo, extrudeGeo } from './engine'
|
||||
import { Quaternion, Vector3 } from 'three'
|
||||
|
||||
type Coords2d = [number, number]
|
||||
|
||||
interface PathReturn {
|
||||
programMemory: ProgramMemory
|
||||
currentPath: Path
|
||||
}
|
||||
|
||||
function getCoordsFromPaths(paths: Path[], index = 0): Coords2d {
|
||||
const currentPath = paths[index]
|
||||
if (!currentPath) {
|
||||
return [0, 0]
|
||||
}
|
||||
if (currentPath.type === 'horizontalLineTo') {
|
||||
const pathBefore = getCoordsFromPaths(paths, index - 1)
|
||||
return [currentPath.x, pathBefore[1]]
|
||||
} else if (currentPath.type === 'toPoint') {
|
||||
return [currentPath.to[0], currentPath.to[1]]
|
||||
}
|
||||
return [0, 0]
|
||||
}
|
||||
|
||||
interface InternalFirstArg {
|
||||
programMemory: ProgramMemory
|
||||
name?: string
|
||||
sourceRange: SourceRange
|
||||
}
|
||||
|
||||
type SketchFn = (internals: InternalFirstArg, ...args: any[]) => PathReturn
|
||||
|
||||
export type SketchFnNames = 'close' | 'lineTo'
|
||||
|
||||
type InternalFn = (internals: InternalFirstArg, ...args: any[]) => any
|
||||
|
||||
export type InternalFnNames =
|
||||
| 'extrude'
|
||||
| 'translate'
|
||||
| 'transform'
|
||||
| 'getExtrudeWallTransform'
|
||||
| 'rx'
|
||||
| 'ry'
|
||||
| 'rz'
|
||||
|
||||
export const sketchFns: { [key in SketchFnNames]: SketchFn } = {
|
||||
close: ({ programMemory, name = '', sourceRange }) => {
|
||||
const firstPath = programMemory?._sketch?.[0] as Path
|
||||
|
||||
let from = getCoordsFromPaths(
|
||||
programMemory?._sketch || [],
|
||||
(programMemory?._sketch?.length || 1) - 1
|
||||
)
|
||||
|
||||
let to = getCoordsFromPaths(programMemory?._sketch || [], 0)
|
||||
const geo = lineGeo({ from: [...from, 0], to: [...to, 0] })
|
||||
const newPath: Path = {
|
||||
type: 'toPoint',
|
||||
from,
|
||||
to,
|
||||
__geoMeta: {
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
geos: [
|
||||
{
|
||||
type: 'line',
|
||||
geo: geo.line,
|
||||
},
|
||||
{
|
||||
type: 'lineEnd',
|
||||
geo: geo.tip,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
if (name) {
|
||||
newPath.name = name
|
||||
}
|
||||
return {
|
||||
programMemory: {
|
||||
...programMemory,
|
||||
_sketch: [
|
||||
{
|
||||
...firstPath,
|
||||
from,
|
||||
},
|
||||
...(programMemory?._sketch || []).slice(1),
|
||||
newPath,
|
||||
],
|
||||
},
|
||||
currentPath: newPath,
|
||||
}
|
||||
},
|
||||
lineTo: ({ programMemory, name = '', sourceRange }, ...args) => {
|
||||
const [x, y] = args
|
||||
if (!programMemory._sketch) {
|
||||
throw new Error('No sketch to draw on')
|
||||
}
|
||||
let from = getCoordsFromPaths(
|
||||
programMemory?._sketch || [],
|
||||
(programMemory?._sketch?.length || 1) - 1
|
||||
)
|
||||
const geo = lineGeo({ from: [...from, 0], to: [x, y, 0] })
|
||||
const currentPath: Path = {
|
||||
type: 'toPoint',
|
||||
to: [x, y],
|
||||
from,
|
||||
__geoMeta: {
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
geos: [
|
||||
{
|
||||
type: 'line',
|
||||
geo: geo.line,
|
||||
},
|
||||
{
|
||||
type: 'lineEnd',
|
||||
geo: geo.tip,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
if (name) {
|
||||
currentPath.name = name
|
||||
}
|
||||
return {
|
||||
programMemory: {
|
||||
...programMemory,
|
||||
_sketch: [...(programMemory._sketch || []), currentPath],
|
||||
},
|
||||
currentPath,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
function rotateOnAxis<T extends SketchGroup | ExtrudeGroup>(
|
||||
axisMultiplier: [number, number, number]
|
||||
): InternalFn {
|
||||
return ({ sourceRange }, rotationD: number, sketch: T): T => {
|
||||
const rotationR = rotationD * (Math.PI / 180)
|
||||
const rotateVec = new Vector3(...axisMultiplier)
|
||||
const quaternion = new Quaternion()
|
||||
quaternion.setFromAxisAngle(rotateVec, rotationR)
|
||||
|
||||
const position = new Vector3(...sketch.position)
|
||||
.applyQuaternion(quaternion)
|
||||
.toArray()
|
||||
|
||||
const existingQuat = new Quaternion(...sketch.rotation)
|
||||
const rotation = quaternion.multiply(existingQuat).toArray()
|
||||
return {
|
||||
...sketch,
|
||||
rotation,
|
||||
position,
|
||||
__meta: [
|
||||
...sketch.__meta,
|
||||
{
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const extrude: InternalFn = (
|
||||
{ sourceRange },
|
||||
length: number,
|
||||
sketchVal: SketchGroup
|
||||
): ExtrudeGroup => {
|
||||
const getSketchGeo = (sketchVal: SketchGroup): SketchGroup => {
|
||||
return sketchVal
|
||||
}
|
||||
|
||||
const sketch = getSketchGeo(sketchVal)
|
||||
const { position, rotation } = sketchVal
|
||||
|
||||
const extrudeSurfaces: ExtrudeSurface[] = []
|
||||
const extrusionDirection = clockwiseSign(sketch.value.map((line) => line.to))
|
||||
sketch.value.map((line, index) => {
|
||||
if (line.type === 'toPoint') {
|
||||
let from: [number, number] = line.from
|
||||
const to = line.to
|
||||
const {
|
||||
geo,
|
||||
position: facePosition,
|
||||
rotation: faceRotation,
|
||||
} = extrudeGeo({
|
||||
from: [from[0], from[1], 0],
|
||||
to: [to[0], to[1], 0],
|
||||
length,
|
||||
extrusionDirection,
|
||||
})
|
||||
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: 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 {
|
||||
type: 'extrudeGroup',
|
||||
value: extrudeSurfaces,
|
||||
height: length,
|
||||
position,
|
||||
rotation,
|
||||
__meta: [
|
||||
{
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
},
|
||||
{
|
||||
sourceRange: sketchVal.__meta[0].sourceRange,
|
||||
pathToNode: sketchVal.__meta[0].pathToNode,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const translate: InternalFn = <T extends SketchGroup | ExtrudeGroup>(
|
||||
{ sourceRange }: InternalFirstArg,
|
||||
vec3: [number, number, number],
|
||||
sketch: T
|
||||
): T => {
|
||||
const oldPosition = new Vector3(...sketch.position)
|
||||
const newPosition = oldPosition.add(new Vector3(...vec3))
|
||||
return {
|
||||
...sketch,
|
||||
position: newPosition.toArray(),
|
||||
__meta: [
|
||||
...sketch.__meta,
|
||||
{
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const transform: InternalFn = <T extends SketchGroup | ExtrudeGroup>(
|
||||
{ sourceRange }: InternalFirstArg,
|
||||
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
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const getExtrudeWallTransform: InternalFn = (
|
||||
_,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
export const internalFns: { [key in InternalFnNames]: InternalFn } = {
|
||||
rx: rotateOnAxis([1, 0, 0]),
|
||||
ry: rotateOnAxis([0, 1, 0]),
|
||||
rz: rotateOnAxis([0, 0, 1]),
|
||||
extrude,
|
||||
translate,
|
||||
transform,
|
||||
getExtrudeWallTransform,
|
||||
}
|
||||
|
||||
function clockwiseSign(points: [number, number][]): number {
|
||||
let sum = 0
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const currentPoint = points[i]
|
||||
const nextPoint = points[(i + 1) % points.length]
|
||||
sum += (nextPoint[0] - currentPoint[0]) * (nextPoint[1] + currentPoint[1])
|
||||
}
|
||||
return sum >= 0 ? 1 : -1
|
||||
}
|
6
src/lang/std/README.md
Normal file
6
src/lang/std/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
The std is as expected, tools that are provided with the language.
|
||||
|
||||
For this language that means functions.
|
||||
|
||||
However because programatically changing the source code is a first class citizen in this lang, there needs to be helpes for adding and modifying these function calls,
|
||||
So it makes sense to group some of these together.
|
101
src/lang/std/extrude.ts
Normal file
101
src/lang/std/extrude.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { InternalFn } from './stdTypes'
|
||||
import {
|
||||
ExtrudeGroup,
|
||||
ExtrudeSurface,
|
||||
SketchGroup,
|
||||
Position,
|
||||
Rotation,
|
||||
} from '../executor'
|
||||
import { Quaternion, Vector3 } from 'three'
|
||||
import { clockwiseSign } from './std'
|
||||
import { extrudeGeo } from '../engine'
|
||||
|
||||
export const extrude: InternalFn = (
|
||||
{ sourceRange },
|
||||
length: number,
|
||||
sketchVal: SketchGroup
|
||||
): ExtrudeGroup => {
|
||||
const getSketchGeo = (sketchVal: SketchGroup): SketchGroup => {
|
||||
return sketchVal
|
||||
}
|
||||
|
||||
const sketch = getSketchGeo(sketchVal)
|
||||
const { position, rotation } = sketchVal
|
||||
|
||||
const extrudeSurfaces: ExtrudeSurface[] = []
|
||||
const extrusionDirection = clockwiseSign(sketch.value.map((line) => line.to))
|
||||
sketch.value.map((line, index) => {
|
||||
if (line.type === 'toPoint') {
|
||||
let from: [number, number] = line.from
|
||||
const to = line.to
|
||||
const {
|
||||
geo,
|
||||
position: facePosition,
|
||||
rotation: faceRotation,
|
||||
} = extrudeGeo({
|
||||
from: [from[0], from[1], 0],
|
||||
to: [to[0], to[1], 0],
|
||||
length,
|
||||
extrusionDirection,
|
||||
})
|
||||
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: 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 {
|
||||
type: 'extrudeGroup',
|
||||
value: extrudeSurfaces,
|
||||
height: length,
|
||||
position,
|
||||
rotation,
|
||||
__meta: [
|
||||
{
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
},
|
||||
{
|
||||
sourceRange: sketchVal.__meta[0].sourceRange,
|
||||
pathToNode: sketchVal.__meta[0].pathToNode,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export const getExtrudeWallTransform: InternalFn = (
|
||||
_,
|
||||
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,
|
||||
}
|
||||
}
|
190
src/lang/std/sketch.test.ts
Normal file
190
src/lang/std/sketch.test.ts
Normal file
@ -0,0 +1,190 @@
|
||||
import {
|
||||
changeSketchArguments,
|
||||
toolTipModification,
|
||||
addTagForSketchOnFace,
|
||||
getYComponent,
|
||||
getXComponent,
|
||||
} from './sketch'
|
||||
import { lexer } from '../tokeniser'
|
||||
import {
|
||||
abstractSyntaxTree,
|
||||
getNodePathFromSourceRange,
|
||||
} from '../abstractSyntaxTree'
|
||||
import { recast } from '../recast'
|
||||
import { executor } from '../executor'
|
||||
|
||||
const eachQuad: [number, [number, number]][] = [
|
||||
[-315, [1, 1]],
|
||||
[-225, [-1, 1]],
|
||||
[-135, [-1, -1]],
|
||||
[-45, [1, -1]],
|
||||
[45, [1, 1]],
|
||||
[135, [-1, 1]],
|
||||
[225, [-1, -1]],
|
||||
[315, [1, -1]],
|
||||
[405, [1, 1]],
|
||||
[495, [-1, 1]],
|
||||
[585, [-1, -1]],
|
||||
[675, [1, -1]],
|
||||
]
|
||||
|
||||
describe('testing getYComponent', () => {
|
||||
it('should return the vertical component of a vector correctly when given angles in each quadrant (and with angles < 0, or > 360)', () => {
|
||||
const expected: [number, number][] = []
|
||||
const results: [number, number][] = []
|
||||
eachQuad.forEach(([angle, expectedResult]) => {
|
||||
results.push(
|
||||
getYComponent(angle, 1).map((a) => Math.round(a)) as [number, number]
|
||||
)
|
||||
expected.push(expectedResult)
|
||||
})
|
||||
expect(results).toEqual(expected)
|
||||
})
|
||||
it('return extreme values on the extremes', () => {
|
||||
let result: [number, number]
|
||||
result = getYComponent(0, 1)
|
||||
expect(result[0]).toBe(1)
|
||||
expect(result[1]).toBe(0)
|
||||
|
||||
result = getYComponent(90, 1)
|
||||
expect(result[0]).toBe(1)
|
||||
expect(result[1]).toBeGreaterThan(100000)
|
||||
|
||||
result = getYComponent(180, 1)
|
||||
expect(result[0]).toBe(-1)
|
||||
expect(result[1]).toBeCloseTo(0)
|
||||
|
||||
result = getYComponent(270, 1)
|
||||
expect(result[0]).toBe(-1)
|
||||
expect(result[1]).toBeLessThan(100000)
|
||||
})
|
||||
})
|
||||
|
||||
describe('testing getXComponent', () => {
|
||||
it('should return the horizontal component of a vector correctly when given angles in each quadrant (and with angles < 0, or > 360)', () => {
|
||||
const expected: [number, number][] = []
|
||||
const results: [number, number][] = []
|
||||
eachQuad.forEach(([angle, expectedResult]) => {
|
||||
results.push(
|
||||
getXComponent(angle, 1).map((a) => Math.round(a)) as [number, number]
|
||||
)
|
||||
expected.push(expectedResult)
|
||||
})
|
||||
expect(results).toEqual(expected)
|
||||
})
|
||||
it('return extreme values on the extremes', () => {
|
||||
let result: [number, number]
|
||||
result = getXComponent(0, 1)
|
||||
expect(result[0]).toBeGreaterThan(100000)
|
||||
expect(result[1]).toBe(1)
|
||||
|
||||
result = getXComponent(90, 1)
|
||||
expect(result[0]).toBeCloseTo(0)
|
||||
expect(result[1]).toBe(1)
|
||||
|
||||
result = getXComponent(180, 1)
|
||||
expect(result[0]).toBeLessThan(100000)
|
||||
expect(result[1]).toBe(1)
|
||||
|
||||
result = getXComponent(270, 1)
|
||||
expect(result[0]).toBeCloseTo(0)
|
||||
expect(result[1]).toBe(-1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('testing changeSketchArguments', () => {
|
||||
const lineToChange = 'lineTo([-1.59, -1.54], %)'
|
||||
const lineAfterChange = 'lineTo([2, 3], %)'
|
||||
test('changeSketchArguments', () => {
|
||||
const genCode = (line: string) => `
|
||||
const mySketch001 = startSketchAt([0, 0])
|
||||
|> ${line}
|
||||
|> lineTo([0.46, -5.82], %)
|
||||
|> rx(45, %)
|
||||
show(mySketch001)`
|
||||
const code = genCode(lineToChange)
|
||||
const expectedCode = genCode(lineAfterChange)
|
||||
const ast = abstractSyntaxTree(lexer(code))
|
||||
const programMemory = executor(ast)
|
||||
const sourceStart = code.indexOf(lineToChange)
|
||||
const { modifiedAst } = changeSketchArguments(
|
||||
ast,
|
||||
programMemory,
|
||||
[sourceStart, sourceStart + lineToChange.length],
|
||||
[2, 3],
|
||||
{
|
||||
mode: 'sketch',
|
||||
sketchMode: 'sketchEdit',
|
||||
isTooltip: true,
|
||||
rotation: [0, 0, 0, 1],
|
||||
position: [0, 0, 0],
|
||||
pathToNode: ['body', 0, 'declarations', '0', 'init'],
|
||||
},
|
||||
[0, 0]
|
||||
)
|
||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||
})
|
||||
})
|
||||
|
||||
describe('testing toolTipModification', () => {
|
||||
const lineToChange = 'lineTo([-1.59, -1.54], %)'
|
||||
const lineAfterChange = 'lineTo([2, 3], %)'
|
||||
test('toolTipModification', () => {
|
||||
const code = `
|
||||
const mySketch001 = startSketchAt([0, 0])
|
||||
|> rx(45, %)
|
||||
|> lineTo([-1.59, -1.54], %)
|
||||
|> lineTo([0.46, -5.82], %)
|
||||
show(mySketch001)`
|
||||
const ast = abstractSyntaxTree(lexer(code))
|
||||
const programMemory = executor(ast)
|
||||
const sourceStart = code.indexOf(lineToChange)
|
||||
const { modifiedAst } = toolTipModification(ast, programMemory, [2, 3], {
|
||||
mode: 'sketch',
|
||||
sketchMode: 'lineTo',
|
||||
isTooltip: true,
|
||||
rotation: [0, 0, 0, 1],
|
||||
position: [0, 0, 0],
|
||||
pathToNode: ['body', 0, 'declarations', '0', 'init'],
|
||||
})
|
||||
const expectedCode = `
|
||||
const mySketch001 = startSketchAt([0, 0])
|
||||
|> rx(45, %)
|
||||
|> lineTo([-1.59, -1.54], %)
|
||||
|> lineTo([0.46, -5.82], %)
|
||||
|> lineTo([2, 3], %)
|
||||
show(mySketch001)`
|
||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||
})
|
||||
})
|
||||
|
||||
describe('testing addTagForSketchOnFace', () => {
|
||||
const originalLine = 'lineTo([-1.59, -1.54], %)'
|
||||
const genCode = (line: string) => `
|
||||
const mySketch001 = startSketchAt([0, 0])
|
||||
|> rx(45, %)
|
||||
|> ${line}
|
||||
|> lineTo([0.46, -5.82], %)
|
||||
show(mySketch001)`
|
||||
const code = genCode(originalLine)
|
||||
const ast = abstractSyntaxTree(lexer(code))
|
||||
const programMemory = executor(ast)
|
||||
const sourceStart = code.indexOf(originalLine)
|
||||
const sourceRange: [number, number] = [
|
||||
sourceStart,
|
||||
sourceStart + originalLine.length,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const { modifiedAst } = addTagForSketchOnFace(
|
||||
{
|
||||
previousProgramMemory: programMemory,
|
||||
pathToNode,
|
||||
node: ast,
|
||||
},
|
||||
'lineTo'
|
||||
)
|
||||
const expectedCode = genCode(
|
||||
"lineTo({ to: [-1.59, -1.54], tag: 'seg01' }, %)"
|
||||
)
|
||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||
})
|
1213
src/lang/std/sketch.ts
Normal file
1213
src/lang/std/sketch.ts
Normal file
File diff suppressed because it is too large
Load Diff
134
src/lang/std/std.ts
Normal file
134
src/lang/std/std.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import {
|
||||
lineTo,
|
||||
xLineTo,
|
||||
yLineTo,
|
||||
line,
|
||||
xLine,
|
||||
yLine,
|
||||
angledLine,
|
||||
angledLineOfXLength,
|
||||
angledLineToX,
|
||||
angledLineOfYLength,
|
||||
angledLineToY,
|
||||
closee,
|
||||
startSketchAt,
|
||||
} from './sketch'
|
||||
import { extrude, getExtrudeWallTransform } from './extrude'
|
||||
import { Quaternion, Vector3 } from 'three'
|
||||
import { SketchGroup, ExtrudeGroup, Position, Rotation } from '../executor'
|
||||
|
||||
import { InternalFn, InternalFnNames, InternalFirstArg } from './stdTypes'
|
||||
|
||||
const transform: InternalFn = <T extends SketchGroup | ExtrudeGroup>(
|
||||
{ sourceRange }: InternalFirstArg,
|
||||
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
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
const translate: InternalFn = <T extends SketchGroup | ExtrudeGroup>(
|
||||
{ sourceRange }: InternalFirstArg,
|
||||
vec3: [number, number, number],
|
||||
sketch: T
|
||||
): T => {
|
||||
const oldPosition = new Vector3(...sketch.position)
|
||||
const newPosition = oldPosition.add(new Vector3(...vec3))
|
||||
return {
|
||||
...sketch,
|
||||
position: newPosition.toArray(),
|
||||
__meta: [
|
||||
...sketch.__meta,
|
||||
{
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export const internalFns: { [key in InternalFnNames]: InternalFn } = {
|
||||
rx: rotateOnAxis([1, 0, 0]),
|
||||
ry: rotateOnAxis([0, 1, 0]),
|
||||
rz: rotateOnAxis([0, 0, 1]),
|
||||
extrude,
|
||||
translate,
|
||||
transform,
|
||||
getExtrudeWallTransform,
|
||||
lineTo: lineTo.fn,
|
||||
xLineTo: xLineTo.fn,
|
||||
yLineTo: yLineTo.fn,
|
||||
line: line.fn,
|
||||
xLine: xLine.fn,
|
||||
yLine: yLine.fn,
|
||||
angledLine: angledLine.fn,
|
||||
angledLineOfXLength: angledLineOfXLength.fn,
|
||||
angledLineToX: angledLineToX.fn,
|
||||
angledLineOfYLength: angledLineOfYLength.fn,
|
||||
angledLineToY: angledLineToY.fn,
|
||||
startSketchAt,
|
||||
closee,
|
||||
}
|
||||
|
||||
function rotateOnAxis<T extends SketchGroup | ExtrudeGroup>(
|
||||
axisMultiplier: [number, number, number]
|
||||
): InternalFn {
|
||||
return ({ sourceRange }, rotationD: number, sketch: T): T => {
|
||||
const rotationR = rotationD * (Math.PI / 180)
|
||||
const rotateVec = new Vector3(...axisMultiplier)
|
||||
const quaternion = new Quaternion()
|
||||
quaternion.setFromAxisAngle(rotateVec, rotationR)
|
||||
|
||||
const position = new Vector3(...sketch.position)
|
||||
.applyQuaternion(quaternion)
|
||||
.toArray()
|
||||
|
||||
const existingQuat = new Quaternion(...sketch.rotation)
|
||||
const rotation = quaternion.multiply(existingQuat).toArray()
|
||||
return {
|
||||
...sketch,
|
||||
rotation,
|
||||
position,
|
||||
__meta: [
|
||||
...sketch.__meta,
|
||||
{
|
||||
sourceRange,
|
||||
pathToNode: [], // TODO
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function clockwiseSign(points: [number, number][]): number {
|
||||
let sum = 0
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
const currentPoint = points[i]
|
||||
const nextPoint = points[(i + 1) % points.length]
|
||||
sum += (nextPoint[0] - currentPoint[0]) * (nextPoint[1] + currentPoint[1])
|
||||
}
|
||||
return sum >= 0 ? 1 : -1
|
||||
}
|
68
src/lang/std/stdTypes.ts
Normal file
68
src/lang/std/stdTypes.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { ProgramMemory, Path, SourceRange } from '../executor'
|
||||
import { Program } from '../abstractSyntaxTree'
|
||||
|
||||
export interface InternalFirstArg {
|
||||
programMemory: ProgramMemory
|
||||
name?: string
|
||||
sourceRange: SourceRange
|
||||
}
|
||||
|
||||
export interface PathReturn {
|
||||
programMemory: ProgramMemory
|
||||
currentPath: Path
|
||||
}
|
||||
|
||||
export type InternalFn = (internals: InternalFirstArg, ...args: any[]) => any
|
||||
|
||||
export type InternalFnNames =
|
||||
| 'extrude'
|
||||
| 'translate'
|
||||
| 'transform'
|
||||
| 'getExtrudeWallTransform'
|
||||
| 'rx'
|
||||
| 'ry'
|
||||
| 'rz'
|
||||
| 'lineTo'
|
||||
| 'yLineTo'
|
||||
| 'xLineTo'
|
||||
| 'line'
|
||||
| 'yLine'
|
||||
| 'xLine'
|
||||
| 'angledLine'
|
||||
| 'angledLineOfXLength'
|
||||
| 'angledLineToX'
|
||||
| 'angledLineOfYLength'
|
||||
| 'angledLineToY'
|
||||
| 'startSketchAt'
|
||||
| 'closee'
|
||||
|
||||
export interface ModifyAstBase {
|
||||
node: Program
|
||||
previousProgramMemory: ProgramMemory
|
||||
pathToNode: (string | number)[]
|
||||
}
|
||||
|
||||
interface addCall extends ModifyAstBase {
|
||||
to: [number, number]
|
||||
}
|
||||
|
||||
interface updateArgs extends ModifyAstBase {
|
||||
from: [number, number]
|
||||
to: [number, number]
|
||||
}
|
||||
|
||||
export interface SketchLineHelper {
|
||||
fn: InternalFn
|
||||
add: (a: addCall) => {
|
||||
modifiedAst: Program
|
||||
pathToNode: (string | number)[]
|
||||
}
|
||||
updateArgs: (a: updateArgs) => {
|
||||
modifiedAst: Program
|
||||
pathToNode: (string | number)[]
|
||||
}
|
||||
addTag: (a: ModifyAstBase) => {
|
||||
modifiedAst: Program
|
||||
tag: string
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { isOverlapping } from './utils'
|
||||
import { isOverlap, roundOff } from './utils'
|
||||
import { Range } from '../useStore'
|
||||
|
||||
describe('testing isOverlapping', () => {
|
||||
testBothOrders([0, 5], [3, 10])
|
||||
testBothOrders([0, 3], [3, 10])
|
||||
testBothOrders([0, 5], [3, 4])
|
||||
testBothOrders([0, 5], [5, 10])
|
||||
testBothOrders([0, 5], [6, 10], false)
|
||||
@ -13,7 +13,22 @@ describe('testing isOverlapping', () => {
|
||||
|
||||
function testBothOrders(a: Range, b: Range, result = true) {
|
||||
it(`test is overlapping ${a} ${b}`, () => {
|
||||
expect(isOverlapping(a, b)).toBe(result)
|
||||
expect(isOverlapping(b, a)).toBe(result)
|
||||
expect(isOverlap(a, b)).toBe(result)
|
||||
expect(isOverlap(b, a)).toBe(result)
|
||||
})
|
||||
}
|
||||
|
||||
describe('testing roundOff', () => {
|
||||
it('defaults to 2 decimal places', () => {
|
||||
expect(roundOff(1.23456789)).toBe(1.23)
|
||||
})
|
||||
it('rounds off to 3 decimal places', () => {
|
||||
expect(roundOff(1.23456789, 3)).toBe(1.235)
|
||||
})
|
||||
it('works with whole numbers', () => {
|
||||
expect(roundOff(1.23456789, 0)).toBe(1)
|
||||
})
|
||||
it('rounds up ok', () => {
|
||||
expect(roundOff(1.273456789, 1)).toBe(1.3)
|
||||
})
|
||||
})
|
||||
|
@ -1,7 +1,24 @@
|
||||
import { Range } from '../useStore'
|
||||
|
||||
export const isOverlapping = (a: Range, b: Range) => {
|
||||
const startingRange = a[0] < b[0] ? a : b
|
||||
const secondRange = a[0] < b[0] ? b : a
|
||||
return startingRange[1] >= secondRange[0]
|
||||
export function isOverlap(a: Range, b: Range) {
|
||||
const [startingRange, secondRange] = a[0] < b[0] ? [a, b] : [b, a]
|
||||
const [lastOfFirst, firstOfSecond] = [startingRange[1], secondRange[0]]
|
||||
return lastOfFirst >= firstOfSecond
|
||||
}
|
||||
|
||||
export function roundOff(num: number, places: number = 2): number {
|
||||
const x = Math.pow(10, places)
|
||||
return Math.round(num * x) / x
|
||||
}
|
||||
|
||||
export function getLength(a: [number, number], b: [number, number]): number {
|
||||
const x = b[0] - a[0]
|
||||
const y = b[1] - a[1]
|
||||
return Math.sqrt(x * x + y * y)
|
||||
}
|
||||
|
||||
export function getAngle(a: [number, number], b: [number, number]): number {
|
||||
const x = b[0] - a[0]
|
||||
const y = b[1] - a[1]
|
||||
return ((Math.atan2(y, x) * 180) / Math.PI + 360) % 360
|
||||
}
|
||||
|
@ -10,14 +10,41 @@ import { recast } from './lang/recast'
|
||||
import { lexer } from './lang/tokeniser'
|
||||
|
||||
export type Range = [number, number]
|
||||
export type TooTip =
|
||||
| 'lineTo'
|
||||
| 'line'
|
||||
| 'angledLine'
|
||||
| 'angledLineOfXLength'
|
||||
| 'angledLineOfYLength'
|
||||
| 'angledLineToX'
|
||||
| 'angledLineToY'
|
||||
| 'xLine'
|
||||
| 'yLine'
|
||||
| 'xLineTo'
|
||||
| 'yLineTo'
|
||||
|
||||
type GuiModes =
|
||||
export const toolTips: TooTip[] = [
|
||||
'lineTo',
|
||||
'line',
|
||||
'angledLine',
|
||||
'angledLineOfXLength',
|
||||
'angledLineOfYLength',
|
||||
'angledLineToX',
|
||||
'angledLineToY',
|
||||
'xLine',
|
||||
'yLine',
|
||||
'xLineTo',
|
||||
'yLineTo',
|
||||
]
|
||||
|
||||
export type GuiModes =
|
||||
| {
|
||||
mode: 'default'
|
||||
}
|
||||
| {
|
||||
mode: 'sketch'
|
||||
sketchMode: 'points'
|
||||
sketchMode: TooTip
|
||||
isTooltip: true
|
||||
rotation: Rotation
|
||||
position: Position
|
||||
id?: string
|
||||
@ -26,6 +53,7 @@ type GuiModes =
|
||||
| {
|
||||
mode: 'sketch'
|
||||
sketchMode: 'sketchEdit'
|
||||
isTooltip: true
|
||||
rotation: Rotation
|
||||
position: Position
|
||||
pathToNode: PathToNode
|
||||
|
Reference in New Issue
Block a user