Merge branch 'main' into achalmers/kw-fn-sketches
@ -4,8 +4,12 @@ excerpt: "Import a CAD file."
|
|||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Import a CAD file.
|
Import a CAD file.
|
||||||
|
|
||||||
|
**DEPRECATED** Prefer to use import statements.
|
||||||
|
|
||||||
For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.
|
For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.
|
||||||
|
|
||||||
Note: The import command currently only works when using the native Modeling App.
|
Note: The import command currently only works when using the native Modeling App.
|
||||||
|
@ -51,7 +51,6 @@ layout: manual
|
|||||||
* [`helixRevolutions`](kcl/helixRevolutions)
|
* [`helixRevolutions`](kcl/helixRevolutions)
|
||||||
* [`hole`](kcl/hole)
|
* [`hole`](kcl/hole)
|
||||||
* [`hollow`](kcl/hollow)
|
* [`hollow`](kcl/hollow)
|
||||||
* [`import`](kcl/import)
|
|
||||||
* [`inch`](kcl/inch)
|
* [`inch`](kcl/inch)
|
||||||
* [`lastSegX`](kcl/lastSegX)
|
* [`lastSegX`](kcl/lastSegX)
|
||||||
* [`lastSegY`](kcl/lastSegY)
|
* [`lastSegY`](kcl/lastSegY)
|
||||||
|
@ -94300,7 +94300,7 @@
|
|||||||
{
|
{
|
||||||
"name": "import",
|
"name": "import",
|
||||||
"summary": "Import a CAD file.",
|
"summary": "Import a CAD file.",
|
||||||
"description": "For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.\n\nNote: The import command currently only works when using the native Modeling App.\n\nFor importing KCL functions using the `import` statement, see the docs on [KCL modules](/docs/kcl/modules).",
|
"description": "**DEPRECATED** Prefer to use import statements.\n\nFor formats lacking unit data (such as STL, OBJ, or PLY files), the default unit of measurement is millimeters. Alternatively you may specify the unit by passing your desired measurement unit in the options parameter. When importing a GLTF file, the bin file will be imported as well. Import paths are relative to the current project directory.\n\nNote: The import command currently only works when using the native Modeling App.\n\nFor importing KCL functions using the `import` statement, see the docs on [KCL modules](/docs/kcl/modules).",
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"keywordArguments": false,
|
"keywordArguments": false,
|
||||||
"args": [
|
"args": [
|
||||||
@ -94705,7 +94705,7 @@
|
|||||||
"labelRequired": true
|
"labelRequired": true
|
||||||
},
|
},
|
||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false,
|
"deprecated": true,
|
||||||
"examples": [
|
"examples": [
|
||||||
"model = import(\"tests/inputs/cube.obj\")",
|
"model = import(\"tests/inputs/cube.obj\")",
|
||||||
"model = import(\"tests/inputs/cube.obj\", { format = \"obj\", units = \"m\" })",
|
"model = import(\"tests/inputs/cube.obj\", { format = \"obj\", units = \"m\" })",
|
||||||
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB |
@ -683,9 +683,9 @@ vite-tsconfig-paths@^4.3.2:
|
|||||||
tsconfck "^3.0.3"
|
tsconfck "^3.0.3"
|
||||||
|
|
||||||
vite@^5.0.0:
|
vite@^5.0.0:
|
||||||
version "5.4.11"
|
version "5.4.14"
|
||||||
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5"
|
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.14.tgz#ff8255edb02134df180dcfca1916c37a6abe8408"
|
||||||
integrity sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==
|
integrity sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild "^0.21.3"
|
esbuild "^0.21.3"
|
||||||
postcss "^8.4.43"
|
postcss "^8.4.43"
|
||||||
|
@ -70,7 +70,8 @@ import {
|
|||||||
codeManager,
|
codeManager,
|
||||||
editorManager,
|
editorManager,
|
||||||
} from 'lib/singletons'
|
} from 'lib/singletons'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath } from 'lang/queryAst'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { executeAst, ToolTip } from 'lang/langHelpers'
|
import { executeAst, ToolTip } from 'lang/langHelpers'
|
||||||
import {
|
import {
|
||||||
createProfileStartHandle,
|
createProfileStartHandle,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { editorManager, engineCommandManager, kclManager } from 'lib/singletons'
|
import { editorManager, engineCommandManager, kclManager } from 'lib/singletons'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath } from 'lang/queryAst'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { trap } from 'lib/trap'
|
import { trap } from 'lib/trap'
|
||||||
import { codeToIdSelections } from 'lib/selections'
|
import { codeToIdSelections } from 'lib/selections'
|
||||||
|
@ -68,11 +68,8 @@ import {
|
|||||||
startSketchOnDefault,
|
startSketchOnDefault,
|
||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm'
|
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm'
|
||||||
import {
|
import { artifactIsPlaneWithPaths, isSingleCursorInPipe } from 'lang/queryAst'
|
||||||
artifactIsPlaneWithPaths,
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
getNodePathFromSourceRange,
|
|
||||||
isSingleCursorInPipe,
|
|
||||||
} from 'lang/queryAst'
|
|
||||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||||
import { Models } from '@kittycad/lib/dist/types/src'
|
import { Models } from '@kittycad/lib/dist/types/src'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { toolTips } from 'lang/langHelpers'
|
import { toolTips } from 'lang/langHelpers'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
|
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
|
||||||
import {
|
import { getNodeFromPath } from '../../lang/queryAst'
|
||||||
getNodePathFromSourceRange,
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
getNodeFromPath,
|
|
||||||
} from '../../lang/queryAst'
|
|
||||||
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
|
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
|
||||||
import {
|
import {
|
||||||
transformSecondarySketchLinesTagFirst,
|
transformSecondarySketchLinesTagFirst,
|
||||||
|
@ -17,7 +17,8 @@ import {
|
|||||||
} from 'lang/std/artifactGraph'
|
} from 'lang/std/artifactGraph'
|
||||||
import { err, reportRejection } from 'lib/trap'
|
import { err, reportRejection } from 'lib/trap'
|
||||||
import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities'
|
import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath } from 'lang/queryAst'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { CallExpression, CallExpressionKw, defaultSourceRange } from 'lang/wasm'
|
import { CallExpression, CallExpressionKw, defaultSourceRange } from 'lang/wasm'
|
||||||
import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine'
|
import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine'
|
||||||
|
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
import {
|
import { getNodeFromPath, LABELED_ARG_FIELD, ARG_INDEX_FIELD } from './queryAst'
|
||||||
getNodePathFromSourceRange,
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
getNodeFromPath,
|
|
||||||
LABELED_ARG_FIELD,
|
|
||||||
ARG_INDEX_FIELD,
|
|
||||||
} from './queryAst'
|
|
||||||
import {
|
import {
|
||||||
Identifier,
|
Identifier,
|
||||||
assertParse,
|
assertParse,
|
||||||
|
@ -25,7 +25,8 @@ import {
|
|||||||
deleteFromSelection,
|
deleteFromSelection,
|
||||||
} from './modifyAst'
|
} from './modifyAst'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst'
|
import { findUsesOfTagInPipe } from './queryAst'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { SimplifiedArgDetails } from './std/stdTypes'
|
import { SimplifiedArgDetails } from './std/stdTypes'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
@ -28,7 +28,6 @@ import {
|
|||||||
findAllPreviousVariables,
|
findAllPreviousVariables,
|
||||||
findAllPreviousVariablesPath,
|
findAllPreviousVariablesPath,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
getNodePathFromSourceRange,
|
|
||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
traverse,
|
traverse,
|
||||||
ARG_INDEX_FIELD,
|
ARG_INDEX_FIELD,
|
||||||
@ -40,6 +39,7 @@ import {
|
|||||||
getConstraintInfo,
|
getConstraintInfo,
|
||||||
getConstraintInfoKw,
|
getConstraintInfoKw,
|
||||||
} from './std/sketch'
|
} from './std/sketch'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import {
|
import {
|
||||||
PathToNodeMap,
|
PathToNodeMap,
|
||||||
isLiteralArrayOrStatic,
|
isLiteralArrayOrStatic,
|
||||||
|
@ -22,7 +22,8 @@ import {
|
|||||||
ChamferParameters,
|
ChamferParameters,
|
||||||
EdgeTreatmentParameters,
|
EdgeTreatmentParameters,
|
||||||
} from './addEdgeTreatment'
|
} from './addEdgeTreatment'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
import { getNodeFromPath } from '../queryAst'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { createLiteral } from 'lang/modifyAst'
|
import { createLiteral } from 'lang/modifyAst'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { Selection, Selections } from 'lib/selections'
|
import { Selection, Selections } from 'lib/selections'
|
||||||
|
@ -21,10 +21,10 @@ import {
|
|||||||
} from '../modifyAst'
|
} from '../modifyAst'
|
||||||
import {
|
import {
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
getNodePathFromSourceRange,
|
|
||||||
hasSketchPipeBeenExtruded,
|
hasSketchPipeBeenExtruded,
|
||||||
traverse,
|
traverse,
|
||||||
} from '../queryAst'
|
} from '../queryAst'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import {
|
import {
|
||||||
addTagForSketchOnFace,
|
addTagForSketchOnFace,
|
||||||
ARG_TAG,
|
ARG_TAG,
|
||||||
|
@ -20,7 +20,8 @@ import {
|
|||||||
findUniqueName,
|
findUniqueName,
|
||||||
createVariableDeclaration,
|
createVariableDeclaration,
|
||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath } from 'lang/queryAst'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import {
|
import {
|
||||||
mutateAstWithTagForSketchSegment,
|
mutateAstWithTagForSketchSegment,
|
||||||
getEdgeTagCall,
|
getEdgeTagCall,
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
findAllPreviousVariables,
|
findAllPreviousVariables,
|
||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
isTypeInValue,
|
isTypeInValue,
|
||||||
getNodePathFromSourceRange,
|
|
||||||
hasExtrudeSketch,
|
hasExtrudeSketch,
|
||||||
findUsesOfTagInPipe,
|
findUsesOfTagInPipe,
|
||||||
hasSketchPipeBeenExtruded,
|
hasSketchPipeBeenExtruded,
|
||||||
@ -19,6 +18,7 @@ import {
|
|||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
doesSceneHaveExtrudedSketch,
|
doesSceneHaveExtrudedSketch,
|
||||||
} from './queryAst'
|
} from './queryAst'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import {
|
import {
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
||||||
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
|
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
|
||||||
import { getAngle } from '../lib/utils'
|
import { getAngle } from '../lib/utils'
|
||||||
@ -423,30 +424,6 @@ function moreNodePathFromSourceRange(
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNodePathFromSourceRange(
|
|
||||||
node: Program,
|
|
||||||
sourceRange: SourceRange,
|
|
||||||
previousPath: PathToNode = [['body', '']]
|
|
||||||
): PathToNode {
|
|
||||||
const [start, end] = sourceRange || []
|
|
||||||
let path: PathToNode = [...previousPath]
|
|
||||||
const _node = { ...node }
|
|
||||||
|
|
||||||
// loop over each statement in body getting the index with a for loop
|
|
||||||
for (
|
|
||||||
let statementIndex = 0;
|
|
||||||
statementIndex < _node.body.length;
|
|
||||||
statementIndex++
|
|
||||||
) {
|
|
||||||
const statement = _node.body[statementIndex]
|
|
||||||
if (statement.start <= start && statement.end >= end) {
|
|
||||||
path.push([statementIndex, 'index'])
|
|
||||||
return moreNodePathFromSourceRange(statement, sourceRange, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
type KCLNode = Node<
|
type KCLNode = Node<
|
||||||
| Expr
|
| Expr
|
||||||
| ExpressionStatement
|
| ExpressionStatement
|
||||||
|
316
src/lang/queryAstNodePathUtils.ts
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
import {
|
||||||
|
Expr,
|
||||||
|
ExpressionStatement,
|
||||||
|
VariableDeclaration,
|
||||||
|
ReturnStatement,
|
||||||
|
SourceRange,
|
||||||
|
PathToNode,
|
||||||
|
Program,
|
||||||
|
} from './wasm'
|
||||||
|
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
||||||
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
|
||||||
|
function moreNodePathFromSourceRange(
|
||||||
|
node: Node<
|
||||||
|
| Expr
|
||||||
|
| ImportStatement
|
||||||
|
| ExpressionStatement
|
||||||
|
| VariableDeclaration
|
||||||
|
| ReturnStatement
|
||||||
|
>,
|
||||||
|
sourceRange: SourceRange,
|
||||||
|
previousPath: PathToNode = [['body', '']]
|
||||||
|
): PathToNode {
|
||||||
|
const [start, end] = sourceRange
|
||||||
|
let path: PathToNode = [...previousPath]
|
||||||
|
const _node = { ...node }
|
||||||
|
|
||||||
|
if (start < _node.start || end > _node.end) return path
|
||||||
|
|
||||||
|
const isInRange = _node.start <= start && _node.end >= end
|
||||||
|
|
||||||
|
if (
|
||||||
|
(_node.type === 'Identifier' ||
|
||||||
|
_node.type === 'Literal' ||
|
||||||
|
_node.type === 'TagDeclarator') &&
|
||||||
|
isInRange
|
||||||
|
) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_node.type === 'CallExpression' && isInRange) {
|
||||||
|
const { callee, arguments: args } = _node
|
||||||
|
if (
|
||||||
|
callee.type === 'Identifier' &&
|
||||||
|
callee.start <= start &&
|
||||||
|
callee.end >= end
|
||||||
|
) {
|
||||||
|
path.push(['callee', 'CallExpression'])
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (args.length > 0) {
|
||||||
|
for (let argIndex = 0; argIndex < args.length; argIndex++) {
|
||||||
|
const arg = args[argIndex]
|
||||||
|
if (arg.start <= start && arg.end >= end) {
|
||||||
|
path.push(['arguments', 'CallExpression'])
|
||||||
|
path.push([argIndex, 'index'])
|
||||||
|
return moreNodePathFromSourceRange(arg, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_node.type === 'CallExpressionKw' && isInRange) {
|
||||||
|
const { callee, arguments: args } = _node
|
||||||
|
if (
|
||||||
|
callee.type === 'Identifier' &&
|
||||||
|
callee.start <= start &&
|
||||||
|
callee.end >= end
|
||||||
|
) {
|
||||||
|
path.push(['callee', 'CallExpressionKw'])
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (args.length > 0) {
|
||||||
|
for (let argIndex = 0; argIndex < args.length; argIndex++) {
|
||||||
|
const arg = args[argIndex].arg
|
||||||
|
if (arg.start <= start && arg.end >= end) {
|
||||||
|
path.push(['arguments', 'CallExpressionKw'])
|
||||||
|
path.push([argIndex, 'index'])
|
||||||
|
return moreNodePathFromSourceRange(arg, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_node.type === 'BinaryExpression' && isInRange) {
|
||||||
|
const { left, right } = _node
|
||||||
|
if (left.start <= start && left.end >= end) {
|
||||||
|
path.push(['left', 'BinaryExpression'])
|
||||||
|
return moreNodePathFromSourceRange(left, sourceRange, path)
|
||||||
|
}
|
||||||
|
if (right.start <= start && right.end >= end) {
|
||||||
|
path.push(['right', 'BinaryExpression'])
|
||||||
|
return moreNodePathFromSourceRange(right, sourceRange, path)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (_node.type === 'PipeExpression' && isInRange) {
|
||||||
|
const { body } = _node
|
||||||
|
for (let i = 0; i < body.length; i++) {
|
||||||
|
const pipe = body[i]
|
||||||
|
if (pipe.start <= start && pipe.end >= end) {
|
||||||
|
path.push(['body', 'PipeExpression'])
|
||||||
|
path.push([i, 'index'])
|
||||||
|
return moreNodePathFromSourceRange(pipe, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (_node.type === 'ArrayExpression' && isInRange) {
|
||||||
|
const { elements } = _node
|
||||||
|
for (let elIndex = 0; elIndex < elements.length; elIndex++) {
|
||||||
|
const element = elements[elIndex]
|
||||||
|
if (element.start <= start && element.end >= end) {
|
||||||
|
path.push(['elements', 'ArrayExpression'])
|
||||||
|
path.push([elIndex, 'index'])
|
||||||
|
return moreNodePathFromSourceRange(element, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (_node.type === 'ObjectExpression' && isInRange) {
|
||||||
|
const { properties } = _node
|
||||||
|
for (let propIndex = 0; propIndex < properties.length; propIndex++) {
|
||||||
|
const property = properties[propIndex]
|
||||||
|
if (property.start <= start && property.end >= end) {
|
||||||
|
path.push(['properties', 'ObjectExpression'])
|
||||||
|
path.push([propIndex, 'index'])
|
||||||
|
if (property.key.start <= start && property.key.end >= end) {
|
||||||
|
path.push(['key', 'Property'])
|
||||||
|
return moreNodePathFromSourceRange(property.key, sourceRange, path)
|
||||||
|
}
|
||||||
|
if (property.value.start <= start && property.value.end >= end) {
|
||||||
|
path.push(['value', 'Property'])
|
||||||
|
return moreNodePathFromSourceRange(property.value, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (_node.type === 'ExpressionStatement' && isInRange) {
|
||||||
|
const { expression } = _node
|
||||||
|
path.push(['expression', 'ExpressionStatement'])
|
||||||
|
return moreNodePathFromSourceRange(expression, sourceRange, path)
|
||||||
|
}
|
||||||
|
if (_node.type === 'VariableDeclaration' && isInRange) {
|
||||||
|
const declaration = _node.declaration
|
||||||
|
|
||||||
|
if (declaration.start <= start && declaration.end >= end) {
|
||||||
|
path.push(['declaration', 'VariableDeclaration'])
|
||||||
|
const init = declaration.init
|
||||||
|
if (init.start <= start && init.end >= end) {
|
||||||
|
path.push(['init', ''])
|
||||||
|
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_node.type === 'VariableDeclaration' && isInRange) {
|
||||||
|
const declaration = _node.declaration
|
||||||
|
|
||||||
|
if (declaration.start <= start && declaration.end >= end) {
|
||||||
|
const init = declaration.init
|
||||||
|
if (init.start <= start && init.end >= end) {
|
||||||
|
path.push(['declaration', 'VariableDeclaration'])
|
||||||
|
path.push(['init', ''])
|
||||||
|
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (_node.type === 'UnaryExpression' && isInRange) {
|
||||||
|
const { argument } = _node
|
||||||
|
if (argument.start <= start && argument.end >= end) {
|
||||||
|
path.push(['argument', 'UnaryExpression'])
|
||||||
|
return moreNodePathFromSourceRange(argument, sourceRange, path)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (_node.type === 'FunctionExpression' && isInRange) {
|
||||||
|
for (let i = 0; i < _node.params.length; i++) {
|
||||||
|
const param = _node.params[i]
|
||||||
|
if (param.identifier.start <= start && param.identifier.end >= end) {
|
||||||
|
path.push(['params', 'FunctionExpression'])
|
||||||
|
path.push([i, 'index'])
|
||||||
|
return moreNodePathFromSourceRange(param.identifier, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_node.body.start <= start && _node.body.end >= end) {
|
||||||
|
path.push(['body', 'FunctionExpression'])
|
||||||
|
const fnBody = _node.body.body
|
||||||
|
for (let i = 0; i < fnBody.length; i++) {
|
||||||
|
const statement = fnBody[i]
|
||||||
|
if (statement.start <= start && statement.end >= end) {
|
||||||
|
path.push(['body', 'FunctionExpression'])
|
||||||
|
path.push([i, 'index'])
|
||||||
|
return moreNodePathFromSourceRange(statement, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (_node.type === 'ReturnStatement' && isInRange) {
|
||||||
|
const { argument } = _node
|
||||||
|
if (argument.start <= start && argument.end >= end) {
|
||||||
|
path.push(['argument', 'ReturnStatement'])
|
||||||
|
return moreNodePathFromSourceRange(argument, sourceRange, path)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (_node.type === 'MemberExpression' && isInRange) {
|
||||||
|
const { object, property } = _node
|
||||||
|
if (object.start <= start && object.end >= end) {
|
||||||
|
path.push(['object', 'MemberExpression'])
|
||||||
|
return moreNodePathFromSourceRange(object, sourceRange, path)
|
||||||
|
}
|
||||||
|
if (property.start <= start && property.end >= end) {
|
||||||
|
path.push(['property', 'MemberExpression'])
|
||||||
|
return moreNodePathFromSourceRange(property, sourceRange, path)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_node.type === 'PipeSubstitution' && isInRange) return path
|
||||||
|
|
||||||
|
if (_node.type === 'IfExpression' && isInRange) {
|
||||||
|
const { cond, then_val, else_ifs, final_else } = _node
|
||||||
|
if (cond.start <= start && cond.end >= end) {
|
||||||
|
path.push(['cond', 'IfExpression'])
|
||||||
|
return moreNodePathFromSourceRange(cond, sourceRange, path)
|
||||||
|
}
|
||||||
|
if (then_val.start <= start && then_val.end >= end) {
|
||||||
|
path.push(['then_val', 'IfExpression'])
|
||||||
|
path.push(['body', 'IfExpression'])
|
||||||
|
return getNodePathFromSourceRange(then_val, sourceRange, path)
|
||||||
|
}
|
||||||
|
for (let i = 0; i < else_ifs.length; i++) {
|
||||||
|
const else_if = else_ifs[i]
|
||||||
|
if (else_if.start <= start && else_if.end >= end) {
|
||||||
|
path.push(['else_ifs', 'IfExpression'])
|
||||||
|
path.push([i, 'index'])
|
||||||
|
const { cond, then_val } = else_if
|
||||||
|
if (cond.start <= start && cond.end >= end) {
|
||||||
|
path.push(['cond', 'IfExpression'])
|
||||||
|
return moreNodePathFromSourceRange(cond, sourceRange, path)
|
||||||
|
}
|
||||||
|
path.push(['then_val', 'IfExpression'])
|
||||||
|
path.push(['body', 'IfExpression'])
|
||||||
|
return getNodePathFromSourceRange(then_val, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (final_else.start <= start && final_else.end >= end) {
|
||||||
|
path.push(['final_else', 'IfExpression'])
|
||||||
|
path.push(['body', 'IfExpression'])
|
||||||
|
return getNodePathFromSourceRange(final_else, sourceRange, path)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_node.type === 'ImportStatement' && isInRange) {
|
||||||
|
if (_node.selector && _node.selector.type === 'List') {
|
||||||
|
path.push(['selector', 'ImportStatement'])
|
||||||
|
const { items } = _node.selector
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const item = items[i]
|
||||||
|
if (item.start <= start && item.end >= end) {
|
||||||
|
path.push(['items', 'ImportSelector'])
|
||||||
|
path.push([i, 'index'])
|
||||||
|
if (item.name.start <= start && item.name.end >= end) {
|
||||||
|
path.push(['name', 'ImportItem'])
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
item.alias &&
|
||||||
|
item.alias.start <= start &&
|
||||||
|
item.alias.end >= end
|
||||||
|
) {
|
||||||
|
path.push(['alias', 'ImportItem'])
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('not implemented: ' + node.type)
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNodePathFromSourceRange(
|
||||||
|
node: Program,
|
||||||
|
sourceRange: SourceRange,
|
||||||
|
previousPath: PathToNode = [['body', '']]
|
||||||
|
): PathToNode {
|
||||||
|
const [start, end] = sourceRange || []
|
||||||
|
let path: PathToNode = [...previousPath]
|
||||||
|
const _node = { ...node }
|
||||||
|
|
||||||
|
// loop over each statement in body getting the index with a for loop
|
||||||
|
for (
|
||||||
|
let statementIndex = 0;
|
||||||
|
statementIndex < _node.body.length;
|
||||||
|
statementIndex++
|
||||||
|
) {
|
||||||
|
const statement = _node.body[statementIndex]
|
||||||
|
if (statement.start <= start && statement.end >= end) {
|
||||||
|
path.push([statementIndex, 'index'])
|
||||||
|
return moreNodePathFromSourceRange(statement, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
@ -16,7 +16,7 @@ import {
|
|||||||
EdgeCut,
|
EdgeCut,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
|
|
||||||
export type { Artifact, ArtifactId, SegmentArtifact } from 'lang/wasm'
|
export type { Artifact, ArtifactId, SegmentArtifact } from 'lang/wasm'
|
||||||
|
@ -16,7 +16,8 @@ import {
|
|||||||
topLevelRange,
|
topLevelRange,
|
||||||
CallExpressionKw,
|
CallExpressionKw,
|
||||||
} from '../wasm'
|
} from '../wasm'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
import { getNodeFromPath } from '../queryAst'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
@ -18,10 +18,10 @@ import {
|
|||||||
import {
|
import {
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
getNodeFromPathCurry,
|
getNodeFromPathCurry,
|
||||||
getNodePathFromSourceRange,
|
|
||||||
getObjExprProperty,
|
getObjExprProperty,
|
||||||
LABELED_ARG_FIELD,
|
LABELED_ARG_FIELD,
|
||||||
} from 'lang/queryAst'
|
} from 'lang/queryAst'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import {
|
import {
|
||||||
isLiteralArrayOrStatic,
|
isLiteralArrayOrStatic,
|
||||||
isNotLiteralArrayOrStatic,
|
isNotLiteralArrayOrStatic,
|
||||||
|
@ -25,11 +25,8 @@ import {
|
|||||||
recast,
|
recast,
|
||||||
LabeledArg,
|
LabeledArg,
|
||||||
} from '../wasm'
|
} from '../wasm'
|
||||||
import {
|
import { getNodeFromPath, getNodeFromPathCurry } from '../queryAst'
|
||||||
getNodeFromPath,
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
getNodeFromPathCurry,
|
|
||||||
getNodePathFromSourceRange,
|
|
||||||
} from '../queryAst'
|
|
||||||
import {
|
import {
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
createBinaryExpression,
|
createBinaryExpression,
|
||||||
|
@ -53,7 +53,7 @@ import { ArtifactId } from 'wasm-lib/kcl/bindings/Artifact'
|
|||||||
import { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
import { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
import { ArtifactGraph as RustArtifactGraph } from 'wasm-lib/kcl/bindings/Artifact'
|
import { ArtifactGraph as RustArtifactGraph } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
import { Artifact } from './std/artifactGraph'
|
import { Artifact } from './std/artifactGraph'
|
||||||
import { getNodePathFromSourceRange } from './queryAst'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
|
|
||||||
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
@ -18,11 +18,8 @@ import { EditorSelection, SelectionRange } from '@codemirror/state'
|
|||||||
import { getNormalisedCoordinates, isOverlap } from 'lib/utils'
|
import { getNormalisedCoordinates, isOverlap } from 'lib/utils'
|
||||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||||
import { Program } from 'lang/wasm'
|
import { Program } from 'lang/wasm'
|
||||||
import {
|
import { getNodeFromPath, isSingleCursorInPipe } from 'lang/queryAst'
|
||||||
getNodeFromPath,
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
getNodePathFromSourceRange,
|
|
||||||
isSingleCursorInPipe,
|
|
||||||
} from 'lang/queryAst'
|
|
||||||
import { CommandArgument } from './commandTypes'
|
import { CommandArgument } from './commandTypes'
|
||||||
import {
|
import {
|
||||||
DefaultPlaneStr,
|
DefaultPlaneStr,
|
||||||
|
@ -16,10 +16,8 @@ import {
|
|||||||
} from 'lib/selections'
|
} from 'lib/selections'
|
||||||
import { assign, fromPromise, fromCallback, setup } from 'xstate'
|
import { assign, fromPromise, fromCallback, setup } from 'xstate'
|
||||||
import { SidebarType } from 'components/ModelingSidebar/ModelingPanes'
|
import { SidebarType } from 'components/ModelingSidebar/ModelingPanes'
|
||||||
import {
|
import { isNodeSafeToReplacePath } from 'lang/queryAst'
|
||||||
isNodeSafeToReplacePath,
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
getNodePathFromSourceRange,
|
|
||||||
} from 'lang/queryAst'
|
|
||||||
import {
|
import {
|
||||||
kclManager,
|
kclManager,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
|
28
src/wasm-lib/Cargo.lock
generated
@ -443,9 +443,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.23"
|
version = "4.5.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
|
checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@ -453,9 +453,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.23"
|
version = "4.5.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
|
checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@ -467,9 +467,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.18"
|
version = "4.5.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -708,9 +708,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "data-encoding"
|
name = "data-encoding"
|
||||||
version = "2.6.0"
|
version = "2.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
|
checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
@ -1844,9 +1844,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad-modeling-cmds"
|
name = "kittycad-modeling-cmds"
|
||||||
version = "0.2.89"
|
version = "0.2.92"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce9e58b34645facea36bc9f4868877bbe6fcac01b92896825e8d4f2f7c71dbd6"
|
checksum = "c5fc91d0cdacd1c2ba906f564e58ce07c70a5c471f19b0f4c0b67e754077bfdc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -3266,9 +3266,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.135"
|
version = "1.0.137"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
|
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.7.0",
|
"indexmap 2.7.0",
|
||||||
"itoa",
|
"itoa",
|
||||||
@ -4184,9 +4184,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.11.1"
|
version = "1.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4"
|
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -76,7 +76,7 @@ members = [
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
http = "1"
|
http = "1"
|
||||||
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
|
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
|
||||||
kittycad-modeling-cmds = { version = "0.2.89", features = [
|
kittycad-modeling-cmds = { version = "0.2.92", features = [
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
"websocket",
|
"websocket",
|
||||||
] }
|
] }
|
||||||
|
@ -16,7 +16,7 @@ async-recursion = "1.1.1"
|
|||||||
async-trait = "0.1.85"
|
async-trait = "0.1.85"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
clap = { version = "4.5.23", default-features = false, optional = true, features = [
|
clap = { version = "4.5.27", default-features = false, optional = true, features = [
|
||||||
"std",
|
"std",
|
||||||
"derive",
|
"derive",
|
||||||
] }
|
] }
|
||||||
|
294
src/wasm-lib/kcl/src/execution/import.rs
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
use std::{ffi::OsStr, path::Path, str::FromStr};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use kcmc::{
|
||||||
|
coord::{Axis, AxisDirectionPair, Direction, System},
|
||||||
|
each_cmd as mcmd,
|
||||||
|
format::InputFormat,
|
||||||
|
ok_response::OkModelingCmdResponse,
|
||||||
|
shared::FileImportFormat,
|
||||||
|
units::UnitLength,
|
||||||
|
websocket::OkWebSocketResponseData,
|
||||||
|
ImportFile, ModelingCmd,
|
||||||
|
};
|
||||||
|
use kittycad_modeling_cmds as kcmc;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
errors::{KclError, KclErrorDetails},
|
||||||
|
execution::{ExecState, ImportedGeometry},
|
||||||
|
fs::FileSystem,
|
||||||
|
source_range::SourceRange,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::ExecutorContext;
|
||||||
|
|
||||||
|
// Zoo co-ordinate system.
|
||||||
|
//
|
||||||
|
// * Forward: -Y
|
||||||
|
// * Up: +Z
|
||||||
|
// * Handedness: Right
|
||||||
|
pub const ZOO_COORD_SYSTEM: System = System {
|
||||||
|
forward: AxisDirectionPair {
|
||||||
|
axis: Axis::Y,
|
||||||
|
direction: Direction::Negative,
|
||||||
|
},
|
||||||
|
up: AxisDirectionPair {
|
||||||
|
axis: Axis::Z,
|
||||||
|
direction: Direction::Positive,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn import_foreign(
|
||||||
|
file_path: &Path,
|
||||||
|
format: Option<InputFormat>,
|
||||||
|
exec_state: &mut ExecState,
|
||||||
|
ctxt: &ExecutorContext,
|
||||||
|
source_range: SourceRange,
|
||||||
|
) -> Result<PreImportedGeometry, KclError> {
|
||||||
|
// Make sure the file exists.
|
||||||
|
if !ctxt.fs.exists(file_path, source_range).await? {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("File `{}` does not exist.", file_path.display()),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let ext_format = get_import_format_from_extension(file_path.extension().ok_or_else(|| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("No file extension found for `{}`", file_path.display()),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?)
|
||||||
|
.map_err(|e| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: e.to_string(),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Get the format type from the extension of the file.
|
||||||
|
let format = if let Some(format) = format {
|
||||||
|
// Validate the given format with the extension format.
|
||||||
|
validate_extension_format(ext_format, format.clone()).map_err(|e| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: e.to_string(),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
format
|
||||||
|
} else {
|
||||||
|
ext_format
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the file contents for each file path.
|
||||||
|
let file_contents = ctxt.fs.read(file_path, source_range).await.map_err(|e| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: e.to_string(),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// We want the file_path to be without the parent.
|
||||||
|
let file_name = std::path::Path::new(&file_path)
|
||||||
|
.file_name()
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("Could not get the file name from the path `{}`", file_path.display()),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
let mut import_files = vec![kcmc::ImportFile {
|
||||||
|
path: file_name.to_string(),
|
||||||
|
data: file_contents.clone(),
|
||||||
|
}];
|
||||||
|
|
||||||
|
// In the case of a gltf importing a bin file we need to handle that! and figure out where the
|
||||||
|
// file is relative to our current file.
|
||||||
|
if let InputFormat::Gltf(..) = format {
|
||||||
|
// Check if the file is a binary gltf file, in that case we don't need to import the bin
|
||||||
|
// file.
|
||||||
|
if !file_contents.starts_with(b"glTF") {
|
||||||
|
let json = gltf_json::Root::from_slice(&file_contents).map_err(|e| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: e.to_string(),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Read the gltf file and check if there is a bin file.
|
||||||
|
for buffer in json.buffers.iter() {
|
||||||
|
if let Some(uri) = &buffer.uri {
|
||||||
|
if !uri.starts_with("data:") {
|
||||||
|
// We want this path relative to the file_path given.
|
||||||
|
let bin_path = std::path::Path::new(&file_path)
|
||||||
|
.parent()
|
||||||
|
.map(|p| p.join(uri))
|
||||||
|
.map(|p| p.to_string_lossy().to_string())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!(
|
||||||
|
"Could not get the parent path of the file `{}`",
|
||||||
|
file_path.display()
|
||||||
|
),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let bin_contents = ctxt.fs.read(&bin_path, source_range).await.map_err(|e| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: e.to_string(),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
import_files.push(ImportFile {
|
||||||
|
path: uri.to_string(),
|
||||||
|
data: bin_contents,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(PreImportedGeometry {
|
||||||
|
id: exec_state.next_uuid(),
|
||||||
|
source_range,
|
||||||
|
command: mcmd::ImportFiles {
|
||||||
|
files: import_files.clone(),
|
||||||
|
format,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct PreImportedGeometry {
|
||||||
|
id: Uuid,
|
||||||
|
command: mcmd::ImportFiles,
|
||||||
|
source_range: SourceRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_to_engine(pre: PreImportedGeometry, ctxt: &ExecutorContext) -> Result<ImportedGeometry, KclError> {
|
||||||
|
if ctxt.is_mock() {
|
||||||
|
return Ok(ImportedGeometry {
|
||||||
|
id: pre.id,
|
||||||
|
value: pre.command.files.iter().map(|f| f.path.to_string()).collect(),
|
||||||
|
meta: vec![pre.source_range.into()],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let resp = ctxt
|
||||||
|
.engine
|
||||||
|
.send_modeling_cmd(pre.id, pre.source_range, &ModelingCmd::from(pre.command.clone()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let OkWebSocketResponseData::Modeling {
|
||||||
|
modeling_response: OkModelingCmdResponse::ImportFiles(imported_files),
|
||||||
|
} = &resp
|
||||||
|
else {
|
||||||
|
return Err(KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("ImportFiles response was not as expected: {:?}", resp),
|
||||||
|
source_ranges: vec![pre.source_range],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ImportedGeometry {
|
||||||
|
id: imported_files.object_id,
|
||||||
|
value: pre.command.files.iter().map(|f| f.path.to_string()).collect(),
|
||||||
|
meta: vec![pre.source_range.into()],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the source format from the extension.
|
||||||
|
fn get_import_format_from_extension(ext: &OsStr) -> Result<InputFormat> {
|
||||||
|
let ext = ext
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Invalid file extension: `{ext:?}`"))?;
|
||||||
|
let format = match FileImportFormat::from_str(ext) {
|
||||||
|
Ok(format) => format,
|
||||||
|
Err(_) => {
|
||||||
|
if ext == "stp" {
|
||||||
|
FileImportFormat::Step
|
||||||
|
} else if ext == "glb" {
|
||||||
|
FileImportFormat::Gltf
|
||||||
|
} else {
|
||||||
|
anyhow::bail!("unknown source format for file extension: {ext}. Try setting the `--src-format` flag explicitly or use a valid format.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make the default units millimeters.
|
||||||
|
let ul = UnitLength::Millimeters;
|
||||||
|
|
||||||
|
// Zoo co-ordinate system.
|
||||||
|
//
|
||||||
|
// * Forward: -Y
|
||||||
|
// * Up: +Z
|
||||||
|
// * Handedness: Right
|
||||||
|
match format {
|
||||||
|
FileImportFormat::Step => Ok(InputFormat::Step(kcmc::format::step::import::Options {
|
||||||
|
split_closed_faces: false,
|
||||||
|
})),
|
||||||
|
FileImportFormat::Stl => Ok(InputFormat::Stl(kcmc::format::stl::import::Options {
|
||||||
|
coords: ZOO_COORD_SYSTEM,
|
||||||
|
units: ul,
|
||||||
|
})),
|
||||||
|
FileImportFormat::Obj => Ok(InputFormat::Obj(kcmc::format::obj::import::Options {
|
||||||
|
coords: ZOO_COORD_SYSTEM,
|
||||||
|
units: ul,
|
||||||
|
})),
|
||||||
|
FileImportFormat::Gltf => Ok(InputFormat::Gltf(kcmc::format::gltf::import::Options {})),
|
||||||
|
FileImportFormat::Ply => Ok(InputFormat::Ply(kcmc::format::ply::import::Options {
|
||||||
|
coords: ZOO_COORD_SYSTEM,
|
||||||
|
units: ul,
|
||||||
|
})),
|
||||||
|
FileImportFormat::Fbx => Ok(InputFormat::Fbx(kcmc::format::fbx::import::Options {})),
|
||||||
|
FileImportFormat::Sldprt => Ok(InputFormat::Sldprt(kcmc::format::sldprt::import::Options {
|
||||||
|
split_closed_faces: false,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_extension_format(ext: InputFormat, given: InputFormat) -> Result<()> {
|
||||||
|
if let InputFormat::Stl(_) = ext {
|
||||||
|
if let InputFormat::Stl(_) = given {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let InputFormat::Obj(_) = ext {
|
||||||
|
if let InputFormat::Obj(_) = given {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let InputFormat::Ply(_) = ext {
|
||||||
|
if let InputFormat::Ply(_) = given {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ext == given {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
anyhow::bail!(
|
||||||
|
"The given format does not match the file extension. Expected: `{}`, Given: `{}`",
|
||||||
|
get_name_of_format(ext),
|
||||||
|
get_name_of_format(given)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_name_of_format(type_: InputFormat) -> &'static str {
|
||||||
|
match type_ {
|
||||||
|
InputFormat::Fbx(_) => "fbx",
|
||||||
|
InputFormat::Gltf(_) => "gltf",
|
||||||
|
InputFormat::Obj(_) => "obj",
|
||||||
|
InputFormat::Ply(_) => "ply",
|
||||||
|
InputFormat::Sldprt(_) => "sldprt",
|
||||||
|
InputFormat::Step(_) => "step",
|
||||||
|
InputFormat::Stl(_) => "stl",
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ type Point2D = kcmc::shared::Point2d<f64>;
|
|||||||
type Point3D = kcmc::shared::Point3d<f64>;
|
type Point3D = kcmc::shared::Point3d<f64>;
|
||||||
|
|
||||||
pub use function_param::FunctionParam;
|
pub use function_param::FunctionParam;
|
||||||
|
pub(crate) use import::{import_foreign, send_to_engine as send_import_to_engine, ZOO_COORD_SYSTEM};
|
||||||
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
|
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ pub(crate) mod cache;
|
|||||||
mod cad_op;
|
mod cad_op;
|
||||||
mod exec_ast;
|
mod exec_ast;
|
||||||
mod function_param;
|
mod function_param;
|
||||||
|
mod import;
|
||||||
mod kcl_value;
|
mod kcl_value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -40,7 +42,7 @@ use crate::{
|
|||||||
execution::cache::{CacheInformation, CacheResult},
|
execution::cache::{CacheInformation, CacheResult},
|
||||||
fs::{FileManager, FileSystem},
|
fs::{FileManager, FileSystem},
|
||||||
parsing::ast::types::{
|
parsing::ast::types::{
|
||||||
BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, NonCodeValue,
|
BodyItem, Expr, FunctionExpression, ImportPath, ImportSelector, ItemVisibility, Node, NodeRef, NonCodeValue,
|
||||||
Program as AstProgram, TagDeclarator, TagNode,
|
Program as AstProgram, TagDeclarator, TagNode,
|
||||||
},
|
},
|
||||||
settings::types::UnitLength,
|
settings::types::UnitLength,
|
||||||
@ -181,34 +183,15 @@ impl ExecState {
|
|||||||
self.global.artifacts.insert(id, artifact);
|
self.global.artifacts.insert(id, artifact);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn add_module(
|
fn add_module(&mut self, id: ModuleId, path: std::path::PathBuf, repr: ModuleRepr) -> ModuleId {
|
||||||
&mut self,
|
debug_assert!(!self.global.path_to_source_id.contains_key(&path));
|
||||||
path: std::path::PathBuf,
|
|
||||||
ctxt: &ExecutorContext,
|
|
||||||
source_range: SourceRange,
|
|
||||||
) -> Result<ModuleId, KclError> {
|
|
||||||
// Need to avoid borrowing self in the closure.
|
|
||||||
let new_module_id = ModuleId::from_usize(self.global.path_to_source_id.len());
|
|
||||||
let mut is_new = false;
|
|
||||||
let id = *self.global.path_to_source_id.entry(path.clone()).or_insert_with(|| {
|
|
||||||
is_new = true;
|
|
||||||
new_module_id
|
|
||||||
});
|
|
||||||
|
|
||||||
if is_new {
|
self.global.path_to_source_id.insert(path.clone(), id);
|
||||||
let source = ctxt.fs.read_to_string(&path, source_range).await?;
|
|
||||||
// TODO handle parsing errors properly
|
|
||||||
let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err()?;
|
|
||||||
|
|
||||||
let module_info = ModuleInfo {
|
let module_info = ModuleInfo { id, repr, path };
|
||||||
id,
|
self.global.module_infos.insert(id, module_info);
|
||||||
path,
|
|
||||||
parsed: Some(parsed),
|
|
||||||
};
|
|
||||||
self.global.module_infos.insert(id, module_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(id)
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn length_unit(&self) -> UnitLen {
|
pub fn length_unit(&self) -> UnitLen {
|
||||||
@ -240,7 +223,7 @@ impl GlobalState {
|
|||||||
ModuleInfo {
|
ModuleInfo {
|
||||||
id: root_id,
|
id: root_id,
|
||||||
path: root_path.clone(),
|
path: root_path.clone(),
|
||||||
parsed: None,
|
repr: ModuleRepr::Root,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
global.path_to_source_id.insert(root_path, root_id);
|
global.path_to_source_id.insert(root_path, root_id);
|
||||||
@ -1253,7 +1236,15 @@ pub struct ModuleInfo {
|
|||||||
id: ModuleId,
|
id: ModuleId,
|
||||||
/// Absolute path of the module's source file.
|
/// Absolute path of the module's source file.
|
||||||
path: std::path::PathBuf,
|
path: std::path::PathBuf,
|
||||||
parsed: Option<Node<AstProgram>>,
|
repr: ModuleRepr,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub enum ModuleRepr {
|
||||||
|
Root,
|
||||||
|
Kcl(Node<AstProgram>),
|
||||||
|
Foreign(import::PreImportedGeometry),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
|
||||||
@ -2511,33 +2502,68 @@ impl ExecutorContext {
|
|||||||
|
|
||||||
async fn open_module(
|
async fn open_module(
|
||||||
&self,
|
&self,
|
||||||
path: &str,
|
path: &ImportPath,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
source_range: SourceRange,
|
source_range: SourceRange,
|
||||||
) -> Result<ModuleId, KclError> {
|
) -> Result<ModuleId, KclError> {
|
||||||
let resolved_path = if let Some(project_dir) = &self.settings.project_directory {
|
match path {
|
||||||
project_dir.join(path)
|
ImportPath::Kcl { filename } => {
|
||||||
} else {
|
let resolved_path = if let Some(project_dir) = &self.settings.project_directory {
|
||||||
std::path::PathBuf::from(&path)
|
project_dir.join(filename)
|
||||||
};
|
} else {
|
||||||
|
std::path::PathBuf::from(filename)
|
||||||
|
};
|
||||||
|
|
||||||
if exec_state.mod_local.import_stack.contains(&resolved_path) {
|
if exec_state.mod_local.import_stack.contains(&resolved_path) {
|
||||||
return Err(KclError::ImportCycle(KclErrorDetails {
|
return Err(KclError::ImportCycle(KclErrorDetails {
|
||||||
message: format!(
|
message: format!(
|
||||||
"circular import of modules is not allowed: {} -> {}",
|
"circular import of modules is not allowed: {} -> {}",
|
||||||
exec_state
|
exec_state
|
||||||
.mod_local
|
.mod_local
|
||||||
.import_stack
|
.import_stack
|
||||||
.iter()
|
.iter()
|
||||||
.map(|p| p.as_path().to_string_lossy())
|
.map(|p| p.as_path().to_string_lossy())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(" -> "),
|
.join(" -> "),
|
||||||
resolved_path.to_string_lossy()
|
resolved_path.to_string_lossy()
|
||||||
),
|
),
|
||||||
|
source_ranges: vec![source_range],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(id) = exec_state.global.path_to_source_id.get(&resolved_path) {
|
||||||
|
return Ok(*id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let source = self.fs.read_to_string(&resolved_path, source_range).await?;
|
||||||
|
let id = ModuleId::from_usize(exec_state.global.path_to_source_id.len());
|
||||||
|
// TODO handle parsing errors properly
|
||||||
|
let parsed = crate::parsing::parse_str(&source, id).parse_errs_as_err()?;
|
||||||
|
let repr = ModuleRepr::Kcl(parsed);
|
||||||
|
|
||||||
|
Ok(exec_state.add_module(id, resolved_path, repr))
|
||||||
|
}
|
||||||
|
ImportPath::Foreign { path } => {
|
||||||
|
let resolved_path = if let Some(project_dir) = &self.settings.project_directory {
|
||||||
|
project_dir.join(path)
|
||||||
|
} else {
|
||||||
|
std::path::PathBuf::from(path)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(id) = exec_state.global.path_to_source_id.get(&resolved_path) {
|
||||||
|
return Ok(*id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let geom = import::import_foreign(&resolved_path, None, exec_state, self, source_range).await?;
|
||||||
|
let repr = ModuleRepr::Foreign(geom);
|
||||||
|
let id = ModuleId::from_usize(exec_state.global.path_to_source_id.len());
|
||||||
|
Ok(exec_state.add_module(id, resolved_path, repr))
|
||||||
|
}
|
||||||
|
i => Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!("Unsupported import: `{i}`"),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
}));
|
})),
|
||||||
}
|
}
|
||||||
exec_state.add_module(resolved_path.clone(), self, source_range).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn exec_module(
|
async fn exec_module(
|
||||||
@ -2551,43 +2577,51 @@ impl ExecutorContext {
|
|||||||
// TODO It sucks that we have to clone the whole module AST here
|
// TODO It sucks that we have to clone the whole module AST here
|
||||||
let info = exec_state.global.module_infos[&module_id].clone();
|
let info = exec_state.global.module_infos[&module_id].clone();
|
||||||
|
|
||||||
let mut local_state = ModuleState {
|
match &info.repr {
|
||||||
import_stack: exec_state.mod_local.import_stack.clone(),
|
ModuleRepr::Root => unreachable!(),
|
||||||
..ModuleState::new(&self.settings)
|
ModuleRepr::Kcl(program) => {
|
||||||
};
|
let mut local_state = ModuleState {
|
||||||
local_state.import_stack.push(info.path.clone());
|
import_stack: exec_state.mod_local.import_stack.clone(),
|
||||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
..ModuleState::new(&self.settings)
|
||||||
let original_execution = self.engine.replace_execution_kind(exec_kind);
|
};
|
||||||
|
local_state.import_stack.push(info.path.clone());
|
||||||
|
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||||
|
let original_execution = self.engine.replace_execution_kind(exec_kind);
|
||||||
|
|
||||||
// The unwrap here is safe since we only elide the AST for the top module.
|
let result = self
|
||||||
let result = self
|
.inner_execute(program, exec_state, crate::execution::BodyType::Root)
|
||||||
.inner_execute(&info.parsed.unwrap(), exec_state, crate::execution::BodyType::Root)
|
.await;
|
||||||
.await;
|
|
||||||
|
|
||||||
let new_units = exec_state.length_unit();
|
let new_units = exec_state.length_unit();
|
||||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||||
if new_units != old_units {
|
if new_units != old_units {
|
||||||
self.engine.set_units(old_units.into(), Default::default()).await?;
|
self.engine.set_units(old_units.into(), Default::default()).await?;
|
||||||
}
|
}
|
||||||
self.engine.replace_execution_kind(original_execution);
|
self.engine.replace_execution_kind(original_execution);
|
||||||
|
|
||||||
let result = result.map_err(|err| {
|
let result = result.map_err(|err| {
|
||||||
if let KclError::ImportCycle(_) = err {
|
if let KclError::ImportCycle(_) = err {
|
||||||
// It was an import cycle. Keep the original message.
|
// It was an import cycle. Keep the original message.
|
||||||
err.override_source_ranges(vec![source_range])
|
err.override_source_ranges(vec![source_range])
|
||||||
} else {
|
} else {
|
||||||
KclError::Semantic(KclErrorDetails {
|
KclError::Semantic(KclErrorDetails {
|
||||||
message: format!(
|
message: format!(
|
||||||
"Error loading imported file. Open it to view more details. {}: {}",
|
"Error loading imported file. Open it to view more details. {}: {}",
|
||||||
info.path.display(),
|
info.path.display(),
|
||||||
err.message()
|
err.message()
|
||||||
),
|
),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((result, local_state.memory, local_state.module_exports))
|
||||||
}
|
}
|
||||||
})?;
|
ModuleRepr::Foreign(geom) => {
|
||||||
|
let geom = send_import_to_engine(geom.clone(), self).await?;
|
||||||
Ok((result, local_state.memory, local_state.module_exports))
|
Ok((Some(KclValue::ImportedGeometry(geom)), ProgramMemory::new(), Vec::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
@ -2608,15 +2642,20 @@ impl ExecutorContext {
|
|||||||
let (result, _, _) = self
|
let (result, _, _) = self
|
||||||
.exec_module(module_id, exec_state, ExecutionKind::Normal, metadata.source_range)
|
.exec_module(module_id, exec_state, ExecutionKind::Normal, metadata.source_range)
|
||||||
.await?;
|
.await?;
|
||||||
result.ok_or_else(|| {
|
result.unwrap_or_else(|| {
|
||||||
KclError::Semantic(KclErrorDetails {
|
// The module didn't have a return value. Currently,
|
||||||
message: format!(
|
// the only way to have a return value is with the final
|
||||||
"Evaluating module `{}` as part of an assembly did not produce a result",
|
// statement being an expression statement.
|
||||||
identifier.name
|
//
|
||||||
),
|
// TODO: Make a warning when we support them in the
|
||||||
source_ranges: vec![metadata.source_range, meta[0].source_range],
|
// execution phase.
|
||||||
})
|
let mut new_meta = vec![metadata.to_owned()];
|
||||||
})?
|
new_meta.extend(meta);
|
||||||
|
KclValue::KclNone {
|
||||||
|
value: Default::default(),
|
||||||
|
meta: new_meta,
|
||||||
|
}
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,8 @@ impl ImportStatement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
hasher.update(slf.visibility.digestable_id());
|
hasher.update(slf.visibility.digestable_id());
|
||||||
let path = slf.path.as_bytes();
|
let path = slf.path.to_string();
|
||||||
|
let path = path.as_bytes();
|
||||||
hasher.update(path.len().to_ne_bytes());
|
hasher.update(path.len().to_ne_bytes());
|
||||||
hasher.update(path);
|
hasher.update(path);
|
||||||
});
|
});
|
||||||
|
@ -1256,6 +1256,32 @@ impl ImportSelector {
|
|||||||
ImportSelector::None { alias: Some(alias) } => alias.rename(old_name, new_name),
|
ImportSelector::None { alias: Some(alias) } => alias.rename(old_name, new_name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn exposes_imported_name(&self) -> bool {
|
||||||
|
matches!(self, ImportSelector::None { alias: None })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn imports_items(&self) -> bool {
|
||||||
|
!matches!(self, ImportSelector::None { .. })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Eq, PartialEq, Debug, Deserialize, Serialize, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum ImportPath {
|
||||||
|
Kcl { filename: String },
|
||||||
|
Foreign { path: String },
|
||||||
|
Std,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ImportPath {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
ImportPath::Kcl { filename: s } | ImportPath::Foreign { path: s } => write!(f, "{s}"),
|
||||||
|
ImportPath::Std => write!(f, "std"),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
@ -1263,7 +1289,7 @@ impl ImportSelector {
|
|||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub struct ImportStatement {
|
pub struct ImportStatement {
|
||||||
pub selector: ImportSelector,
|
pub selector: ImportSelector,
|
||||||
pub path: String,
|
pub path: ImportPath,
|
||||||
#[serde(default, skip_serializing_if = "ItemVisibility::is_default")]
|
#[serde(default, skip_serializing_if = "ItemVisibility::is_default")]
|
||||||
pub visibility: ItemVisibility,
|
pub visibility: ItemVisibility,
|
||||||
|
|
||||||
@ -1312,12 +1338,15 @@ impl ImportStatement {
|
|||||||
return Some(alias.name.clone());
|
return Some(alias.name.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut parts = self.path.split('.');
|
let mut parts = match &self.path {
|
||||||
|
ImportPath::Kcl { filename: s } | ImportPath::Foreign { path: s } => s.split('.'),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
let name = parts.next()?;
|
let name = parts.next()?;
|
||||||
let ext = parts.next()?;
|
let _ext = parts.next()?;
|
||||||
let rest = parts.next();
|
let rest = parts.next();
|
||||||
|
|
||||||
if rest.is_some() || ext != "kcl" {
|
if rest.is_some() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,10 @@ use winnow::{
|
|||||||
token::{any, one_of, take_till},
|
token::{any, one_of, take_till},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{ast::types::LabelledExpression, token::NumericSuffix};
|
use super::{
|
||||||
|
ast::types::{ImportPath, LabelledExpression},
|
||||||
|
token::NumericSuffix,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
docs::StdLibFn,
|
docs::StdLibFn,
|
||||||
errors::{CompilationError, Severity, Tag},
|
errors::{CompilationError, Severity, Tag},
|
||||||
@ -1545,33 +1548,11 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut end: usize = path.end;
|
let mut end: usize = path.end;
|
||||||
let path_string = match path.inner.value {
|
|
||||||
LiteralValue::String(s) => s,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
if path_string.is_empty() {
|
|
||||||
return Err(ErrMode::Cut(
|
|
||||||
CompilationError::fatal(
|
|
||||||
SourceRange::new(path.start, path.end, path.module_id),
|
|
||||||
"import path cannot be empty",
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if path_string
|
|
||||||
.chars()
|
|
||||||
.any(|c| !c.is_ascii_alphanumeric() && c != '_' && c != '-' && c != '.')
|
|
||||||
{
|
|
||||||
return Err(ErrMode::Cut(
|
|
||||||
CompilationError::fatal(
|
|
||||||
SourceRange::new(path.start, path.end, path.module_id),
|
|
||||||
"import path may only contain alphanumeric characters, underscore, hyphen, and period. Files in other directories are not yet supported.",
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let ImportSelector::None { alias: ref mut a } = selector {
|
if let ImportSelector::None {
|
||||||
|
alias: ref mut selector_alias,
|
||||||
|
} = selector
|
||||||
|
{
|
||||||
if let Some(alias) = opt(preceded(
|
if let Some(alias) = opt(preceded(
|
||||||
(whitespace, import_as_keyword, whitespace),
|
(whitespace, import_as_keyword, whitespace),
|
||||||
identifier.context(expected("an identifier to alias the import")),
|
identifier.context(expected("an identifier to alias the import")),
|
||||||
@ -1579,35 +1560,40 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
|
|||||||
.parse_next(i)?
|
.parse_next(i)?
|
||||||
{
|
{
|
||||||
end = alias.end;
|
end = alias.end;
|
||||||
*a = Some(alias);
|
*selector_alias = Some(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseContext::warn(CompilationError::err(
|
ParseContext::warn(CompilationError::err(
|
||||||
SourceRange::new(start, path.end, path.module_id),
|
SourceRange::new(start, path.end, path.module_id),
|
||||||
"Importing a whole module is experimental, likely to be buggy, and likely to change",
|
"Importing a whole module is experimental, likely to be buggy, and likely to change",
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
if a.is_none()
|
let path_string = match path.inner.value {
|
||||||
&& (!path_string.ends_with(".kcl")
|
LiteralValue::String(s) => s,
|
||||||
|| path_string.starts_with("_")
|
_ => unreachable!(),
|
||||||
|| path_string.contains('-')
|
};
|
||||||
|| path_string[0..path_string.len() - 4].contains('.'))
|
let path = validate_path_string(
|
||||||
{
|
path_string,
|
||||||
return Err(ErrMode::Cut(
|
selector.exposes_imported_name(),
|
||||||
CompilationError::fatal(
|
SourceRange::new(path.start, path.end, path.module_id),
|
||||||
SourceRange::new(path.start, path.end, path.module_id),
|
)?;
|
||||||
"import path is not a valid identifier and must be aliased.".to_owned(),
|
|
||||||
)
|
if matches!(path, ImportPath::Foreign { .. }) && selector.imports_items() {
|
||||||
.into(),
|
return Err(ErrMode::Cut(
|
||||||
));
|
CompilationError::fatal(
|
||||||
}
|
SourceRange::new(start, end, module_id),
|
||||||
|
"individual items can only be imported from KCL files",
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Node::boxed(
|
Ok(Node::boxed(
|
||||||
ImportStatement {
|
ImportStatement {
|
||||||
selector,
|
selector,
|
||||||
visibility,
|
visibility,
|
||||||
path: path_string,
|
path,
|
||||||
digest: None,
|
digest: None,
|
||||||
},
|
},
|
||||||
start,
|
start,
|
||||||
@ -1616,6 +1602,72 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FOREIGN_IMPORT_EXTENSIONS: [&str; 8] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "step", "stl"];
|
||||||
|
|
||||||
|
/// Validates the path string in an `import` statement.
|
||||||
|
///
|
||||||
|
/// `var_name` is `true` if the path will be used as a variable name.
|
||||||
|
fn validate_path_string(path_string: String, var_name: bool, path_range: SourceRange) -> PResult<ImportPath> {
|
||||||
|
if path_string.is_empty() {
|
||||||
|
return Err(ErrMode::Cut(
|
||||||
|
CompilationError::fatal(path_range, "import path cannot be empty").into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if var_name
|
||||||
|
&& (path_string.starts_with("_")
|
||||||
|
|| path_string.contains('-')
|
||||||
|
|| path_string.chars().filter(|c| *c == '.').count() > 1)
|
||||||
|
{
|
||||||
|
return Err(ErrMode::Cut(
|
||||||
|
CompilationError::fatal(path_range, "import path is not a valid identifier and must be aliased.").into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = if path_string.ends_with(".kcl") {
|
||||||
|
if path_string
|
||||||
|
.chars()
|
||||||
|
.any(|c| !c.is_ascii_alphanumeric() && c != '_' && c != '-' && c != '.')
|
||||||
|
{
|
||||||
|
return Err(ErrMode::Cut(
|
||||||
|
CompilationError::fatal(
|
||||||
|
path_range,
|
||||||
|
"import path may only contain alphanumeric characters, underscore, hyphen, and period. KCL files in other directories are not yet supported.",
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImportPath::Kcl { filename: path_string }
|
||||||
|
} else if path_string.starts_with("std") {
|
||||||
|
ParseContext::warn(CompilationError::err(
|
||||||
|
path_range,
|
||||||
|
"explicit imports from the standard library are experimental, likely to be buggy, and likely to change.",
|
||||||
|
));
|
||||||
|
|
||||||
|
ImportPath::Std
|
||||||
|
} else if path_string.contains('.') {
|
||||||
|
let extn = &path_string[path_string.rfind('.').unwrap() + 1..];
|
||||||
|
if !FOREIGN_IMPORT_EXTENSIONS.contains(&extn) {
|
||||||
|
ParseContext::warn(CompilationError::err(
|
||||||
|
path_range,
|
||||||
|
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", FOREIGN_IMPORT_EXTENSIONS.join(", ")),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
ImportPath::Foreign { path: path_string }
|
||||||
|
} else {
|
||||||
|
return Err(ErrMode::Cut(
|
||||||
|
CompilationError::fatal(
|
||||||
|
path_range,
|
||||||
|
format!("unsupported import path format. KCL files can be imported from the current project, CAD files with the following formats are supported: {}", FOREIGN_IMPORT_EXTENSIONS.join(", ")),
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(path)
|
||||||
|
}
|
||||||
|
|
||||||
fn import_item(i: &mut TokenSlice) -> PResult<Node<ImportItem>> {
|
fn import_item(i: &mut TokenSlice) -> PResult<Node<ImportItem>> {
|
||||||
let name = nameable_identifier
|
let name = nameable_identifier
|
||||||
.context(expected("an identifier to import"))
|
.context(expected("an identifier to import"))
|
||||||
@ -3611,7 +3663,11 @@ mySk1 = startSketchAt([0, 0])"#;
|
|||||||
fn assert_err(p: &str, msg: &str, src_expected: [usize; 2]) {
|
fn assert_err(p: &str, msg: &str, src_expected: [usize; 2]) {
|
||||||
let result = crate::parsing::top_level_parse(p);
|
let result = crate::parsing::top_level_parse(p);
|
||||||
let err = result.unwrap_errs().next().unwrap();
|
let err = result.unwrap_errs().next().unwrap();
|
||||||
assert_eq!(err.message, msg);
|
assert!(
|
||||||
|
err.message.starts_with(msg),
|
||||||
|
"Found `{}`, expected `{msg}`",
|
||||||
|
err.message
|
||||||
|
);
|
||||||
let src_actual = [err.source_range.start(), err.source_range.end()];
|
let src_actual = [err.source_range.start(), err.source_range.end()];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
src_expected,
|
src_expected,
|
||||||
@ -3977,7 +4033,7 @@ e
|
|||||||
fn bad_imports() {
|
fn bad_imports() {
|
||||||
assert_err(
|
assert_err(
|
||||||
r#"import cube from "../cube.kcl""#,
|
r#"import cube from "../cube.kcl""#,
|
||||||
"import path may only contain alphanumeric characters, underscore, hyphen, and period. Files in other directories are not yet supported.",
|
"import path may only contain alphanumeric characters, underscore, hyphen, and period. KCL files in other directories are not yet supported.",
|
||||||
[17, 30],
|
[17, 30],
|
||||||
);
|
);
|
||||||
assert_err(
|
assert_err(
|
||||||
@ -3985,17 +4041,21 @@ e
|
|||||||
"as is not the 'from' keyword",
|
"as is not the 'from' keyword",
|
||||||
[9, 11],
|
[9, 11],
|
||||||
);
|
);
|
||||||
assert_err(r#"import a from "dsfs" as b"#, "Unexpected token: as", [21, 23]);
|
assert_err(
|
||||||
assert_err(r#"import * from "dsfs" as b"#, "Unexpected token: as", [21, 23]);
|
r#"import a from "dsfs" as b"#,
|
||||||
|
"unsupported import path format",
|
||||||
|
[14, 20],
|
||||||
|
);
|
||||||
|
assert_err(
|
||||||
|
r#"import * from "dsfs" as b"#,
|
||||||
|
"unsupported import path format",
|
||||||
|
[14, 20],
|
||||||
|
);
|
||||||
assert_err(r#"import a from b"#, "invalid string literal", [14, 15]);
|
assert_err(r#"import a from b"#, "invalid string literal", [14, 15]);
|
||||||
assert_err(r#"import * "dsfs""#, "\"dsfs\" is not the 'from' keyword", [9, 15]);
|
assert_err(r#"import * "dsfs""#, "\"dsfs\" is not the 'from' keyword", [9, 15]);
|
||||||
assert_err(r#"import from "dsfs""#, "\"dsfs\" is not the 'from' keyword", [12, 18]);
|
assert_err(r#"import from "dsfs""#, "\"dsfs\" is not the 'from' keyword", [12, 18]);
|
||||||
assert_err(r#"import "dsfs.kcl" as *"#, "Unexpected token: as", [18, 20]);
|
assert_err(r#"import "dsfs.kcl" as *"#, "Unexpected token: as", [18, 20]);
|
||||||
assert_err(
|
assert_err(r#"import "dsfs""#, "unsupported import path format", [7, 13]);
|
||||||
r#"import "dsfs""#,
|
|
||||||
"import path is not a valid identifier and must be aliased.",
|
|
||||||
[7, 13],
|
|
||||||
);
|
|
||||||
assert_err(
|
assert_err(
|
||||||
r#"import "foo.bar.kcl""#,
|
r#"import "foo.bar.kcl""#,
|
||||||
"import path is not a valid identifier and must be aliased.",
|
"import path is not a valid identifier and must be aliased.",
|
||||||
@ -4017,7 +4077,19 @@ e
|
|||||||
fn warn_import() {
|
fn warn_import() {
|
||||||
let some_program_string = r#"import "foo.kcl""#;
|
let some_program_string = r#"import "foo.kcl""#;
|
||||||
let (_, errs) = assert_no_err(some_program_string);
|
let (_, errs) = assert_no_err(some_program_string);
|
||||||
assert_eq!(errs.len(), 1);
|
assert_eq!(errs.len(), 1, "{errs:#?}");
|
||||||
|
|
||||||
|
let some_program_string = r#"import "foo.obj""#;
|
||||||
|
let (_, errs) = assert_no_err(some_program_string);
|
||||||
|
assert_eq!(errs.len(), 1, "{errs:#?}");
|
||||||
|
|
||||||
|
let some_program_string = r#"import "foo.sldprt""#;
|
||||||
|
let (_, errs) = assert_no_err(some_program_string);
|
||||||
|
assert_eq!(errs.len(), 1, "{errs:#?}");
|
||||||
|
|
||||||
|
let some_program_string = r#"import "foo.bad""#;
|
||||||
|
let (_, errs) = assert_no_err(some_program_string);
|
||||||
|
assert_eq!(errs.len(), 2, "{errs:#?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -872,6 +872,27 @@ mod import_side_effect {
|
|||||||
super::execute(TEST_NAME, false).await
|
super::execute(TEST_NAME, false).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mod import_foreign {
|
||||||
|
const TEST_NAME: &str = "import_foreign";
|
||||||
|
|
||||||
|
/// Test parsing KCL.
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
super::parse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||||
|
#[test]
|
||||||
|
fn unparse() {
|
||||||
|
super::unparse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that KCL is executed correctly.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn kcl_test_execute() {
|
||||||
|
super::execute(TEST_NAME, false).await
|
||||||
|
}
|
||||||
|
}
|
||||||
mod array_elem_push_fail {
|
mod array_elem_push_fail {
|
||||||
const TEST_NAME: &str = "array_elem_push_fail";
|
const TEST_NAME: &str = "array_elem_push_fail";
|
||||||
|
|
||||||
|
@ -140,7 +140,7 @@ async fn inner_helix(data: HelixData, exec_state: &mut ExecState, args: Args) ->
|
|||||||
args.batch_modeling_cmd(
|
args.batch_modeling_cmd(
|
||||||
id,
|
id,
|
||||||
ModelingCmd::from(mcmd::EntityMakeHelixFromParams {
|
ModelingCmd::from(mcmd::EntityMakeHelixFromParams {
|
||||||
radius: data.radius,
|
radius: LengthUnit(data.radius),
|
||||||
is_clockwise: !data.ccw,
|
is_clockwise: !data.ccw,
|
||||||
length: LengthUnit(length),
|
length: LengthUnit(length),
|
||||||
revolutions: data.revolutions,
|
revolutions: data.revolutions,
|
||||||
@ -157,7 +157,7 @@ async fn inner_helix(data: HelixData, exec_state: &mut ExecState, args: Args) ->
|
|||||||
args.batch_modeling_cmd(
|
args.batch_modeling_cmd(
|
||||||
id,
|
id,
|
||||||
ModelingCmd::from(mcmd::EntityMakeHelixFromEdge {
|
ModelingCmd::from(mcmd::EntityMakeHelixFromEdge {
|
||||||
radius: data.radius,
|
radius: LengthUnit(data.radius),
|
||||||
is_clockwise: !data.ccw,
|
is_clockwise: !data.ccw,
|
||||||
length: data.length.map(LengthUnit),
|
length: data.length.map(LengthUnit),
|
||||||
revolutions: data.revolutions,
|
revolutions: data.revolutions,
|
||||||
|
@ -1,44 +1,16 @@
|
|||||||
//! Standard library functions involved in importing files.
|
//! Standard library functions involved in importing files.
|
||||||
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use derive_docs::stdlib;
|
use derive_docs::stdlib;
|
||||||
use kcmc::{
|
use kcmc::{coord::System, format::InputFormat, units::UnitLength};
|
||||||
coord::{Axis, AxisDirectionPair, Direction, System},
|
|
||||||
each_cmd as mcmd,
|
|
||||||
format::InputFormat,
|
|
||||||
ok_response::OkModelingCmdResponse,
|
|
||||||
shared::FileImportFormat,
|
|
||||||
units::UnitLength,
|
|
||||||
websocket::OkWebSocketResponseData,
|
|
||||||
ImportFile, ModelingCmd,
|
|
||||||
};
|
|
||||||
use kittycad_modeling_cmds as kcmc;
|
use kittycad_modeling_cmds as kcmc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{ExecState, ImportedGeometry, KclValue},
|
execution::{import_foreign, send_import_to_engine, ExecState, ImportedGeometry, KclValue, ZOO_COORD_SYSTEM},
|
||||||
fs::FileSystem,
|
|
||||||
std::Args,
|
std::Args,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Zoo co-ordinate system.
|
|
||||||
//
|
|
||||||
// * Forward: -Y
|
|
||||||
// * Up: +Z
|
|
||||||
// * Handedness: Right
|
|
||||||
const ZOO_COORD_SYSTEM: System = System {
|
|
||||||
forward: AxisDirectionPair {
|
|
||||||
axis: Axis::Y,
|
|
||||||
direction: Direction::Negative,
|
|
||||||
},
|
|
||||||
up: AxisDirectionPair {
|
|
||||||
axis: Axis::Z,
|
|
||||||
direction: Direction::Positive,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Import format specifier
|
/// Import format specifier
|
||||||
#[derive(serde :: Serialize, serde :: Deserialize, PartialEq, Debug, Clone, schemars :: JsonSchema)]
|
#[derive(serde :: Serialize, serde :: Deserialize, PartialEq, Debug, Clone, schemars :: JsonSchema)]
|
||||||
#[cfg_attr(feature = "tabled", derive(tabled::Tabled))]
|
#[cfg_attr(feature = "tabled", derive(tabled::Tabled))]
|
||||||
@ -135,6 +107,8 @@ pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
|
|
||||||
/// Import a CAD file.
|
/// Import a CAD file.
|
||||||
///
|
///
|
||||||
|
/// **DEPRECATED** Prefer to use import statements.
|
||||||
|
///
|
||||||
/// For formats lacking unit data (such as STL, OBJ, or PLY files), the default
|
/// For formats lacking unit data (such as STL, OBJ, or PLY files), the default
|
||||||
/// unit of measurement is millimeters. Alternatively you may specify the unit
|
/// unit of measurement is millimeters. Alternatively you may specify the unit
|
||||||
/// by passing your desired measurement unit in the options parameter. When
|
/// by passing your desired measurement unit in the options parameter. When
|
||||||
@ -178,6 +152,7 @@ pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "import",
|
name = "import",
|
||||||
feature_tree_operation = true,
|
feature_tree_operation = true,
|
||||||
|
deprecated = true,
|
||||||
tags = [],
|
tags = [],
|
||||||
}]
|
}]
|
||||||
async fn inner_import(
|
async fn inner_import(
|
||||||
@ -193,232 +168,17 @@ async fn inner_import(
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the file exists.
|
let format = options.map(InputFormat::from);
|
||||||
if !args.ctx.fs.exists(&file_path, args.source_range).await? {
|
send_import_to_engine(
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
import_foreign(
|
||||||
message: format!("File `{}` does not exist.", file_path),
|
std::path::Path::new(&file_path),
|
||||||
source_ranges: vec![args.source_range],
|
format,
|
||||||
}));
|
exec_state,
|
||||||
}
|
&args.ctx,
|
||||||
|
args.source_range,
|
||||||
let ext_format = get_import_format_from_extension(file_path.split('.').last().ok_or_else(|| {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("No file extension found for `{}`", file_path),
|
|
||||||
source_ranges: vec![args.source_range],
|
|
||||||
})
|
|
||||||
})?)
|
|
||||||
.map_err(|e| {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
message: e.to_string(),
|
|
||||||
source_ranges: vec![args.source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Get the format type from the extension of the file.
|
|
||||||
let format = if let Some(options) = options {
|
|
||||||
// Validate the given format with the extension format.
|
|
||||||
let format: InputFormat = options.into();
|
|
||||||
validate_extension_format(ext_format, format.clone()).map_err(|e| {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
message: e.to_string(),
|
|
||||||
source_ranges: vec![args.source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
format
|
|
||||||
} else {
|
|
||||||
ext_format
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the file contents for each file path.
|
|
||||||
let file_contents = args.ctx.fs.read(&file_path, args.source_range).await.map_err(|e| {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
message: e.to_string(),
|
|
||||||
source_ranges: vec![args.source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// We want the file_path to be without the parent.
|
|
||||||
let file_name = std::path::Path::new(&file_path)
|
|
||||||
.file_name()
|
|
||||||
.map(|p| p.to_string_lossy().to_string())
|
|
||||||
.ok_or_else(|| {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("Could not get the file name from the path `{}`", file_path),
|
|
||||||
source_ranges: vec![args.source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
let mut import_files = vec![kcmc::ImportFile {
|
|
||||||
path: file_name.to_string(),
|
|
||||||
data: file_contents.clone(),
|
|
||||||
}];
|
|
||||||
|
|
||||||
// In the case of a gltf importing a bin file we need to handle that! and figure out where the
|
|
||||||
// file is relative to our current file.
|
|
||||||
if let InputFormat::Gltf(..) = format {
|
|
||||||
// Check if the file is a binary gltf file, in that case we don't need to import the bin
|
|
||||||
// file.
|
|
||||||
if !file_contents.starts_with(b"glTF") {
|
|
||||||
let json = gltf_json::Root::from_slice(&file_contents).map_err(|e| {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
message: e.to_string(),
|
|
||||||
source_ranges: vec![args.source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Read the gltf file and check if there is a bin file.
|
|
||||||
for buffer in json.buffers.iter() {
|
|
||||||
if let Some(uri) = &buffer.uri {
|
|
||||||
if !uri.starts_with("data:") {
|
|
||||||
// We want this path relative to the file_path given.
|
|
||||||
let bin_path = std::path::Path::new(&file_path)
|
|
||||||
.parent()
|
|
||||||
.map(|p| p.join(uri))
|
|
||||||
.map(|p| p.to_string_lossy().to_string())
|
|
||||||
.ok_or_else(|| {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!("Could not get the parent path of the file `{}`", file_path),
|
|
||||||
source_ranges: vec![args.source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let bin_contents = args.ctx.fs.read(&bin_path, args.source_range).await.map_err(|e| {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
message: e.to_string(),
|
|
||||||
source_ranges: vec![args.source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
import_files.push(ImportFile {
|
|
||||||
path: uri.to_string(),
|
|
||||||
data: bin_contents,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if args.ctx.is_mock() {
|
|
||||||
return Ok(ImportedGeometry {
|
|
||||||
id: exec_state.next_uuid(),
|
|
||||||
value: import_files.iter().map(|f| f.path.to_string()).collect(),
|
|
||||||
meta: vec![args.source_range.into()],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = exec_state.next_uuid();
|
|
||||||
let resp = args
|
|
||||||
.send_modeling_cmd(
|
|
||||||
id,
|
|
||||||
ModelingCmd::from(mcmd::ImportFiles {
|
|
||||||
files: import_files.clone(),
|
|
||||||
format,
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?,
|
||||||
|
&args.ctx,
|
||||||
let OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: OkModelingCmdResponse::ImportFiles(imported_files),
|
|
||||||
} = &resp
|
|
||||||
else {
|
|
||||||
return Err(KclError::Engine(KclErrorDetails {
|
|
||||||
message: format!("ImportFiles response was not as expected: {:?}", resp),
|
|
||||||
source_ranges: vec![args.source_range],
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(ImportedGeometry {
|
|
||||||
id: imported_files.object_id,
|
|
||||||
value: import_files.iter().map(|f| f.path.to_string()).collect(),
|
|
||||||
meta: vec![args.source_range.into()],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the source format from the extension.
|
|
||||||
fn get_import_format_from_extension(ext: &str) -> Result<InputFormat> {
|
|
||||||
let format = match FileImportFormat::from_str(ext) {
|
|
||||||
Ok(format) => format,
|
|
||||||
Err(_) => {
|
|
||||||
if ext == "stp" {
|
|
||||||
FileImportFormat::Step
|
|
||||||
} else if ext == "glb" {
|
|
||||||
FileImportFormat::Gltf
|
|
||||||
} else {
|
|
||||||
anyhow::bail!("unknown source format for file extension: {}. Try setting the `--src-format` flag explicitly or use a valid format.", ext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Make the default units millimeters.
|
|
||||||
let ul = UnitLength::Millimeters;
|
|
||||||
|
|
||||||
// Zoo co-ordinate system.
|
|
||||||
//
|
|
||||||
// * Forward: -Y
|
|
||||||
// * Up: +Z
|
|
||||||
// * Handedness: Right
|
|
||||||
match format {
|
|
||||||
FileImportFormat::Step => Ok(InputFormat::Step(kcmc::format::step::import::Options {
|
|
||||||
split_closed_faces: false,
|
|
||||||
})),
|
|
||||||
FileImportFormat::Stl => Ok(InputFormat::Stl(kcmc::format::stl::import::Options {
|
|
||||||
coords: ZOO_COORD_SYSTEM,
|
|
||||||
units: ul,
|
|
||||||
})),
|
|
||||||
FileImportFormat::Obj => Ok(InputFormat::Obj(kcmc::format::obj::import::Options {
|
|
||||||
coords: ZOO_COORD_SYSTEM,
|
|
||||||
units: ul,
|
|
||||||
})),
|
|
||||||
FileImportFormat::Gltf => Ok(InputFormat::Gltf(kcmc::format::gltf::import::Options {})),
|
|
||||||
FileImportFormat::Ply => Ok(InputFormat::Ply(kcmc::format::ply::import::Options {
|
|
||||||
coords: ZOO_COORD_SYSTEM,
|
|
||||||
units: ul,
|
|
||||||
})),
|
|
||||||
FileImportFormat::Fbx => Ok(InputFormat::Fbx(kcmc::format::fbx::import::Options {})),
|
|
||||||
FileImportFormat::Sldprt => Ok(InputFormat::Sldprt(kcmc::format::sldprt::import::Options {
|
|
||||||
split_closed_faces: false,
|
|
||||||
})),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_extension_format(ext: InputFormat, given: InputFormat) -> Result<()> {
|
|
||||||
if let InputFormat::Stl(_) = ext {
|
|
||||||
if let InputFormat::Stl(_) = given {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let InputFormat::Obj(_) = ext {
|
|
||||||
if let InputFormat::Obj(_) = given {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let InputFormat::Ply(_) = ext {
|
|
||||||
if let InputFormat::Ply(_) = given {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ext == given {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
anyhow::bail!(
|
|
||||||
"The given format does not match the file extension. Expected: `{}`, Given: `{}`",
|
|
||||||
get_name_of_format(ext),
|
|
||||||
get_name_of_format(given)
|
|
||||||
)
|
)
|
||||||
}
|
.await
|
||||||
|
|
||||||
fn get_name_of_format(type_: InputFormat) -> &'static str {
|
|
||||||
match type_ {
|
|
||||||
InputFormat::Fbx(_) => "fbx",
|
|
||||||
InputFormat::Gltf(_) => "gltf",
|
|
||||||
InputFormat::Obj(_) => "obj",
|
|
||||||
InputFormat::Ply(_) => "ply",
|
|
||||||
InputFormat::Sldprt(_) => "sldprt",
|
|
||||||
InputFormat::Step(_) => "step",
|
|
||||||
InputFormat::Stl(_) => "stl",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,10 @@ description: Result of parsing import_constant.kcl
|
|||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
"end": 39,
|
"end": 39,
|
||||||
"path": "export_constant.kcl",
|
"path": {
|
||||||
|
"type": "Kcl",
|
||||||
|
"filename": "export_constant.kcl"
|
||||||
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
"type": "List",
|
"type": "List",
|
||||||
"items": [
|
"items": [
|
||||||
|
@ -7,7 +7,10 @@ description: Result of parsing import_cycle1.kcl
|
|||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
"end": 35,
|
"end": 35,
|
||||||
"path": "import_cycle2.kcl",
|
"path": {
|
||||||
|
"type": "Kcl",
|
||||||
|
"filename": "import_cycle2.kcl"
|
||||||
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
"type": "List",
|
"type": "List",
|
||||||
"items": [
|
"items": [
|
||||||
|
@ -7,7 +7,10 @@ description: Result of parsing import_export.kcl
|
|||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
"end": 32,
|
"end": 32,
|
||||||
"path": "export_1.kcl",
|
"path": {
|
||||||
|
"type": "Kcl",
|
||||||
|
"filename": "export_1.kcl"
|
||||||
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
"type": "List",
|
"type": "List",
|
||||||
"items": [
|
"items": [
|
||||||
|
3281
src/wasm-lib/kcl/tests/import_foreign/artifact_commands.snap
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Artifact graph flowchart import_glob.kcl
|
||||||
|
extension: md
|
||||||
|
snapshot_kind: binary
|
||||||
|
---
|
@ -0,0 +1,3 @@
|
|||||||
|
```mermaid
|
||||||
|
flowchart LR
|
||||||
|
```
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Artifact graph mind map import_glob.kcl
|
||||||
|
extension: md
|
||||||
|
snapshot_kind: binary
|
||||||
|
---
|
@ -0,0 +1,4 @@
|
|||||||
|
```mermaid
|
||||||
|
mindmap
|
||||||
|
root
|
||||||
|
```
|
71
src/wasm-lib/kcl/tests/import_foreign/ast.snap
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Result of parsing import_foreign.kcl
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"Ok": {
|
||||||
|
"body": [
|
||||||
|
{
|
||||||
|
"end": 36,
|
||||||
|
"path": {
|
||||||
|
"type": "Foreign",
|
||||||
|
"path": "../inputs/cube.gltf"
|
||||||
|
},
|
||||||
|
"selector": {
|
||||||
|
"type": "None",
|
||||||
|
"alias": {
|
||||||
|
"end": 36,
|
||||||
|
"name": "cube",
|
||||||
|
"start": 32,
|
||||||
|
"type": "Identifier"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"start": 0,
|
||||||
|
"type": "ImportStatement",
|
||||||
|
"type": "ImportStatement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"declaration": {
|
||||||
|
"end": 50,
|
||||||
|
"id": {
|
||||||
|
"end": 43,
|
||||||
|
"name": "model",
|
||||||
|
"start": 38,
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"init": {
|
||||||
|
"end": 50,
|
||||||
|
"name": "cube",
|
||||||
|
"start": 46,
|
||||||
|
"type": "Identifier",
|
||||||
|
"type": "Identifier"
|
||||||
|
},
|
||||||
|
"start": 38,
|
||||||
|
"type": "VariableDeclarator"
|
||||||
|
},
|
||||||
|
"end": 50,
|
||||||
|
"kind": "const",
|
||||||
|
"start": 38,
|
||||||
|
"type": "VariableDeclaration",
|
||||||
|
"type": "VariableDeclaration"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"end": 51,
|
||||||
|
"nonCodeMeta": {
|
||||||
|
"nonCodeNodes": {
|
||||||
|
"0": [
|
||||||
|
{
|
||||||
|
"end": 38,
|
||||||
|
"start": 36,
|
||||||
|
"type": "NonCodeNode",
|
||||||
|
"value": {
|
||||||
|
"type": "newLine"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"startNodes": []
|
||||||
|
},
|
||||||
|
"start": 0
|
||||||
|
}
|
||||||
|
}
|
3
src/wasm-lib/kcl/tests/import_foreign/input.kcl
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import "../inputs/cube.gltf" as cube
|
||||||
|
|
||||||
|
model = cube
|
6
src/wasm-lib/kcl/tests/import_foreign/ops.snap
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Operations executed import_glob.kcl
|
||||||
|
snapshot_kind: text
|
||||||
|
---
|
||||||
|
[]
|
64
src/wasm-lib/kcl/tests/import_foreign/program_memory.snap
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
source: kcl/src/simulation_tests.rs
|
||||||
|
description: Program memory after executing import_foreign.kcl
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"environments": [
|
||||||
|
{
|
||||||
|
"bindings": {
|
||||||
|
"HALF_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 180.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 90.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"THREE_QUARTER_TURN": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 270.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"ZERO": {
|
||||||
|
"type": "Number",
|
||||||
|
"value": 0.0,
|
||||||
|
"__meta": []
|
||||||
|
},
|
||||||
|
"cube": {
|
||||||
|
"type": "Module",
|
||||||
|
"value": 1,
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
0,
|
||||||
|
36,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"type": "ImportedGeometry",
|
||||||
|
"id": "[uuid]",
|
||||||
|
"value": [
|
||||||
|
"cube.gltf"
|
||||||
|
],
|
||||||
|
"__meta": [
|
||||||
|
{
|
||||||
|
"sourceRange": [
|
||||||
|
0,
|
||||||
|
36,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parent": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"currentEnv": 0,
|
||||||
|
"return": null
|
||||||
|
}
|
@ -7,7 +7,10 @@ description: Result of parsing import_glob.kcl
|
|||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
"end": 35,
|
"end": 35,
|
||||||
"path": "export_constant.kcl",
|
"path": {
|
||||||
|
"type": "Kcl",
|
||||||
|
"filename": "export_constant.kcl"
|
||||||
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
"end": 8,
|
"end": 8,
|
||||||
"start": 7,
|
"start": 7,
|
||||||
|
@ -7,7 +7,10 @@ description: Result of parsing import_side_effect.kcl
|
|||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
"end": 40,
|
"end": 40,
|
||||||
"path": "export_side_effect.kcl",
|
"path": {
|
||||||
|
"type": "Kcl",
|
||||||
|
"filename": "export_side_effect.kcl"
|
||||||
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
"type": "List",
|
"type": "List",
|
||||||
"items": [
|
"items": [
|
||||||
|
@ -7,7 +7,10 @@ description: Result of parsing import_whole.kcl
|
|||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
"end": 66,
|
"end": 66,
|
||||||
"path": "exported_mod.kcl",
|
"path": {
|
||||||
|
"type": "Kcl",
|
||||||
|
"filename": "exported_mod.kcl"
|
||||||
|
},
|
||||||
"selector": {
|
"selector": {
|
||||||
"type": "None",
|
"type": "None",
|
||||||
"alias": {
|
"alias": {
|
||||||
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 103 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |