Compare commits

...

50 Commits

Author SHA1 Message Date
c26709c06a Merge remote-tracking branch 'origin' into kurt-circle 2024-09-10 21:03:22 +10:00
fa580d4035 clean up by grouping segment labels 2024-09-10 17:30:21 +10:00
93d3df4877 more renaming 2024-09-10 17:23:11 +10:00
9fafc90c57 remove surplus | 'radius's 2024-09-10 13:23:21 +10:00
04e82bf728 comments and naming tweaks 2024-09-10 13:15:11 +10:00
0abe4c4082 some xstate v5 stuff that got missed from merge with main 2024-09-10 11:51:03 +10:00
266c601fd4 Merge remote-tracking branch 'origin' into kurt-circle 2024-09-10 11:34:42 +10:00
28a63194c7 more clean up 2024-09-10 01:49:07 +10:00
a6aff874bb more types clean up 2024-09-10 01:43:34 +10:00
25080e9895 clean up create call back double function stuff 2024-09-10 01:08:57 +10:00
99ffc82ffa change types that that effected a lot of places 2024-09-09 22:58:36 +10:00
4219a2c31d more minor clean up renaming 2024-09-09 22:38:02 +10:00
bb265ca833 few more minor renames 2024-09-09 22:10:02 +10:00
63dea715bc some renames 2024-09-09 22:04:43 +10:00
15871e6245 cargo fmt 2024-09-09 20:32:57 +10:00
7ab9b3ee46 Merge remote-tracking branch 'origin' into kurt-circle 2024-09-09 20:29:47 +10:00
e794c5b0e9 Update some circle function calls and twenty twenty stuff 2024-09-09 20:28:24 +10:00
a1dd884013 stdlib docs update 2024-09-09 19:34:12 +10:00
994f71bce5 make clippy happy 2024-09-09 19:30:51 +10:00
b55652f9b7 fix circle line thickness and handles scale correctly on zoom 2024-09-09 17:10:40 +10:00
9d6a09766c A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest) 2024-09-09 06:55:27 +00:00
840e75e5a1 fix dragging circle circumference error 2024-09-09 16:37:41 +10:00
466511ac46 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest) 2024-09-09 16:35:54 +10:00
85abcde086 make sure behaviour is sane betwen center and radius clicks 2024-09-09 16:28:22 +10:00
48d347b4de add e2e tests 2024-09-09 16:18:39 +10:00
b3c4aec8db try increas playwright timeout 2024-09-09 14:36:00 +10:00
a59de4efa3 hopefully fix e2e tests 2024-09-09 13:43:52 +10:00
57a460f57a update samples 2024-09-09 13:23:34 +10:00
59284ffa50 rely a little less on arbitary indexs 2024-09-09 12:45:59 +10:00
5592185371 Merge remote-tracking branch 'origin' into kurt-circle 2024-09-09 12:00:32 +10:00
998b194712 fix unit tests 2024-09-09 11:52:23 +10:00
6753a9e9f8 make lint happy 2024-09-07 15:40:59 +10:00
abb8b95b88 Merge remote-tracking branch 'origin' into kurt-circle 2024-09-07 09:09:33 +10:00
94b0510521 big refactor of var values for astMods 2024-09-07 09:04:07 +10:00
72e522dd51 change circle args 2024-09-04 18:55:05 +10:00
706af591c5 Merge remote-tracking branch 'origin' into kurt-circle 2024-09-02 20:35:39 +10:00
a1f8ac4548 Merge remote-tracking branch 'origin' into kurt-circle 2024-08-29 15:05:33 +10:00
0d5a8aea93 change start end angle for draft circle so that it animates from the oposite side to the handl 2024-08-29 15:05:22 +10:00
3e4316b614 Merge remote-tracking branch 'origin' into kurt-circle 2024-08-28 20:07:37 +10:00
c6577a5ae6 remove error when circle has second click 2024-08-28 20:03:36 +10:00
379960561d fix weird tear down issue 2024-08-28 19:57:26 +10:00
8d912fa62a make sure cursor is consistent with other tool tips 2024-08-28 19:41:16 +10:00
bfd92a626b remove extra segment handle 2024-08-28 19:40:59 +10:00
44e07ca6e5 fix draft arc not being complete 2024-08-28 19:23:05 +10:00
22c854815a can use circle tool tip 2024-08-28 19:07:26 +10:00
3409aa5cfd Merge remote-tracking branch 'origin' into kurt-circle 2024-08-27 21:26:59 +10:00
c56c446e15 add circle segment to searches 2024-08-24 21:59:35 +10:00
1988888699 update is editing existing sketch 2024-08-24 21:43:39 +10:00
acfa95f728 basic circle edit working 2024-08-24 21:09:21 +10:00
b7c71c01cf xstate start 2024-08-24 20:36:07 +10:00
81 changed files with 3375 additions and 1222 deletions

View File

@ -262,7 +262,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-latest, windows-latest, macos-14] os: [ubuntu-latest, windows-latest, macos-14]
timeout-minutes: 40 timeout-minutes: 60
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
needs: check-rust-changes needs: check-rust-changes
steps: steps:

View File

@ -270,6 +270,26 @@ const extrusion = extrude(5, sketch001)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -479,6 +499,26 @@ const extrusion = extrude(5, sketch001)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],

View File

@ -274,6 +274,26 @@ const extrusion = extrude(5, sketch001)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -483,6 +503,26 @@ const extrusion = extrude(5, sketch001)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],

View File

@ -189,6 +189,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -398,6 +418,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -609,6 +649,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -818,6 +878,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],

View File

@ -188,6 +188,26 @@ const extrusion = extrude(10, sketch001)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -397,6 +417,26 @@ const extrusion = extrude(10, sketch001)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -608,6 +648,26 @@ const extrusion = extrude(10, sketch001)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -817,6 +877,26 @@ const extrusion = extrude(10, sketch001)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],

View File

@ -190,6 +190,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -399,6 +419,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -610,6 +650,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -819,6 +879,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],

View File

@ -282,6 +282,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -491,6 +511,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -702,6 +742,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -911,6 +971,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],

View File

@ -187,6 +187,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -396,6 +416,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -607,6 +647,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -816,6 +876,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],

View File

@ -187,6 +187,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -396,6 +416,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -607,6 +647,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -816,6 +876,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],

View File

@ -200,6 +200,26 @@ const exampleSketch = startSketchOn('XZ')
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -409,6 +429,26 @@ const exampleSketch = startSketchOn('XZ')
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -620,6 +660,26 @@ const exampleSketch = startSketchOn('XZ')
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -829,6 +889,26 @@ const exampleSketch = startSketchOn('XZ')
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],

View File

@ -193,6 +193,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -402,6 +422,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -613,6 +653,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -822,6 +882,26 @@ const example = extrude(10, exampleSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],

View File

@ -415,6 +415,26 @@ const mountingPlate = extrude(thickness, mountingPlateSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],
@ -819,6 +839,26 @@ const mountingPlate = extrude(thickness, mountingPlateSketch)
to: [number, number], to: [number, number],
type: "TangentialArc", type: "TangentialArc",
} | } |
{
// arc's direction
ccw: bool,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// the arc's radius
radius: number,
// The tag of the path.
tag: {
digest: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number],
end: number,
start: number,
value: string,
},
// The to point.
to: [number, number],
type: "Circle",
} |
{ {
// The from point. // The from point.
from: [number, number], from: [number, number],

View File

@ -558,7 +558,7 @@ test.describe('Editor tests', () => {
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
await page.keyboard.type(`const extrusion = startSketchOn('XY') await page.keyboard.type(`const extrusion = startSketchOn('XY')
|> circle([0, 0], dia/2, %) |> circle({ center: [0, 0], radius: dia/2 }, %)
|> hole(squareHole(length, width, height), %) |> hole(squareHole(length, width, height), %)
|> extrude(height, %)`) |> extrude(height, %)`)

View File

@ -149,14 +149,16 @@ test.describe('Sketch tests', () => {
await page.getByRole('button', { name: 'line Line', exact: true }).click() await page.getByRole('button', { name: 'line Line', exact: true }).click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(700, 200) await expect(async () => {
await page.mouse.click(700, 200)
await expect.poll(u.normalisedEditorCode) await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
.toBe(`const sketch001 = startSketchOn('XZ') .toBe(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([12.34, -12.34], %) |> startProfileAt([12.34, -12.34], %)
|> line([-12.34, 12.34], %) |> line([-12.34, 12.34], %)
`) `)
}).toPass({ timeout: 40_000, intervals: [1_000] })
}) })
test('Can exit selection of face', async ({ page }) => { test('Can exit selection of face', async ({ page }) => {
// Load the app with the code panes // Load the app with the code panes
@ -344,6 +346,92 @@ test.describe('Sketch tests', () => {
}) })
}) })
test('Can edit a circle center and radius by dragging its handles', async ({
page,
}) => {
const u = await getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const sketch001 = startSketchOn('XZ')
|> circle({ center: [4.61, -5.01], radius: 8 }, %)`
)
})
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await page.waitForTimeout(100)
await u.openAndClearDebugPanel()
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
vantage: { x: 0, y: -1250, z: 580 },
center: { x: 0, y: 0, z: 0 },
up: { x: 0, y: 0, z: 1 },
},
})
await page.waitForTimeout(100)
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await page.waitForTimeout(100)
const startPX = [667, 325]
const dragPX = 40
await page
.getByText('circle({ center: [4.61, -5.01], radius: 8 }, %)')
.click()
await expect(
page.getByRole('button', { name: 'Edit Sketch' })
).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400)
let prevContent = await page.locator('.cm-content').innerText()
await expect(page.getByTestId('segment-overlay')).toHaveCount(1)
await test.step('drag circle center handle', async () => {
await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: startPX[0], y: startPX[1] },
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] - dragPX },
})
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
})
await test.step('drag circle radius handle', async () => {
await page.waitForTimeout(100)
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
await page.waitForTimeout(100)
await page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX },
})
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
})
// expect the code to have changed
await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ')
|> circle({ center: [7.26, -2.37], radius: 11.79 }, %)
`)
})
test('Can edit a sketch that has been extruded in the same pipe', async ({ test('Can edit a sketch that has been extruded in the same pipe', async ({
page, page,
}) => { }) => {

View File

@ -532,6 +532,64 @@ test(
}) })
} }
) )
test(
'Draft circle should look right',
{ tag: '@snapshot' },
async ({ page, context }) => {
// FIXME: Skip on macos its being weird.
// test.skip(process.platform === 'darwin', 'Skip on macos')
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
200
)
// select a plane
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const sketch001 = startSketchOn('XZ')`
)
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
await u.closeDebugPanel()
const startXPx = 600
// Equip the rectangle tool
// await page.getByRole('button', { name: 'line Line', exact: true }).click()
await page.getByTestId('circle-center').click()
// Draw the rectangle
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 10, { steps: 5 })
// Ensure the draft rectangle looks the same as it usually does
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
await expect(page.locator('.cm-content')).toHaveText(
`const sketch001 = startSketchOn('XZ')
|> circle({ center: [14.44, -2.44], radius: 1 }, %)`
)
}
)
test.describe( test.describe(
'Client side scene scale should match engine scale', 'Client side scene scale should match engine scale',

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -365,10 +365,10 @@ const box = startSketchOn('XY')
svg(startSketchOn(keychain, 'end'), [-33, 32], -thickness) svg(startSketchOn(keychain, 'end'), [-33, 32], -thickness)
startSketchOn(keychain, 'end') startSketchOn(keychain, 'end')
|> circle([ |> circle({ center: [
width / 2, width / 2,
height - (keychainHoleSize + 1.5) height - (keychainHoleSize + 1.5)
], keychainHoleSize, %) ], radius: keychainHoleSize }, %)
|> extrude(-thickness, %)` |> extrude(-thickness, %)`
export const TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR = `const thing = 1` export const TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR = `const thing = 1`

View File

@ -774,6 +774,80 @@ const part001 = startSketchOn('XZ')
locator: '[data-overlay-toolbar-index="12"]', locator: '[data-overlay-toolbar-index="12"]',
}) })
}) })
test('for segment [circle]', async ({ page }) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('XZ')
|> circle({ center: [1 + 0, 0], radius: 8 }, %)
`
)
localStorage.setItem('disableAxis', 'true')
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
// wait for execution done
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
await page
.getByText('circle({ center: [1 + 0, 0], radius: 8 }, %)')
.click()
await page.waitForTimeout(100)
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500)
await expect(page.getByTestId('segment-overlay')).toHaveCount(1)
const clickUnconstrained = _clickUnconstrained(page)
const clickConstrained = _clickConstrained(page)
const hoverPos = { x: 789, y: 114 } as const
let ang = await u.getAngle('[data-overlay-index="0"]')
console.log('angl', ang)
console.log('circle center x')
await clickConstrained({
hoverPos,
constraintType: 'xAbsolute',
expectBeforeUnconstrained:
'circle({ center: [1 + 0, 0], radius: 8 }, %)',
expectAfterUnconstrained: 'circle({ center: [1, 0], radius: 8 }, %)',
expectFinal: 'circle({ center: [xAbs001, 0], radius: 8 }, %)',
ang: ang + 105,
steps: 6,
locator: '[data-overlay-toolbar-index="0"]',
})
console.log('circle center y')
await clickUnconstrained({
hoverPos,
constraintType: 'yAbsolute',
expectBeforeUnconstrained:
'circle({ center: [xAbs001, 0], radius: 8 }, %)',
expectAfterUnconstrained:
'circle({ center: [xAbs001, yAbs001], radius: 8 }, %)',
expectFinal: 'circle({ center: [xAbs001, 0], radius: 8 }, %)',
ang: ang + 105,
steps: 10,
locator: '[data-overlay-toolbar-index="0"]',
})
console.log('circle radius')
await clickUnconstrained({
hoverPos,
constraintType: 'radius',
expectBeforeUnconstrained:
'circle({ center: [xAbs001, 0], radius: 8 }, %)',
expectAfterUnconstrained:
'circle({ center: [xAbs001, 0], radius: radius001 }, %)',
expectFinal: 'circle({ center: [xAbs001, 0], radius: 8 }, %)',
ang: ang + 105,
steps: 10,
locator: '[data-overlay-toolbar-index="0"]',
})
})
}) })
test.describe('Testing deleting a segment', () => { test.describe('Testing deleting a segment', () => {
const _deleteSegmentSequence = const _deleteSegmentSequence =

View File

@ -34,13 +34,11 @@ import { CustomIcon, CustomIconName } from 'components/CustomIcon'
import { ConstrainInfo } from 'lang/std/stdTypes' import { ConstrainInfo } from 'lang/std/stdTypes'
import { getConstraintInfo } from 'lang/std/sketch' import { getConstraintInfo } from 'lang/std/sketch'
import { Dialog, Popover, Transition } from '@headlessui/react' import { Dialog, Popover, Transition } from '@headlessui/react'
import { LineInputsType } from 'lang/std/sketchcombos'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { InstanceProps, create } from 'react-modal-promise' import { InstanceProps, create } from 'react-modal-promise'
import { executeAst } from 'lang/langHelpers' import { executeAst } from 'lang/langHelpers'
import { import {
deleteSegmentFromPipeExpression, deleteSegmentFromPipeExpression,
makeRemoveSingleConstraintInput,
removeSingleConstraintInfo, removeSingleConstraintInfo,
} from 'lang/modifyAst' } from 'lang/modifyAst'
import { ActionButton } from 'components/ActionButton' import { ActionButton } from 'components/ActionButton'
@ -126,7 +124,8 @@ export const ClientSideScene = ({
} else if ( } else if (
state.matches({ Sketch: 'Line tool' }) || state.matches({ Sketch: 'Line tool' }) ||
state.matches({ Sketch: 'Tangential arc to' }) || state.matches({ Sketch: 'Tangential arc to' }) ||
state.matches({ Sketch: 'Rectangle tool' }) state.matches({ Sketch: 'Rectangle tool' }) ||
state.matches({ Sketch: 'Circle tool' })
) { ) {
cursor = 'crosshair' cursor = 'crosshair'
} else { } else {
@ -514,6 +513,11 @@ const ConstraintSymbol = ({
displayName: 'Intersection Offset', displayName: 'Intersection Offset',
iconName: 'intersection-offset', iconName: 'intersection-offset',
}, },
radius: {
varName: 'radius',
displayName: 'Radius',
iconName: 'dimension',
},
// implicit constraints // implicit constraints
vertical: { vertical: {
@ -542,12 +546,10 @@ const ConstraintSymbol = ({
iconName: 'dimension', iconName: 'dimension',
}, },
} }
const varName = const varName = _type in varNameMap ? varNameMap[_type].varName : 'var'
_type in varNameMap ? varNameMap[_type as LineInputsType].varName : 'var' const name: CustomIconName = varNameMap[_type].iconName
const name: CustomIconName = varNameMap[_type as LineInputsType].iconName const displayName = varNameMap[_type]?.displayName
const displayName = varNameMap[_type as LineInputsType]?.displayName const implicitDesc = varNameMap[_type]?.implicitConstraintDesc
const implicitDesc =
varNameMap[_type as LineInputsType]?.implicitConstraintDesc
const _node = useMemo( const _node = useMemo(
() => getNodeFromPath<Expr>(kclManager.ast, pathToNode), () => getNodeFromPath<Expr>(kclManager.ast, pathToNode),
@ -604,13 +606,10 @@ const ConstraintSymbol = ({
if (trap(_node1)) return Promise.reject(_node1) if (trap(_node1)) return Promise.reject(_node1)
const shallowPath = _node1.shallowPath const shallowPath = _node1.shallowPath
const input = makeRemoveSingleConstraintInput( if (!context.sketchDetails || !argPosition) return
argPosition,
shallowPath
)
if (!input || !context.sketchDetails) return
const transform = removeSingleConstraintInfo( const transform = removeSingleConstraintInfo(
input, shallowPath,
argPosition,
kclManager.ast, kclManager.ast,
kclManager.programMemory kclManager.programMemory
) )

View File

@ -62,9 +62,10 @@ import {
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { executeAst } from 'lang/langHelpers' import { executeAst } from 'lang/langHelpers'
import { import {
circleSegment,
createArcGeometry, createArcGeometry,
dashedStraight, dashedStraight,
profileStart, createProfileStartHandle,
straightSegment, straightSegment,
tangentialArcToSegment, tangentialArcToSegment,
} from './segments' } from './segments'
@ -72,6 +73,7 @@ import {
addCallExpressionsToPipe, addCallExpressionsToPipe,
addCloseToPipe, addCloseToPipe,
addNewSketchLn, addNewSketchLn,
changeCircleArguments,
changeSketchArguments, changeSketchArguments,
updateStartProfileAtArgs, updateStartProfileAtArgs,
} from 'lang/std/sketch' } from 'lang/std/sketch'
@ -87,6 +89,7 @@ import {
createArrayExpression, createArrayExpression,
createCallExpressionStdLib, createCallExpressionStdLib,
createLiteral, createLiteral,
createObjectExpression,
createPipeExpression, createPipeExpression,
createPipeSubstitution, createPipeSubstitution,
findUniqueName, findUniqueName,
@ -119,9 +122,22 @@ export const TANGENTIAL_ARC_TO__SEGMENT_DASH =
'tangential-arc-to-segment-body-dashed' 'tangential-arc-to-segment-body-dashed'
export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment' export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment'
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body' export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
export const CIRCLE_SEGMENT = 'circle-segment'
export const CIRCLE_SEGMENT_BODY = 'circle-segment-body'
export const CIRCLE_SEGMENT_DASH = 'circle-segment-body-dashed'
export const CIRCLE_CENTER_HANDLE = 'circle-center-handle'
export const SEGMENT_WIDTH_PX = 1.6 export const SEGMENT_WIDTH_PX = 1.6
export const HIDE_SEGMENT_LENGTH = 75 // in pixels export const HIDE_SEGMENT_LENGTH = 75 // in pixels
export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels
export const SEGMENT_BODIES = [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
CIRCLE_SEGMENT,
]
export const SEGMENT_BODIES_PLUS_PROFILE_START = [
...SEGMENT_BODIES,
PROFILE_START,
]
type Vec3Array = [number, number, number] type Vec3Array = [number, number, number]
@ -186,6 +202,26 @@ export class SceneEntities {
}) })
) )
} }
if (
segment.userData.from &&
segment.userData.to &&
segment.userData.center &&
segment.userData.radius &&
segment.userData.type === CIRCLE_SEGMENT
) {
callbacks.push(
this.updateCircleSegment({
prevSegment: segment.userData.prevSegment,
from: segment.userData.from,
to: segment.userData.to,
center: segment.userData.center,
radius: segment.userData.radius,
group: segment,
scale: factor,
})
)
}
if (segment.name === PROFILE_START) { if (segment.name === PROFILE_START) {
segment.scale.set(factor, factor, factor) segment.scale.set(factor, factor, factor)
} }
@ -422,19 +458,21 @@ export class SceneEntities {
maybeModdedAst, maybeModdedAst,
sketchGroup.start.__geoMeta.sourceRange sketchGroup.start.__geoMeta.sourceRange
) )
const _profileStart = profileStart({ if (sketchGroup?.value?.[0].type !== 'Circle') {
from: sketchGroup.start.from, const _profileStart = createProfileStartHandle({
id: sketchGroup.start.__geoMeta.id, from: sketchGroup.start.from,
pathToNode: segPathToNode, id: sketchGroup.start.__geoMeta.id,
scale: factor, pathToNode: segPathToNode,
theme: sceneInfra._theme, scale: factor,
}) theme: sceneInfra._theme,
_profileStart.layers.set(SKETCH_LAYER) })
_profileStart.traverse((child) => { _profileStart.layers.set(SKETCH_LAYER)
child.layers.set(SKETCH_LAYER) _profileStart.traverse((child) => {
}) child.layers.set(SKETCH_LAYER)
group.add(_profileStart) })
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart group.add(_profileStart)
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
}
const callbacks: (() => SegmentOverlayPayload | null)[] = [] const callbacks: (() => SegmentOverlayPayload | null)[] = []
sketchGroup.value.forEach((segment, index) => { sketchGroup.value.forEach((segment, index) => {
let segPathToNode = getNodePathFromSourceRange( let segPathToNode = getNodePathFromSourceRange(
@ -499,6 +537,32 @@ export class SceneEntities {
scale: factor, scale: factor,
}) })
) )
} else if (segment.type === 'Circle') {
seg = circleSegment({
prevSegment: sketchGroup.value[index - 1],
from: segment.from,
to: segment.to,
center: segment.center,
radius: segment.radius,
id: segment.__geoMeta.id,
pathToNode: segPathToNode,
isDraftSegment,
scale: factor,
texture: sceneInfra.extraSegmentTexture,
theme: sceneInfra._theme,
isSelected,
})
callbacks.push(
this.updateCircleSegment({
prevSegment: sketchGroup.value[index - 1],
from: segment.from,
to: segment.to,
center: segment.center,
radius: segment.radius,
group: seg,
scale: factor,
})
)
} else { } else {
seg = straightSegment({ seg = straightSegment({
from: segment.from, from: segment.from,
@ -603,16 +667,18 @@ export class SceneEntities {
kclManager.programMemory.get(variableDeclarationName), kclManager.programMemory.get(variableDeclarationName),
variableDeclarationName variableDeclarationName
) )
if (err(sg)) return sg if (err(sg)) return Promise.reject(sg)
const lastSeg = sg.value?.slice(-1)[0] || sg.start const lastSeg = sg?.value?.slice(-1)[0] || sg.start
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1` const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
const mod = addNewSketchLn({ const mod = addNewSketchLn({
node: _ast, node: _ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
to: [lastSeg.to[0], lastSeg.to[1]], input: {
from: [lastSeg.to[0], lastSeg.to[1]], type: 'straight-segment',
to: lastSeg.to,
from: lastSeg.to,
},
fnName: segmentName, fnName: segmentName,
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
}) })
@ -683,8 +749,11 @@ export class SceneEntities {
const tmp = addNewSketchLn({ const tmp = addNewSketchLn({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
to: [intersection2d.x, intersection2d.y], input: {
from: [lastSegment.to[0], lastSegment.to[1]], type: 'straight-segment',
to: [intersection2d.x, intersection2d.y],
from: lastSegment.to,
},
fnName: fnName:
lastSegment.type === 'TangentialArcTo' lastSegment.type === 'TangentialArcTo'
? 'tangentialArcTo' ? 'tangentialArcTo'
@ -748,6 +817,11 @@ export class SceneEntities {
const startSketchOn = _node1.node?.declarations const startSketchOn = _node1.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init const startSketchOnInit = startSketchOn?.[0]?.init
const sg = sketchGroupFromKclValue(
kclManager.programMemory.get(variableDeclarationName),
variableDeclarationName
)
if (err(sg)) return sg
const tags: [string, string, string] = [ const tags: [string, string, string] = [
findUniqueName(_ast, 'rectangleSegmentA'), findUniqueName(_ast, 'rectangleSegmentA'),
findUniqueName(_ast, 'rectangleSegmentB'), findUniqueName(_ast, 'rectangleSegmentB'),
@ -805,7 +879,7 @@ export class SceneEntities {
programMemory.get(variableDeclarationName), programMemory.get(variableDeclarationName),
variableDeclarationName variableDeclarationName
) )
if (err(sketchGroup)) return sketchGroup if (err(sketchGroup)) return Promise.reject(sketchGroup)
const sgPaths = sketchGroup.value const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
@ -862,7 +936,7 @@ export class SceneEntities {
programMemory.get(variableDeclarationName), programMemory.get(variableDeclarationName),
variableDeclarationName variableDeclarationName
) )
if (err(sketchGroup)) return sketchGroup if (err(sketchGroup)) return Promise.reject(sketchGroup)
const sgPaths = sketchGroup.value const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
@ -883,6 +957,151 @@ export class SceneEntities {
}, },
}) })
} }
setupDraftCircle = async (
sketchPathToNode: PathToNode,
forward: [number, number, number],
up: [number, number, number],
sketchOrigin: [number, number, number],
circleCenter: [x: number, y: number]
) => {
let _ast = structuredClone(kclManager.ast)
const _node1 = getNodeFromPath<VariableDeclaration>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
)
if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName =
_node1.node?.declarations?.[0]?.id?.name || ''
const startSketchOn = _node1.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
startSketchOn[0].init = createPipeExpression([
startSketchOnInit,
createCallExpressionStdLib('circle', [
createObjectExpression({
center: createArrayExpression([
createLiteral(roundOff(circleCenter[0])),
createLiteral(roundOff(circleCenter[1])),
]),
radius: createLiteral(1),
}),
createPipeSubstitution(),
]),
])
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
// do a quick mock execution to get the program memory up-to-date
await kclManager.executeAstMock(_ast)
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
sketchPathToNode,
forward,
up,
position: sketchOrigin,
maybeModdedAst: _ast,
draftExpressionsIndices: { start: 0, end: 0 },
})
sceneInfra.setCallbacks({
onMove: async (args) => {
const pathToNodeTwo = structuredClone(sketchPathToNode)
pathToNodeTwo[1][0] = 0
const _node = getNodeFromPath<VariableDeclaration>(
truncatedAst,
pathToNodeTwo || [],
'VariableDeclaration'
)
let modded = structuredClone(truncatedAst)
if (trap(_node)) return Promise.reject(_node)
const sketchInit = _node.node?.declarations?.[0]?.init
const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0]
const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1]
if (sketchInit.type === 'PipeExpression') {
const moddedResult = changeCircleArguments(
modded,
kclManager.programMemory,
[..._node.deepPath, ['body', 'PipeExpression'], [1, 'index']],
circleCenter,
Math.sqrt(x ** 2 + y ** 2)
)
if (err(moddedResult)) return Promise.reject(moddedResult)
modded = moddedResult.modifiedAst
}
const { programMemory } = await executeAst({
ast: modded,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
})
this.sceneProgramMemory = programMemory
const sketchGroup = sketchGroupFromKclValue(
programMemory.get(variableDeclarationName),
variableDeclarationName
)
if (err(sketchGroup)) return sketchGroup
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(
sketchGroup.start,
0,
0,
_ast,
orthoFactor,
sketchGroup
)
sgPaths.forEach((seg, index) =>
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketchGroup)
)
},
onClick: async (args) => {
// Commit the rectangle to the full AST/code and return to sketch.idle
const cornerPoint = args.intersectionPoint?.twoD
if (!cornerPoint || args.mouseEvent.button !== 0) return
const x = roundOff((cornerPoint.x || 0) - circleCenter[0])
const y = roundOff((cornerPoint.y || 0) - circleCenter[1])
const _node = getNodeFromPath<VariableDeclaration>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
)
if (trap(_node)) return Promise.reject(_node)
const sketchInit = _node.node?.declarations?.[0]?.init
let modded = structuredClone(_ast)
if (sketchInit.type === 'PipeExpression') {
const moddedResult = changeCircleArguments(
modded,
kclManager.programMemory,
[..._node.deepPath, ['body', 'PipeExpression'], [1, 'index']],
circleCenter,
Math.sqrt(x ** 2 + y ** 2)
)
if (err(moddedResult)) return Promise.reject(moddedResult)
modded = moddedResult.modifiedAst
let _recastAst = parse(recast(modded))
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
// Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast)
sceneInfra.modelingSend({ type: 'Finish circle' })
}
},
})
}
setupSketchIdleCallbacks = ({ setupSketchIdleCallbacks = ({
pathToNode, pathToNode,
up, up,
@ -951,8 +1170,11 @@ export class SceneEntities {
const mod = addNewSketchLn({ const mod = addNewSketchLn({
node: kclManager.ast, node: kclManager.ast,
programMemory: kclManager.programMemory, programMemory: kclManager.programMemory,
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y], input: {
from: [prevSegment.from[0], prevSegment.from[1]], type: 'straight-segment',
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
from: prevSegment.from,
},
// TODO assuming it's always a straight segments being added // TODO assuming it's always a straight segments being added
// as this is easiest, and we'll need to add "tabbing" behavior // as this is easiest, and we'll need to add "tabbing" behavior
// to support other segment types // to support other segment types
@ -1051,11 +1273,8 @@ export class SceneEntities {
? new Vector2(profileStart.position.x, profileStart.position.y) ? new Vector2(profileStart.position.x, profileStart.position.y)
: _intersection2d : _intersection2d
const group = getParentGroup(object, [ const group = getParentGroup(object, SEGMENT_BODIES_PLUS_PROFILE_START)
STRAIGHT_SEGMENT, const subGroup = getParentGroup(object, [ARROWHEAD, CIRCLE_CENTER_HANDLE])
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
if (!group) return if (!group) return
const pathToNode: PathToNode = structuredClone(group.userData.pathToNode) const pathToNode: PathToNode = structuredClone(group.userData.pathToNode)
const varDecIndex = pathToNode[1][0] const varDecIndex = pathToNode[1][0]
@ -1073,7 +1292,7 @@ export class SceneEntities {
group.userData.from[0], group.userData.from[0],
group.userData.from[1], group.userData.from[1],
] ]
const to: [number, number] = [intersection2d.x, intersection2d.y] const dragTo: [number, number] = [intersection2d.x, intersection2d.y]
let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast } let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast }
const _node = getNodeFromPath<CallExpression>( const _node = getNodeFromPath<CallExpression>(
@ -1096,17 +1315,59 @@ export class SceneEntities {
modded = updateStartProfileAtArgs({ modded = updateStartProfileAtArgs({
node: modifiedAst, node: modifiedAst,
pathToNode, pathToNode,
to, input: {
from, type: 'straight-segment',
to: dragTo,
from,
},
previousProgramMemory: kclManager.programMemory, previousProgramMemory: kclManager.programMemory,
}) })
} else if (
group.name === CIRCLE_SEGMENT &&
// !subGroup treats grabbing the outer circumference of the circle
// as a drag of the center handle
(!subGroup || subGroup?.name === ARROWHEAD)
) {
// is dragging the radius handle
modded = changeSketchArguments(
modifiedAst,
kclManager.programMemory,
[node.start, node.end],
{
type: 'arc-segment',
from,
center: group.userData.center,
radius: Math.sqrt(
(group.userData.center[0] - dragTo[0]) ** 2 +
(group.userData.center[0] - dragTo[0]) ** 2
),
}
)
} else if (
group.name === CIRCLE_SEGMENT &&
subGroup?.name === CIRCLE_CENTER_HANDLE
) {
modded = changeSketchArguments(
modifiedAst,
kclManager.programMemory,
[node.start, node.end],
{
type: 'arc-segment',
from,
center: dragTo,
radius: group.userData.radius,
}
)
} else { } else {
modded = changeSketchArguments( modded = changeSketchArguments(
modifiedAst, modifiedAst,
kclManager.programMemory, kclManager.programMemory,
[node.start, node.end], [node.start, node.end],
to, {
from type: 'straight-segment',
from,
to: dragTo,
}
) )
} }
if (trap(modded)) return if (trap(modded)) return
@ -1224,6 +1485,20 @@ export class SceneEntities {
group, group,
scale: factor, scale: factor,
}) })
} else if (
type === CIRCLE_SEGMENT &&
'center' in segment &&
'radius' in segment
) {
return this.updateCircleSegment({
prevSegment: sgPaths[index - 1],
from: segment.from,
to: segment.to,
center: segment.center,
radius: segment.radius,
group,
scale: factor,
})
} else if (type === PROFILE_START) { } else if (type === PROFILE_START) {
group.position.set(segment.from[0], segment.from[1], 0) group.position.set(segment.from[0], segment.from[1], 0)
group.scale.set(factor, factor, factor) group.scale.set(factor, factor, factor)
@ -1249,6 +1524,9 @@ export class SceneEntities {
group.userData.prevSegment = prevSegment group.userData.prevSegment = prevSegment
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE) const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
if (!prevSegment) {
console.trace('prevSegment is undefined')
}
const previousPoint = const previousPoint =
prevSegment?.type === 'TangentialArcTo' prevSegment?.type === 'TangentialArcTo'
@ -1344,6 +1622,111 @@ export class SceneEntities {
angle, angle,
}) })
} }
updateCircleSegment({
prevSegment,
from,
to,
center,
radius,
group,
scale = 1,
}: {
prevSegment: SketchGroup['value'][number]
from: [number, number]
to: [number, number]
center: [number, number]
radius: number
group: Group
scale?: number
}): () => SegmentOverlayPayload | null {
group.userData.from = from
group.userData.to = to
group.userData.center = center
group.userData.radius = radius
group.userData.prevSegment = prevSegment
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const circleCenterHandle = group.getObjectByName(
CIRCLE_CENTER_HANDLE
) as Group
const pxLength = (2 * radius * Math.PI) / scale
const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
const hoveredParent =
sceneInfra.hoveredObject &&
getParentGroup(sceneInfra.hoveredObject, [CIRCLE_SEGMENT])
let isHandlesVisible = !shouldHideIdle
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
isHandlesVisible = !shouldHideHover
}
if (arrowGroup) {
arrowGroup.position.set(
center[0] + Math.cos(Math.PI / 4) * radius,
center[1] + Math.sin(Math.PI / 4) * radius,
0
)
const arrowheadAngle = Math.PI / 4
arrowGroup.quaternion.setFromUnitVectors(
new Vector3(0, 1, 0),
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
)
arrowGroup.scale.set(scale, scale, scale)
arrowGroup.visible = isHandlesVisible
}
if (circleCenterHandle) {
circleCenterHandle.position.set(center[0], center[1], 0)
circleCenterHandle.scale.set(scale, scale, scale)
circleCenterHandle.visible = isHandlesVisible
}
const circleSegmentBody = group.children.find(
(child) => child.userData.type === CIRCLE_SEGMENT_BODY
) as Mesh
if (circleSegmentBody) {
const newGeo = createArcGeometry({
radius,
center,
startAngle: 0,
endAngle: Math.PI * 2,
ccw: true,
scale,
})
circleSegmentBody.geometry = newGeo
}
const circleSegmentBodyDashed = group.children.find(
(child) => child.userData.type === CIRCLE_SEGMENT_DASH
) as Mesh
if (circleSegmentBodyDashed) {
// consider throttling the whole updateTangentialArcToSegment
// if there are more perf considerations going forward
this.throttledUpdateDashedArcGeo({
// ...arcInfo,
center,
radius,
ccw: true,
// make the start end where the handle is
startAngle: Math.PI * 0.25,
endAngle: Math.PI * 2.25,
mesh: circleSegmentBodyDashed,
isDashed: true,
scale,
})
}
return () =>
sceneInfra.updateOverlayDetails({
arrowGroup,
group,
isHandlesVisible,
from: to,
to: [center[0], center[1]],
angle: Math.PI / 4,
})
}
throttledUpdateDashedArcGeo = throttle( throttledUpdateDashedArcGeo = throttle(
( (
args: Parameters<typeof createArcGeometry>[0] & { args: Parameters<typeof createArcGeometry>[0] & {
@ -1478,7 +1861,7 @@ export class SceneEntities {
} }
private _tearDownSketch( private _tearDownSketch(
callDepth = 0, callDepth = 0,
resolve: (val: unknown) => void, resolve: any,
reject: () => void, reject: () => void,
{ removeAxis = true }: { removeAxis?: boolean } { removeAxis = true }: { removeAxis?: boolean }
) { ) {
@ -1508,7 +1891,7 @@ export class SceneEntities {
this._tearDownSketch(callDepth + 1, resolve, reject, { removeAxis }) this._tearDownSketch(callDepth + 1, resolve, reject, { removeAxis })
}, delay) }, delay)
} else { } else {
reject() resolve(true)
} }
} }
sceneInfra.camControls.enableRotate = true sceneInfra.camControls.enableRotate = true
@ -1520,7 +1903,7 @@ export class SceneEntities {
removeAxis = true, removeAxis = true,
}: { }: {
removeAxis?: boolean removeAxis?: boolean
} = {}) { } = {}): Promise<void | Error> {
// I think promisifying this is mostly a side effect of not having // I think promisifying this is mostly a side effect of not having
// "setupSketch" correctly capture a promise when it's done // "setupSketch" correctly capture a promise when it's done
// so we're effectively waiting for to be finished setting up the scene just to tear it down // so we're effectively waiting for to be finished setting up the scene just to tear it down
@ -1538,11 +1921,10 @@ export class SceneEntities {
mat.color.set(obj.userData.baseColor) mat.color.set(obj.userData.baseColor)
mat.color.offsetHSL(0, 0, 0.5) mat.color.offsetHSL(0, 0, 0.5)
} }
const parent = getParentGroup(selected, [ const parent = getParentGroup(
STRAIGHT_SEGMENT, selected,
TANGENTIAL_ARC_TO_SEGMENT, SEGMENT_BODIES_PLUS_PROFILE_START
PROFILE_START, )
])
if (parent?.userData?.pathToNode) { if (parent?.userData?.pathToNode) {
const updatedAst = parse(recast(kclManager.ast)) const updatedAst = parse(recast(kclManager.ast))
if (trap(updatedAst)) return if (trap(updatedAst)) return
@ -1586,6 +1968,16 @@ export class SceneEntities {
group: parent, group: parent,
scale: factor, scale: factor,
}) })
} else if (parent.name === CIRCLE_SEGMENT) {
this.updateCircleSegment({
prevSegment: parent.userData.prevSegment,
from: parent.userData.from,
to: parent.userData.to,
center: parent.userData.center,
radius: parent.userData.radius,
group: parent,
scale: factor,
})
} }
return return
} }
@ -1593,11 +1985,10 @@ export class SceneEntities {
}, },
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => { onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
editorManager.setHighlightRange([[0, 0]]) editorManager.setHighlightRange([[0, 0]])
const parent = getParentGroup(selected, [ const parent = getParentGroup(
STRAIGHT_SEGMENT, selected,
TANGENTIAL_ARC_TO_SEGMENT, SEGMENT_BODIES_PLUS_PROFILE_START
PROFILE_START, )
])
if (parent) { if (parent) {
const orthoFactor = orthoScale(sceneInfra.camControls.camera) const orthoFactor = orthoScale(sceneInfra.camControls.camera)
@ -1621,6 +2012,16 @@ export class SceneEntities {
group: parent, group: parent,
scale: factor, scale: factor,
}) })
} else if (parent.name === CIRCLE_SEGMENT) {
this.updateCircleSegment({
prevSegment: parent.userData.prevSegment,
from: parent.userData.from,
to: parent.userData.to,
center: parent.userData.center,
radius: parent.userData.radius,
group: parent,
scale: factor,
})
} }
} }
const isSelected = parent?.userData?.isSelected const isSelected = parent?.userData?.isSelected
@ -1783,7 +2184,7 @@ function prepareTruncatedMemoryAndAst(
export function getParentGroup( export function getParentGroup(
object: any, object: any,
stopAt: string[] = [STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT] stopAt: string[] = SEGMENT_BODIES
): Group | null { ): Group | null {
if (stopAt.includes(object?.userData?.type)) { if (stopAt.includes(object?.userData?.type)) {
return object return object
@ -1830,10 +2231,7 @@ function colorSegment(object: any, color: number) {
}) })
return return
} }
const straightSegmentBody = getParentGroup(object, [ const straightSegmentBody = getParentGroup(object, SEGMENT_BODIES)
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
])
if (straightSegmentBody) { if (straightSegmentBody) {
straightSegmentBody.traverse((child) => { straightSegmentBody.traverse((child) => {
if (child instanceof Mesh && !child.userData.ignoreColorChange) { if (child instanceof Mesh && !child.userData.ignoreColorChange) {

View File

@ -111,21 +111,21 @@ export class SceneInfra {
} }
extraSegmentTexture: Texture extraSegmentTexture: Texture
lastMouseState: MouseState = { type: 'idle' } lastMouseState: MouseState = { type: 'idle' }
onDragStartCallback: (arg: OnDragCallbackArgs) => void = () => {} onDragStartCallback: (arg: OnDragCallbackArgs) => any = () => {}
onDragEndCallback: (arg: OnDragCallbackArgs) => void = () => {} onDragEndCallback: (arg: OnDragCallbackArgs) => any = () => {}
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {} onDragCallback: (arg: OnDragCallbackArgs) => any = () => {}
onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {} onMoveCallback: (arg: OnMoveCallbackArgs) => any = () => {}
onClickCallback: (arg: OnClickCallbackArgs) => void = () => {} onClickCallback: (arg: OnClickCallbackArgs) => any = () => {}
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {} onMouseEnter: (arg: OnMouseEnterLeaveArgs) => any = () => {}
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {} onMouseLeave: (arg: OnMouseEnterLeaveArgs) => any = () => {}
setCallbacks = (callbacks: { setCallbacks = (callbacks: {
onDragStart?: (arg: OnDragCallbackArgs) => void onDragStart?: (arg: OnDragCallbackArgs) => any
onDragEnd?: (arg: OnDragCallbackArgs) => void onDragEnd?: (arg: OnDragCallbackArgs) => any
onDrag?: (arg: OnDragCallbackArgs) => void onDrag?: (arg: OnDragCallbackArgs) => any
onMove?: (arg: OnMoveCallbackArgs) => void onMove?: (arg: OnMoveCallbackArgs) => any
onClick?: (arg: OnClickCallbackArgs) => void onClick?: (arg: OnClickCallbackArgs) => any
onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => any
onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => any
}) => { }) => {
this.onDragStartCallback = callbacks.onDragStart || this.onDragStartCallback this.onDragStartCallback = callbacks.onDragStart || this.onDragStartCallback
this.onDragEndCallback = callbacks.onDragEnd || this.onDragEndCallback this.onDragEndCallback = callbacks.onDragEnd || this.onDragEndCallback

View File

@ -24,6 +24,10 @@ import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm' import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
import { import {
CIRCLE_CENTER_HANDLE,
CIRCLE_SEGMENT,
CIRCLE_SEGMENT_BODY,
CIRCLE_SEGMENT_DASH,
EXTRA_SEGMENT_HANDLE, EXTRA_SEGMENT_HANDLE,
EXTRA_SEGMENT_OFFSET_PX, EXTRA_SEGMENT_OFFSET_PX,
HIDE_SEGMENT_LENGTH, HIDE_SEGMENT_LENGTH,
@ -46,7 +50,7 @@ import {
import { Themes, getThemeColorForThreeJs } from 'lib/theme' import { Themes, getThemeColorForThreeJs } from 'lib/theme'
import { roundOff } from 'lib/utils' import { roundOff } from 'lib/utils'
export function profileStart({ export function createProfileStartHandle({
from, from,
id, id,
pathToNode, pathToNode,
@ -225,6 +229,28 @@ function createArrowhead(scale = 1, theme: Themes, color?: number): Group {
arrowGroup.scale.set(scale, scale, scale) arrowGroup.scale.set(scale, scale, scale)
return arrowGroup return arrowGroup
} }
function createCircleCenterHandle(
scale = 1,
theme: Themes,
color?: number
): Group {
const circleCenterGroup = new Group()
const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
const baseColor = getThemeColorForThreeJs(theme)
const body = new MeshBasicMaterial({ color })
const mesh = new Mesh(geometry, body)
circleCenterGroup.add(mesh)
circleCenterGroup.userData = {
type: CIRCLE_CENTER_HANDLE,
baseColor,
}
circleCenterGroup.name = CIRCLE_CENTER_HANDLE
circleCenterGroup.scale.set(scale, scale, scale)
return circleCenterGroup
}
function createExtraSegmentHandle( function createExtraSegmentHandle(
scale: number, scale: number,
@ -300,6 +326,86 @@ function createLengthIndicator({
return lengthIndicatorGroup return lengthIndicatorGroup
} }
export function circleSegment({
prevSegment,
from,
to,
center,
radius,
id,
pathToNode,
isDraftSegment,
scale = 1,
texture,
theme,
isSelected,
}: {
prevSegment: SketchGroup['value'][number]
from: Coords2d
center: Coords2d
radius: number
to: Coords2d
id: string
pathToNode: PathToNode
isDraftSegment?: boolean
scale?: number
texture: Texture
theme: Themes
isSelected?: boolean
}): Group {
const group = new Group()
const geometry = createArcGeometry({
center,
radius,
startAngle: 0,
endAngle: Math.PI * 2,
ccw: true,
isDashed: isDraftSegment,
scale,
})
const baseColor = getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
const body = new MeshBasicMaterial({ color })
const mesh = new Mesh(geometry, body)
mesh.userData.type = isDraftSegment
? CIRCLE_SEGMENT_DASH
: CIRCLE_SEGMENT_BODY
group.userData = {
type: CIRCLE_SEGMENT,
id,
from,
to,
radius,
center,
ccw: true,
prevSegment,
pathToNode,
isSelected,
baseColor,
}
group.name = CIRCLE_SEGMENT
const arrowGroup = createArrowhead(scale, theme, color)
arrowGroup.position.set(
center[0] + Math.cos(Math.PI / 4) * radius,
center[1] + Math.sin(Math.PI / 4) * radius,
0
)
const circleCenterGroup = createCircleCenterHandle(scale, theme, color)
circleCenterGroup.position.set(center[0], center[1], 0)
const arrowheadAngle = Math.PI / 4
arrowGroup.quaternion.setFromUnitVectors(
new Vector3(0, 1, 0),
new Vector3(Math.cos(arrowheadAngle), Math.sin(arrowheadAngle), 0)
)
group.add(mesh, arrowGroup, circleCenterGroup)
return group
}
export function tangentialArcToSegment({ export function tangentialArcToSegment({
prevSegment, prevSegment,
from, from,

View File

@ -50,8 +50,7 @@ import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
import useStateMachineCommands from 'hooks/useStateMachineCommands' import useStateMachineCommands from 'hooks/useStateMachineCommands'
import { modelingMachineCommandConfig } from 'lib/commandBarConfigs/modelingCommandConfig' import { modelingMachineCommandConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
import { import {
STRAIGHT_SEGMENT, SEGMENT_BODIES,
TANGENTIAL_ARC_TO_SEGMENT,
getParentGroup, getParentGroup,
getSketchOrientationDetails, getSketchOrientationDetails,
} from 'clientSideScene/sceneEntities' } from 'clientSideScene/sceneEntities'
@ -168,10 +167,7 @@ export const ModelingMachineProvider = ({
if (event.type !== 'Set mouse state') return {} if (event.type !== 'Set mouse state') return {}
const nextSegmentHoverMap = () => { const nextSegmentHoverMap = () => {
if (event.data.type === 'isHovering') { if (event.data.type === 'isHovering') {
const parent = getParentGroup(event.data.on, [ const parent = getParentGroup(event.data.on, SEGMENT_BODIES)
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
])
const pathToNode = parent?.userData?.pathToNode const pathToNode = parent?.userData?.pathToNode
const pathToNodeString = JSON.stringify(pathToNode) const pathToNodeString = JSON.stringify(pathToNode)
if (!parent || !pathToNode) return context.segmentHoverMap if (!parent || !pathToNode) return context.segmentHoverMap
@ -187,10 +183,10 @@ export const ModelingMachineProvider = ({
event.data.type === 'idle' && event.data.type === 'idle' &&
context.mouseState.type === 'isHovering' context.mouseState.type === 'isHovering'
) { ) {
const mouseOnParent = getParentGroup(context.mouseState.on, [ const mouseOnParent = getParentGroup(
STRAIGHT_SEGMENT, context.mouseState.on,
TANGENTIAL_ARC_TO_SEGMENT, SEGMENT_BODIES
]) )
if (!mouseOnParent || !mouseOnParent?.userData?.pathToNode) if (!mouseOnParent || !mouseOnParent?.userData?.pathToNode)
return context.segmentHoverMap return context.segmentHoverMap
const pathToNodeString = JSON.stringify( const pathToNodeString = JSON.stringify(
@ -204,7 +200,8 @@ export const ModelingMachineProvider = ({
pathToNodeString, pathToNodeString,
}, },
}) })
}, 800) as unknown as number // overlay timeout is 1s
}, 1000) as unknown as number
return { return {
...context.segmentHoverMap, ...context.segmentHoverMap,
[pathToNodeString]: timeoutId, [pathToNodeString]: timeoutId,

View File

@ -10,10 +10,10 @@ import {
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
PathToNodeMap, PathToNodeMap,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { TransformInfo } from 'lang/std/stdTypes'
export function equalAngleInfo({ export function equalAngleInfo({
selectionRanges, selectionRanges,

View File

@ -10,8 +10,8 @@ import {
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
PathToNodeMap, PathToNodeMap,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'

View File

@ -9,8 +9,8 @@ import {
PathToNodeMap, PathToNodeMap,
getTransformInfos, getTransformInfos,
transformAstSketchLines, transformAstSketchLines,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'

View File

@ -11,8 +11,8 @@ import {
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
PathToNodeMap, PathToNodeMap,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal' import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst' import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'

View File

@ -9,8 +9,8 @@ import {
PathToNodeMap, PathToNodeMap,
getRemoveConstraintsTransforms, getRemoveConstraintsTransforms,
transformAstSketchLines, transformAstSketchLines,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'

View File

@ -9,8 +9,8 @@ import {
getTransformInfos, getTransformInfos,
transformAstSketchLines, transformAstSketchLines,
PathToNodeMap, PathToNodeMap,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { import {
SetAngleLengthModal, SetAngleLengthModal,
createSetAngleLengthModal, createSetAngleLengthModal,

View File

@ -10,8 +10,8 @@ import {
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
PathToNodeMap, PathToNodeMap,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal' import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst' import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'

View File

@ -9,8 +9,8 @@ import {
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
PathToNodeMap, PathToNodeMap,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal' import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst' import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'

View File

@ -9,8 +9,8 @@ import {
PathToNodeMap, PathToNodeMap,
getTransformInfos, getTransformInfos,
transformAstSketchLines, transformAstSketchLines,
TransformInfo,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { TransformInfo } from 'lang/std/stdTypes'
import { import {
SetAngleLengthModal, SetAngleLengthModal,
createSetAngleLengthModal, createSetAngleLengthModal,

View File

@ -24,11 +24,9 @@ export type ToolTip =
| 'yLineTo' | 'yLineTo'
| 'angledLineThatIntersects' | 'angledLineThatIntersects'
| 'tangentialArcTo' | 'tangentialArcTo'
| 'circle'
export const toolTips = [ export const toolTips: Array<ToolTip> = [
'sketch_line',
'move',
// original tooltips
'line', 'line',
'lineTo', 'lineTo',
'angledLine', 'angledLine',
@ -42,7 +40,7 @@ export const toolTips = [
'yLineTo', 'yLineTo',
'angledLineThatIntersects', 'angledLineThatIntersects',
'tangentialArcTo', 'tangentialArcTo',
] as any as ToolTip[] ]
export async function executeAst({ export async function executeAst({
ast, ast,

View File

@ -20,6 +20,7 @@ import {
import { enginelessExecutor } from '../lib/testHelpers' import { enginelessExecutor } from '../lib/testHelpers'
import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst' import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { SimplifiedArgDetails, InputArgKeys } from './std/stdTypes'
beforeAll(async () => { beforeAll(async () => {
await initPromise await initPromise
@ -638,11 +639,27 @@ describe('Testing removeSingleConstraintInfo', () => {
code.indexOf(lineOfInterest) + lineOfInterest.length, code.indexOf(lineOfInterest) + lineOfInterest.length,
] ]
const pathToNode = getNodePathFromSourceRange(ast, range) const pathToNode = getNodePathFromSourceRange(ast, range)
let argPosition: SimplifiedArgDetails
if (key === 'arrayIndex' && typeof value === 'number') {
argPosition = {
type: 'arrayItem',
index: value === 0 ? 0 : 1,
}
} else if (key === 'objectProperty' && typeof value === 'string') {
argPosition = {
type: 'objectProperty',
key: value as InputArgKeys,
}
} else if (key === '') {
argPosition = {
type: 'singleValue',
}
} else {
throw new Error('argPosition is undefined')
}
const mod = removeSingleConstraintInfo( const mod = removeSingleConstraintInfo(
{ pathToNode,
pathToCallExp: pathToNode, argPosition,
[key]: value,
},
ast, ast,
programMemory programMemory
) )
@ -675,12 +692,24 @@ describe('Testing removeSingleConstraintInfo', () => {
code.indexOf(lineOfInterest) + 1, code.indexOf(lineOfInterest) + 1,
code.indexOf(lineOfInterest) + lineOfInterest.length, code.indexOf(lineOfInterest) + lineOfInterest.length,
] ]
let argPosition: SimplifiedArgDetails
if (key === 'arrayIndex' && typeof value === 'number') {
argPosition = {
type: 'arrayItem',
index: value === 0 ? 0 : 1,
}
} else if (key === 'objectProperty' && typeof value === 'string') {
argPosition = {
type: 'objectProperty',
key: value as InputArgKeys,
}
} else {
throw new Error('argPosition is undefined')
}
const pathToNode = getNodePathFromSourceRange(ast, range) const pathToNode = getNodePathFromSourceRange(ast, range)
const mod = removeSingleConstraintInfo( const mod = removeSingleConstraintInfo(
{ pathToNode,
pathToCallExp: pathToNode, argPosition,
[key]: value,
},
ast, ast,
programMemory programMemory
) )

View File

@ -38,7 +38,7 @@ import {
import { DefaultPlaneStr } from 'clientSideScene/sceneEntities' import { DefaultPlaneStr } from 'clientSideScene/sceneEntities'
import { isOverlap, roundOff } from 'lib/utils' import { isOverlap, roundOff } from 'lib/utils'
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants' import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
import { ConstrainInfo } from './std/stdTypes' import { SimplifiedArgDetails } from './std/stdTypes'
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator' import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
@ -799,15 +799,10 @@ export function deleteSegmentFromPipeExpression(
) )
if (!constraintInfo) return if (!constraintInfo) return
const input = makeRemoveSingleConstraintInput( if (!constraintInfo.argPosition) return
constraintInfo.argPosition,
callExp.shallowPath
)
if (!input) return
const transform = removeSingleConstraintInfo( const transform = removeSingleConstraintInfo(
{ callExp.shallowPath,
...input, constraintInfo.argPosition,
},
_modifiedAst, _modifiedAst,
programMemory programMemory
) )
@ -834,37 +829,9 @@ export function deleteSegmentFromPipeExpression(
return _modifiedAst return _modifiedAst
} }
export function makeRemoveSingleConstraintInput(
argPosition: ConstrainInfo['argPosition'],
pathToNode: PathToNode
): Parameters<typeof removeSingleConstraintInfo>[0] | false {
return argPosition?.type === 'singleValue'
? {
pathToCallExp: pathToNode,
}
: argPosition?.type === 'arrayItem'
? {
pathToCallExp: pathToNode,
arrayIndex: argPosition.index,
}
: argPosition?.type === 'objectProperty'
? {
pathToCallExp: pathToNode,
objectProperty: argPosition.key,
}
: false
}
export function removeSingleConstraintInfo( export function removeSingleConstraintInfo(
{ pathToCallExp: PathToNode,
pathToCallExp, argDetails: SimplifiedArgDetails,
arrayIndex,
objectProperty,
}: {
pathToCallExp: PathToNode
arrayIndex?: number
objectProperty?: string
},
ast: Program, ast: Program,
programMemory: ProgramMemory programMemory: ProgramMemory
): ):
@ -875,8 +842,7 @@ export function removeSingleConstraintInfo(
| false { | false {
const transform = removeSingleConstraint({ const transform = removeSingleConstraint({
pathToCallExp, pathToCallExp,
arrayIndex, inputDetails: argDetails,
objectProperty,
ast, ast,
}) })
if (!transform) return false if (!transform) return false

View File

@ -16,6 +16,7 @@ import {
VariableDeclaration, VariableDeclaration,
VariableDeclarator, VariableDeclarator,
sketchGroupFromKclValue, sketchGroupFromKclValue,
ObjectExpression,
} from './wasm' } from './wasm'
import { createIdentifier, splitPathAtLastIndex } from './modifyAst' import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints' import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
@ -934,3 +935,12 @@ export function hasExtrudableGeometry(ast: Program) {
}) })
return Object.keys(theMap).length > 0 return Object.keys(theMap).length > 0
} }
export function getObjExpProperty(
node: ObjectExpression,
propName: string
): { exp: Expr; index: number } | null {
const index = node.properties.findIndex(({ key }) => key.name === propName)
if (index === -1) return null
return { exp: node.properties[index].value, index }
}

View File

@ -123,8 +123,11 @@ describe('testing changeSketchArguments', () => {
ast, ast,
programMemory, programMemory,
[sourceStart, sourceStart + lineToChange.length], [sourceStart, sourceStart + lineToChange.length],
[2, 3], {
[0, 0] type: 'straight-segment',
from: [0, 0],
to: [2, 3],
}
) )
if (err(changeSketchArgsRetVal)) return changeSketchArgsRetVal if (err(changeSketchArgsRetVal)) return changeSketchArgsRetVal
expect(recast(changeSketchArgsRetVal.modifiedAst)).toBe(expectedCode) expect(recast(changeSketchArgsRetVal.modifiedAst)).toBe(expectedCode)
@ -150,8 +153,11 @@ const mySketch001 = startSketchOn('XY')
const newSketchLnRetVal = addNewSketchLn({ const newSketchLnRetVal = addNewSketchLn({
node: ast, node: ast,
programMemory, programMemory,
to: [2, 3], input: {
from: [0, 0], type: 'straight-segment',
from: [0, 0],
to: [2, 3],
},
fnName: 'lineTo', fnName: 'lineTo',
pathToNode: [ pathToNode: [
['body', ''], ['body', ''],

File diff suppressed because it is too large Load Diff

View File

@ -43,6 +43,16 @@ export function getSketchSegmentFromSourceRange(
index: number index: number
} }
| Error { | Error {
const lineIndex = sketchGroup.value.findIndex(
({ __geoMeta: { sourceRange } }: Path) =>
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
)
const line = sketchGroup.value[lineIndex]
if (line)
return {
segment: line,
index: lineIndex,
}
const startSourceRange = sketchGroup.start?.__geoMeta.sourceRange const startSourceRange = sketchGroup.start?.__geoMeta.sourceRange
if ( if (
startSourceRange && startSourceRange &&
@ -51,17 +61,7 @@ export function getSketchSegmentFromSourceRange(
sketchGroup.start sketchGroup.start
) )
return { segment: { ...sketchGroup.start, type: 'Base' }, index: -1 } return { segment: { ...sketchGroup.start, type: 'Base' }, index: -1 }
return new Error('could not find matching segment')
const lineIndex = sketchGroup.value.findIndex(
({ __geoMeta: { sourceRange } }: Path) =>
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
)
const line = sketchGroup.value[lineIndex]
if (!line) return new Error('could not find matching line')
return {
segment: line,
index: lineIndex,
}
} }
export function isSketchVariablesLinked( export function isSketchVariablesLinked(

File diff suppressed because it is too large Load Diff

View File

@ -9,22 +9,8 @@ import {
CallExpression, CallExpression,
Literal, Literal,
} from '../wasm' } from '../wasm'
import { EngineCommandManager } from './engineConnection'
import { LineInputsType } from './sketchcombos' import { LineInputsType } from './sketchcombos'
export interface InternalFirstArg {
programMemory: ProgramMemory
name?: string
sourceRange: SourceRange
engineCommandManager: EngineCommandManager
code: string
}
export interface PathReturn {
programMemory: ProgramMemory
currentPath: Path
}
export interface ModifyAstBase { export interface ModifyAstBase {
node: Program node: Program
// TODO #896: Remove ProgramMemory from this interface // TODO #896: Remove ProgramMemory from this interface
@ -37,85 +23,196 @@ export interface AddTagInfo {
pathToNode: PathToNode pathToNode: PathToNode
} }
interface addCall extends ModifyAstBase { /** Inputs for all straight segments, to and from are absolute values, as this gives a
to: [number, number] * consistent base that can be converted to all of the line, angledLine, etc segment types
* One notable exception to "straight segment" is that tangentialArcTo is included in this
* Input type since it too only takes x-y values and is able to get extra info it needs
* to be tangential from the previous segment */
interface StraightSegmentInput {
type: 'straight-segment'
from: [number, number] from: [number, number]
to: [number, number]
}
/** Inputs for arcs, excluding tangentialArcTo for reasons explain in
* the @straightSegmentInput comment */
interface ArcSegmentInput {
type: 'arc-segment'
from: [number, number]
center: [number, number]
radius: number
}
/**
* SegmentInputs is a union type that can be either a StraightSegmentInput or an ArcSegmentInput.
*
* - StraightSegmentInput: Represents a straight segment with a starting point (from) and an ending point (to).
* - ArcSegmentInput: Represents an arc segment with a starting point (from), a center point, and a radius.
*/
export type SegmentInputs = StraightSegmentInput | ArcSegmentInput
/**
* Interface for adding or replacing a sketch stblib call expression to a sketch.
* Replacing normally means adding or removing a constraint
*
* @property segmentInput - The input segment data, which can be either a straight segment or an arc segment.
* @property replaceExistingCallback - An optional callback function to replace an existing call expression,
* if not provided, a new call expression will be added using segMentInput values.
* @property referencedSegment - An optional path to a referenced segment.
* @property spliceBetween=false - Defaults to false. Normal behavior is to add a new callExpression to the end of the pipeExpression.
*/
interface addCall extends ModifyAstBase {
segmentInput: SegmentInputs
replaceExistingCallback?: (rawArgs: RawArgs) => CreatedSketchExprResult
referencedSegment?: Path referencedSegment?: Path
replaceExisting?: boolean
createCallback?: TransformCallback // TODO: #29 probably should not be optional
/// defaults to false, normal behavior is to add a new callExpression to the end of the pipeExpression
spliceBetween?: boolean spliceBetween?: boolean
} }
interface updateArgs extends ModifyAstBase { interface updateArgs extends ModifyAstBase {
from: [number, number] input: SegmentInputs
to: [number, number]
} }
export type VarValueKeys = 'angle' | 'offset' | 'length' | 'to' | 'intersectTag' export type InputArgKeys =
| 'angle'
| 'offset'
| 'length'
| 'to'
| 'intersectTag'
| 'radius'
| 'center'
export interface SingleValueInput<T> { export interface SingleValueInput<T> {
type: 'singleValue' type: 'singleValue'
argType: LineInputsType argType: LineInputsType
value: T expr: T
} }
export interface ArrayItemInput<T> { export interface ArrayItemInput<T> {
type: 'arrayItem' type: 'arrayItem'
index: 0 | 1 index: 0 | 1
argType: LineInputsType argType: LineInputsType
value: T expr: T
} }
export interface ObjectPropertyInput<T> { export interface ObjectPropertyInput<T> {
type: 'objectProperty' type: 'objectProperty'
key: VarValueKeys key: InputArgKeys
argType: LineInputsType argType: LineInputsType
value: T expr: T
} }
export interface ArrayOrObjItemInput<T> { interface ArrayOrObjItemInput<T> {
type: 'arrayOrObjItem' type: 'arrayOrObjItem'
key: VarValueKeys key: InputArgKeys
index: 0 | 1 index: 0 | 1
argType: LineInputsType argType: LineInputsType
value: T expr: T
} }
export type _VarValue<T> = interface ArrayInObject<T> {
type: 'arrayInObject'
key: InputArgKeys
argType: LineInputsType
index: 0 | 1
expr: T
}
type _InputArg<T> =
| SingleValueInput<T> | SingleValueInput<T>
| ArrayItemInput<T> | ArrayItemInput<T>
| ObjectPropertyInput<T> | ObjectPropertyInput<T>
| ArrayOrObjItemInput<T> | ArrayOrObjItemInput<T>
| ArrayInObject<T>
export type VarValue = _VarValue<Expr> /**
export type RawValue = _VarValue<Literal> * {@link RawArg.expr} is the current expression for each of the args for a segment
* i.e. if the expression is 5 + 6, {@link RawArg.expr} will be that binary expression
*
* Other properties on this type describe how the args are defined for this particular segment
* i.e. line uses [x, y] style inputs, while angledLine uses either [angle, length] or {angle, length}
* and circle uses {center: [x, y], radius: number}
* Which is why a union type is used that can be type narrowed using the {@link RawArg.type} property
* {@link RawArg.expr} is common to all of these types
*/
export type InputArg = _InputArg<Expr>
export type VarValues = Array<VarValue> /**
export type RawValues = Array<RawValue> * {@link RawArg.expr} is the literal equivalent of whatever current expression is
* i.e. if the expression is 5 + 6, the literal would be 11
* but of course works for expressions like myVar + someFn() etc too
* This is useful in cases where we want to "un-constrain" inputs to segments
*/
type RawArg = _InputArg<Literal>
type SimplifiedVarValue = export type InputArgs = Array<InputArg>
/**
* The literal equivalent of whatever current expression is
* i.e. if the expression is 5 + 6, the literal would be 11
* but of course works for expressions like myVar + someFn() etc too
* This is useful in cases where we want to "un-constrain" inputs to segments
*/
export type RawArgs = Array<RawArg>
/**
* Serves the same role as {@link InputArg} on {@link RawArg}
* but without the {@link RawArg.expr} property, since it is not needed
* when we only need to know where there arg is.
*/
export type SimplifiedArgDetails =
| { | {
type: 'singleValue' type: 'singleValue'
} }
| { type: 'arrayItem'; index: 0 | 1 } | { type: 'arrayItem'; index: 0 | 1 }
| { type: 'objectProperty'; key: VarValueKeys } | { type: 'objectProperty'; key: InputArgKeys }
| {
type: 'arrayInObject'
key: InputArgKeys
index: 0 | 1
}
export type TransformCallback = ( /**
args: [Expr, Expr], * Represents the result of creating a sketch expression (line, tangentialArcTo, angledLine, circle, etc.).
literalValues: RawValues, *
referencedSegment?: Path * @property {Expr} callExp - This is the main result; recasting the expression should give the user the new function call.
) => { * @property {number} [valueUsedInTransform] - Aside from `callExp`, we also return the number used in the transform, which is useful for constraints.
* For example, when adding a "horizontal distance" constraint, we don't want the segments to move, just constrain them in place.
* So the second segment will probably be something like `lineTo([segEndX($firstSegTag) + someLiteral, 123], %)` where `someLiteral` is
* the value of the current horizontal distance, that we calculate to constrain the second segment without it moving.
* We can run the ast-mod to get this constraint `valueUsedInTransform` without applying the mod so that we can surface this to the user in a modal.
* We show them the modal where they can specify the distance they want to constrain to.
* We pre-fill this with the current value `valueUsedInTransform`, which they can accept or change, and we'll use that to apply the final ast-mod.
*/
export interface CreatedSketchExprResult {
callExp: Expr callExp: Expr
valueUsedInTransform?: number valueUsedInTransform?: number
} }
export type CreateStdLibSketchCallExpr = (args: {
inputs: InputArgs
rawArgs: RawArgs
referenceSegName: string
tag?: Expr
forceValueUsedInTransform?: Expr
referencedSegment?: Path
}) => CreatedSketchExprResult
export type TransformInfo = {
tooltip: ToolTip
createNode: CreateStdLibSketchCallExpr
}
export interface ConstrainInfo { export interface ConstrainInfo {
stdLibFnName: ToolTip stdLibFnName: ToolTip
type: LineInputsType | 'vertical' | 'horizontal' | 'tangentialWithPrevious' type:
| LineInputsType
| 'vertical'
| 'horizontal'
| 'tangentialWithPrevious'
| 'radius'
isConstrained: boolean isConstrained: boolean
sourceRange: SourceRange sourceRange: SourceRange
pathToNode: PathToNode pathToNode: PathToNode
value: string value: string
calculatedValue?: any calculatedValue?: any
argPosition?: SimplifiedVarValue argPosition?: SimplifiedArgDetails
} }
export interface SketchLineHelper { export interface SketchLineHelper {

View File

@ -20,10 +20,8 @@ import {
} from 'lang/queryAst' } from 'lang/queryAst'
import { CommandArgument } from './commandTypes' import { CommandArgument } from './commandTypes'
import { import {
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
getParentGroup, getParentGroup,
PROFILE_START, SEGMENT_BODIES_PLUS_PROFILE_START,
} from 'clientSideScene/sceneEntities' } from 'clientSideScene/sceneEntities'
import { Mesh, Object3D, Object3DEventMap } from 'three' import { Mesh, Object3D, Object3DEventMap } from 'three'
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra' import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
@ -162,11 +160,7 @@ export async function getEventForSelectWithPoint({
export function getEventForSegmentSelection( export function getEventForSegmentSelection(
obj: Object3D<Object3DEventMap> obj: Object3D<Object3DEventMap>
): ModelingMachineEvent | null { ): ModelingMachineEvent | null {
const group = getParentGroup(obj, [ const group = getParentGroup(obj, SEGMENT_BODIES_PLUS_PROFILE_START)
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
const axisGroup = getParentGroup(obj, [AXIS_GROUP]) const axisGroup = getParentGroup(obj, [AXIS_GROUP])
if (!group && !axisGroup) return null if (!group && !axisGroup) return null
if (axisGroup?.userData.type === AXIS_GROUP) { if (axisGroup?.userData.type === AXIS_GROUP) {
@ -303,12 +297,7 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) {
const updated = kclManager.ast const updated = kclManager.ast
Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => { Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => {
if ( if (!SEGMENT_BODIES_PLUS_PROFILE_START.includes(segmentGroup?.name)) return
![STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, PROFILE_START].includes(
segmentGroup?.name
)
)
return
const nodeMeta = getNodeFromPath<CallExpression>( const nodeMeta = getNodeFromPath<CallExpression>(
updated, updated,
segmentGroup.userData.pathToNode, segmentGroup.userData.pathToNode,

View File

@ -2,7 +2,8 @@ import { CustomIconName } from 'components/CustomIcon'
import { DEV } from 'env' import { DEV } from 'env'
import { commandBarMachine } from 'machines/commandBarMachine' import { commandBarMachine } from 'machines/commandBarMachine'
import { import {
canRectangleTool, canRectangleOrCircleTool,
isClosedSketch,
isEditingExistingSketch, isEditingExistingSketch,
modelingMachine, modelingMachine,
} from 'machines/modelingMachine' } from 'machines/modelingMachine'
@ -301,7 +302,11 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
state.matches('Sketch no face') || state.matches('Sketch no face') ||
state.matches({ state.matches({
Sketch: { 'Rectangle tool': 'Awaiting second corner' }, Sketch: { 'Rectangle tool': 'Awaiting second corner' },
}), }) ||
state.matches({
Sketch: { 'Circle tool': 'Awaiting Radius' },
}) ||
isClosedSketch(state.context),
title: 'Line', title: 'Line',
hotkey: (state) => hotkey: (state) =>
state.matches({ Sketch: 'Line tool' }) ? ['Esc', 'L'] : 'L', state.matches({ Sketch: 'Line tool' }) ? ['Esc', 'L'] : 'L',
@ -324,8 +329,15 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
icon: 'arc', icon: 'arc',
status: 'available', status: 'available',
disabled: (state) => disabled: (state) =>
!isEditingExistingSketch(state.context) && (!isEditingExistingSketch(state.context) &&
!state.matches({ Sketch: 'Tangential arc to' }), !state.matches({ Sketch: 'Tangential arc to' })) ||
state.matches({
Sketch: { 'Rectangle tool': 'Awaiting second corner' },
}) ||
state.matches({
Sketch: { 'Circle tool': 'Awaiting Radius' },
}) ||
isClosedSketch(state.context),
title: 'Tangential Arc', title: 'Tangential Arc',
hotkey: (state) => hotkey: (state) =>
state.matches({ Sketch: 'Tangential arc to' }) ? ['Esc', 'A'] : 'A', state.matches({ Sketch: 'Tangential arc to' }) ? ['Esc', 'A'] : 'A',
@ -363,10 +375,22 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
[ [
{ {
id: 'circle-center', id: 'circle-center',
onClick: () => console.error('Center circle not yet implemented'), onClick: ({ modelingState, modelingSend }) =>
modelingSend({
type: 'change tool',
data: {
tool: !modelingState.matches({ Sketch: 'Circle tool' })
? 'circle'
: 'none',
},
}),
icon: 'circle', icon: 'circle',
status: 'unavailable', status: 'available',
title: 'Center circle', title: 'Center circle',
disabled: (state) =>
!canRectangleOrCircleTool(state.context) &&
!state.matches({ Sketch: 'Circle tool' }),
isActive: (state) => state.matches({ Sketch: 'Circle tool' }),
showTitle: false, showTitle: false,
description: 'Start drawing a circle from its center', description: 'Start drawing a circle from its center',
links: [ links: [
@ -382,7 +406,6 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
console.error('Three-point circle not yet implemented'), console.error('Three-point circle not yet implemented'),
icon: 'circle', icon: 'circle',
status: 'unavailable', status: 'unavailable',
disabled: () => true,
title: 'Three-point circle', title: 'Three-point circle',
showTitle: false, showTitle: false,
description: 'Draw a circle defined by three points', description: 'Draw a circle defined by three points',
@ -404,7 +427,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
icon: 'rectangle', icon: 'rectangle',
status: 'available', status: 'available',
disabled: (state) => disabled: (state) =>
!canRectangleTool(state.context) && !canRectangleOrCircleTool(state.context) &&
!state.matches({ Sketch: 'Rectangle tool' }), !state.matches({ Sketch: 'Rectangle tool' }),
title: 'Corner rectangle', title: 'Corner rectangle',
hotkey: (state) => hotkey: (state) =>

View File

@ -58,6 +58,8 @@ import { deleteSegment } from 'clientSideScene/ClientSideSceneComp'
import { executeAst } from 'lang/langHelpers' import { executeAst } from 'lang/langHelpers'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { ToolbarModeName } from 'lib/toolbar' import { ToolbarModeName } from 'lib/toolbar'
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
import { Vector3 } from 'three'
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY' export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
@ -159,7 +161,12 @@ export interface Store {
openPanes: SidebarType[] openPanes: SidebarType[]
} }
export type SketchTool = 'line' | 'tangentialArc' | 'rectangle' | 'none' export type SketchTool =
| 'line'
| 'tangentialArc'
| 'rectangle'
| 'circle'
| 'none'
export type ModelingMachineEvent = export type ModelingMachineEvent =
| { | {
@ -211,6 +218,10 @@ export type ModelingMachineEvent =
type: 'Add rectangle origin' type: 'Add rectangle origin'
data: [x: number, y: number] data: [x: number, y: number]
} }
| {
type: 'Add circle origin'
data: [x: number, y: number]
}
| { | {
type: 'xstate.done.actor.animate-to-face' type: 'xstate.done.actor.animate-to-face'
output: SketchDetails output: SketchDetails
@ -244,6 +255,7 @@ export type ModelingMachineEvent =
} }
} }
| { type: 'Finish rectangle' } | { type: 'Finish rectangle' }
| { type: 'Finish circle' }
| { type: 'Artifact graph populated' } | { type: 'Artifact graph populated' }
| { type: 'Artifact graph emptied' } | { type: 'Artifact graph emptied' }
@ -464,7 +476,10 @@ export const modelingMachine = setup({
isEditingExistingSketch({ sketchDetails }), isEditingExistingSketch({ sketchDetails }),
'next is rectangle': ({ context: { sketchDetails, currentTool } }) => 'next is rectangle': ({ context: { sketchDetails, currentTool } }) =>
currentTool === 'rectangle' && canRectangleTool({ sketchDetails }), currentTool === 'rectangle' &&
canRectangleOrCircleTool({ sketchDetails }),
'next is circle': ({ context: { sketchDetails, currentTool } }) =>
currentTool === 'circle' && canRectangleOrCircleTool({ sketchDetails }),
'next is line': ({ context }) => context.currentTool === 'line', 'next is line': ({ context }) => context.currentTool === 'line',
'next is none': ({ context }) => { 'next is none': ({ context }) => {
console.log('is next none?', context) console.log('is next none?', context)
@ -694,6 +709,42 @@ export const modelingMachine = setup({
}, },
}) })
}, },
'listen for circle origin': ({ context: { sketchDetails } }) => {
if (!sketchDetails) return
sceneEntitiesManager.createIntersectionPlane()
const quaternion = quaternionFromUpNForward(
new Vector3(...sketchDetails.yAxis),
new Vector3(...sketchDetails.zAxis)
)
// Position the click raycast plane
if (sceneEntitiesManager.intersectionPlane) {
sceneEntitiesManager.intersectionPlane.setRotationFromQuaternion(
quaternion
)
sceneEntitiesManager.intersectionPlane.position.copy(
new Vector3(...(sketchDetails?.origin || [0, 0, 0]))
)
}
sceneInfra.setCallbacks({
onClick: async (args) => {
if (!args) return
if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args
if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode)
return
const twoD = args.intersectionPoint?.twoD
if (twoD) {
sceneInfra.modelingSend({
type: 'Add circle origin',
data: [twoD.x, twoD.y],
})
} else {
console.error('No intersection point found')
}
},
})
},
'set up draft rectangle': ({ context: { sketchDetails }, event }) => { 'set up draft rectangle': ({ context: { sketchDetails }, event }) => {
if (event.type !== 'Add rectangle origin') return if (event.type !== 'Add rectangle origin') return
if (!sketchDetails || !event.data) return if (!sketchDetails || !event.data) return
@ -706,6 +757,18 @@ export const modelingMachine = setup({
event.data event.data
) )
}, },
'set up draft circle': ({ context: { sketchDetails }, event }) => {
if (event.type !== 'Add circle origin') return
if (!sketchDetails || !event.data) return
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sceneEntitiesManager.setupDraftCircle(
sketchDetails.sketchPathToNode,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin,
event.data
)
},
'set up draft line without teardown': ({ context: { sketchDetails } }) => { 'set up draft line without teardown': ({ context: { sketchDetails } }) => {
if (!sketchDetails) return if (!sketchDetails) return
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
@ -1805,10 +1868,43 @@ export const modelingMachine = setup({
target: 'Tangential arc to', target: 'Tangential arc to',
guard: 'next is tangential arc', guard: 'next is tangential arc',
}, },
{
target: 'Circle tool',
guard: 'next is circle',
},
], ],
entry: 'assign tool in context', entry: 'assign tool in context',
}, },
'Circle tool': {
on: {
'change tool': 'Change Tool',
},
states: {
'Awaiting origin': {
on: {
'Add circle origin': {
target: 'Awaiting Radius',
actions: 'set up draft circle',
},
},
},
'Awaiting Radius': {
on: {
'Finish circle': 'Finished Circle',
},
},
'Finished Circle': {
always: '#Modeling.Sketch.SketchIdle',
},
},
initial: 'Awaiting origin',
entry: 'listen for circle origin',
},
}, },
initial: 'Init', initial: 'Init',
@ -1946,10 +2042,13 @@ export function isEditingExistingSketch({
(item) => (item) =>
item.type === 'CallExpression' && item.callee.name === 'startProfileAt' item.type === 'CallExpression' && item.callee.name === 'startProfileAt'
) )
return hasStartProfileAt && pipeExpression.body.length > 2 const hasCircle = pipeExpression.body.some(
(item) => item.type === 'CallExpression' && item.callee.name === 'circle'
)
return (hasStartProfileAt && pipeExpression.body.length > 2) || hasCircle
} }
export function canRectangleTool({ export function canRectangleOrCircleTool({
sketchDetails, sketchDetails,
}: { }: {
sketchDetails: SketchDetails | null sketchDetails: SketchDetails | null
@ -1964,3 +2063,25 @@ export function canRectangleTool({
if (err(node)) return false if (err(node)) return false
return node.node?.declarations?.[0]?.init.type !== 'PipeExpression' return node.node?.declarations?.[0]?.init.type !== 'PipeExpression'
} }
/** If the sketch contains `close` or `circle` stdlib functions it must be closed */
export function isClosedSketch({
sketchDetails,
}: {
sketchDetails: SketchDetails | null
}): boolean {
const node = getNodeFromPath<VariableDeclaration>(
kclManager.ast,
sketchDetails?.sketchPathToNode || [],
'VariableDeclaration'
)
// This should not be returning false, and it should be caught
// but we need to simulate old behavior to move on.
if (err(node)) return false
if (node.node?.declarations?.[0]?.init.type !== 'PipeExpression') return false
return node.node.declarations[0].init.body.some(
(yo) =>
yo.type === 'CallExpression' &&
(yo.callee.name === 'close' || yo.callee.name === 'circle')
)
}

View File

@ -937,6 +937,9 @@ mod tests {
fn get_autocomplete_snippet_circle() { fn get_autocomplete_snippet_circle() {
let circle_fn: Box<dyn StdLibFn> = Box::new(crate::std::shapes::Circle); let circle_fn: Box<dyn StdLibFn> = Box::new(crate::std::shapes::Circle);
let snippet = circle_fn.to_autocomplete_snippet().unwrap(); let snippet = circle_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"circle([${0:3.14}, ${1:3.14}], ${2:3.14}, ${3:%})${}"#); assert_eq!(
snippet,
r#"circle({ center: [${0:3.14}, ${1:3.14}], radius: ${2:3.14} }, ${3:%})${}"#
);
} }
} }

View File

@ -1416,6 +1416,19 @@ pub enum Path {
/// arc's direction /// arc's direction
ccw: bool, ccw: bool,
}, },
/// a complete arc
Circle {
#[serde(flatten)]
base: BasePath,
/// the arc's center
#[ts(type = "[number, number]")]
center: [f64; 2],
/// the arc's radius
radius: f64,
/// arc's direction
// Maybe this one's not needed since it's a full revolution?
ccw: bool,
},
/// A path that is horizontal. /// A path that is horizontal.
Horizontal { Horizontal {
#[serde(flatten)] #[serde(flatten)]
@ -1448,6 +1461,7 @@ impl Path {
Path::Base { base } => base.geo_meta.id, Path::Base { base } => base.geo_meta.id,
Path::TangentialArcTo { base, .. } => base.geo_meta.id, Path::TangentialArcTo { base, .. } => base.geo_meta.id,
Path::TangentialArc { base, .. } => base.geo_meta.id, Path::TangentialArc { base, .. } => base.geo_meta.id,
Path::Circle { base, .. } => base.geo_meta.id,
} }
} }
@ -1459,6 +1473,7 @@ impl Path {
Path::Base { base } => base.tag.clone(), Path::Base { base } => base.tag.clone(),
Path::TangentialArcTo { base, .. } => base.tag.clone(), Path::TangentialArcTo { base, .. } => base.tag.clone(),
Path::TangentialArc { base, .. } => base.tag.clone(), Path::TangentialArc { base, .. } => base.tag.clone(),
Path::Circle { base, .. } => base.tag.clone(),
} }
} }
@ -1470,6 +1485,7 @@ impl Path {
Path::Base { base } => base, Path::Base { base } => base,
Path::TangentialArcTo { base, .. } => base, Path::TangentialArcTo { base, .. } => base,
Path::TangentialArc { base, .. } => base, Path::TangentialArc { base, .. } => base,
Path::Circle { base, .. } => base,
} }
} }
@ -1481,6 +1497,7 @@ impl Path {
Path::Base { base } => Some(base), Path::Base { base } => Some(base),
Path::TangentialArcTo { base, .. } => Some(base), Path::TangentialArcTo { base, .. } => Some(base),
Path::TangentialArc { base, .. } => Some(base), Path::TangentialArc { base, .. } => Some(base),
Path::Circle { base, .. } => Some(base),
} }
} }
} }
@ -2572,7 +2589,7 @@ fn transform = (replicaId) => {
fn layer = () => { fn layer = () => {
return startSketchOn("XY") return startSketchOn("XY")
|> circle([0, 0], 1, %, $tag1) |> circle({ center: [0, 0], radius: 1 }, %, $tag1)
|> extrude(10, %) |> extrude(10, %)
} }
@ -2700,7 +2717,7 @@ fn transform = (replicaId) => {
fn layer = () => { fn layer = () => {
return startSketchOn("XY") return startSketchOn("XY")
|> circle([0, 0], 1, %, $tag1) |> circle({ center: [0, 0], radius: 1 }, %, $tag1)
|> extrude(10, %) |> extrude(10, %)
} }

View File

@ -261,8 +261,7 @@ impl Args {
&self, &self,
) -> Result< ) -> Result<
( (
[f64; 2], crate::std::shapes::CircleData,
f64,
crate::std::shapes::SketchSurfaceOrGroup, crate::std::shapes::SketchSurfaceOrGroup,
Option<TagDeclarator>, Option<TagDeclarator>,
), ),
@ -619,6 +618,7 @@ fn from_user_val<T: DeserializeOwned>(arg: &KclValue) -> Option<T> {
impl_from_arg_via_json!(super::sketch::AngledLineData); impl_from_arg_via_json!(super::sketch::AngledLineData);
impl_from_arg_via_json!(super::sketch::AngledLineToData); impl_from_arg_via_json!(super::sketch::AngledLineToData);
impl_from_arg_via_json!(super::sketch::AngledLineThatIntersectsData); impl_from_arg_via_json!(super::sketch::AngledLineThatIntersectsData);
impl_from_arg_via_json!(super::shapes::CircleData);
impl_from_arg_via_json!(super::sketch::ArcData); impl_from_arg_via_json!(super::sketch::ArcData);
impl_from_arg_via_json!(super::sketch::TangentialArcData); impl_from_arg_via_json!(super::sketch::TangentialArcData);
impl_from_arg_via_json!(super::sketch::BezierData); impl_from_arg_via_json!(super::sketch::BezierData);

View File

@ -50,7 +50,7 @@ pub async fn int(args: Args) -> Result<KclValue, KclError> {
/// ///
/// ```no_run /// ```no_run
/// const sketch001 = startSketchOn('XZ') /// const sketch001 = startSketchOn('XZ')
/// |> circle([0, 0], 2, %) /// |> circle({ center: [0, 0], radius: 2 }, %)
/// const extrude001 = extrude(5, sketch001) /// const extrude001 = extrude(5, sketch001)
/// ///
/// const pattern01 = patternTransform(int(ceil(5 / 2)), (id) => { /// const pattern01 = patternTransform(int(ceil(5 / 2)), (id) => {

View File

@ -231,7 +231,7 @@ pub(crate) async fn do_post_extrude(
.flat_map(|path| { .flat_map(|path| {
if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) { if let Some(Some(actual_face_id)) = face_id_map.get(&path.get_base().geo_meta.id) {
match path { match path {
Path::TangentialArc { .. } | Path::TangentialArcTo { .. } => { Path::TangentialArc { .. } | Path::TangentialArcTo { .. } | Path::Circle { .. } => {
let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::executor::ExtrudeArc { let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::executor::ExtrudeArc {
face_id: *actual_face_id, face_id: *actual_face_id,
tag: path.get_base().tag.clone(), tag: path.get_base().tag.clone(),

View File

@ -42,7 +42,7 @@ pub async fn helix(args: Args) -> Result<KclValue, KclError> {
/// ///
/// ```no_run /// ```no_run
/// const part001 = startSketchOn('XY') /// const part001 = startSketchOn('XY')
/// |> circle([5, 5], 10, %) /// |> circle({ center: [5, 5], radius: 10 }, %)
/// |> extrude(10, %) /// |> extrude(10, %)
/// |> helix({ /// |> helix({
/// angleStart: 0, /// angleStart: 0,

View File

@ -91,10 +91,10 @@ pub async fn loft(args: Args) -> Result<KclValue, KclError> {
/// |> close(%) /// |> close(%)
/// ///
/// const circleSketch0 = startSketchOn(offsetPlane('XY', 75)) /// const circleSketch0 = startSketchOn(offsetPlane('XY', 75))
/// |> circle([0, 100], 50, %) /// |> circle({ center: [0, 100], radius: 50 }, %)
/// ///
/// const circleSketch1 = startSketchOn(offsetPlane('XY', 150)) /// const circleSketch1 = startSketchOn(offsetPlane('XY', 150))
/// |> circle([0, 100], 20, %) /// |> circle({ center: [0, 100], radius: 20 }, %)
/// ///
/// loft([squareSketch, circleSketch0, circleSketch1]) /// loft([squareSketch, circleSketch0, circleSketch1])
/// ``` /// ```
@ -110,10 +110,10 @@ pub async fn loft(args: Args) -> Result<KclValue, KclError> {
/// |> close(%) /// |> close(%)
/// ///
/// const circleSketch0 = startSketchOn(offsetPlane('XY', 75)) /// const circleSketch0 = startSketchOn(offsetPlane('XY', 75))
/// |> circle([0, 100], 50, %) /// |> circle({ center: [0, 100], radius: 50 }, %)
/// ///
/// const circleSketch1 = startSketchOn(offsetPlane('XY', 150)) /// const circleSketch1 = startSketchOn(offsetPlane('XY', 150))
/// |> circle([0, 100], 20, %) /// |> circle({ center: [0, 100], radius: 20 }, %)
/// ///
/// loft([squareSketch, circleSketch0, circleSketch1], { /// loft([squareSketch, circleSketch0, circleSketch1], {
/// // This can be set to override the automatically determined /// // This can be set to override the automatically determined

View File

@ -113,7 +113,7 @@ pub async fn pi(args: Args) -> Result<KclValue, KclError> {
/// const circumference = 70 /// const circumference = 70
/// ///
/// const exampleSketch = startSketchOn("XZ") /// const exampleSketch = startSketchOn("XZ")
/// |> circle([0, 0], circumference/ (2 * pi()), %) /// |> circle({ center: [0, 0], radius: circumference/ (2 * pi()) }, %)
/// ///
/// const example = extrude(5, exampleSketch) /// const example = extrude(5, exampleSketch)
/// ``` /// ```

View File

@ -37,7 +37,7 @@ use crate::{
ast::types::FunctionExpression, ast::types::FunctionExpression,
docs::StdLibFn, docs::StdLibFn,
errors::KclError, errors::KclError,
executor::{KclValue, ProgramMemory, SketchGroup, SketchSurface}, executor::{KclValue, ProgramMemory},
std::kcl_stdlib::KclStdLibFn, std::kcl_stdlib::KclStdLibFn,
}; };

View File

@ -117,7 +117,7 @@ pub async fn pattern_transform(args: Args) -> Result<KclValue, KclError> {
/// // Each layer is just a pretty thin cylinder. /// // Each layer is just a pretty thin cylinder.
/// fn layer = () => { /// fn layer = () => {
/// return startSketchOn("XY") // or some other plane idk /// return startSketchOn("XY") // or some other plane idk
/// |> circle([0, 0], 1, %, $tag1) /// |> circle({ center: [0, 0], radius: 1 }, %, $tag1)
/// |> extrude(h, %) /// |> extrude(h, %)
/// } /// }
/// // The vase is 100 layers tall. /// // The vase is 100 layers tall.
@ -318,7 +318,7 @@ pub async fn pattern_linear_2d(args: Args) -> Result<KclValue, KclError> {
/// ///
/// ```no_run /// ```no_run
/// const exampleSketch = startSketchOn('XZ') /// const exampleSketch = startSketchOn('XZ')
/// |> circle([0, 0], 1, %) /// |> circle({ center: [0, 0], radius: 1 }, %)
/// |> patternLinear2d({ /// |> patternLinear2d({
/// axis: [1, 0], /// axis: [1, 0],
/// repetitions: 6, /// repetitions: 6,
@ -651,7 +651,7 @@ pub async fn pattern_circular_3d(args: Args) -> Result<KclValue, KclError> {
/// ///
/// ```no_run /// ```no_run
/// const exampleSketch = startSketchOn('XZ') /// const exampleSketch = startSketchOn('XZ')
/// |> circle([0, 0], 1, %) /// |> circle({ center: [0, 0], radius: 1 }, %)
/// ///
/// const example = extrude(-5, exampleSketch) /// const example = extrude(-5, exampleSketch)
/// |> patternCircular3d({ /// |> patternCircular3d({

View File

@ -77,7 +77,7 @@ pub async fn offset_plane(args: Args) -> Result<KclValue, KclError> {
/// |> close(%) /// |> close(%)
/// ///
/// const circleSketch = startSketchOn(offsetPlane('XY', 150)) /// const circleSketch = startSketchOn(offsetPlane('XY', 150))
/// |> circle([0, 100], 50, %) /// |> circle({ center: [0, 100], radius: 50 }, %)
/// ///
/// loft([squareSketch, circleSketch]) /// loft([squareSketch, circleSketch])
/// ``` /// ```
@ -93,7 +93,7 @@ pub async fn offset_plane(args: Args) -> Result<KclValue, KclError> {
/// |> close(%) /// |> close(%)
/// ///
/// const circleSketch = startSketchOn(offsetPlane('XZ', 150)) /// const circleSketch = startSketchOn(offsetPlane('XZ', 150))
/// |> circle([0, 100], 50, %) /// |> circle({ center: [0, 100], radius: 50 }, %)
/// ///
/// loft([squareSketch, circleSketch]) /// loft([squareSketch, circleSketch])
/// ``` /// ```
@ -109,7 +109,7 @@ pub async fn offset_plane(args: Args) -> Result<KclValue, KclError> {
/// |> close(%) /// |> close(%)
/// ///
/// const circleSketch = startSketchOn(offsetPlane('YZ', 150)) /// const circleSketch = startSketchOn(offsetPlane('YZ', 150))
/// |> circle([0, 100], 50, %) /// |> circle({ center: [0, 100], radius: 50 }, %)
/// ///
/// loft([squareSketch, circleSketch]) /// loft([squareSketch, circleSketch])
/// ``` /// ```
@ -125,7 +125,7 @@ pub async fn offset_plane(args: Args) -> Result<KclValue, KclError> {
/// |> close(%) /// |> close(%)
/// ///
/// const circleSketch = startSketchOn(offsetPlane('-XZ', -150)) /// const circleSketch = startSketchOn(offsetPlane('-XZ', -150))
/// |> circle([0, 100], 50, %) /// |> circle({ center: [0, 100], radius: 50 }, %)
/// ///
/// loft([squareSketch, circleSketch]) /// loft([squareSketch, circleSketch])
/// ``` /// ```

View File

@ -133,7 +133,7 @@ pub async fn revolve(args: Args) -> Result<KclValue, KclError> {
/// ```no_run /// ```no_run
/// // A donut shape. /// // A donut shape.
/// const sketch001 = startSketchOn('XY') /// const sketch001 = startSketchOn('XY')
/// |> circle([15, 0], 5, %) /// |> circle({ center: [15, 0], radius: 5 }, %)
/// |> revolve({ /// |> revolve({
/// angle: 360, /// angle: 360,
/// axis: 'y' /// axis: 'y'
@ -185,7 +185,7 @@ pub async fn revolve(args: Args) -> Result<KclValue, KclError> {
/// |> extrude(20, %) /// |> extrude(20, %)
/// ///
/// const sketch001 = startSketchOn(box, "END") /// const sketch001 = startSketchOn(box, "END")
/// |> circle([10,10], 4, %) /// |> circle({ center: [10,10], radius: 4 }, %)
/// |> revolve({ /// |> revolve({
/// angle: -90, /// angle: -90,
/// axis: 'y' /// axis: 'y'
@ -202,7 +202,7 @@ pub async fn revolve(args: Args) -> Result<KclValue, KclError> {
/// |> extrude(20, %) /// |> extrude(20, %)
/// ///
/// const sketch001 = startSketchOn(box, "END") /// const sketch001 = startSketchOn(box, "END")
/// |> circle([10,10], 4, %) /// |> circle({ center: [10,10], radius: 4 }, %)
/// |> revolve({ /// |> revolve({
/// angle: 90, /// angle: 90,
/// axis: getOppositeEdge(revolveAxis) /// axis: getOppositeEdge(revolveAxis)
@ -219,7 +219,7 @@ pub async fn revolve(args: Args) -> Result<KclValue, KclError> {
/// |> extrude(20, %) /// |> extrude(20, %)
/// ///
/// const sketch001 = startSketchOn(box, "END") /// const sketch001 = startSketchOn(box, "END")
/// |> circle([10,10], 4, %) /// |> circle({ center: [10,10], radius: 4 }, %)
/// |> revolve({ /// |> revolve({
/// angle: 90, /// angle: 90,
/// axis: getOppositeEdge(revolveAxis), /// axis: getOppositeEdge(revolveAxis),

View File

@ -2,14 +2,15 @@
use anyhow::Result; use anyhow::Result;
use derive_docs::stdlib; use derive_docs::stdlib;
use kittycad::types::{Angle, ModelingCmd};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
ast::types::TagDeclarator, ast::types::TagDeclarator,
errors::KclError, errors::{KclError, KclErrorDetails},
executor::KclValue, executor::{BasePath, GeoMeta, KclValue, Path, SketchGroup, SketchSurface},
std::{Args, SketchGroup, SketchSurface}, std::Args,
}; };
/// A sketch surface or a sketch group. /// A sketch surface or a sketch group.
@ -21,12 +22,24 @@ pub enum SketchSurfaceOrGroup {
SketchGroup(Box<SketchGroup>), SketchGroup(Box<SketchGroup>),
} }
/// Data for drawing an angled line that intersects with a given line.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
// TODO: make sure the docs on the args below are correct.
pub struct CircleData {
/// The center of the circle.
pub center: [f64; 2],
/// The circle radius
pub radius: f64,
}
/// Sketch a circle. /// Sketch a circle.
pub async fn circle(args: Args) -> Result<KclValue, KclError> { pub async fn circle(args: Args) -> Result<KclValue, KclError> {
let (center, radius, sketch_surface_or_group, tag): ([f64; 2], f64, SketchSurfaceOrGroup, Option<TagDeclarator>) = let (data, sketch_surface_or_group, tag): (CircleData, SketchSurfaceOrGroup, Option<TagDeclarator>) =
args.get_circle_args()?; args.get_circle_args()?;
let sketch_group = inner_circle(center, radius, sketch_surface_or_group, tag, args).await?; let sketch_group = inner_circle(data, sketch_surface_or_group, tag, args).await?;
Ok(KclValue::new_user_val(sketch_group.meta.clone(), sketch_group)) Ok(KclValue::new_user_val(sketch_group.meta.clone(), sketch_group))
} }
@ -35,7 +48,7 @@ pub async fn circle(args: Args) -> Result<KclValue, KclError> {
/// ///
/// ```no_run /// ```no_run
/// const exampleSketch = startSketchOn("-XZ") /// const exampleSketch = startSketchOn("-XZ")
/// |> circle([0, 0], 10, %) /// |> circle({ center: [0, 0], radius: 10 }, %)
/// ///
/// const example = extrude(5, exampleSketch) /// const example = extrude(5, exampleSketch)
/// ``` /// ```
@ -47,7 +60,7 @@ pub async fn circle(args: Args) -> Result<KclValue, KclError> {
/// |> line([0, 30], %) /// |> line([0, 30], %)
/// |> line([-30, 0], %) /// |> line([-30, 0], %)
/// |> close(%) /// |> close(%)
/// |> hole(circle([0, 15], 5, %), %) /// |> hole(circle({ center: [0, 15], radius: 5 }, %), %)
/// ///
/// const example = extrude(5, exampleSketch) /// const example = extrude(5, exampleSketch)
/// ``` /// ```
@ -55,8 +68,7 @@ pub async fn circle(args: Args) -> Result<KclValue, KclError> {
name = "circle", name = "circle",
}] }]
async fn inner_circle( async fn inner_circle(
center: [f64; 2], data: CircleData,
radius: f64,
sketch_surface_or_group: SketchSurfaceOrGroup, sketch_surface_or_group: SketchSurfaceOrGroup,
tag: Option<TagDeclarator>, tag: Option<TagDeclarator>,
args: Args, args: Args,
@ -65,23 +77,70 @@ async fn inner_circle(
SketchSurfaceOrGroup::SketchSurface(surface) => surface, SketchSurfaceOrGroup::SketchSurface(surface) => surface,
SketchSurfaceOrGroup::SketchGroup(group) => group.on, SketchSurfaceOrGroup::SketchGroup(group) => group.on,
}; };
let mut sketch_group = let sketch_group = crate::std::sketch::inner_start_profile_at(
crate::std::sketch::inner_start_profile_at([center[0] + radius, center[1]], sketch_surface, None, args.clone()) [data.center[0] + data.radius, data.center[1]],
.await?; sketch_surface,
None,
// Call arc.
sketch_group = crate::std::sketch::inner_arc(
crate::std::sketch::ArcData::AnglesAndRadius {
angle_start: 0.0,
angle_end: 360.0,
radius,
},
sketch_group,
tag,
args.clone(), args.clone(),
) )
.await?; .await?;
// Call close. let angle_start = Angle::from_degrees(0.0);
crate::std::sketch::inner_close(sketch_group, None, args).await let angle_end = Angle::from_degrees(360.0);
if angle_start == angle_end {
return Err(KclError::Type(KclErrorDetails {
message: "Arc start and end angles must be different".to_string(),
source_ranges: vec![args.source_range],
}));
}
let id = uuid::Uuid::new_v4();
args.batch_modeling_cmd(
id,
ModelingCmd::ExtendPath {
path: sketch_group.id,
segment: kittycad::types::PathSegment::Arc {
start: angle_start,
end: angle_end,
center: data.center.into(),
radius: data.radius,
relative: false,
},
},
)
.await?;
let current_path = Path::Circle {
base: BasePath {
from: data.center,
to: data.center,
tag: tag.clone(),
geo_meta: GeoMeta {
id,
metadata: args.source_range.into(),
},
},
radius: data.radius,
center: data.center,
ccw: angle_start.degrees() < angle_end.degrees(),
};
let mut new_sketch_group = sketch_group.clone();
if let Some(tag) = &tag {
new_sketch_group.add_tag(tag, &current_path);
}
new_sketch_group.value.push(current_path);
args.batch_modeling_cmd(
id,
ModelingCmd::ClosePath {
path_id: new_sketch_group.id,
},
)
.await?;
Ok(new_sketch_group)
} }

View File

@ -2059,8 +2059,8 @@ pub async fn hole(args: Args) -> Result<KclValue, KclError> {
/// |> line([5, 0], %) /// |> line([5, 0], %)
/// |> line([0, -5], %) /// |> line([0, -5], %)
/// |> close(%) /// |> close(%)
/// |> hole(circle([1, 1], .25, %), %) /// |> hole(circle({ center: [1, 1], radius: .25 }, %), %)
/// |> hole(circle([1, 4], .25, %), %) /// |> hole(circle({ center: [1, 4], radius: .25 }, %), %)
/// ///
/// const example = extrude(1, exampleSketch) /// const example = extrude(1, exampleSketch)
/// ``` /// ```
@ -2077,7 +2077,7 @@ pub async fn hole(args: Args) -> Result<KclValue, KclError> {
/// } /// }
/// ///
/// const exampleSketch = startSketchOn('-XZ') /// const exampleSketch = startSketchOn('-XZ')
/// |> circle([0, 0], 3, %) /// |> circle({ center: [0, 0], radius: 3 }, %)
/// |> hole(squareHoleSketch(), %) /// |> hole(squareHoleSketch(), %)
/// const example = extrude(1, exampleSketch) /// const example = extrude(1, exampleSketch)
/// ``` /// ```

View File

@ -1028,10 +1028,10 @@ const tabs_r = startSketchOn({
|> line([0, -10], %) |> line([0, -10], %)
|> line([-10, -5], %) |> line([-10, -5], %)
|> close(%) |> close(%)
|> hole(circle([ |> hole(circle({ center: [
width / 2 + thk + hole_diam, width / 2 + thk + hole_diam,
length / 2 - hole_diam length / 2 - hole_diam
], hole_diam / 2, %), %) ], radius: hole_diam / 2 }, %), %)
|> extrude(-thk, %) |> extrude(-thk, %)
|> patternLinear3d({ |> patternLinear3d({
axis: [0, -1, 0], axis: [0, -1, 0],
@ -1052,10 +1052,10 @@ const tabs_l = startSketchOn({
|> line([0, -10], %) |> line([0, -10], %)
|> line([10, -5], %) |> line([10, -5], %)
|> close(%) |> close(%)
|> hole(circle([ |> hole(circle({ center: [
-width / 2 - thk - hole_diam, -width / 2 - thk - hole_diam,
length / 2 - hole_diam length / 2 - hole_diam
], hole_diam / 2, %), %) ], radius: hole_diam / 2 }, %), %)
|> extrude(-thk, %) |> extrude(-thk, %)
|> patternLinear3d({ |> patternLinear3d({
axis: [0, -1, 0], axis: [0, -1, 0],
@ -1148,10 +1148,10 @@ const tabs_r = startSketchOn({
|> line([0, -10], %) |> line([0, -10], %)
|> line([-10, -5], %) |> line([-10, -5], %)
|> close(%) |> close(%)
|> hole(circle([ |> hole(circle({ center: [
width / 2 + thk + hole_diam, width / 2 + thk + hole_diam,
length / 2 - hole_diam length / 2 - hole_diam
], hole_diam / 2, %), %) ], radius: hole_diam / 2 }, %), %)
|> extrude(-thk, %) |> extrude(-thk, %)
|> patternLinear3d({ |> patternLinear3d({
axis: [0, -1, 0], axis: [0, -1, 0],
@ -1172,10 +1172,10 @@ const tabs_l = startSketchOn({
|> line([0, -10], %) |> line([0, -10], %)
|> line([10, -5], %) |> line([10, -5], %)
|> close(%) |> close(%)
|> hole(circle([ |> hole(circle({ center: [
-width / 2 - thk - hole_diam, -width / 2 - thk - hole_diam,
length / 2 - hole_diam length / 2 - hole_diam
], hole_diam / 2, %), %) ], radius: hole_diam / 2 }, %), %)
|> extrude(-thk, %) |> extrude(-thk, %)
|> patternLinear3d({ |> patternLinear3d({
axis: [0, -1, 0], axis: [0, -1, 0],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 102 KiB

View File

@ -1,3 +1,3 @@
const cylinder = startSketchOn('XY') const cylinder = startSketchOn('XY')
|> circle([0,0], 22, %) |> circle({ center: [0, 0], radius: 22 }, %)
|> extrude(14, %) |> extrude(14, %)

View File

@ -61,8 +61,8 @@ const case = startSketchOn('XY')
fn m25Screw = (x, y, height) => { fn m25Screw = (x, y, height) => {
const screw = startSketchOn("XY") const screw = startSketchOn("XY")
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> circle([x, y], 2.5, %) |> circle({ center: [x, y], radius: 2.5 }, %)
|> hole(circle([x, y], 1.25, %), %) |> hole(circle({ center: [x, y], radius: 1.25 }, %), %)
|> extrude(height, %) |> extrude(height, %)
return screw return screw
} }

View File

@ -1,6 +1,7 @@
// A mounting bracket for the Focusrite Scarlett Solo audio interface // A mounting bracket for the Focusrite Scarlett Solo audio interface
// This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material // This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material
// define constants in mm // define constants in mm
const radius = 6.0 const radius = 6.0
const width = 144.0 const width = 144.0
@ -79,10 +80,13 @@ const tabsR = startSketchOn(tabPlane)
|> line([0, -tabLength / 3 * 2], %, $edge12) |> line([0, -tabLength / 3 * 2], %, $edge12)
|> line([-tabWidth, -tabLength / 3], %, $edge13) |> line([-tabWidth, -tabLength / 3], %, $edge13)
|> close(%, $edge14) |> close(%, $edge14)
|> hole(circle([ |> hole(circle({
width / 2 + thk + tabWidth / 2, center: [
length / 2 + thk - (tabLength / (3 / 2)) width / 2 + thk + tabWidth / 2,
], holeDiam / 2, %), %) length / 2 + thk - (tabLength / (3 / 2))
],
radius: holeDiam / 2
}, %), %)
|> extrude(-tabThk, %) |> extrude(-tabThk, %)
|> fillet({ |> fillet({
radius: holeDiam / 2, radius: holeDiam / 2,
@ -104,10 +108,13 @@ const tabsL = startSketchOn(tabPlane)
|> line([0, -tabLength / 3 * 2], %, $edge22) |> line([0, -tabLength / 3 * 2], %, $edge22)
|> line([tabWidth, -tabLength / 3], %, $edge23) |> line([tabWidth, -tabLength / 3], %, $edge23)
|> close(%, $edge24) |> close(%, $edge24)
|> hole(circle([ |> hole(circle({
-width / 2 - thk - (tabWidth / 2), center: [
length / 2 + thk - (tabLength / (3 / 2)) -width / 2 - thk - (tabWidth / 2),
], holeDiam / 2, %), %) length / 2 + thk - (tabLength / (3 / 2))
],
radius: holeDiam / 2
}, %), %)
|> extrude(-tabThk, %) |> extrude(-tabThk, %)
|> fillet({ |> fillet({
radius: holeDiam / 2, radius: holeDiam / 2,

View File

@ -80,10 +80,13 @@ const tabsR = startSketchOn(tabPlane)
|> line([0, -tabLength / 3 * 2], %, $edge12) |> line([0, -tabLength / 3 * 2], %, $edge12)
|> line([-tabWidth, -tabLength / 3], %, $edge13) |> line([-tabWidth, -tabLength / 3], %, $edge13)
|> close(%, $edge14) |> close(%, $edge14)
|> hole(circle([ |> hole(circle({
width / 2 + thk + tabWidth / 2, center: [
length / 2 + thk - (tabLength / (3 / 2)) width / 2 + thk + tabWidth / 2,
], holeDiam / 2, %), %) length / 2 + thk - (tabLength / (3 / 2))
],
radius: holeDiam / 2
}, %), %)
|> extrude(-tabThk, %) |> extrude(-tabThk, %)
|> fillet({ |> fillet({
radius: holeDiam / 2, radius: holeDiam / 2,
@ -105,10 +108,13 @@ const tabsL = startSketchOn(tabPlane)
|> line([0, -tabLength / 3 * 2], %, $edge22) |> line([0, -tabLength / 3 * 2], %, $edge22)
|> line([tabWidth, -tabLength / 3], %, $edge23) |> line([tabWidth, -tabLength / 3], %, $edge23)
|> close(%, $edge24) |> close(%, $edge24)
|> hole(circle([ |> hole(circle({
-width / 2 - thk - (tabWidth / 2), center: [
length / 2 + thk - (tabLength / (3 / 2)) -width / 2 - thk - (tabWidth / 2),
], holeDiam / 2, %), %) length / 2 + thk - (tabLength / (3 / 2))
],
radius: holeDiam / 2
}, %), %)
|> extrude(-tabThk, %) |> extrude(-tabThk, %)
|> fillet({ |> fillet({
radius: holeDiam / 2, radius: holeDiam / 2,

View File

@ -1,4 +1,4 @@
const part001 = startSketchOn('XY') const part001 = startSketchOn('XY')
|> circle([5, 5], 10, %) |> circle({ center: [5, 5], radius: 10 }, %)
|> extrude(10, %) |> extrude(10, %)
|> helix({revolutions: 16, angle_start: 0, ccw: true}, %) |> helix({revolutions: 16, angle_start: 0, ccw: true}, %)

View File

@ -1,4 +1,4 @@
const part001 = startSketchOn('XY') const part001 = startSketchOn('XY')
|> circle([5, 5], 10, %) |> circle({ center: [5, 5], radius: 10 }, %)
|> extrude(10, %) |> extrude(10, %)
|> helix({revolutions: 16, angle_start: 0}, %) |> helix({revolutions: 16, angle_start: 0}, %)

View File

@ -1,4 +1,4 @@
const part001 = startSketchOn('XY') const part001 = startSketchOn('XY')
|> circle([5, 5], 10, %) |> circle({ center: [5, 5], radius: 10 }, %)
|> extrude(-10, %) |> extrude(-10, %)
|> helix({revolutions: 16, angle_start: 0}, %) |> helix({revolutions: 16, angle_start: 0}, %)

View File

@ -1,4 +1,4 @@
const part001 = startSketchOn('XY') const part001 = startSketchOn('XY')
|> circle([5, 5], 10, %) |> circle({ center: [5, 5], radius: 10 }, %)
|> extrude(10, %) |> extrude(10, %)
|> helix({revolutions: 16, angle_start: 0, length: 3}, %) |> helix({revolutions: 16, angle_start: 0, length: 3}, %)

View File

@ -39,10 +39,10 @@ const shellExtrude = startSketchOn(s, "start")
|> extrude(-(height - t), %) |> extrude(-(height - t), %)
const peg = startSketchOn(s, "end") const peg = startSketchOn(s, "end")
|> circle([ |> circle({ center: [
-(total_width / 2 - wSegments), -(total_width / 2 - wSegments),
-(total_length / 2 - lSegments) -(total_length / 2 - lSegments)
], bumpDiam / 2, %) ], radius: bumpDiam / 2 }, %)
|> patternLinear2d({ |> patternLinear2d({
axis: [1, 0], axis: [1, 0],
repetitions: 5, repetitions: 5,

View File

@ -14,7 +14,7 @@ fn transform = (replicaId) => {
// Each layer is just a pretty thin cylinder with a fillet. // Each layer is just a pretty thin cylinder with a fillet.
fn layer = () => { fn layer = () => {
return startSketchOn("XY") // or some other plane idk return startSketchOn("XY") // or some other plane idk
|> circle([0, 0], 1, %, $tag1) |> circle({ center: [0, 0], radius: 1 }, %, $tag1)
|> extrude(h, %) |> extrude(h, %)
// |> fillet({ // |> fillet({
// radius: h / 2.01, // radius: h / 2.01,

View File

@ -30,22 +30,22 @@ fn caster = (originStart) => {
|> xLine(-3.543, %) |> xLine(-3.543, %)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
|> hole(circle([ |> hole(circle({ center: [
(3.543 - 2.756) / 2, (3.543 - 2.756) / 2,
(3.543 - 2.756) / 2 (3.543 - 2.756) / 2
], 8.8 / 2 / 25.4, %), %) ], radius: 8.8 / 2 / 25.4}, %), %)
|> hole(circle([ |> hole(circle({ center: [
(3.543 - 2.756) / 2 + 2.756, (3.543 - 2.756) / 2 + 2.756,
(3.543 - 2.756) / 2 (3.543 - 2.756) / 2
], 8.8 / 2 / 25.4, %), %) ], radius: 8.8 / 2 / 25.4 }, %), %)
|> hole(circle([ |> hole(circle({ center: [
(3.543 - 2.756) / 2, (3.543 - 2.756) / 2,
(3.543 - 2.756) / 2 + 2.756 (3.543 - 2.756) / 2 + 2.756
], 8.8 / 2 / 25.4, %), %) ], radius: 8.8 / 2 / 25.4 }, %), %)
|> hole(circle([ |> hole(circle({ center: [
(3.543 - 2.756) / 2 + 2.756, (3.543 - 2.756) / 2 + 2.756,
(3.543 - 2.756) / 2 + 2.756 (3.543 - 2.756) / 2 + 2.756
], 8.8 / 2 / 25.4, %), %) ], radius: 8.8 / 2 / 25.4 }, %), %)
|> extrude(-.25, %) |> extrude(-.25, %)
const sketch002c = startSketchOn(sketch001c, 'START') const sketch002c = startSketchOn(sketch001c, 'START')
@ -71,7 +71,7 @@ fn caster = (originStart) => {
} }
} }
const sketch003c = startSketchOn(plane002c) const sketch003c = startSketchOn(plane002c)
|> circle([0, 1.2], 2.48 / 2, %) |> circle({ center: [0, 1.2], radius 2.48 / 2 }, %)
const examplec = extrude(-1 - (3 / 16), sketch003c) const examplec = extrude(-1 - (3 / 16), sketch003c)
return examplec return examplec
} }

View File

@ -28,22 +28,22 @@ fn caster = (originStart) => {
|> xLine(-3.543, %) |> xLine(-3.543, %)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
|> hole(circle([ |> hole(circle({ center: [
(3.543 - 2.756) / 2, (3.543 - 2.756) / 2,
(3.543 - 2.756) / 2 (3.543 - 2.756) / 2
], 8.8 / 2 / 25.4, %), %) ], radius: 8.8 / 2 / 25.4 }, %), %)
|> hole(circle([ |> hole(circle({ center: [
(3.543 - 2.756) / 2 + 2.756, (3.543 - 2.756) / 2 + 2.756,
(3.543 - 2.756) / 2 (3.543 - 2.756) / 2
], 8.8 / 2 / 25.4, %), %) ], radius: 8.8 / 2 / 25.4 }, %), %)
|> hole(circle([ |> hole(circle({ center: [
(3.543 - 2.756) / 2, (3.543 - 2.756) / 2,
(3.543 - 2.756) / 2 + 2.756 (3.543 - 2.756) / 2 + 2.756
], 8.8 / 2 / 25.4, %), %) ], radius: 8.8 / 2 / 25.4 }, %), %)
|> hole(circle([ |> hole(circle({ center: [
(3.543 - 2.756) / 2 + 2.756, (3.543 - 2.756) / 2 + 2.756,
(3.543 - 2.756) / 2 + 2.756 (3.543 - 2.756) / 2 + 2.756
], 8.8 / 2 / 25.4, %), %) ], radius: 8.8 / 2 / 25.4 }, %), %)
|> extrude(-.25, %) |> extrude(-.25, %)
const sketch002c = startSketchOn(sketch001c, 'START') const sketch002c = startSketchOn(sketch001c, 'START')
@ -69,7 +69,7 @@ fn caster = (originStart) => {
} }
} }
const sketch003c = startSketchOn(plane002c) const sketch003c = startSketchOn(plane002c)
|> circle([0, 1.2], 2.48 / 2, %) |> circle({ center: [0, 1.2], radisu: 2.48 / 2 }, %)
const examplec = extrude(-1 - (3 / 16), sketch003c) const examplec = extrude(-1 - (3 / 16), sketch003c)
return examplec return examplec
} }

View File

@ -12,5 +12,5 @@ const part001 = cube([0,0], 20)
|> extrude(20, %) |> extrude(20, %)
const part002 = startSketchOn(part001, "end") const part002 = startSketchOn(part001, "end")
|> circle([0, 0], 5, %, $myCircle) |> circle({ center: [0, 0], radisu: 5 }, %, $myCircle)
|> extrude(5, %) |> extrude(5, %)

View File

@ -62,10 +62,10 @@ fn tr = (i) => {
// Create the pegs on the top of the base // Create the pegs on the top of the base
const totalBumps = (wbumps * lbumps)-1 const totalBumps = (wbumps * lbumps)-1
const peg = startSketchOn(s, 'end') const peg = startSketchOn(s, 'end')
|> circle([ |> circle({ center: [
-(pitch*(wbumps-1)/2), -(pitch*(wbumps-1)/2),
-(pitch*(lbumps-1)/2) -(pitch*(lbumps-1)/2)
], bumpDiam / 2, %) ], radius: bumpDiam / 2 }, %)
|> patternLinear2d({ |> patternLinear2d({
axis: [1, 0], axis: [1, 0],
repetitions: wbumps-1, repetitions: wbumps-1,

View File

@ -301,8 +301,8 @@ async fn kcl_test_holes() {
|> line([10, 0], %) |> line([10, 0], %)
|> line([0, -10], %) |> line([0, -10], %)
|> close(%) |> close(%)
|> hole(circle([2, 2], .5, %), %) |> hole(circle({ center: [2, 2], radius: .5 }, %), %)
|> hole(circle([2, 8], .5, %), %) |> hole(circle({ center: [2, 8], radius: .5 }, %), %)
|> extrude(2, %) |> extrude(2, %)
"#; "#;
@ -354,10 +354,10 @@ const holeRadius = 1
const holeIndex = 6 const holeIndex = 6
const part = roundedRectangle([0, 0], 20, 20, 4) const part = roundedRectangle([0, 0], 20, 20, 4)
|> hole(circle([-holeIndex, holeIndex], holeRadius, %), %) |> hole(circle({ center: [-holeIndex, holeIndex], radius: holeRadius }, %), %)
|> hole(circle([holeIndex, holeIndex], holeRadius, %), %) |> hole(circle({ center: [holeIndex, holeIndex], radius: holeRadius }, %), %)
|> hole(circle([-holeIndex, -holeIndex], holeRadius, %), %) |> hole(circle({ center: [-holeIndex, -holeIndex], radius: holeRadius }, %), %)
|> hole(circle([holeIndex, -holeIndex], holeRadius, %), %) |> hole(circle({ center: [holeIndex, -holeIndex], radius: holeRadius }, %), %)
|> extrude(2, %) |> extrude(2, %)
"#; "#;
@ -367,7 +367,7 @@ const part = roundedRectangle([0, 0], 20, 20, 4)
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn kcl_test_top_level_expression() { async fn kcl_test_top_level_expression() {
let code = r#"startSketchOn('XY') |> circle([0,0], 22, %) |> extrude(14, %)"#; let code = r#"startSketchOn('XY') |> circle({ center: [0,0], radius: 22 }, %) |> extrude(14, %)"#;
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
assert_out("top_level_expression", &result); assert_out("top_level_expression", &result);
@ -378,7 +378,7 @@ async fn kcl_test_patterns_linear_basic_with_math() {
let code = r#"const num = 12 let code = r#"const num = 12
const distance = 5 const distance = 5
const part = startSketchOn('XY') const part = startSketchOn('XY')
|> circle([0,0], 2, %) |> circle({ center: [0,0], radius: 2 }, %)
|> patternLinear2d({axis: [0,1], repetitions: num -1, distance: distance - 1}, %) |> patternLinear2d({axis: [0,1], repetitions: num -1, distance: distance - 1}, %)
|> extrude(1, %) |> extrude(1, %)
"#; "#;
@ -390,7 +390,7 @@ const part = startSketchOn('XY')
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn kcl_test_patterns_linear_basic() { async fn kcl_test_patterns_linear_basic() {
let code = r#"const part = startSketchOn('XY') let code = r#"const part = startSketchOn('XY')
|> circle([0,0], 2, %) |> circle({ center: [0,0], radius: 2 }, %)
|> patternLinear2d({axis: [0,1], repetitions: 12, distance: 4}, %) |> patternLinear2d({axis: [0,1], repetitions: 12, distance: 4}, %)
|> extrude(1, %) |> extrude(1, %)
"#; "#;
@ -418,7 +418,7 @@ async fn kcl_test_patterns_linear_basic_3d() {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn kcl_test_patterns_linear_basic_negative_distance() { async fn kcl_test_patterns_linear_basic_negative_distance() {
let code = r#"const part = startSketchOn('XY') let code = r#"const part = startSketchOn('XY')
|> circle([0,0], 2, %) |> circle({ center: [0,0], radius: 2 }, %)
|> patternLinear2d({axis: [0,1], repetitions: 12, distance: -2}, %) |> patternLinear2d({axis: [0,1], repetitions: 12, distance: -2}, %)
|> extrude(1, %) |> extrude(1, %)
"#; "#;
@ -430,7 +430,7 @@ async fn kcl_test_patterns_linear_basic_negative_distance() {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn kcl_test_patterns_linear_basic_negative_axis() { async fn kcl_test_patterns_linear_basic_negative_axis() {
let code = r#"const part = startSketchOn('XY') let code = r#"const part = startSketchOn('XY')
|> circle([0,0], 2, %) |> circle({ center: [0,0], radius: 2 }, %)
|> patternLinear2d({axis: [0,-1], repetitions: 12, distance: 2}, %) |> patternLinear2d({axis: [0,-1], repetitions: 12, distance: 2}, %)
|> extrude(1, %) |> extrude(1, %)
"#; "#;
@ -442,7 +442,7 @@ async fn kcl_test_patterns_linear_basic_negative_axis() {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn kcl_test_patterns_linear_basic_holes() { async fn kcl_test_patterns_linear_basic_holes() {
let code = r#"const circles = startSketchOn('XY') let code = r#"const circles = startSketchOn('XY')
|> circle([5, 5], 1, %) |> circle({ center: [5, 5], radius: 1 }, %)
|> patternLinear2d({axis: [1,1], repetitions: 12, distance: 3}, %) |> patternLinear2d({axis: [1,1], repetitions: 12, distance: 3}, %)
const rectangle = startSketchOn('XY') const rectangle = startSketchOn('XY')
@ -463,7 +463,7 @@ const rectangle = startSketchOn('XY')
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn kcl_test_patterns_circular_basic_2d() { async fn kcl_test_patterns_circular_basic_2d() {
let code = r#"const part = startSketchOn('XY') let code = r#"const part = startSketchOn('XY')
|> circle([0,0], 2, %) |> circle({ center: [0,0], radius: 2 }, %)
|> patternCircular2d({center: [20, 20], repetitions: 12, arcDegrees: 210, rotateDuplicates: true}, %) |> patternCircular2d({center: [20, 20], repetitions: 12, arcDegrees: 210, rotateDuplicates: true}, %)
|> extrude(1, %) |> extrude(1, %)
"#; "#;
@ -787,8 +787,8 @@ async fn kcl_test_stdlib_kcl_error_right_code_path() {
|> line([10, 0], %) |> line([10, 0], %)
|> line([0, -10], %) |> line([0, -10], %)
|> close(%) |> close(%)
|> hole(circle([2, 2], .5), %) |> hole(circle({ center: [2, 2], radius: .5 }), %)
|> hole(circle([2, 8], .5, %), %) |> hole(circle({ center: [2, 8], radius: .5 }, %), %)
|> extrude(2, %) |> extrude(2, %)
"#; "#;
@ -816,7 +816,7 @@ const part001 = cube([0,0], 20)
|> extrude(20, %) |> extrude(20, %)
const part002 = startSketchOn(part001, "end") const part002 = startSketchOn(part001, "end")
|> circle([0, 0], 5, %) |> circle({ center: [0, 0], radius: 5 }, %)
|> extrude(5, %) |> extrude(5, %)
"#; "#;
@ -1085,7 +1085,7 @@ async fn kcl_test_revolve_on_face_circle_edge() {
|> extrude(20, %) |> extrude(20, %)
const sketch001 = startSketchOn(box, "END") const sketch001 = startSketchOn(box, "END")
|> circle([10,10], 4, %) |> circle({ center: [10,10], radius: 4 }, %)
|> revolve({ |> revolve({
angle: 90, angle: 90,
axis: getOppositeEdge(revolveAxis) axis: getOppositeEdge(revolveAxis)
@ -1107,7 +1107,7 @@ async fn kcl_test_revolve_on_face_circle() {
|> extrude(20, %) |> extrude(20, %)
const sketch001 = startSketchOn(box, "END") const sketch001 = startSketchOn(box, "END")
|> circle([10,10], 4, %) |> circle({ center: [10,10], radius: 4 }, %)
|> revolve({ |> revolve({
angle: -90, angle: -90,
axis: 'y' axis: 'y'
@ -1147,7 +1147,7 @@ const sketch001 = startSketchOn(box, "end")
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn kcl_test_basic_revolve_circle() { async fn kcl_test_basic_revolve_circle() {
let code = r#"const sketch001 = startSketchOn('XY') let code = r#"const sketch001 = startSketchOn('XY')
|> circle([15, 0], 5, %) |> circle({ center: [15, 0], radius: 5 }, %)
|> revolve({ |> revolve({
angle: 360, angle: 360,
axis: 'y' axis: 'y'
@ -1271,10 +1271,10 @@ async fn kcl_test_member_expression_in_params() {
z_axis: { x: 0, y: 1, z: 0 } z_axis: { x: 0, y: 1, z: 0 }
} }
}) })
|> circle([0, 0], capDia / 2, %) |> circle({ center: [0, 0], radius: capDia / 2 }, %)
|> extrude(capHeadLength, %) |> extrude(capHeadLength, %)
const screw = startSketchOn(screwHead, "start") const screw = startSketchOn(screwHead, "start")
|> circle([0, 0], dia / 2, %) |> circle({ center: [0, 0], radius: dia / 2 }, %)
|> extrude(length, %) |> extrude(length, %)
return screw return screw
} }
@ -1343,7 +1343,7 @@ async fn kcl_test_error_empty_start_sketch_on_string() {
|> extrude(100, %) |> extrude(100, %)
const secondSketch = startSketchOn(part001, '') const secondSketch = startSketchOn(part001, '')
|> circle([-20, 50], 40, %) |> circle({ center: [-20, 50], radius: 40 }, %)
|> extrude(20, %) |> extrude(20, %)
"#; "#;
@ -1373,7 +1373,7 @@ fn squareHole = (l, w) => {
} }
const extrusion = startSketchOn('XY') const extrusion = startSketchOn('XY')
|> circle([0, 0], dia/2, %) |> circle({ center: [0, 0], radius: dia/2 }, %)
|> hole(squareHole(length, width, height), %) |> hole(squareHole(length, width, height), %)
|> extrude(height, %) |> extrude(height, %)
"#; "#;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 0 B