Linear patterns (#1362)
* add linear patterns Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix clippy Signed-off-by: Jess Frazelle <github@jessfraz.com> * add failing test for serialisation issue * cleanup tests Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup memoryitem Signed-off-by: Jess Frazelle <github@jessfraz.com> * add test to serialize memory item from rust Signed-off-by: Jess Frazelle <github@jessfraz.com> * update test Signed-off-by: Jess Frazelle <github@jessfraz.com> * run cargo sort everywhere Signed-off-by: Jess Frazelle <github@jessfraz.com> * run fmt everywhere Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix typo Signed-off-by: Jess Frazelle <github@jessfraz.com> * clean up linear paterns on re-execute * selections fix for patterns * fix clippy Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2964
docs/kcl/std.json
157
docs/kcl/std.md
@ -40,6 +40,7 @@
|
|||||||
* [`log2`](#log2)
|
* [`log2`](#log2)
|
||||||
* [`max`](#max)
|
* [`max`](#max)
|
||||||
* [`min`](#min)
|
* [`min`](#min)
|
||||||
|
* [`patternLinear`](#patternLinear)
|
||||||
* [`pi`](#pi)
|
* [`pi`](#pi)
|
||||||
* [`pow`](#pow)
|
* [`pow`](#pow)
|
||||||
* [`segAng`](#segAng)
|
* [`segAng`](#segAng)
|
||||||
@ -2430,12 +2431,12 @@ Use a sketch to cut a hole in another sketch.
|
|||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
hole(hole_sketch_group: SketchGroup, sketch_group: SketchGroup) -> SketchGroup
|
hole(hole_sketch_group: SketchGroupSet, sketch_group: SketchGroup) -> SketchGroup
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Arguments
|
#### Arguments
|
||||||
|
|
||||||
* `hole_sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
|
* `hole_sketch_group`: `SketchGroupSet` - A sketch group or a group of sketch groups.
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
@ -2455,6 +2456,7 @@ hole(hole_sketch_group: SketchGroup, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
// The to point.
|
// The to point.
|
||||||
to: [number, number],
|
to: [number, number],
|
||||||
},
|
},
|
||||||
|
type: string,
|
||||||
// The paths in the sketch group.
|
// The paths in the sketch group.
|
||||||
value: [{
|
value: [{
|
||||||
// The from point.
|
// The from point.
|
||||||
@ -2517,6 +2519,9 @@ hole(hole_sketch_group: SketchGroup, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
yAxis: [number, number, number],
|
yAxis: [number, number, number],
|
||||||
// The z-axis of the sketch group base plane in the 3D space
|
// The z-axis of the sketch group base plane in the 3D space
|
||||||
zAxis: [number, number, number],
|
zAxis: [number, number, number],
|
||||||
|
} |
|
||||||
|
{
|
||||||
|
type: string,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
|
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
|
||||||
@ -3475,6 +3480,154 @@ min(args: [number]) -> number
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### patternLinear
|
||||||
|
|
||||||
|
A linear pattern.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
patternLinear(data: LinearPatternData, geometry: Geometry) -> Geometries
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `data`: `LinearPatternData` - Data for a linear pattern.
|
||||||
|
```
|
||||||
|
{
|
||||||
|
// The axis of the pattern. This is a 3D vector.
|
||||||
|
axis: [number, number, number],
|
||||||
|
// The distance between each repetition. This can also be referred to as spacing.
|
||||||
|
distance: number,
|
||||||
|
// The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once.
|
||||||
|
repetitions: number,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* `geometry`: `Geometry` - A geometry.
|
||||||
|
```
|
||||||
|
{
|
||||||
|
// The id of the sketch group.
|
||||||
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
|
// The position of the sketch group.
|
||||||
|
position: [number, number, number],
|
||||||
|
// The rotation of the sketch group base plane.
|
||||||
|
rotation: [number, number, number, number],
|
||||||
|
// The starting path.
|
||||||
|
start: {
|
||||||
|
// The from point.
|
||||||
|
from: [number, number],
|
||||||
|
// The name of the path.
|
||||||
|
name: string,
|
||||||
|
// The to point.
|
||||||
|
to: [number, number],
|
||||||
|
},
|
||||||
|
type: string,
|
||||||
|
// The paths in the sketch group.
|
||||||
|
value: [{
|
||||||
|
// The from point.
|
||||||
|
from: [number, number],
|
||||||
|
// The name of the path.
|
||||||
|
name: string,
|
||||||
|
// The to point.
|
||||||
|
to: [number, number],
|
||||||
|
type: string,
|
||||||
|
} |
|
||||||
|
{
|
||||||
|
// arc's direction
|
||||||
|
ccw: string,
|
||||||
|
// the arc's center
|
||||||
|
center: [number, number],
|
||||||
|
// The from point.
|
||||||
|
from: [number, number],
|
||||||
|
// The name of the path.
|
||||||
|
name: string,
|
||||||
|
// The to point.
|
||||||
|
to: [number, number],
|
||||||
|
type: string,
|
||||||
|
} |
|
||||||
|
{
|
||||||
|
// The from point.
|
||||||
|
from: [number, number],
|
||||||
|
// The name of the path.
|
||||||
|
name: string,
|
||||||
|
// The to point.
|
||||||
|
to: [number, number],
|
||||||
|
type: string,
|
||||||
|
// The x coordinate.
|
||||||
|
x: number,
|
||||||
|
} |
|
||||||
|
{
|
||||||
|
// The from point.
|
||||||
|
from: [number, number],
|
||||||
|
// The name of the path.
|
||||||
|
name: string,
|
||||||
|
// The to point.
|
||||||
|
to: [number, number],
|
||||||
|
type: string,
|
||||||
|
// The x coordinate.
|
||||||
|
x: number,
|
||||||
|
// The y coordinate.
|
||||||
|
y: number,
|
||||||
|
} |
|
||||||
|
{
|
||||||
|
// The from point.
|
||||||
|
from: [number, number],
|
||||||
|
// The name of the path.
|
||||||
|
name: string,
|
||||||
|
// The to point.
|
||||||
|
to: [number, number],
|
||||||
|
type: string,
|
||||||
|
}],
|
||||||
|
// The x-axis of the sketch group base plane in the 3D space
|
||||||
|
xAxis: [number, number, number],
|
||||||
|
// The y-axis of the sketch group base plane in the 3D space
|
||||||
|
yAxis: [number, number, number],
|
||||||
|
// The z-axis of the sketch group base plane in the 3D space
|
||||||
|
zAxis: [number, number, number],
|
||||||
|
} |
|
||||||
|
{
|
||||||
|
// The height of the extrude group.
|
||||||
|
height: number,
|
||||||
|
// The id of the extrude group.
|
||||||
|
id: uuid,
|
||||||
|
// The position of the extrude group.
|
||||||
|
position: [number, number, number],
|
||||||
|
// The rotation of the extrude group.
|
||||||
|
rotation: [number, number, number, number],
|
||||||
|
type: string,
|
||||||
|
// The extrude surfaces.
|
||||||
|
value: [{
|
||||||
|
// The id of the geometry.
|
||||||
|
id: uuid,
|
||||||
|
// The name.
|
||||||
|
name: string,
|
||||||
|
// The position.
|
||||||
|
position: [number, number, number],
|
||||||
|
// The rotation.
|
||||||
|
rotation: [number, number, number, number],
|
||||||
|
// The source range.
|
||||||
|
sourceRange: [number, number],
|
||||||
|
type: string,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `Geometries` - A set of geometry.
|
||||||
|
```
|
||||||
|
{
|
||||||
|
type: string,
|
||||||
|
} |
|
||||||
|
{
|
||||||
|
type: string,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### pi
|
### pi
|
||||||
|
|
||||||
Return the value of `pi`. Archimedes’ constant (π).
|
Return the value of `pi`. Archimedes’ constant (π).
|
||||||
|
@ -810,3 +810,42 @@ const part002 = startSketchOn('XY')
|
|||||||
|> line([-47.44, 0], %)`.replace(/\s/g, '')
|
|> line([-47.44, 0], %)`.replace(/\s/g, '')
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('ProgramMemory can be serialised', async ({ page, context }) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await context.addInitScript(async (token) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const part = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([0, 1], %)
|
||||||
|
|> line([1, 0], %)
|
||||||
|
|> line([0, -1], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(1, %)
|
||||||
|
|> patternLinear({
|
||||||
|
axis: [1, 0, 1],
|
||||||
|
repetitions: 3,
|
||||||
|
distance: 6
|
||||||
|
}, %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
const messages: string[] = []
|
||||||
|
|
||||||
|
// Listen for all console events and push the message text to an array
|
||||||
|
page.on('console', (message) => messages.push(message.text()))
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
|
const forbiddenMessages = ['cannot serialize tagged newtype variant']
|
||||||
|
forbiddenMessages.forEach((forbiddenMessage) => {
|
||||||
|
messages.forEach((message) => {
|
||||||
|
expect(message).not.toContain(forbiddenMessage)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -1075,7 +1075,7 @@ export class EngineCommandManager {
|
|||||||
|
|
||||||
if (command && command.type === 'pending') {
|
if (command && command.type === 'pending') {
|
||||||
const resolve = command.resolve
|
const resolve = command.resolve
|
||||||
this.artifactMap[id] = {
|
const artifact = {
|
||||||
type: 'result',
|
type: 'result',
|
||||||
range: command.range,
|
range: command.range,
|
||||||
pathToNode: command.pathToNode,
|
pathToNode: command.pathToNode,
|
||||||
@ -1083,6 +1083,13 @@ export class EngineCommandManager {
|
|||||||
parentId: command.parentId ? command.parentId : undefined,
|
parentId: command.parentId ? command.parentId : undefined,
|
||||||
data: modelingResponse,
|
data: modelingResponse,
|
||||||
raw: message,
|
raw: message,
|
||||||
|
} as const
|
||||||
|
this.artifactMap[id] = artifact
|
||||||
|
if (command.commandType === 'entity_linear_pattern') {
|
||||||
|
const entities = (modelingResponse as any)?.data?.entity_ids
|
||||||
|
entities?.forEach((entity: string) => {
|
||||||
|
this.artifactMap[entity] = artifact
|
||||||
|
})
|
||||||
}
|
}
|
||||||
resolve({
|
resolve({
|
||||||
id,
|
id,
|
||||||
@ -1194,12 +1201,14 @@ export class EngineCommandManager {
|
|||||||
// this fact is very opaque in the api and docs (as to what should can be deleted).
|
// this fact is very opaque in the api and docs (as to what should can be deleted).
|
||||||
// Using an array is the list is likely to grow.
|
// Using an array is the list is likely to grow.
|
||||||
'start_path',
|
'start_path',
|
||||||
|
'entity_linear_pattern',
|
||||||
]
|
]
|
||||||
if (!artifactTypesToDelete.includes(artifact.commandType)) return
|
if (artifactTypesToDelete.includes(artifact.commandType)) {
|
||||||
artifactsToDelete[id] = artifact
|
artifactsToDelete[id] = artifact
|
||||||
|
}
|
||||||
})
|
})
|
||||||
Object.keys(artifactsToDelete).forEach((id) => {
|
Object.keys(artifactsToDelete).forEach((id) => {
|
||||||
const deletCmd: EngineCommand = {
|
const deleteCmd: EngineCommand = {
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
@ -1207,7 +1216,7 @@ export class EngineCommandManager {
|
|||||||
object_ids: [id],
|
object_ids: [id],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
this.engineConnection?.send(deletCmd)
|
this.engineConnection?.send(deleteCmd)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
addCommandLog(message: CommandLog) {
|
addCommandLog(message: CommandLog) {
|
||||||
|
@ -147,6 +147,7 @@ export const _executor = async (
|
|||||||
)
|
)
|
||||||
return memory
|
return memory
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
console.log(e)
|
||||||
const parsed: RustKclError = JSON.parse(e.toString())
|
const parsed: RustKclError = JSON.parse(e.toString())
|
||||||
const kclError = new KCLError(
|
const kclError = new KCLError(
|
||||||
parsed.kind,
|
parsed.kind,
|
||||||
|
@ -352,7 +352,7 @@ export function processCodeMirrorRanges({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
const idBasedSelections: SelectionToEngine[] = codeBasedSelections
|
const idBasedSelections: SelectionToEngine[] = codeBasedSelections
|
||||||
.map(({ type, range }): null | SelectionToEngine => {
|
.flatMap(({ type, range }): null | SelectionToEngine[] => {
|
||||||
// TODO #868: loops over all artifacts will become inefficient at a large scale
|
// TODO #868: loops over all artifacts will become inefficient at a large scale
|
||||||
const entriesWithOverlap = Object.entries(
|
const entriesWithOverlap = Object.entries(
|
||||||
engineCommandManager.artifactMap || {}
|
engineCommandManager.artifactMap || {}
|
||||||
@ -362,8 +362,7 @@ export function processCodeMirrorRanges({
|
|||||||
: false
|
: false
|
||||||
})
|
})
|
||||||
if (entriesWithOverlap.length) {
|
if (entriesWithOverlap.length) {
|
||||||
const [id, artifact] = entriesWithOverlap?.[0]
|
return entriesWithOverlap.map(([id, artifact]) => ({
|
||||||
return {
|
|
||||||
type,
|
type,
|
||||||
id:
|
id:
|
||||||
type === 'line-end' &&
|
type === 'line-end' &&
|
||||||
@ -371,7 +370,7 @@ export function processCodeMirrorRanges({
|
|||||||
artifact.headVertexId
|
artifact.headVertexId
|
||||||
? artifact.headVertexId
|
? artifact.headVertexId
|
||||||
: id,
|
: id,
|
||||||
}
|
}))
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
@ -52,9 +52,9 @@ debug = true
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"derive-docs",
|
"derive-docs",
|
||||||
"grackle",
|
"grackle",
|
||||||
"kcl",
|
"kcl",
|
||||||
"kcl-macros",
|
"kcl-macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
@ -75,4 +75,4 @@ path = "tests/modify/main.rs"
|
|||||||
# Example: how to point modeling-api at a different repo (e.g. a branch or a local clone)
|
# Example: how to point modeling-api at a different repo (e.g. a branch or a local clone)
|
||||||
# [patch."https://github.com/KittyCAD/modeling-api"]
|
# [patch."https://github.com/KittyCAD/modeling-api"]
|
||||||
# kittycad-execution-plan = { path = "../../../modeling-api/execution-plan" }
|
# kittycad-execution-plan = { path = "../../../modeling-api/execution-plan" }
|
||||||
# kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" }
|
# kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" }
|
||||||
|
@ -6,7 +6,6 @@ edition = "2021"
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
rust-version = "1.73"
|
rust-version = "1.73"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
@ -3,7 +3,6 @@ name = "grackle"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "A new executor for KCL which compiles to Execution Plans"
|
description = "A new executor for KCL which compiles to Execution Plans"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@ -14,7 +13,6 @@ kittycad-modeling-session = { workspace = true }
|
|||||||
thiserror = "1.0.57"
|
thiserror = "1.0.57"
|
||||||
tokio = { version = "1.36.0", features = ["macros", "rt"] }
|
tokio = { version = "1.36.0", features = ["macros", "rt"] }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1"
|
pretty_assertions = "1"
|
||||||
serde_json = "1.0.113"
|
serde_json = "1.0.113"
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
use kcl_lib::ast::types::LiteralIdentifier;
|
|
||||||
use kcl_lib::ast::types::LiteralValue;
|
|
||||||
|
|
||||||
use crate::CompileError;
|
|
||||||
use crate::KclFunction;
|
|
||||||
|
|
||||||
use super::native_functions;
|
|
||||||
use super::Address;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use kcl_lib::ast::types::{LiteralIdentifier, LiteralValue};
|
||||||
|
|
||||||
|
use super::{native_functions, Address};
|
||||||
|
use crate::{CompileError, KclFunction};
|
||||||
|
|
||||||
/// KCL values which can be written to KCEP memory.
|
/// KCL values which can be written to KCEP memory.
|
||||||
/// This is recursive. For example, the bound value might be an array, which itself contains bound values.
|
/// This is recursive. For example, the bound value might be an array, which itself contains bound values.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -17,9 +17,11 @@ use kittycad_execution_plan_traits as ept;
|
|||||||
use kittycad_execution_plan_traits::{Address, NumericPrimitive};
|
use kittycad_execution_plan_traits::{Address, NumericPrimitive};
|
||||||
use kittycad_modeling_session::Session;
|
use kittycad_modeling_session::Session;
|
||||||
|
|
||||||
use self::binding_scope::{BindingScope, EpBinding, GetFnResult};
|
use self::{
|
||||||
use self::error::{CompileError, Error};
|
binding_scope::{BindingScope, EpBinding, GetFnResult},
|
||||||
use self::kcl_value_group::SingleValue;
|
error::{CompileError, Error},
|
||||||
|
kcl_value_group::SingleValue,
|
||||||
|
};
|
||||||
|
|
||||||
/// Execute a KCL program by compiling into an execution plan, then running that.
|
/// Execute a KCL program by compiling into an execution plan, then running that.
|
||||||
pub async fn execute(ast: Program, session: Option<Session>) -> Result<ep::Memory, Error> {
|
pub async fn execute(ast: Program, session: Option<Session>) -> Result<ep::Memory, Error> {
|
||||||
|
@ -5,7 +5,6 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
@ -8,7 +8,6 @@ repository = "https://github.com/KittyCAD/modeling-app"
|
|||||||
rust-version = "1.73"
|
rust-version = "1.73"
|
||||||
authors = ["Jess Frazelle", "Adam Chalmers", "KittyCAD, Inc"]
|
authors = ["Jess Frazelle", "Adam Chalmers", "KittyCAD, Inc"]
|
||||||
keywords = ["kcl", "KittyCAD", "CAD"]
|
keywords = ["kcl", "KittyCAD", "CAD"]
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -10,8 +10,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use serde_json::{Map, Value as JValue};
|
use serde_json::{Map, Value as JValue};
|
||||||
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, Range as LspRange, SymbolKind};
|
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, Range as LspRange, SymbolKind};
|
||||||
|
|
||||||
pub use self::literal_value::LiteralValue;
|
pub use self::{literal_value::LiteralValue, none::KclNone};
|
||||||
pub use self::none::KclNone;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
docs::StdLibFn,
|
docs::StdLibFn,
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
|
@ -4,9 +4,8 @@ use databake::*;
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::executor::{MemoryItem, SourceRange, UserVal};
|
|
||||||
|
|
||||||
use super::ConstraintLevel;
|
use super::ConstraintLevel;
|
||||||
|
use crate::executor::{MemoryItem, SourceRange, UserVal};
|
||||||
|
|
||||||
/// KCL value for an optional parameter which was not given an argument.
|
/// KCL value for an optional parameter which was not given an argument.
|
||||||
/// (remember, parameters are in the function declaration,
|
/// (remember, parameters are in the function declaration,
|
||||||
|
@ -69,13 +69,13 @@ impl EngineConnection {
|
|||||||
async fn start_write_actor(mut tcp_write: WebSocketTcpWrite, mut engine_req_rx: mpsc::Receiver<ToEngineReq>) {
|
async fn start_write_actor(mut tcp_write: WebSocketTcpWrite, mut engine_req_rx: mpsc::Receiver<ToEngineReq>) {
|
||||||
while let Some(req) = engine_req_rx.recv().await {
|
while let Some(req) = engine_req_rx.recv().await {
|
||||||
let ToEngineReq { req, request_sent } = req;
|
let ToEngineReq { req, request_sent } = req;
|
||||||
let res = if let kittycad::types::WebSocketRequest::ModelingCmdReq { cmd, cmd_id: _ } = &req {
|
let res = if let kittycad::types::WebSocketRequest::ModelingCmdReq {
|
||||||
if let kittycad::types::ModelingCmd::ImportFiles { .. } = cmd {
|
cmd: kittycad::types::ModelingCmd::ImportFiles { .. },
|
||||||
// Send it as binary.
|
cmd_id: _,
|
||||||
Self::inner_send_to_engine_binary(req, &mut tcp_write).await
|
} = &req
|
||||||
} else {
|
{
|
||||||
Self::inner_send_to_engine(req, &mut tcp_write).await
|
// Send it as binary.
|
||||||
}
|
Self::inner_send_to_engine_binary(req, &mut tcp_write).await
|
||||||
} else {
|
} else {
|
||||||
Self::inner_send_to_engine(req, &mut tcp_write).await
|
Self::inner_send_to_engine(req, &mut tcp_write).await
|
||||||
};
|
};
|
||||||
|
@ -102,6 +102,7 @@ impl ProgramReturn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A memory item.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
@ -109,7 +110,13 @@ pub enum MemoryItem {
|
|||||||
UserVal(UserVal),
|
UserVal(UserVal),
|
||||||
Plane(Box<Plane>),
|
Plane(Box<Plane>),
|
||||||
SketchGroup(Box<SketchGroup>),
|
SketchGroup(Box<SketchGroup>),
|
||||||
|
SketchGroups {
|
||||||
|
value: Vec<Box<SketchGroup>>,
|
||||||
|
},
|
||||||
ExtrudeGroup(Box<ExtrudeGroup>),
|
ExtrudeGroup(Box<ExtrudeGroup>),
|
||||||
|
ExtrudeGroups {
|
||||||
|
value: Vec<Box<ExtrudeGroup>>,
|
||||||
|
},
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
ExtrudeTransform(Box<ExtrudeTransform>),
|
ExtrudeTransform(Box<ExtrudeTransform>),
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
@ -122,6 +129,42 @@ pub enum MemoryItem {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A geometry.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Geometry {
|
||||||
|
SketchGroup(Box<SketchGroup>),
|
||||||
|
ExtrudeGroup(Box<ExtrudeGroup>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Geometry {
|
||||||
|
pub fn id(&self) -> uuid::Uuid {
|
||||||
|
match self {
|
||||||
|
Geometry::SketchGroup(s) => s.id,
|
||||||
|
Geometry::ExtrudeGroup(e) => e.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of geometry.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Geometries {
|
||||||
|
SketchGroups(Vec<Box<SketchGroup>>),
|
||||||
|
ExtrudeGroups(Vec<Box<ExtrudeGroup>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A sketch group or a group of sketch groups.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
|
pub enum SketchGroupSet {
|
||||||
|
SketchGroup(Box<SketchGroup>),
|
||||||
|
SketchGroups(Vec<Box<SketchGroup>>),
|
||||||
|
}
|
||||||
|
|
||||||
/// A plane.
|
/// A plane.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -212,7 +255,15 @@ impl From<MemoryItem> for Vec<SourceRange> {
|
|||||||
match item {
|
match item {
|
||||||
MemoryItem::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(),
|
||||||
MemoryItem::SketchGroup(s) => s.meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::SketchGroup(s) => s.meta.iter().map(|m| m.source_range).collect(),
|
||||||
|
MemoryItem::SketchGroups { value } => value
|
||||||
|
.iter()
|
||||||
|
.flat_map(|sg| sg.meta.iter().map(|m| m.source_range))
|
||||||
|
.collect(),
|
||||||
MemoryItem::ExtrudeGroup(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::ExtrudeGroup(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
||||||
|
MemoryItem::ExtrudeGroups { value } => value
|
||||||
|
.iter()
|
||||||
|
.flat_map(|eg| eg.meta.iter().map(|m| m.source_range))
|
||||||
|
.collect(),
|
||||||
MemoryItem::ExtrudeTransform(e) => e.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::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
||||||
MemoryItem::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
|
||||||
@ -1040,9 +1091,8 @@ fn assign_args_to_params(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use crate::ast::types::{Identifier, Parameter};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::ast::types::{Identifier, Parameter};
|
||||||
|
|
||||||
pub async fn parse_execute(code: &str) -> Result<ProgramMemory> {
|
pub async fn parse_execute(code: &str) -> Result<ProgramMemory> {
|
||||||
let tokens = crate::token::lexer(code);
|
let tokens = crate::token::lexer(code);
|
||||||
@ -1703,4 +1753,13 @@ show(bracket)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialize_memory_item() {
|
||||||
|
let mem = MemoryItem::ExtrudeGroups {
|
||||||
|
value: Default::default(),
|
||||||
|
};
|
||||||
|
let json = serde_json::to_string(&mem).unwrap();
|
||||||
|
assert_eq!(json, r#"{"type":"ExtrudeGroups","value":[]}"#);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,10 @@ async fn inner_extrude(length: f64, sketch_group: Box<SketchGroup>, args: Args)
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Box::new(ExtrudeGroup {
|
Ok(Box::new(ExtrudeGroup {
|
||||||
id,
|
// Ok so you would think that the id would be the id of the extrude group,
|
||||||
|
// that we passed in to the function, but it's actually the id of the
|
||||||
|
// sketch group.
|
||||||
|
id: sketch_group.id,
|
||||||
// TODO, this is just an empty array now, should be deleted. This
|
// TODO, this is just an empty array now, should be deleted. This
|
||||||
// comment was originally in the JS code.
|
// comment was originally in the JS code.
|
||||||
value: Default::default(),
|
value: Default::default(),
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
pub mod extrude;
|
pub mod extrude;
|
||||||
pub mod kcl_stdlib;
|
pub mod kcl_stdlib;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
|
pub mod patterns;
|
||||||
pub mod segment;
|
pub mod segment;
|
||||||
pub mod shapes;
|
pub mod shapes;
|
||||||
pub mod sketch;
|
pub mod sketch;
|
||||||
@ -24,7 +25,9 @@ use crate::{
|
|||||||
docs::StdLibFn,
|
docs::StdLibFn,
|
||||||
engine::EngineManager,
|
engine::EngineManager,
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{ExecutorContext, ExtrudeGroup, MemoryItem, Metadata, Plane, SketchGroup, SourceRange},
|
executor::{
|
||||||
|
ExecutorContext, ExtrudeGroup, Geometry, MemoryItem, Metadata, Plane, SketchGroup, SketchGroupSet, SourceRange,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type StdFn = fn(Args) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<MemoryItem, KclError>>>>;
|
pub type StdFn = fn(Args) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<MemoryItem, KclError>>>>;
|
||||||
@ -67,6 +70,7 @@ lazy_static! {
|
|||||||
Box::new(crate::std::sketch::TangentialArcTo),
|
Box::new(crate::std::sketch::TangentialArcTo),
|
||||||
Box::new(crate::std::sketch::BezierCurve),
|
Box::new(crate::std::sketch::BezierCurve),
|
||||||
Box::new(crate::std::sketch::Hole),
|
Box::new(crate::std::sketch::Hole),
|
||||||
|
Box::new(crate::std::patterns::PatternLinear),
|
||||||
Box::new(crate::std::math::Cos),
|
Box::new(crate::std::math::Cos),
|
||||||
Box::new(crate::std::math::Sin),
|
Box::new(crate::std::math::Sin),
|
||||||
Box::new(crate::std::math::Tan),
|
Box::new(crate::std::math::Tan),
|
||||||
@ -284,7 +288,7 @@ impl Args {
|
|||||||
Ok((segment_name, sketch_group))
|
Ok((segment_name, sketch_group))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sketch_groups(&self) -> Result<(Box<SketchGroup>, Box<SketchGroup>), KclError> {
|
fn get_sketch_groups(&self) -> Result<(SketchGroupSet, Box<SketchGroup>), KclError> {
|
||||||
let first_value = self.args.first().ok_or_else(|| {
|
let first_value = self.args.first().ok_or_else(|| {
|
||||||
KclError::Type(KclErrorDetails {
|
KclError::Type(KclErrorDetails {
|
||||||
message: format!("Expected a SketchGroup as the first argument, found `{:?}`", self.args),
|
message: format!("Expected a SketchGroup as the first argument, found `{:?}`", self.args),
|
||||||
@ -292,11 +296,16 @@ impl Args {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let sketch_group = if let MemoryItem::SketchGroup(sg) = first_value {
|
let sketch_set = if let MemoryItem::SketchGroup(sg) = first_value {
|
||||||
sg.clone()
|
SketchGroupSet::SketchGroup(sg.clone())
|
||||||
|
} else if let MemoryItem::SketchGroups { value } = first_value {
|
||||||
|
SketchGroupSet::SketchGroups(value.clone())
|
||||||
} else {
|
} else {
|
||||||
return Err(KclError::Type(KclErrorDetails {
|
return Err(KclError::Type(KclErrorDetails {
|
||||||
message: format!("Expected a SketchGroup as the first argument, found `{:?}`", self.args),
|
message: format!(
|
||||||
|
"Expected a SketchGroup or Vector of SketchGroups as the first argument, found `{:?}`",
|
||||||
|
self.args
|
||||||
|
),
|
||||||
source_ranges: vec![self.source_range],
|
source_ranges: vec![self.source_range],
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
@ -308,7 +317,7 @@ impl Args {
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let second_sketch_group = if let MemoryItem::SketchGroup(sg) = second_value {
|
let sketch_group = if let MemoryItem::SketchGroup(sg) = second_value {
|
||||||
sg.clone()
|
sg.clone()
|
||||||
} else {
|
} else {
|
||||||
return Err(KclError::Type(KclErrorDetails {
|
return Err(KclError::Type(KclErrorDetails {
|
||||||
@ -317,7 +326,7 @@ impl Args {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok((sketch_group, second_sketch_group))
|
Ok((sketch_set, sketch_group))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_sketch_group(&self) -> Result<Box<SketchGroup>, KclError> {
|
fn get_sketch_group(&self) -> Result<Box<SketchGroup>, KclError> {
|
||||||
@ -400,6 +409,49 @@ impl Args {
|
|||||||
Ok((data, sketch_group))
|
Ok((data, sketch_group))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_data_and_geometry<T: serde::de::DeserializeOwned>(&self) -> Result<(T, Geometry), 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: T = serde_json::from_value(first_value).map_err(|e| {
|
||||||
|
KclError::Type(KclErrorDetails {
|
||||||
|
message: format!("Failed to deserialize struct from JSON: {}", e),
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let second_value = self.args.get(1).ok_or_else(|| {
|
||||||
|
KclError::Type(KclErrorDetails {
|
||||||
|
message: format!("Expected a SketchGroup as the second argument, found `{:?}`", self.args),
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let geometry = if let MemoryItem::SketchGroup(sg) = second_value {
|
||||||
|
Geometry::SketchGroup(sg.clone())
|
||||||
|
} else if let MemoryItem::ExtrudeGroup(eg) = second_value {
|
||||||
|
Geometry::ExtrudeGroup(eg.clone())
|
||||||
|
} else {
|
||||||
|
return Err(KclError::Type(KclErrorDetails {
|
||||||
|
message: format!(
|
||||||
|
"Expected a SketchGroup or ExtrudeGroup as the second argument, found `{:?}`",
|
||||||
|
self.args
|
||||||
|
),
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((data, geometry))
|
||||||
|
}
|
||||||
|
|
||||||
fn get_data_and_plane<T: serde::de::DeserializeOwned>(&self) -> Result<(T, Box<Plane>), KclError> {
|
fn get_data_and_plane<T: serde::de::DeserializeOwned>(&self) -> Result<(T, Box<Plane>), KclError> {
|
||||||
let first_value = self
|
let first_value = self
|
||||||
.args
|
.args
|
||||||
@ -741,8 +793,6 @@ mod tests {
|
|||||||
buf.push_str(&fn_docs);
|
buf.push_str(&fn_docs);
|
||||||
}
|
}
|
||||||
|
|
||||||
// uncomment to update
|
|
||||||
// std::fs::write("../../../docs/kcl/std.md", &buf).expect("Unable to write to file");
|
|
||||||
expectorate::assert_contents("../../../docs/kcl/std.md", &buf);
|
expectorate::assert_contents("../../../docs/kcl/std.md", &buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -756,8 +806,6 @@ mod tests {
|
|||||||
let internal_fn = stdlib.fns.get(key).unwrap();
|
let internal_fn = stdlib.fns.get(key).unwrap();
|
||||||
json_data.push(internal_fn.to_json().unwrap());
|
json_data.push(internal_fn.to_json().unwrap());
|
||||||
}
|
}
|
||||||
// uncomment to update
|
|
||||||
// std::fs::write("../../../docs/kcl/std.json", json_output).expect("Unable to write to file");
|
|
||||||
expectorate::assert_contents(
|
expectorate::assert_contents(
|
||||||
"../../../docs/kcl/std.json",
|
"../../../docs/kcl/std.json",
|
||||||
&serde_json::to_string_pretty(&json_data).unwrap(),
|
&serde_json::to_string_pretty(&json_data).unwrap(),
|
||||||
|
101
src/wasm-lib/kcl/src/std/patterns.rs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
//! Standard library patterns.
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use derive_docs::stdlib;
|
||||||
|
use kittycad::types::ModelingCmd;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
errors::{KclError, KclErrorDetails},
|
||||||
|
executor::{Geometries, Geometry, MemoryItem},
|
||||||
|
std::Args,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Data for a linear pattern.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct LinearPatternData {
|
||||||
|
/// The number of repetitions. Must be greater than 0.
|
||||||
|
/// This excludes the original entity. For example, if `repetitions` is 1,
|
||||||
|
/// the original entity will be copied once.
|
||||||
|
pub repetitions: usize,
|
||||||
|
/// The distance between each repetition. This can also be referred to as spacing.
|
||||||
|
pub distance: f64,
|
||||||
|
/// The axis of the pattern. This is a 3D vector.
|
||||||
|
pub axis: [f64; 3],
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A linear pattern.
|
||||||
|
pub async fn pattern_linear(args: Args) -> Result<MemoryItem, KclError> {
|
||||||
|
let (data, geometry): (LinearPatternData, Geometry) = args.get_data_and_geometry()?;
|
||||||
|
|
||||||
|
if data.axis == [0.0, 0.0, 0.0] {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message:
|
||||||
|
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
|
||||||
|
.to_string(),
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_geometries = inner_pattern_linear(data, geometry, args).await?;
|
||||||
|
match new_geometries {
|
||||||
|
Geometries::SketchGroups(sketch_groups) => Ok(MemoryItem::SketchGroups { value: sketch_groups }),
|
||||||
|
Geometries::ExtrudeGroups(extrude_groups) => Ok(MemoryItem::ExtrudeGroups { value: extrude_groups }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A linear pattern.
|
||||||
|
#[stdlib {
|
||||||
|
name = "patternLinear",
|
||||||
|
}]
|
||||||
|
async fn inner_pattern_linear(data: LinearPatternData, geometry: Geometry, args: Args) -> Result<Geometries, KclError> {
|
||||||
|
let id = uuid::Uuid::new_v4();
|
||||||
|
|
||||||
|
let resp = args
|
||||||
|
.send_modeling_cmd(
|
||||||
|
id,
|
||||||
|
ModelingCmd::EntityLinearPattern {
|
||||||
|
axis: data.axis.into(),
|
||||||
|
entity_id: geometry.id(),
|
||||||
|
num_repetitions: data.repetitions as u32,
|
||||||
|
spacing: data.distance,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||||
|
modeling_response: kittycad::types::OkModelingCmdResponse::EntityLinearPattern { data: pattern_info },
|
||||||
|
} = &resp
|
||||||
|
else {
|
||||||
|
return Err(KclError::Engine(KclErrorDetails {
|
||||||
|
message: format!("EntityLinearPattern response was not as expected: {:?}", resp),
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
let geometries = match geometry {
|
||||||
|
Geometry::SketchGroup(sketch_group) => {
|
||||||
|
let mut geometries = vec![sketch_group.clone()];
|
||||||
|
for id in pattern_info.entity_ids.iter() {
|
||||||
|
let mut new_sketch_group = sketch_group.clone();
|
||||||
|
new_sketch_group.id = *id;
|
||||||
|
geometries.push(new_sketch_group);
|
||||||
|
}
|
||||||
|
Geometries::SketchGroups(geometries)
|
||||||
|
}
|
||||||
|
Geometry::ExtrudeGroup(extrude_group) => {
|
||||||
|
let mut geometries = vec![extrude_group.clone()];
|
||||||
|
for id in pattern_info.entity_ids.iter() {
|
||||||
|
let mut new_extrude_group = extrude_group.clone();
|
||||||
|
new_extrude_group.id = *id;
|
||||||
|
geometries.push(new_extrude_group);
|
||||||
|
}
|
||||||
|
Geometries::ExtrudeGroups(geometries)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(geometries)
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
//! Functions related to sketching.
|
//! Functions related to sketching.
|
||||||
|
|
||||||
use crate::std::utils::{get_tangent_point_from_previous_arc, get_tangential_arc_to_info, TangentialArcInfoInput};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use derive_docs::stdlib;
|
use derive_docs::stdlib;
|
||||||
use kittycad::types::{Angle, ModelingCmd, Point3D};
|
use kittycad::types::{Angle, ModelingCmd, Point3D};
|
||||||
@ -8,14 +7,17 @@ use kittycad_execution_plan_macros::ExecutionPlanValue;
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::executor::SourceRange;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{
|
executor::{
|
||||||
BasePath, GeoMeta, MemoryItem, Path, Plane, PlaneType, Point2d, Point3d, Position, Rotation, SketchGroup,
|
BasePath, GeoMeta, MemoryItem, Path, Plane, PlaneType, Point2d, Point3d, Position, Rotation, SketchGroup,
|
||||||
|
SketchGroupSet, SourceRange,
|
||||||
},
|
},
|
||||||
std::{
|
std::{
|
||||||
utils::{arc_angles, arc_center_and_end, get_x_component, get_y_component, intersection_with_parallel_line},
|
utils::{
|
||||||
|
arc_angles, arc_center_and_end, get_tangent_point_from_previous_arc, get_tangential_arc_to_info,
|
||||||
|
get_x_component, get_y_component, intersection_with_parallel_line, TangentialArcInfoInput,
|
||||||
|
},
|
||||||
Args,
|
Args,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -1419,7 +1421,7 @@ async fn inner_bezier_curve(
|
|||||||
|
|
||||||
/// Use a sketch to cut a hole in another sketch.
|
/// Use a sketch to cut a hole in another sketch.
|
||||||
pub async fn hole(args: Args) -> Result<MemoryItem, KclError> {
|
pub async fn hole(args: Args) -> Result<MemoryItem, KclError> {
|
||||||
let (hole_sketch_group, sketch_group): (Box<SketchGroup>, Box<SketchGroup>) = args.get_sketch_groups()?;
|
let (hole_sketch_group, sketch_group): (SketchGroupSet, Box<SketchGroup>) = args.get_sketch_groups()?;
|
||||||
|
|
||||||
let new_sketch_group = inner_hole(hole_sketch_group, sketch_group, args).await?;
|
let new_sketch_group = inner_hole(hole_sketch_group, sketch_group, args).await?;
|
||||||
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
Ok(MemoryItem::SketchGroup(new_sketch_group))
|
||||||
@ -1430,31 +1432,57 @@ pub async fn hole(args: Args) -> Result<MemoryItem, KclError> {
|
|||||||
name = "hole",
|
name = "hole",
|
||||||
}]
|
}]
|
||||||
async fn inner_hole(
|
async fn inner_hole(
|
||||||
hole_sketch_group: Box<SketchGroup>,
|
hole_sketch_group: SketchGroupSet,
|
||||||
sketch_group: Box<SketchGroup>,
|
sketch_group: Box<SketchGroup>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
//TODO: batch these (once we have batch)
|
//TODO: batch these (once we have batch)
|
||||||
|
|
||||||
args.send_modeling_cmd(
|
match hole_sketch_group {
|
||||||
uuid::Uuid::new_v4(),
|
SketchGroupSet::SketchGroup(hole_sketch_group) => {
|
||||||
ModelingCmd::Solid2DAddHole {
|
args.send_modeling_cmd(
|
||||||
object_id: sketch_group.id,
|
uuid::Uuid::new_v4(),
|
||||||
hole_id: hole_sketch_group.id,
|
ModelingCmd::Solid2DAddHole {
|
||||||
},
|
object_id: sketch_group.id,
|
||||||
)
|
hole_id: hole_sketch_group.id,
|
||||||
.await?;
|
},
|
||||||
|
)
|
||||||
//suggestion (mike)
|
.await?;
|
||||||
//we also hide the source hole since its essentially "consumed" by this operation
|
// suggestion (mike)
|
||||||
args.send_modeling_cmd(
|
// we also hide the source hole since its essentially "consumed" by this operation
|
||||||
uuid::Uuid::new_v4(),
|
args.send_modeling_cmd(
|
||||||
ModelingCmd::ObjectVisible {
|
uuid::Uuid::new_v4(),
|
||||||
object_id: hole_sketch_group.id,
|
ModelingCmd::ObjectVisible {
|
||||||
hidden: true,
|
object_id: hole_sketch_group.id,
|
||||||
},
|
hidden: true,
|
||||||
)
|
},
|
||||||
.await?;
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
object_id: sketch_group.id,
|
||||||
|
hole_id: hole_sketch_group.id,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
// suggestion (mike)
|
||||||
|
// we also hide the source hole since its essentially "consumed" by this operation
|
||||||
|
args.send_modeling_cmd(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
ModelingCmd::ObjectVisible {
|
||||||
|
object_id: hole_sketch_group.id,
|
||||||
|
hidden: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: should we modify the sketch group to include the hole data, probably?
|
// TODO: should we modify the sketch group to include the hole data, probably?
|
||||||
|
|
||||||
|
@ -638,9 +638,10 @@ pub fn get_tangential_arc_to_info(input: TangentialArcInfoInput) -> TangentialAr
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod get_tangential_arc_to_info_tests {
|
mod get_tangential_arc_to_info_tests {
|
||||||
use super::*;
|
|
||||||
use approx::assert_relative_eq;
|
use approx::assert_relative_eq;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
fn round_to_three_decimals(num: f64) -> f64 {
|
fn round_to_three_decimals(num: f64) -> f64 {
|
||||||
(num * 1000.0).round() / 1000.0
|
(num * 1000.0).round() / 1000.0
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use winnow::{
|
|||||||
error::{ContextError, ParseError},
|
error::{ContextError, ParseError},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
stream::{Location, Stream},
|
stream::{Location, Stream},
|
||||||
token::{any, none_of, one_of, take_till1, take_until0},
|
token::{any, none_of, one_of, take_till, take_until},
|
||||||
Located,
|
Located,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -47,13 +47,13 @@ pub fn token(i: &mut Located<&str>) -> PResult<Token> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn block_comment(i: &mut Located<&str>) -> PResult<Token> {
|
fn block_comment(i: &mut Located<&str>) -> PResult<Token> {
|
||||||
let inner = ("/*", take_until0("*/"), "*/").recognize();
|
let inner = ("/*", take_until(0.., "*/"), "*/").recognize();
|
||||||
let (value, range) = inner.with_span().parse_next(i)?;
|
let (value, range) = inner.with_span().parse_next(i)?;
|
||||||
Ok(Token::from_range(range, TokenType::BlockComment, value.to_string()))
|
Ok(Token::from_range(range, TokenType::BlockComment, value.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line_comment(i: &mut Located<&str>) -> PResult<Token> {
|
fn line_comment(i: &mut Located<&str>) -> PResult<Token> {
|
||||||
let inner = (r#"//"#, take_till1(['\n', '\r'])).recognize();
|
let inner = (r#"//"#, take_till(1.., ['\n', '\r'])).recognize();
|
||||||
let (value, range) = inner.with_span().parse_next(i)?;
|
let (value, range) = inner.with_span().parse_next(i)?;
|
||||||
Ok(Token::from_range(range, TokenType::LineComment, value.to_string()))
|
Ok(Token::from_range(range, TokenType::LineComment, value.to_string()))
|
||||||
}
|
}
|
||||||
|
@ -481,10 +481,10 @@ async fn optional_params() {
|
|||||||
|> startProfileAt(pos, %)
|
|> startProfileAt(pos, %)
|
||||||
|> arc({angle_end: 360, angle_start: 0, radius: radius}, %)
|
|> arc({angle_end: 360, angle_start: 0, radius: radius}, %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|
|
||||||
return sg
|
return sg
|
||||||
}
|
}
|
||||||
|
|
||||||
show(circle([2, 2], 20))
|
show(circle([2, 2], 20))
|
||||||
"#;
|
"#;
|
||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
@ -559,3 +559,136 @@ circle([0,0], 22) |> extrude(14, %)"#;
|
|||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/top_level_expression.png", &result, 0.999);
|
twenty_twenty::assert_image("tests/executor/outputs/top_level_expression.png", &result, 0.999);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_patterns_linear_basic() {
|
||||||
|
let code = r#"fn circle = (pos, radius) => {
|
||||||
|
const sg = startSketchOn('XY')
|
||||||
|
|> startProfileAt([pos[0] + radius, pos[1]], %)
|
||||||
|
|> arc({
|
||||||
|
angle_end: 360,
|
||||||
|
angle_start: 0,
|
||||||
|
radius: radius
|
||||||
|
}, %)
|
||||||
|
|> close(%)
|
||||||
|
return sg
|
||||||
|
}
|
||||||
|
|
||||||
|
const part = circle([0,0], 2)
|
||||||
|
|> patternLinear({axis: [0,0,1], repetitions: 12, distance: 2}, %)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
|
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic.png", &result, 0.999);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_patterns_linear_basic_3d() {
|
||||||
|
let code = r#"fn circle = (pos, radius) => {
|
||||||
|
const sg = startSketchOn('XY')
|
||||||
|
|> startProfileAt([pos[0] + radius, pos[1]], %)
|
||||||
|
|> arc({
|
||||||
|
angle_end: 360,
|
||||||
|
angle_start: 0,
|
||||||
|
radius: radius
|
||||||
|
}, %)
|
||||||
|
|> close(%)
|
||||||
|
return sg
|
||||||
|
}
|
||||||
|
|
||||||
|
const part = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([0,1], %)
|
||||||
|
|> line([1, 0], %)
|
||||||
|
|> line([0, -1], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(1, %)
|
||||||
|
|> patternLinear({axis: [1, 0,1], repetitions: 3, distance: 6}, %)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
|
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic_3d.png", &result, 0.999);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_patterns_linear_basic_negative_distance() {
|
||||||
|
let code = r#"fn circle = (pos, radius) => {
|
||||||
|
const sg = startSketchOn('XY')
|
||||||
|
|> startProfileAt([pos[0] + radius, pos[1]], %)
|
||||||
|
|> arc({
|
||||||
|
angle_end: 360,
|
||||||
|
angle_start: 0,
|
||||||
|
radius: radius
|
||||||
|
}, %)
|
||||||
|
|> close(%)
|
||||||
|
return sg
|
||||||
|
}
|
||||||
|
|
||||||
|
const part = circle([0,0], 2)
|
||||||
|
|> patternLinear({axis: [0,0,1], repetitions: 12, distance: -2}, %)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
|
twenty_twenty::assert_image(
|
||||||
|
"tests/executor/outputs/patterns_linear_basic_negative_distance.png",
|
||||||
|
&result,
|
||||||
|
0.999,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_patterns_linear_basic_negative_axis() {
|
||||||
|
let code = r#"fn circle = (pos, radius) => {
|
||||||
|
const sg = startSketchOn('XY')
|
||||||
|
|> startProfileAt([pos[0] + radius, pos[1]], %)
|
||||||
|
|> arc({
|
||||||
|
angle_end: 360,
|
||||||
|
angle_start: 0,
|
||||||
|
radius: radius
|
||||||
|
}, %)
|
||||||
|
|> close(%)
|
||||||
|
return sg
|
||||||
|
}
|
||||||
|
|
||||||
|
const part = circle([0,0], 2)
|
||||||
|
|> patternLinear({axis: [0,0,-1], repetitions: 12, distance: 2}, %)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
|
twenty_twenty::assert_image(
|
||||||
|
"tests/executor/outputs/patterns_linear_basic_negative_axis.png",
|
||||||
|
&result,
|
||||||
|
0.999,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn serial_test_patterns_linear_basic_holes() {
|
||||||
|
let code = r#"fn circle = (pos, radius) => {
|
||||||
|
const sg = startSketchOn('XY')
|
||||||
|
|> startProfileAt([pos[0] + radius, pos[1]], %)
|
||||||
|
|> arc({
|
||||||
|
angle_end: 360,
|
||||||
|
angle_start: 0,
|
||||||
|
radius: radius
|
||||||
|
}, %)
|
||||||
|
|> close(%)
|
||||||
|
return sg
|
||||||
|
}
|
||||||
|
|
||||||
|
const circles = circle([5, 5], 1)
|
||||||
|
|> patternLinear({axis: [1,1,0], repetitions: 12, distance: 3}, %)
|
||||||
|
|
||||||
|
const rectangle = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([0, 50], %)
|
||||||
|
|> line([50, 0], %)
|
||||||
|
|> line([0, -50], %)
|
||||||
|
|> close(%)
|
||||||
|
|> hole(circles, %)
|
||||||
|
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
|
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic_holes.png", &result, 0.999);
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
BIN
src/wasm-lib/tests/executor/outputs/patterns_linear_basic.png
Normal file
After Width: | Height: | Size: 72 KiB |
BIN
src/wasm-lib/tests/executor/outputs/patterns_linear_basic_3d.png
Normal file
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 71 KiB |