Import files (#1393)
* initial shit Signed-off-by: Jess Frazelle <github@jessfraz.com> * make file system work locally or with tauri Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixxes Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates; Signed-off-by: Jess Frazelle <github@jessfraz.com> * add tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * add more tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * better errrors Signed-off-by: Jess Frazelle <github@jessfraz.com> * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * better docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * better docs Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix Signed-off-by: Jess Frazelle <github@jessfraz.com> * better errors Signed-off-by: Jess Frazelle <github@jessfraz.com> * make no assign work Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * closer Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * more additions to passing around fs Signed-off-by: Jess Frazelle <github@jessfraz.com> * make work Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -1,3 +1,3 @@
|
||||
[codespell]
|
||||
ignore-words-list: crate,everytime,inout
|
||||
ignore-words-list: crate,everytime,inout,co-ordinate
|
||||
skip: **/target,node_modules,build,**/Cargo.lock
|
||||
|
@ -15212,6 +15212,656 @@
|
||||
"unpublished": false,
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "import",
|
||||
"summary": "Import a CAD file.",
|
||||
"description": "For formats lacking unit data (STL, OBJ, PLY), the default import unit is millimeters. Otherwise you can specify the unit by passing in the options parameter. If you import a gltf file, we will try to find the bin file and import it as well.",
|
||||
"tags": [],
|
||||
"args": [
|
||||
{
|
||||
"name": "file_path",
|
||||
"type": "String",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "options",
|
||||
"type": "ImportFormat",
|
||||
"schema": {
|
||||
"description": "Import format specifier",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Autodesk Filmbox (FBX) format",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"fbx"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Binary glTF 2.0. We refer to this as glTF since that is how our customers refer to it, but this can also import binary glTF (glb).",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"gltf"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Wavefront OBJ format.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"units"
|
||||
],
|
||||
"properties": {
|
||||
"coords": {
|
||||
"description": "Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"forward",
|
||||
"up"
|
||||
],
|
||||
"properties": {
|
||||
"forward": {
|
||||
"description": "Axis the front face of a model looks along.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"axis",
|
||||
"direction"
|
||||
],
|
||||
"properties": {
|
||||
"axis": {
|
||||
"description": "Axis specifier.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "'Y' axis.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"y"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "'Z' axis.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"z"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"direction": {
|
||||
"description": "Specifies which direction the axis is pointing.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Increasing numbers.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"positive"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Decreasing numbers.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"negative"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"up": {
|
||||
"description": "Axis pointing up and away from a model.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"axis",
|
||||
"direction"
|
||||
],
|
||||
"properties": {
|
||||
"axis": {
|
||||
"description": "Axis specifier.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "'Y' axis.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"y"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "'Z' axis.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"z"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"direction": {
|
||||
"description": "Specifies which direction the axis is pointing.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Increasing numbers.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"positive"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Decreasing numbers.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"negative"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"obj"
|
||||
]
|
||||
},
|
||||
"units": {
|
||||
"description": "The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Centimeters <https://en.wikipedia.org/wiki/Centimeter>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"cm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Feet <https://en.wikipedia.org/wiki/Foot_(unit)>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ft"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Inches <https://en.wikipedia.org/wiki/Inch>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"in"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Meters <https://en.wikipedia.org/wiki/Meter>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"m"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Millimeters <https://en.wikipedia.org/wiki/Millimeter>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"mm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Yards <https://en.wikipedia.org/wiki/Yard>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"yd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "The PLY Polygon File Format.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"units"
|
||||
],
|
||||
"properties": {
|
||||
"coords": {
|
||||
"description": "Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"forward",
|
||||
"up"
|
||||
],
|
||||
"properties": {
|
||||
"forward": {
|
||||
"description": "Axis the front face of a model looks along.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"axis",
|
||||
"direction"
|
||||
],
|
||||
"properties": {
|
||||
"axis": {
|
||||
"description": "Axis specifier.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "'Y' axis.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"y"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "'Z' axis.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"z"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"direction": {
|
||||
"description": "Specifies which direction the axis is pointing.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Increasing numbers.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"positive"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Decreasing numbers.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"negative"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"up": {
|
||||
"description": "Axis pointing up and away from a model.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"axis",
|
||||
"direction"
|
||||
],
|
||||
"properties": {
|
||||
"axis": {
|
||||
"description": "Axis specifier.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "'Y' axis.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"y"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "'Z' axis.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"z"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"direction": {
|
||||
"description": "Specifies which direction the axis is pointing.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Increasing numbers.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"positive"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Decreasing numbers.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"negative"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ply"
|
||||
]
|
||||
},
|
||||
"units": {
|
||||
"description": "The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Centimeters <https://en.wikipedia.org/wiki/Centimeter>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"cm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Feet <https://en.wikipedia.org/wiki/Foot_(unit)>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ft"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Inches <https://en.wikipedia.org/wiki/Inch>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"in"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Meters <https://en.wikipedia.org/wiki/Meter>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"m"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Millimeters <https://en.wikipedia.org/wiki/Millimeter>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"mm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Yards <https://en.wikipedia.org/wiki/Yard>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"yd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "SolidWorks part (SLDPRT) format.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"sldprt"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ISO 10303-21 (STEP) format.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"step"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "ST**ereo**L**ithography format.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type",
|
||||
"units"
|
||||
],
|
||||
"properties": {
|
||||
"coords": {
|
||||
"description": "Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"forward",
|
||||
"up"
|
||||
],
|
||||
"properties": {
|
||||
"forward": {
|
||||
"description": "Axis the front face of a model looks along.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"axis",
|
||||
"direction"
|
||||
],
|
||||
"properties": {
|
||||
"axis": {
|
||||
"description": "Axis specifier.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "'Y' axis.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"y"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "'Z' axis.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"z"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"direction": {
|
||||
"description": "Specifies which direction the axis is pointing.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Increasing numbers.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"positive"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Decreasing numbers.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"negative"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"up": {
|
||||
"description": "Axis pointing up and away from a model.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"axis",
|
||||
"direction"
|
||||
],
|
||||
"properties": {
|
||||
"axis": {
|
||||
"description": "Axis specifier.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "'Y' axis.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"y"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "'Z' axis.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"z"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"direction": {
|
||||
"description": "Specifies which direction the axis is pointing.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Increasing numbers.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"positive"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Decreasing numbers.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"negative"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"nullable": true
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"stl"
|
||||
]
|
||||
},
|
||||
"units": {
|
||||
"description": "The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters.",
|
||||
"oneOf": [
|
||||
{
|
||||
"description": "Centimeters <https://en.wikipedia.org/wiki/Centimeter>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"cm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Feet <https://en.wikipedia.org/wiki/Foot_(unit)>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ft"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Inches <https://en.wikipedia.org/wiki/Inch>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"in"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Meters <https://en.wikipedia.org/wiki/Meter>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"m"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Millimeters <https://en.wikipedia.org/wiki/Millimeter>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"mm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Yards <https://en.wikipedia.org/wiki/Yard>",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"yd"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"returnValue": {
|
||||
"name": "",
|
||||
"type": "ImportedGeometry",
|
||||
"schema": {
|
||||
"description": "Data for an imported geometry.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"__meta",
|
||||
"id",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"description": "Metadata.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"sourceRange"
|
||||
],
|
||||
"properties": {
|
||||
"sourceRange": {
|
||||
"description": "The source range.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"description": "The ID of the imported geometry.",
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"value": {
|
||||
"description": "The original file paths.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"unpublished": false,
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "lastSegX",
|
||||
"summary": "Returns the last segment of x.",
|
||||
|
646
docs/kcl/std.md
646
docs/kcl/std.md
File diff suppressed because it is too large
Load Diff
@ -44,6 +44,7 @@ import FileMachineProvider from 'components/FileMachineProvider'
|
||||
import { sep } from '@tauri-apps/api/path'
|
||||
import { paths } from 'lib/paths'
|
||||
import { IndexLoaderData, HomeLoaderData } from 'lib/types'
|
||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||
|
||||
if (VITE_KC_SENTRY_DSN && !TEST) {
|
||||
Sentry.init({
|
||||
@ -174,6 +175,10 @@ const router = createBrowserRouter(
|
||||
const children = await readDir(projectPath, { recursive: true })
|
||||
kclManager.setCodeAndExecute(code, false)
|
||||
|
||||
// Set the file system manager to the project path
|
||||
// So that WASM gets an updated path for operations
|
||||
fileSystemManager.dir = projectPath
|
||||
|
||||
return {
|
||||
code,
|
||||
project: {
|
||||
|
62
src/lang/std/fileSystemManager.ts
Normal file
62
src/lang/std/fileSystemManager.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import {
|
||||
readBinaryFile,
|
||||
exists as tauriExists,
|
||||
BaseDirectory,
|
||||
} from '@tauri-apps/api/fs'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { join } from '@tauri-apps/api/path'
|
||||
|
||||
/// FileSystemManager is a class that provides a way to read files from the local file system.
|
||||
/// It assumes that you are in a project since it is solely used by the std lib
|
||||
/// when executing code.
|
||||
class FileSystemManager {
|
||||
private _dir: string | null = null
|
||||
|
||||
get dir() {
|
||||
if (this._dir === null) {
|
||||
throw new Error('current project dir is not set')
|
||||
}
|
||||
|
||||
return this._dir
|
||||
}
|
||||
|
||||
set dir(dir: string) {
|
||||
this._dir = dir
|
||||
}
|
||||
|
||||
readFile(path: string): Promise<Uint8Array | void> {
|
||||
// Using local file system only works from Tauri.
|
||||
if (!isTauri()) {
|
||||
throw new Error(
|
||||
'This function can only be called from a Tauri application'
|
||||
)
|
||||
}
|
||||
|
||||
return join(this.dir, path)
|
||||
.catch((error) => {
|
||||
throw new Error(`Error reading file: ${error}`)
|
||||
})
|
||||
.then((file) => {
|
||||
return readBinaryFile(file)
|
||||
})
|
||||
}
|
||||
|
||||
exists(path: string): Promise<boolean | void> {
|
||||
// Using local file system only works from Tauri.
|
||||
if (!isTauri()) {
|
||||
throw new Error(
|
||||
'This function can only be called from a Tauri application'
|
||||
)
|
||||
}
|
||||
|
||||
return join(this.dir, path)
|
||||
.catch((error) => {
|
||||
throw new Error(`Error checking file exists: ${error}`)
|
||||
})
|
||||
.then((file) => {
|
||||
return tauriExists(file)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const fileSystemManager = new FileSystemManager()
|
@ -16,6 +16,7 @@ import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
||||
import type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||
import type { Token } from '../wasm-lib/kcl/bindings/Token'
|
||||
import { Coords2d } from './std/sketch'
|
||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||
|
||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
||||
@ -144,7 +145,8 @@ export const _executor = async (
|
||||
const memory: ProgramMemory = await execute_wasm(
|
||||
JSON.stringify(node),
|
||||
JSON.stringify(programMemory),
|
||||
engineCommandManager
|
||||
engineCommandManager,
|
||||
fileSystemManager
|
||||
)
|
||||
return memory
|
||||
} catch (e: any) {
|
||||
|
45
src/wasm-lib/Cargo.lock
generated
45
src/wasm-lib/Cargo.lock
generated
@ -937,7 +937,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "derive-docs"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"expectorate",
|
||||
@ -952,22 +952,6 @@ dependencies = [
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive-docs"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8511b1486a7ec6820e889c6a2b57b8b699ab5e5eab507ffd1daa0e07871d2305"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel_derives"
|
||||
version = "2.1.2"
|
||||
@ -1431,6 +1415,30 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gltf-derive"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "438ffe1a5540d75403feaf23636b164e816e93f6f03131674722b3886ce32a57"
|
||||
dependencies = [
|
||||
"inflections",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gltf-json"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "655951ba557f2bc69ea4b0799446bae281fa78efae6319968bdd2c3e9a06d8e1"
|
||||
dependencies = [
|
||||
"gltf-derive",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grackle"
|
||||
version = "0.1.0"
|
||||
@ -1880,9 +1888,10 @@ dependencies = [
|
||||
"criterion",
|
||||
"dashmap",
|
||||
"databake",
|
||||
"derive-docs 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"derive-docs",
|
||||
"expectorate",
|
||||
"futures",
|
||||
"gltf-json",
|
||||
"insta",
|
||||
"itertools 0.12.1",
|
||||
"js-sys",
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "derive-docs"
|
||||
description = "A tool for generating documentation from Rust derive macros"
|
||||
version = "0.1.5"
|
||||
version = "0.1.6"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
|
@ -194,41 +194,7 @@ fn do_stdlib_inner(
|
||||
syn::FnArg::Typed(pat) => pat.ty.as_ref().into_token_stream(),
|
||||
};
|
||||
|
||||
let mut ty_string = ty.to_string().replace('&', "").replace("mut", "").replace(' ', "");
|
||||
if ty_string.starts_with("Args") {
|
||||
ty_string = "Args".to_string();
|
||||
}
|
||||
let ty_string = ty_string.trim().to_string();
|
||||
let ty_ident = if ty_string.starts_with("Vec<") {
|
||||
let ty_string = ty_string.trim_start_matches("Vec<").trim_end_matches('>');
|
||||
let ty_ident = format_ident!("{}", ty_string);
|
||||
quote! {
|
||||
Vec<#ty_ident>
|
||||
}
|
||||
} else if ty_string.starts_with("Option<") {
|
||||
let ty_string = ty_string.trim_start_matches("Option<").trim_end_matches('>');
|
||||
let ty_ident = format_ident!("{}", ty_string);
|
||||
quote! {
|
||||
Option<#ty_ident>
|
||||
}
|
||||
} else if let Some((inner_array_type, num)) = parse_array_type(&ty_string) {
|
||||
let ty_string = inner_array_type.to_owned();
|
||||
let ty_ident = format_ident!("{}", ty_string);
|
||||
quote! {
|
||||
[#ty_ident; #num]
|
||||
}
|
||||
} else if ty_string.starts_with("Box<") {
|
||||
let ty_string = ty_string.trim_start_matches("Box<").trim_end_matches('>');
|
||||
let ty_ident = format_ident!("{}", ty_string);
|
||||
quote! {
|
||||
#ty_ident
|
||||
}
|
||||
} else {
|
||||
let ty_ident = format_ident!("{}", ty_string);
|
||||
quote! {
|
||||
#ty_ident
|
||||
}
|
||||
};
|
||||
let (ty_string, ty_ident) = clean_ty_string(ty.to_string().as_str());
|
||||
|
||||
let ty_string = rust_type_to_openapi_type(&ty_string);
|
||||
|
||||
@ -499,6 +465,52 @@ impl Parse for ItemFnForSignature {
|
||||
}
|
||||
}
|
||||
|
||||
fn clean_ty_string(t: &str) -> (String, proc_macro2::TokenStream) {
|
||||
let mut ty_string = t.replace('&', "").replace("mut", "").replace(' ', "");
|
||||
if ty_string.starts_with("Args") {
|
||||
ty_string = "Args".to_string();
|
||||
}
|
||||
let ty_string = ty_string.trim().to_string();
|
||||
let ty_ident = if ty_string.starts_with("Vec<") {
|
||||
let ty_string = ty_string.trim_start_matches("Vec<").trim_end_matches('>');
|
||||
let (_, ty_ident) = clean_ty_string(&ty_string);
|
||||
quote! {
|
||||
Vec<#ty_ident>
|
||||
}
|
||||
} else if ty_string.starts_with("kittycad::types::") {
|
||||
let ty_string = ty_string.trim_start_matches("kittycad::types::").trim_end_matches('>');
|
||||
let ty_ident = format_ident!("{}", ty_string);
|
||||
quote! {
|
||||
kittycad::types::#ty_ident
|
||||
}
|
||||
} else if ty_string.starts_with("Option<") {
|
||||
let ty_string = ty_string.trim_start_matches("Option<").trim_end_matches('>');
|
||||
let (_, ty_ident) = clean_ty_string(&ty_string);
|
||||
quote! {
|
||||
Option<#ty_ident>
|
||||
}
|
||||
} else if let Some((inner_array_type, num)) = parse_array_type(&ty_string) {
|
||||
let ty_string = inner_array_type.to_owned();
|
||||
let (_, ty_ident) = clean_ty_string(&ty_string);
|
||||
quote! {
|
||||
[#ty_ident; #num]
|
||||
}
|
||||
} else if ty_string.starts_with("Box<") {
|
||||
let ty_string = ty_string.trim_start_matches("Box<").trim_end_matches('>');
|
||||
let (_, ty_ident) = clean_ty_string(&ty_string);
|
||||
quote! {
|
||||
#ty_ident
|
||||
}
|
||||
} else {
|
||||
let ty_ident = format_ident!("{}", ty_string);
|
||||
quote! {
|
||||
#ty_ident
|
||||
}
|
||||
};
|
||||
|
||||
(ty_string, ty_ident)
|
||||
}
|
||||
|
||||
fn rust_type_to_openapi_type(t: &str) -> String {
|
||||
let mut t = t.to_string();
|
||||
// Turn vecs into arrays.
|
||||
@ -689,4 +701,28 @@ mod tests {
|
||||
assert!(errors.is_empty());
|
||||
expectorate::assert_contents("tests/array.gen", &openapitor::types::get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stdlib_option_input_format() {
|
||||
let (item, errors) = do_stdlib(
|
||||
quote! {
|
||||
name = "import",
|
||||
},
|
||||
quote! {
|
||||
fn inner_import(
|
||||
/// The args to do shit to.
|
||||
args: Option<kittycad::types::InputFormat>
|
||||
) -> Box<f64> {
|
||||
args
|
||||
}
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(errors.is_empty());
|
||||
expectorate::assert_contents(
|
||||
"tests/option_input_format.gen",
|
||||
&openapitor::types::get_text_fmt(&item).unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
84
src/wasm-lib/derive-docs/tests/option_input_format.gen
Normal file
84
src/wasm-lib/derive-docs/tests/option_input_format.gen
Normal file
@ -0,0 +1,84 @@
|
||||
#[allow(non_camel_case_types, missing_docs)]
|
||||
#[doc = "Std lib function: import"]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, schemars :: JsonSchema, ts_rs :: TS)]
|
||||
#[ts(export)]
|
||||
pub(crate) struct Import {}
|
||||
|
||||
#[allow(non_upper_case_globals, missing_docs)]
|
||||
#[doc = "Std lib function: import"]
|
||||
pub(crate) const Import: Import = Import {};
|
||||
fn boxed_import(
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::MemoryItem, crate::errors::KclError>,
|
||||
>,
|
||||
>,
|
||||
> {
|
||||
Box::pin(import(args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for Import {
|
||||
fn name(&self) -> String {
|
||||
"import".to_string()
|
||||
}
|
||||
|
||||
fn summary(&self) -> String {
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
fn tags(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
|
||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||
settings.inline_subschemas = true;
|
||||
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
||||
vec![crate::docs::StdLibFnArg {
|
||||
name: "args".to_string(),
|
||||
type_: "kittycad::types::InputFormat".to_string(),
|
||||
schema: <Option<kittycad::types::InputFormat>>::json_schema(&mut generator),
|
||||
required: true,
|
||||
}]
|
||||
}
|
||||
|
||||
fn return_value(&self) -> Option<crate::docs::StdLibFnArg> {
|
||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||
settings.inline_subschemas = true;
|
||||
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
||||
Some(crate::docs::StdLibFnArg {
|
||||
name: "".to_string(),
|
||||
type_: "number".to_string(),
|
||||
schema: f64::json_schema(&mut generator),
|
||||
required: true,
|
||||
})
|
||||
}
|
||||
|
||||
fn unpublished(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn deprecated(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn std_lib_fn(&self) -> crate::std::StdFn {
|
||||
boxed_import
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn crate::docs::StdLibFn> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_import(
|
||||
#[doc = r" The args to do shit to."] args: Option<kittycad::types::InputFormat>,
|
||||
) -> Box<f64> {
|
||||
args
|
||||
}
|
@ -17,8 +17,9 @@ async-trait = "0.1.77"
|
||||
clap = { version = "4.5.0", features = ["cargo", "derive", "env", "unicode"], optional = true }
|
||||
dashmap = "5.5.3"
|
||||
databake = { version = "0.1.7", features = ["derive"] }
|
||||
derive-docs = { version = "0.1.5" }
|
||||
# derive-docs = { path = "../derive-docs" }
|
||||
#derive-docs = { version = "0.1.6" }
|
||||
derive-docs = { path = "../derive-docs" }
|
||||
gltf-json = "1.4.0"
|
||||
kittycad = { workspace = true }
|
||||
kittycad-execution-plan-macros = { workspace = true }
|
||||
kittycad-execution-plan-traits = { workspace = true }
|
||||
|
@ -284,6 +284,25 @@ pub fn get_description_string_from_schema(schema: &schemars::schema::Schema) ->
|
||||
pub fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<(String, bool)> {
|
||||
match schema {
|
||||
schemars::schema::Schema::Object(o) => {
|
||||
if let Some(enum_values) = &o.enum_values {
|
||||
let mut parsed_enum_values: Vec<String> = Default::default();
|
||||
let mut had_enum_string = false;
|
||||
for enum_value in enum_values {
|
||||
if let serde_json::value::Value::String(enum_value) = enum_value {
|
||||
had_enum_string = true;
|
||||
parsed_enum_values.push(format!("\"{}\"", enum_value));
|
||||
} else {
|
||||
had_enum_string = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if had_enum_string {
|
||||
return Ok((parsed_enum_values.join(" | "), false));
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there
|
||||
if let Some(format) = &o.format {
|
||||
if format == "uuid" {
|
||||
return Ok((Primitive::Uuid.to_string(), false));
|
||||
@ -336,6 +355,34 @@ pub fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<
|
||||
if let Some(subschemas) = &o.subschemas {
|
||||
let mut fn_docs = String::new();
|
||||
if let Some(items) = &subschemas.one_of {
|
||||
let mut had_enum_string = false;
|
||||
let mut parsed_enum_values: Vec<String> = Vec::new();
|
||||
for item in items {
|
||||
if let schemars::schema::Schema::Object(o) = item {
|
||||
if let Some(enum_values) = &o.enum_values {
|
||||
for enum_value in enum_values {
|
||||
if let serde_json::value::Value::String(enum_value) = enum_value {
|
||||
had_enum_string = true;
|
||||
parsed_enum_values.push(format!("\"{}\"", enum_value));
|
||||
} else {
|
||||
had_enum_string = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !had_enum_string {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
had_enum_string = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
had_enum_string = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !had_enum_string {
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
// Let's print out the object's properties.
|
||||
fn_docs.push_str(&get_type_string_from_schema(item)?.0.to_string());
|
||||
@ -343,6 +390,9 @@ pub fn get_type_string_from_schema(schema: &schemars::schema::Schema) -> Result<
|
||||
fn_docs.push_str(" |\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fn_docs.push_str(&parsed_enum_values.join(" | "));
|
||||
}
|
||||
} else if let Some(items) = &subschemas.any_of {
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
// Let's print out the object's properties.
|
||||
|
@ -13,13 +13,13 @@ extern "C" {
|
||||
#[derive(Debug, Clone)]
|
||||
pub type EngineCommandManager;
|
||||
|
||||
#[wasm_bindgen(method)]
|
||||
fn sendModelingCommandFromWasm(
|
||||
#[wasm_bindgen(method, js_name = sendModelingCommandFromWasm, catch)]
|
||||
fn send_modeling_cmd_from_wasm(
|
||||
this: &EngineCommandManager,
|
||||
id: String,
|
||||
rangeStr: String,
|
||||
cmdStr: String,
|
||||
) -> js_sys::Promise;
|
||||
) -> Result<js_sys::Promise, js_sys::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -59,7 +59,13 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
|
||||
let promise = self
|
||||
.manager
|
||||
.sendModelingCommandFromWasm(id.to_string(), source_range_str, cmd_str);
|
||||
.send_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str)
|
||||
.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: e.to_string().into(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let value = wasm_bindgen_futures::JsFuture::from(promise).await.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
|
@ -15,6 +15,7 @@ use crate::{
|
||||
ast::types::{BodyItem, FunctionExpression, KclNone, Value},
|
||||
engine::EngineConnection,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
fs::FileManager,
|
||||
std::{FunctionKind, StdLib},
|
||||
};
|
||||
|
||||
@ -146,6 +147,7 @@ pub enum MemoryItem {
|
||||
ExtrudeGroups {
|
||||
value: Vec<Box<ExtrudeGroup>>,
|
||||
},
|
||||
ImportedGeometry(ImportedGeometry),
|
||||
#[ts(skip)]
|
||||
ExtrudeTransform(Box<ExtrudeTransform>),
|
||||
#[ts(skip)]
|
||||
@ -194,6 +196,19 @@ pub enum SketchGroupSet {
|
||||
SketchGroups(Vec<Box<SketchGroup>>),
|
||||
}
|
||||
|
||||
/// Data for an imported geometry.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ImportedGeometry {
|
||||
/// The ID of the imported geometry.
|
||||
pub id: uuid::Uuid,
|
||||
/// The original file paths.
|
||||
pub value: Vec<String>,
|
||||
#[serde(rename = "__meta")]
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
|
||||
/// A plane.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
@ -293,6 +308,7 @@ impl From<MemoryItem> for Vec<SourceRange> {
|
||||
.iter()
|
||||
.flat_map(|eg| eg.meta.iter().map(|m| m.source_range))
|
||||
.collect(),
|
||||
MemoryItem::ImportedGeometry(i) => i.meta.iter().map(|m| m.source_range).collect(),
|
||||
MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
||||
MemoryItem::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
||||
MemoryItem::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
|
||||
@ -835,9 +851,33 @@ impl Default for PipeInfo {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExecutorContext {
|
||||
pub engine: EngineConnection,
|
||||
pub fs: FileManager,
|
||||
pub stdlib: Arc<StdLib>,
|
||||
}
|
||||
|
||||
impl ExecutorContext {
|
||||
/// Create a new default executor context.
|
||||
#[cfg(test)]
|
||||
pub async fn new() -> Result<Self> {
|
||||
Ok(Self {
|
||||
engine: EngineConnection::new().await?,
|
||||
fs: FileManager::new(),
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new default executor context.
|
||||
#[cfg(not(test))]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn new(ws: reqwest::Upgraded) -> Result<Self> {
|
||||
Ok(Self {
|
||||
engine: EngineConnection::new(ws).await?,
|
||||
fs: FileManager::new(),
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a AST's program.
|
||||
#[async_recursion(?Send)]
|
||||
pub async fn execute(
|
||||
@ -901,6 +941,10 @@ pub async fn execute(
|
||||
}
|
||||
|
||||
memory.return_ = Some(ProgramReturn::Arguments(call_expr.arguments.clone()));
|
||||
} else {
|
||||
let args = crate::std::Args::new(args, call_expr.into(), ctx.clone());
|
||||
let result = func.std_lib_fn()(args).await?;
|
||||
memory.return_ = Some(ProgramReturn::Value(result));
|
||||
}
|
||||
}
|
||||
FunctionKind::Std(func) => {
|
||||
@ -1128,11 +1172,7 @@ mod tests {
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
let program = parser.ast()?;
|
||||
let mut mem: ProgramMemory = Default::default();
|
||||
let engine = EngineConnection::new().await?;
|
||||
let ctx = ExecutorContext {
|
||||
engine,
|
||||
stdlib: Arc::new(StdLib::default()),
|
||||
};
|
||||
let ctx = ExecutorContext::new().await?;
|
||||
let memory = execute(program, &mut mem, BodyType::Root, &ctx).await?;
|
||||
|
||||
Ok(memory)
|
||||
|
56
src/wasm-lib/kcl/src/fs/local.rs
Normal file
56
src/wasm-lib/kcl/src/fs/local.rs
Normal file
@ -0,0 +1,56 @@
|
||||
//! Functions for interacting with a file system locally.
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
fs::FileSystem,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileManager {}
|
||||
|
||||
impl FileManager {
|
||||
pub fn new() -> FileManager {
|
||||
FileManager {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FileManager {
|
||||
fn default() -> Self {
|
||||
FileManager::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl FileSystem for FileManager {
|
||||
async fn read<P: AsRef<std::path::Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
source_range: crate::executor::SourceRange,
|
||||
) -> Result<Vec<u8>, KclError> {
|
||||
tokio::fs::read(&path).await.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to read file `{}`: {}", path.as_ref().display(), e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async fn exists<P: AsRef<std::path::Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
source_range: crate::executor::SourceRange,
|
||||
) -> Result<bool, crate::errors::KclError> {
|
||||
tokio::fs::metadata(&path).await.map(|_| true).or_else(|e| {
|
||||
if e.kind() == std::io::ErrorKind::NotFound {
|
||||
Ok(false)
|
||||
} else {
|
||||
Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to check if file `{}` exists: {}", path.as_ref().display(), e),
|
||||
source_ranges: vec![source_range],
|
||||
}))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
32
src/wasm-lib/kcl/src/fs/mod.rs
Normal file
32
src/wasm-lib/kcl/src/fs/mod.rs
Normal file
@ -0,0 +1,32 @@
|
||||
//! Functions for interacting with files on a machine.
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod local;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use local::FileManager;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(test))]
|
||||
pub mod wasm;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(test))]
|
||||
pub use wasm::FileManager;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait FileSystem: Clone {
|
||||
/// Read a file from the local file system.
|
||||
async fn read<P: AsRef<std::path::Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
source_range: crate::executor::SourceRange,
|
||||
) -> Result<Vec<u8>, crate::errors::KclError>;
|
||||
|
||||
/// Check if a file exists on the local file system.
|
||||
async fn exists<P: AsRef<std::path::Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
source_range: crate::executor::SourceRange,
|
||||
) -> Result<bool, crate::errors::KclError>;
|
||||
}
|
115
src/wasm-lib/kcl/src/fs/wasm.rs
Normal file
115
src/wasm-lib/kcl/src/fs/wasm.rs
Normal file
@ -0,0 +1,115 @@
|
||||
//! Functions for interacting with a file system via wasm.
|
||||
|
||||
use anyhow::Result;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
fs::FileSystem,
|
||||
};
|
||||
|
||||
#[wasm_bindgen(module = "/../../lang/std/fileSystemManager.ts")]
|
||||
extern "C" {
|
||||
#[derive(Debug, Clone)]
|
||||
pub type FileSystemManager;
|
||||
|
||||
#[wasm_bindgen(method, js_name = readFile, catch)]
|
||||
fn read_file(this: &FileSystemManager, path: String) -> Result<js_sys::Promise, js_sys::Error>;
|
||||
|
||||
#[wasm_bindgen(method, js_name = exists, catch)]
|
||||
fn exists(this: &FileSystemManager, path: String) -> Result<js_sys::Promise, js_sys::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FileManager {
|
||||
manager: FileSystemManager,
|
||||
}
|
||||
|
||||
impl FileManager {
|
||||
pub fn new(manager: FileSystemManager) -> FileManager {
|
||||
FileManager { manager }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl FileSystem for FileManager {
|
||||
async fn read<P: AsRef<std::path::Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
source_range: crate::executor::SourceRange,
|
||||
) -> Result<Vec<u8>, KclError> {
|
||||
let promise = self
|
||||
.manager
|
||||
.read_file(
|
||||
path.as_ref()
|
||||
.to_str()
|
||||
.ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: "Failed to convert path to string".to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?
|
||||
.to_string(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: e.to_string().into(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let value = wasm_bindgen_futures::JsFuture::from(promise).await.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to wait for promise from engine: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let array = js_sys::Uint8Array::new(&value);
|
||||
let bytes: Vec<u8> = array.to_vec();
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
async fn exists<P: AsRef<std::path::Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
source_range: crate::executor::SourceRange,
|
||||
) -> Result<bool, crate::errors::KclError> {
|
||||
let promise = self
|
||||
.manager
|
||||
.exists(
|
||||
path.as_ref()
|
||||
.to_str()
|
||||
.ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: "Failed to convert path to string".to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?
|
||||
.to_string(),
|
||||
)
|
||||
.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: e.to_string().into(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let value = wasm_bindgen_futures::JsFuture::from(promise).await.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to wait for promise from engine: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let it_exists = value.as_bool().ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: "Failed to convert value to bool".to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(it_exists)
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ pub mod docs;
|
||||
pub mod engine;
|
||||
pub mod errors;
|
||||
pub mod executor;
|
||||
pub mod fs;
|
||||
pub mod parser;
|
||||
pub mod server;
|
||||
pub mod std;
|
||||
|
358
src/wasm-lib/kcl/src/std/import.rs
Normal file
358
src/wasm-lib/kcl/src/std/import.rs
Normal file
@ -0,0 +1,358 @@
|
||||
//! Standard library functions involved in importing files.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use derive_docs::stdlib;
|
||||
use kittycad::types::ModelingCmd;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{ImportedGeometry, MemoryItem},
|
||||
fs::FileSystem,
|
||||
std::Args,
|
||||
};
|
||||
|
||||
// Zoo co-ordinate system.
|
||||
//
|
||||
// * Forward: -Y
|
||||
// * Up: +Z
|
||||
// * Handedness: Right
|
||||
const ZOO_COORD_SYSTEM: kittycad::types::System = kittycad::types::System {
|
||||
forward: kittycad::types::AxisDirectionPair {
|
||||
axis: kittycad::types::Axis::Y,
|
||||
direction: kittycad::types::Direction::Negative,
|
||||
},
|
||||
up: kittycad::types::AxisDirectionPair {
|
||||
axis: kittycad::types::Axis::Z,
|
||||
direction: kittycad::types::Direction::Positive,
|
||||
},
|
||||
};
|
||||
|
||||
/// Import format specifier
|
||||
#[derive(serde :: Serialize, serde :: Deserialize, PartialEq, Debug, Clone, schemars :: JsonSchema)]
|
||||
#[cfg_attr(feature = "tabled", derive(tabled::Tabled))]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ImportFormat {
|
||||
/// Autodesk Filmbox (FBX) format
|
||||
#[serde(rename = "fbx")]
|
||||
Fbx {},
|
||||
/// Binary glTF 2.0. We refer to this as glTF since that is how our customers refer to
|
||||
/// it, but this can also import binary glTF (glb).
|
||||
#[serde(rename = "gltf")]
|
||||
Gltf {},
|
||||
/// Wavefront OBJ format.
|
||||
#[serde(rename = "obj")]
|
||||
Obj {
|
||||
/// Co-ordinate system of input data.
|
||||
/// Defaults to the [KittyCAD co-ordinate system.
|
||||
coords: Option<kittycad::types::System>,
|
||||
/// The units of the input data. This is very important for correct scaling and when
|
||||
/// calculating physics properties like mass, etc.
|
||||
/// Defaults to millimeters.
|
||||
units: kittycad::types::UnitLength,
|
||||
},
|
||||
/// The PLY Polygon File Format.
|
||||
#[serde(rename = "ply")]
|
||||
Ply {
|
||||
/// Co-ordinate system of input data.
|
||||
/// Defaults to the [KittyCAD co-ordinate system.
|
||||
coords: Option<kittycad::types::System>,
|
||||
/// The units of the input data. This is very important for correct scaling and when
|
||||
/// calculating physics properties like mass, etc.
|
||||
/// Defaults to millimeters.
|
||||
units: kittycad::types::UnitLength,
|
||||
},
|
||||
/// SolidWorks part (SLDPRT) format.
|
||||
#[serde(rename = "sldprt")]
|
||||
Sldprt {},
|
||||
/// ISO 10303-21 (STEP) format.
|
||||
#[serde(rename = "step")]
|
||||
Step {},
|
||||
/// *ST**ereo**L**ithography format.
|
||||
#[serde(rename = "stl")]
|
||||
Stl {
|
||||
/// Co-ordinate system of input data.
|
||||
/// Defaults to the [KittyCAD co-ordinate system.
|
||||
coords: Option<kittycad::types::System>,
|
||||
/// The units of the input data. This is very important for correct scaling and when
|
||||
/// calculating physics properties like mass, etc.
|
||||
/// Defaults to millimeters.
|
||||
units: kittycad::types::UnitLength,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<ImportFormat> for kittycad::types::InputFormat {
|
||||
fn from(format: ImportFormat) -> Self {
|
||||
match format {
|
||||
ImportFormat::Fbx {} => kittycad::types::InputFormat::Fbx {},
|
||||
ImportFormat::Gltf {} => kittycad::types::InputFormat::Gltf {},
|
||||
ImportFormat::Obj { coords, units } => kittycad::types::InputFormat::Obj {
|
||||
coords: coords.unwrap_or(ZOO_COORD_SYSTEM),
|
||||
units,
|
||||
},
|
||||
ImportFormat::Ply { coords, units } => kittycad::types::InputFormat::Ply {
|
||||
coords: coords.unwrap_or(ZOO_COORD_SYSTEM),
|
||||
units,
|
||||
},
|
||||
ImportFormat::Sldprt {} => kittycad::types::InputFormat::Sldprt {},
|
||||
ImportFormat::Step {} => kittycad::types::InputFormat::Step {},
|
||||
ImportFormat::Stl { coords, units } => kittycad::types::InputFormat::Stl {
|
||||
coords: coords.unwrap_or(ZOO_COORD_SYSTEM),
|
||||
units,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Import a CAD file.
|
||||
/// For formats lacking unit data (STL, OBJ, PLY), the default import unit is millimeters.
|
||||
/// Otherwise you can specify the unit by passing in the options parameter.
|
||||
/// If you import a gltf file, we will try to find the bin file and import it as well.
|
||||
pub async fn import(args: Args) -> Result<MemoryItem, KclError> {
|
||||
let (file_path, options): (String, Option<ImportFormat>) = args.get_import_data()?;
|
||||
|
||||
let imported_geometry = inner_import(file_path, options, args).await?;
|
||||
Ok(MemoryItem::ImportedGeometry(imported_geometry))
|
||||
}
|
||||
|
||||
/// Import a CAD file.
|
||||
/// For formats lacking unit data (STL, OBJ, PLY), the default import unit is millimeters.
|
||||
/// Otherwise you can specify the unit by passing in the options parameter.
|
||||
/// If you import a gltf file, we will try to find the bin file and import it as well.
|
||||
#[stdlib {
|
||||
name = "import",
|
||||
}]
|
||||
async fn inner_import(
|
||||
file_path: String,
|
||||
options: Option<ImportFormat>,
|
||||
args: Args,
|
||||
) -> Result<ImportedGeometry, KclError> {
|
||||
if file_path.is_empty() {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "No file path was provided.".to_string(),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
// Make sure the file exists.
|
||||
if !args.ctx.fs.exists(&file_path, args.source_range).await? {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("File `{}` does not exist.", file_path),
|
||||
source_ranges: vec![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: kittycad::types::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![kittycad::types::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 kittycad::types::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(kittycad::types::ImportFile {
|
||||
path: uri.to_string(),
|
||||
data: bin_contents,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
let resp = args
|
||||
.send_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::ImportFiles {
|
||||
files: import_files.clone(),
|
||||
format,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||
modeling_response: kittycad::types::OkModelingCmdResponse::ImportFiles { data: 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<kittycad::types::InputFormat> {
|
||||
let format = match kittycad::types::FileImportFormat::from_str(ext) {
|
||||
Ok(format) => format,
|
||||
Err(_) => {
|
||||
if ext == "stp" {
|
||||
kittycad::types::FileImportFormat::Step
|
||||
} else if ext == "glb" {
|
||||
kittycad::types::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 = kittycad::types::UnitLength::Mm;
|
||||
|
||||
// Zoo co-ordinate system.
|
||||
//
|
||||
// * Forward: -Y
|
||||
// * Up: +Z
|
||||
// * Handedness: Right
|
||||
match format {
|
||||
kittycad::types::FileImportFormat::Step => Ok(kittycad::types::InputFormat::Step {}),
|
||||
kittycad::types::FileImportFormat::Stl => Ok(kittycad::types::InputFormat::Stl {
|
||||
coords: ZOO_COORD_SYSTEM,
|
||||
units: ul,
|
||||
}),
|
||||
kittycad::types::FileImportFormat::Obj => Ok(kittycad::types::InputFormat::Obj {
|
||||
coords: ZOO_COORD_SYSTEM,
|
||||
units: ul,
|
||||
}),
|
||||
kittycad::types::FileImportFormat::Gltf => Ok(kittycad::types::InputFormat::Gltf {}),
|
||||
kittycad::types::FileImportFormat::Ply => Ok(kittycad::types::InputFormat::Ply {
|
||||
coords: ZOO_COORD_SYSTEM,
|
||||
units: ul,
|
||||
}),
|
||||
kittycad::types::FileImportFormat::Fbx => Ok(kittycad::types::InputFormat::Fbx {}),
|
||||
kittycad::types::FileImportFormat::Sldprt => Ok(kittycad::types::InputFormat::Sldprt {}),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_extension_format(ext: kittycad::types::InputFormat, given: kittycad::types::InputFormat) -> Result<()> {
|
||||
if let kittycad::types::InputFormat::Stl { coords: _, units: _ } = ext {
|
||||
if let kittycad::types::InputFormat::Stl { coords: _, units: _ } = given {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let kittycad::types::InputFormat::Obj { coords: _, units: _ } = ext {
|
||||
if let kittycad::types::InputFormat::Obj { coords: _, units: _ } = given {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let kittycad::types::InputFormat::Ply { coords: _, units: _ } = ext {
|
||||
if let kittycad::types::InputFormat::Ply { coords: _, units: _ } = 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_: kittycad::types::InputFormat) -> String {
|
||||
match type_ {
|
||||
kittycad::types::InputFormat::Fbx {} => "fbx".to_string(),
|
||||
kittycad::types::InputFormat::Gltf {} => "gltf".to_string(),
|
||||
kittycad::types::InputFormat::Obj { coords: _, units: _ } => "obj".to_string(),
|
||||
kittycad::types::InputFormat::Ply { coords: _, units: _ } => "ply".to_string(),
|
||||
kittycad::types::InputFormat::Sldprt {} => "sldprt".to_string(),
|
||||
kittycad::types::InputFormat::Step {} => "step".to_string(),
|
||||
kittycad::types::InputFormat::Stl { coords: _, units: _ } => "stl".to_string(),
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
//! Functions implemented for language execution.
|
||||
|
||||
pub mod extrude;
|
||||
pub mod import;
|
||||
pub mod kcl_stdlib;
|
||||
pub mod math;
|
||||
pub mod patterns;
|
||||
@ -71,6 +72,7 @@ lazy_static! {
|
||||
Box::new(crate::std::sketch::BezierCurve),
|
||||
Box::new(crate::std::sketch::Hole),
|
||||
Box::new(crate::std::patterns::PatternLinear),
|
||||
Box::new(crate::std::import::Import),
|
||||
Box::new(crate::std::math::Cos),
|
||||
Box::new(crate::std::math::Sin),
|
||||
Box::new(crate::std::math::Tan),
|
||||
@ -371,6 +373,39 @@ impl Args {
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn get_import_data(&self) -> Result<(String, Option<crate::std::import::ImportFormat>), KclError> {
|
||||
let first_value = self
|
||||
.args
|
||||
.first()
|
||||
.ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!("Expected a struct as the first argument, found `{:?}`", self.args),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?
|
||||
.get_json_value()?;
|
||||
|
||||
let data: String = serde_json::from_value(first_value).map_err(|e| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!("Expected a file path string: {}", e),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
if let Some(second_value) = self.args.get(1) {
|
||||
let options: crate::std::import::ImportFormat = serde_json::from_value(second_value.get_json_value()?)
|
||||
.map_err(|e| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!("Expected input format data: {}", e),
|
||||
source_ranges: vec![self.source_range],
|
||||
})
|
||||
})?;
|
||||
Ok((data, Some(options)))
|
||||
} else {
|
||||
Ok((data, None))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_data_and_sketch_group<T: serde::de::DeserializeOwned>(&self) -> Result<(T, Box<SketchGroup>), KclError> {
|
||||
let first_value = self
|
||||
.args
|
||||
|
@ -1461,7 +1461,6 @@ async fn inner_hole(
|
||||
}
|
||||
SketchGroupSet::SketchGroups(hole_sketch_groups) => {
|
||||
for hole_sketch_group in hole_sketch_groups {
|
||||
println!("hole_sketch_group: {:?} {}", hole_sketch_group.id, sketch_group.id);
|
||||
args.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
ModelingCmd::Solid2DAddHole {
|
||||
|
@ -17,7 +17,8 @@ use wasm_bindgen::prelude::*;
|
||||
pub async fn execute_wasm(
|
||||
program_str: &str,
|
||||
memory_str: &str,
|
||||
manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
|
||||
engine_manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
|
||||
fs_manager: kcl_lib::fs::wasm::FileSystemManager,
|
||||
) -> Result<JsValue, String> {
|
||||
// deserialize the ast from a stringified json
|
||||
|
||||
@ -25,11 +26,13 @@ pub async fn execute_wasm(
|
||||
let program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?;
|
||||
let mut mem: kcl_lib::executor::ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?;
|
||||
|
||||
let engine = kcl_lib::engine::EngineConnection::new(manager)
|
||||
let engine = kcl_lib::engine::EngineConnection::new(engine_manager)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))?;
|
||||
let fs = kcl_lib::fs::FileManager::new(fs_manager);
|
||||
let ctx = ExecutorContext {
|
||||
engine,
|
||||
fs,
|
||||
stdlib: std::sync::Arc::new(kcl_lib::std::StdLib::new()),
|
||||
};
|
||||
|
||||
|
108
src/wasm-lib/tests/executor/inputs/cube-embedded.gltf
Normal file
108
src/wasm-lib/tests/executor/inputs/cube-embedded.gltf
Normal file
@ -0,0 +1,108 @@
|
||||
{
|
||||
"asset": {
|
||||
"generator": "Khronos glTF Blender I/O v3.4.50",
|
||||
"version": "2.0"
|
||||
},
|
||||
"scene": 0,
|
||||
"scenes": [
|
||||
{
|
||||
"name": "Scene",
|
||||
"nodes": [0]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"mesh": 0,
|
||||
"name": "Cube"
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
{
|
||||
"doubleSided": true,
|
||||
"name": "Material",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.800000011920929, 0.800000011920929, 0.800000011920929, 1
|
||||
],
|
||||
"metallicFactor": 0,
|
||||
"roughnessFactor": 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"meshes": [
|
||||
{
|
||||
"name": "Cube",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"POSITION": 0,
|
||||
"TEXCOORD_0": 1,
|
||||
"NORMAL": 2
|
||||
},
|
||||
"indices": 3,
|
||||
"material": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 0,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"max": [1, 1, 1],
|
||||
"min": [-1, -1, -1],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"type": "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 3,
|
||||
"componentType": 5123,
|
||||
"count": 36,
|
||||
"type": "SCALAR"
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 288,
|
||||
"byteOffset": 0,
|
||||
"target": 34962
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 192,
|
||||
"byteOffset": 288,
|
||||
"target": 34962
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 288,
|
||||
"byteOffset": 480,
|
||||
"target": 34962
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 72,
|
||||
"byteOffset": 768,
|
||||
"target": 34963
|
||||
}
|
||||
],
|
||||
"buffers": [
|
||||
{
|
||||
"byteLength": 840,
|
||||
"uri": "data:application/octet-stream;base64,AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AAAAPgAAAD8AAMA+AABAPwAAwD4AAEA/AAAgPwAAAAAAACA/AACAPwAAYD8AAIA+AAAAPgAAgD4AAMA+AAAAAAAAwD4AAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAQAOABQAAQAUAAcACgAGABIACgASABYAFwATAAwAFwAMABAADwADAAkADwAJABUABQACAAgABQAIAAsAEQANAAAAEQAAAAQA"
|
||||
}
|
||||
]
|
||||
}
|
BIN
src/wasm-lib/tests/executor/inputs/cube.bin
Normal file
BIN
src/wasm-lib/tests/executor/inputs/cube.bin
Normal file
Binary file not shown.
BIN
src/wasm-lib/tests/executor/inputs/cube.glb
Normal file
BIN
src/wasm-lib/tests/executor/inputs/cube.glb
Normal file
Binary file not shown.
108
src/wasm-lib/tests/executor/inputs/cube.gltf
Normal file
108
src/wasm-lib/tests/executor/inputs/cube.gltf
Normal file
@ -0,0 +1,108 @@
|
||||
{
|
||||
"asset": {
|
||||
"generator": "Khronos glTF Blender I/O v3.4.50",
|
||||
"version": "2.0"
|
||||
},
|
||||
"scene": 0,
|
||||
"scenes": [
|
||||
{
|
||||
"name": "Scene",
|
||||
"nodes": [0]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"mesh": 0,
|
||||
"name": "Cube"
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
{
|
||||
"doubleSided": true,
|
||||
"name": "Material",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.800000011920929, 0.800000011920929, 0.800000011920929, 1
|
||||
],
|
||||
"metallicFactor": 0,
|
||||
"roughnessFactor": 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"meshes": [
|
||||
{
|
||||
"name": "Cube",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"POSITION": 0,
|
||||
"TEXCOORD_0": 1,
|
||||
"NORMAL": 2
|
||||
},
|
||||
"indices": 3,
|
||||
"material": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 0,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"max": [1, 1, 1],
|
||||
"min": [-1, -1, -1],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"type": "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"componentType": 5126,
|
||||
"count": 24,
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 3,
|
||||
"componentType": 5123,
|
||||
"count": 36,
|
||||
"type": "SCALAR"
|
||||
}
|
||||
],
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 288,
|
||||
"byteOffset": 0,
|
||||
"target": 34962
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 192,
|
||||
"byteOffset": 288,
|
||||
"target": 34962
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 288,
|
||||
"byteOffset": 480,
|
||||
"target": 34962
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 72,
|
||||
"byteOffset": 768,
|
||||
"target": 34963
|
||||
}
|
||||
],
|
||||
"buffers": [
|
||||
{
|
||||
"byteLength": 840,
|
||||
"uri": "cube.bin"
|
||||
}
|
||||
]
|
||||
}
|
12
src/wasm-lib/tests/executor/inputs/cube.mtl
Normal file
12
src/wasm-lib/tests/executor/inputs/cube.mtl
Normal file
@ -0,0 +1,12 @@
|
||||
# Blender MTL File: 'None'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Material
|
||||
Ns 359.999993
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.800000 0.800000 0.800000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.000000 0.000000 0.000000
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
40
src/wasm-lib/tests/executor/inputs/cube.obj
Normal file
40
src/wasm-lib/tests/executor/inputs/cube.obj
Normal file
@ -0,0 +1,40 @@
|
||||
# Blender v3.2.2 OBJ File: ''
|
||||
# www.blender.org
|
||||
mtllib cube.mtl
|
||||
o Cube
|
||||
v -1.000000 -1.000000 1.000000
|
||||
v -1.000000 -1.000000 -1.000000
|
||||
v -1.000000 1.000000 1.000000
|
||||
v -1.000000 1.000000 -1.000000
|
||||
v 1.000000 -1.000000 1.000000
|
||||
v 1.000000 -1.000000 -1.000000
|
||||
v 1.000000 1.000000 1.000000
|
||||
v 1.000000 1.000000 -1.000000
|
||||
vt 0.625000 0.500000
|
||||
vt 0.875000 0.500000
|
||||
vt 0.875000 0.750000
|
||||
vt 0.625000 0.750000
|
||||
vt 0.375000 0.750000
|
||||
vt 0.625000 1.000000
|
||||
vt 0.375000 1.000000
|
||||
vt 0.375000 0.000000
|
||||
vt 0.625000 0.000000
|
||||
vt 0.625000 0.250000
|
||||
vt 0.375000 0.250000
|
||||
vt 0.125000 0.500000
|
||||
vt 0.375000 0.500000
|
||||
vt 0.125000 0.750000
|
||||
vn 0.0000 0.0000 1.0000
|
||||
vn 0.0000 1.0000 0.0000
|
||||
vn 1.0000 0.0000 0.0000
|
||||
vn 0.0000 0.0000 -1.0000
|
||||
vn -1.0000 0.0000 0.0000
|
||||
vn 0.0000 -1.0000 0.0000
|
||||
usemtl Material
|
||||
s off
|
||||
f 1/1/1 5/2/1 7/3/1 3/4/1
|
||||
f 4/5/2 3/4/2 7/6/2 8/7/2
|
||||
f 8/8/3 7/9/3 5/10/3 6/11/3
|
||||
f 6/12/4 2/13/4 4/5/4 8/14/4
|
||||
f 2/13/5 1/1/5 3/4/5 4/5/5
|
||||
f 6/11/6 5/10/6 1/1/6 2/13/6
|
@ -1,7 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_lib::{engine::EngineManager, std::StdLib};
|
||||
use kcl_lib::engine::EngineManager;
|
||||
|
||||
/// Executes a kcl program and takes a snapshot of the result.
|
||||
/// This returns the bytes of the snapshot.
|
||||
@ -38,11 +36,7 @@ async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
|
||||
let parser = kcl_lib::parser::Parser::new(tokens);
|
||||
let program = parser.ast()?;
|
||||
let mut mem: kcl_lib::executor::ProgramMemory = Default::default();
|
||||
let engine = kcl_lib::engine::EngineConnection::new(ws).await?;
|
||||
let ctx = kcl_lib::executor::ExecutorContext {
|
||||
engine,
|
||||
stdlib: Arc::new(StdLib::default()),
|
||||
};
|
||||
let ctx = kcl_lib::executor::ExecutorContext::new(ws).await?;
|
||||
let _ = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &ctx).await?;
|
||||
|
||||
// Send a snapshot request to the engine.
|
||||
@ -692,3 +686,75 @@ const rectangle = startSketchOn('XY')
|
||||
let result = execute_and_snapshot(code).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic_holes.png", &result, 0.999);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_import_file_doesnt_exist() {
|
||||
let code = r#"const model = import("thing.obj")"#;
|
||||
|
||||
let result = execute_and_snapshot(code).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.err().unwrap().to_string(),
|
||||
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([14, 33])], message: "File `thing.obj` does not exist." }"#
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_import_obj_with_mtl() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube.obj")"#;
|
||||
|
||||
let result = execute_and_snapshot(code).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_obj_with_mtl.png", &result, 0.999);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_import_obj_with_mtl_units() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube.obj", {type: "obj", units: "m"})"#;
|
||||
|
||||
let result = execute_and_snapshot(code).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_obj_with_mtl_units.png", &result, 0.999);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_import_gltf_with_bin() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube.gltf")"#;
|
||||
|
||||
let result = execute_and_snapshot(code).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_gltf_with_bin.png", &result, 0.999);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_import_gltf_embedded() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube-embedded.gltf")"#;
|
||||
|
||||
let result = execute_and_snapshot(code).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_gltf_embedded.png", &result, 0.999);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_import_glb() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube.glb")"#;
|
||||
|
||||
let result = execute_and_snapshot(code).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_glb.png", &result, 0.999);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_import_glb_no_assign() {
|
||||
let code = r#"import("tests/executor/inputs/cube.glb")"#;
|
||||
|
||||
let result = execute_and_snapshot(code).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_glb_no_assign.png", &result, 0.999);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_import_ext_doesnt_match() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube.gltf", {type: "obj", units: "m"})"#;
|
||||
|
||||
let result = execute_and_snapshot(code).await;
|
||||
assert!(result.is_err());
|
||||
assert_eq!(
|
||||
result.err().unwrap().to_string(),
|
||||
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([14, 82])], message: "The given format does not match the file extension. Expected: `gltf`, Given: `obj`" }"#
|
||||
);
|
||||
}
|
||||
|
BIN
src/wasm-lib/tests/executor/outputs/import_glb.png
Normal file
BIN
src/wasm-lib/tests/executor/outputs/import_glb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
BIN
src/wasm-lib/tests/executor/outputs/import_glb_no_assign.png
Normal file
BIN
src/wasm-lib/tests/executor/outputs/import_glb_no_assign.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
BIN
src/wasm-lib/tests/executor/outputs/import_gltf_embedded.png
Normal file
BIN
src/wasm-lib/tests/executor/outputs/import_gltf_embedded.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
BIN
src/wasm-lib/tests/executor/outputs/import_gltf_with_bin.png
Normal file
BIN
src/wasm-lib/tests/executor/outputs/import_gltf_with_bin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
BIN
src/wasm-lib/tests/executor/outputs/import_obj_with_mtl.png
Normal file
BIN
src/wasm-lib/tests/executor/outputs/import_obj_with_mtl.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
Binary file not shown.
After Width: | Height: | Size: 68 KiB |
@ -1,11 +1,8 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_lib::{
|
||||
ast::{modify::modify_ast_for_sketch, types::Program},
|
||||
engine::EngineManager,
|
||||
executor::{ExecutorContext, MemoryItem, PlaneType, SourceRange},
|
||||
std::StdLib,
|
||||
};
|
||||
use kittycad::types::{ModelingCmd, Point3D};
|
||||
use pretty_assertions::assert_eq;
|
||||
@ -40,11 +37,7 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid
|
||||
let parser = kcl_lib::parser::Parser::new(tokens);
|
||||
let program = parser.ast()?;
|
||||
let mut mem: kcl_lib::executor::ProgramMemory = Default::default();
|
||||
let engine = kcl_lib::engine::EngineConnection::new(ws).await?;
|
||||
let ctx = ExecutorContext {
|
||||
engine,
|
||||
stdlib: Arc::new(StdLib::default()),
|
||||
};
|
||||
let ctx = kcl_lib::executor::ExecutorContext::new(ws).await?;
|
||||
let memory = kcl_lib::executor::execute(program.clone(), &mut mem, kcl_lib::executor::BodyType::Root, &ctx).await?;
|
||||
|
||||
// We need to get the sketch ID.
|
||||
|
Reference in New Issue
Block a user