Fix sketchOnFace point&click for booleans (#6713)

* fix bool sketchOnFace

* fix chamfer test

* add test

* Kurt composite attempt (#6722)

* composite attempt

* Add forward edge to CSG artifacts in artifact graph

* Fix comment

* Move the doc comments above the attributes

* Fix to update the correct field of Path

* Update output

---------

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>

* use rust defined composite solid edges instead

* Update src/hooks/useEngineConnectionSubscriptions.ts

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* Revert PNG screenshots

* Fix TS formatting

---------

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
This commit is contained in:
Kurt Hutten
2025-05-07 08:25:12 +10:00
committed by GitHub
parent 996517f5c4
commit d187a29e55
17 changed files with 256 additions and 73 deletions

View File

@ -241,6 +241,12 @@ export class ToolbarFixture {
async openFeatureTreePane() { async openFeatureTreePane() {
return this.openPane(this.featureTreeId) return this.openPane(this.featureTreeId)
} }
async waitForFeatureTreeToBeBuilt() {
await this.openFeatureTreePane()
await expect(this.page.getByText('Building feature tree')).not.toBeVisible()
await this.closeFeatureTreePane()
}
async closeFeatureTreePane() { async closeFeatureTreePane() {
await this.closePane(this.featureTreeId) await this.closePane(this.featureTreeId)
} }

View File

@ -244,6 +244,7 @@ test.describe('Point-and-click tests', () => {
await scene.moveCameraTo(cameraPos, cameraTarget) await scene.moveCameraTo(cameraPos, cameraTarget)
await test.step('check chamfer selection changes cursor position', async () => { await test.step('check chamfer selection changes cursor position', async () => {
await toolbar.waitForFeatureTreeToBeBuilt()
await expect(async () => { await expect(async () => {
// sometimes initial click doesn't register // sometimes initial click doesn't register
await clickChamfer() await clickChamfer()
@ -283,6 +284,7 @@ test.describe('Point-and-click tests', () => {
highlightedCode: '', highlightedCode: '',
diagnostics: [], diagnostics: [],
}) })
await toolbar.waitForFeatureTreeToBeBuilt()
}) })
} }
test('works on all edge selections and can break up multi edges in a chamfer array', async ({ test('works on all edge selections and can break up multi edges in a chamfer array', async ({
@ -402,6 +404,7 @@ test.describe('Point-and-click tests', () => {
await test.step('verify at the end of the test that final code is what is expected', async () => { await test.step('verify at the end of the test that final code is what is expected', async () => {
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
`@settings(defaultLengthUnit = in) `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ) sketch001 = startSketchOn(XZ)
|> startProfile(at = [75.8, 317.2]) // [$startCapTag, $EndCapTag] |> startProfile(at = [75.8, 317.2]) // [$startCapTag, $EndCapTag]
|> angledLine(angle = 0, length = 268.43, tag = $rectangleSegmentA001) |> angledLine(angle = 0, length = 268.43, tag = $rectangleSegmentA001)
@ -414,18 +417,11 @@ extrude001 = extrude(sketch001, length = 100)
|> chamfer(length = 30, tags = [seg01], tag = $seg04) |> chamfer(length = 30, tags = [seg01], tag = $seg04)
|> chamfer(length = 30, tags = [getNextAdjacentEdge(seg02)], tag = $seg05) |> chamfer(length = 30, tags = [getNextAdjacentEdge(seg02)], tag = $seg05)
|> chamfer(length = 30, tags = [getNextAdjacentEdge(yo)], tag = $seg06) |> chamfer(length = 30, tags = [getNextAdjacentEdge(yo)], tag = $seg06)
sketch005 = startSketchOn(extrude001, face = seg06) sketch002 = startSketchOn(extrude001, face = seg03)
profile004=startProfile(sketch005, at = [-23.43,19.69]) profile001 = startProfile(sketch002, at = [205.96, 254.59])
|> angledLine(angle = 0, length = 9.1, tag = $rectangleSegmentA005) |> angledLine(angle = 0, length = 11.39, tag = $rectangleSegmentA002)
|> angledLine(angle = segAng(rectangleSegmentA005) - 90, length = 84.07) |> angledLine(angle = segAng(rectangleSegmentA002) - 90, length = 105.26)
|> angledLine(angle = segAng(rectangleSegmentA005), length = -segLen(rectangleSegmentA005)) |> angledLine(angle = segAng(rectangleSegmentA002), length = -segLen(rectangleSegmentA002))
|> line(endAbsolute=[profileStartX(%), profileStartY(%)])
|> close()
sketch004 = startSketchOn(extrude001, face = seg05)
profile003 = startProfile(sketch004, at = [82.57, 322.96])
|> angledLine(angle = 0, length = 11.16, tag = $rectangleSegmentA004)
|> angledLine(angle = segAng(rectangleSegmentA004) - 90, length = 103.07)
|> angledLine(angle = segAng(rectangleSegmentA004), length = -segLen(rectangleSegmentA004))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
sketch003 = startSketchOn(extrude001, face = seg04) sketch003 = startSketchOn(extrude001, face = seg04)
@ -435,11 +431,18 @@ profile002 = startProfile(sketch003, at = [-209.64, 255.28])
|> angledLine(angle = segAng(rectangleSegmentA003), length = -segLen(rectangleSegmentA003)) |> angledLine(angle = segAng(rectangleSegmentA003), length = -segLen(rectangleSegmentA003))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
sketch002 = startSketchOn(extrude001, face = seg03) sketch004 = startSketchOn(extrude001, face = seg05)
profile001 = startProfile(sketch002, at = [205.96, 254.59]) profile003 = startProfile(sketch004, at = [82.57, 322.96])
|> angledLine(angle = 0, length = 11.39, tag = $rectangleSegmentA002) |> angledLine(angle = 0, length = 11.16, tag = $rectangleSegmentA004)
|> angledLine(angle = segAng(rectangleSegmentA002) - 90, length = 105.26) |> angledLine(angle = segAng(rectangleSegmentA004) - 90, length = 103.07)
|> angledLine(angle = segAng(rectangleSegmentA002), length = -segLen(rectangleSegmentA002)) |> angledLine(angle = segAng(rectangleSegmentA004), length = -segLen(rectangleSegmentA004))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch005 = startSketchOn(extrude001, face = seg06)
profile004 = startProfile(sketch005, at = [-23.43, 19.69])
|> angledLine(angle = 0, length = 9.1, tag = $rectangleSegmentA005)
|> angledLine(angle = segAng(rectangleSegmentA005) - 90, length = 84.07)
|> angledLine(angle = segAng(rectangleSegmentA005), length = -segLen(rectangleSegmentA005))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close() |> close()
`, `,

View File

@ -1260,6 +1260,97 @@ profile001 = startProfile(sketch001, at = [299.72, 230.82])
}).toPass({ timeout: 40_000, intervals: [1_000] }) }).toPass({ timeout: 40_000, intervals: [1_000] })
}) })
test('sketch on face of a boolean works', async ({
page,
homePage,
scene,
cmdBar,
toolbar,
editor,
}) => {
await page.setBodyDimensions({ width: 1000, height: 500 })
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = mm)
myVar = 50
sketch001 = startSketchOn(XZ)
profile001 = circle(sketch001, center = [myVar, 43.9], radius = 41.05)
extrude001 = extrude(profile001, length = 200)
|> translate(x = 3.14, y = 3.14, z = -50.154)
sketch002 = startSketchOn(XY)
profile002 = startProfile(sketch002, at = [72.2, -52.05])
|> angledLine(angle = 0, length = 181.26, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 21.54)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $mySeg)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
|> close()
extrude002 = extrude(profile002, length = 151)
|> chamfer(
%,
length = 15,
tags = [mySeg],
tag = $seg02,
)
solid001 = subtract([extrude001], tools = [extrude002])
`
)
})
const [selectChamferFaceClk] = scene.makeMouseHelpers(671, 283)
const [circleCenterClk] = scene.makeMouseHelpers(700, 272)
const [circleRadiusClk] = scene.makeMouseHelpers(694, 264)
await test.step('Setup', async () => {
await homePage.goToModelingScene()
await scene.settled(cmdBar)
await scene.moveCameraTo(
{ x: 180, y: -75, z: 116 },
{ x: 67, y: -114, z: -15 }
)
})
await test.step('sketch on chamfer face that is part of a boolean', async () => {
await toolbar.startSketchPlaneSelection()
await selectChamferFaceClk()
await expect
.poll(async () => {
const lineBtn = page.getByRole('button', { name: 'line Line' })
return lineBtn.getAttribute('aria-pressed')
})
.toBe('true')
await editor.expectEditor.toContain(
'startSketchOn(solid001, face = seg02)'
)
})
await test.step('verify sketching still works', async () => {
await toolbar.circleBtn.click()
await expect
.poll(async () => {
const circleBtn = page.getByRole('button', { name: 'circle Circle' })
return circleBtn.getAttribute('aria-pressed')
})
.toBe('true')
await circleCenterClk()
await editor.expectEditor.toContain(
'profile003 = circle(sketch003, center'
)
await circleRadiusClk()
await editor.expectEditor.toContain(
'profile003 = circle(sketch003, center = [119.41, -56.05], radius = 1.82)'
)
})
})
test('Can sketch on face when user defined function was used in the sketch', async ({ test('Can sketch on face when user defined function was used in the sketch', async ({
page, page,
homePage, homePage,

View File

@ -150,6 +150,10 @@ pub struct CompositeSolid {
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_ids: Vec<ArtifactId>, pub tool_ids: Vec<ArtifactId>,
pub code_ref: CodeRef, pub code_ref: CodeRef,
/// This is the ID of the composite solid that this is part of, if any, as a
/// composite solid can be used as input for another composite solid.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub composite_solid_id: Option<ArtifactId>,
} }
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)] #[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, ts_rs::TS)]
@ -182,6 +186,10 @@ pub struct Path {
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
pub solid2d_id: Option<ArtifactId>, pub solid2d_id: Option<ArtifactId>,
pub code_ref: CodeRef, pub code_ref: CodeRef,
/// This is the ID of the composite solid that this is part of, if any, as
/// this can be used as input for another composite solid.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub composite_solid_id: Option<ArtifactId>,
} }
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)] #[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
@ -585,6 +593,7 @@ impl CompositeSolid {
}; };
merge_ids(&mut self.solid_ids, new.solid_ids); merge_ids(&mut self.solid_ids, new.solid_ids);
merge_ids(&mut self.tool_ids, new.tool_ids); merge_ids(&mut self.tool_ids, new.tool_ids);
merge_opt_id(&mut self.composite_solid_id, new.composite_solid_id);
None None
} }
@ -609,6 +618,7 @@ impl Path {
merge_opt_id(&mut self.sweep_id, new.sweep_id); merge_opt_id(&mut self.sweep_id, new.sweep_id);
merge_ids(&mut self.seg_ids, new.seg_ids); merge_ids(&mut self.seg_ids, new.seg_ids);
merge_opt_id(&mut self.solid2d_id, new.solid2d_id); merge_opt_id(&mut self.solid2d_id, new.solid2d_id);
merge_opt_id(&mut self.composite_solid_id, new.composite_solid_id);
None None
} }
@ -921,6 +931,7 @@ fn artifacts_to_update(
sweep_id: None, sweep_id: None,
solid2d_id: None, solid2d_id: None,
code_ref, code_ref,
composite_solid_id: None,
})); }));
let plane = artifacts.get(&ArtifactId::new(*current_plane_id)); let plane = artifacts.get(&ArtifactId::new(*current_plane_id));
if let Some(Artifact::Plane(plane)) = plane { if let Some(Artifact::Plane(plane)) = plane {
@ -1359,20 +1370,59 @@ fn artifacts_to_update(
.for_each(|id| new_solid_ids.push(id)), .for_each(|id| new_solid_ids.push(id)),
_ => {} _ => {}
} }
let return_arr = new_solid_ids
.into_iter()
.map(|solid_id| {
Artifact::CompositeSolid(CompositeSolid {
id: solid_id,
sub_type,
solid_ids: solid_ids.clone(),
tool_ids: tool_ids.clone(),
code_ref: code_ref.clone(),
})
})
.collect::<Vec<_>>();
// TODO: Should we add the reverse graph edges? let mut return_arr = Vec::new();
// Create the new composite solids and update their linked artifacts
for solid_id in &new_solid_ids {
// Create the composite solid
return_arr.push(Artifact::CompositeSolid(CompositeSolid {
id: *solid_id,
sub_type,
solid_ids: solid_ids.clone(),
tool_ids: tool_ids.clone(),
code_ref: code_ref.clone(),
composite_solid_id: None,
}));
// Update the artifacts that were used as input for this composite solid
for input_id in &solid_ids {
if let Some(artifact) = artifacts.get(input_id) {
match artifact {
Artifact::CompositeSolid(comp) => {
let mut new_comp = comp.clone();
new_comp.composite_solid_id = Some(*solid_id);
return_arr.push(Artifact::CompositeSolid(new_comp));
}
Artifact::Path(path) => {
let mut new_path = path.clone();
new_path.composite_solid_id = Some(*solid_id);
return_arr.push(Artifact::Path(new_path));
}
_ => {}
}
}
}
// Update the tool artifacts if this is a subtract operation
for tool_id in &tool_ids {
if let Some(artifact) = artifacts.get(tool_id) {
match artifact {
Artifact::CompositeSolid(comp) => {
let mut new_comp = comp.clone();
new_comp.composite_solid_id = Some(*solid_id);
return_arr.push(Artifact::CompositeSolid(new_comp));
}
Artifact::Path(path) => {
let mut new_path = path.clone();
new_path.composite_solid_id = Some(*solid_id);
return_arr.push(Artifact::Path(new_path));
}
_ => {}
}
}
}
}
return Ok(return_arr); return Ok(return_arr);
} }

View File

@ -92,10 +92,14 @@ impl Artifact {
/// the graph. /// the graph.
pub(crate) fn child_ids(&self) -> Vec<ArtifactId> { pub(crate) fn child_ids(&self) -> Vec<ArtifactId> {
match self { match self {
Artifact::CompositeSolid(_) => { Artifact::CompositeSolid(a) => {
// Note: Don't include these since they're parents: solid_ids, // Note: Don't include these since they're parents: solid_ids,
// tool_ids. // tool_ids.
Vec::new() let mut ids = Vec::new();
if let Some(composite_solid_id) = a.composite_solid_id {
ids.push(composite_solid_id);
}
ids
} }
Artifact::Plane(a) => a.path_ids.clone(), Artifact::Plane(a) => a.path_ids.clone(),
Artifact::Path(a) => { Artifact::Path(a) => {
@ -107,6 +111,9 @@ impl Artifact {
if let Some(solid2d_id) = a.solid2d_id { if let Some(solid2d_id) = a.solid2d_id {
ids.push(solid2d_id); ids.push(solid2d_id);
} }
if let Some(composite_solid_id) = a.composite_solid_id {
ids.push(composite_solid_id);
}
ids ids
} }
Artifact::Segment(a) => { Artifact::Segment(a) => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -57,14 +57,14 @@ flowchart LR
3 --- 11 3 --- 11
3 --- 13 3 --- 13
3 ---- 16 3 ---- 16
3 <--x 17 3 --- 17
4 --- 6 4 --- 6
4 --- 7 4 --- 7
4 --- 9 4 --- 9
4 --- 12 4 --- 12
4 --- 14 4 --- 14
4 ---- 15 4 ---- 15
4 <--x 17 4 --- 17
5 --- 24 5 --- 24
5 x--> 26 5 x--> 26
5 --- 35 5 --- 35

View File

@ -317,84 +317,84 @@ flowchart LR
13 --- 67 13 --- 67
13 --- 73 13 --- 73
13 ---- 90 13 ---- 90
13 <--x 106 13 --- 106
14 --- 27 14 --- 27
14 --- 45 14 --- 45
14 --- 50 14 --- 50
14 --- 65 14 --- 65
14 --- 74 14 --- 74
14 ---- 92 14 ---- 92
14 <--x 101 14 --- 101
15 --- 32 15 --- 32
15 --- 48 15 --- 48
15 --- 51 15 --- 51
15 --- 71 15 --- 71
15 --- 75 15 --- 75
15 ---- 93 15 ---- 93
15 <--x 99 15 --- 99
16 --- 29 16 --- 29
16 --- 40 16 --- 40
16 --- 56 16 --- 56
16 --- 69 16 --- 69
16 --- 76 16 --- 76
16 ---- 96 16 ---- 96
16 <--x 100 16 --- 100
17 --- 26 17 --- 26
17 --- 44 17 --- 44
17 --- 52 17 --- 52
17 --- 63 17 --- 63
17 --- 77 17 --- 77
17 ---- 95 17 ---- 95
17 <--x 105 17 --- 105
18 --- 28 18 --- 28
18 --- 47 18 --- 47
18 --- 58 18 --- 58
18 --- 64 18 --- 64
18 --- 78 18 --- 78
18 ---- 94 18 ---- 94
18 <--x 102 18 --- 102
19 --- 35 19 --- 35
19 --- 41 19 --- 41
19 --- 57 19 --- 57
19 --- 66 19 --- 66
19 --- 79 19 --- 79
19 ---- 89 19 ---- 89
19 <--x 98 19 --- 98
20 --- 34 20 --- 34
20 --- 42 20 --- 42
20 --- 55 20 --- 55
20 --- 68 20 --- 68
20 --- 80 20 --- 80
20 ---- 87 20 ---- 87
20 <--x 104 20 --- 104
21 --- 30 21 --- 30
21 --- 39 21 --- 39
21 --- 60 21 --- 60
21 --- 70 21 --- 70
21 --- 81 21 --- 81
21 ---- 91 21 ---- 91
21 <--x 103 21 --- 103
22 --- 25 22 --- 25
22 --- 46 22 --- 46
22 --- 54 22 --- 54
22 --- 72 22 --- 72
22 --- 82 22 --- 82
22 ---- 88 22 ---- 88
22 <--x 104 22 --- 104
23 --- 36 23 --- 36
23 --- 37 23 --- 37
23 --- 49 23 --- 49
23 --- 62 23 --- 62
23 --- 83 23 --- 83
23 ---- 86 23 ---- 86
23 <--x 107 23 --- 107
24 --- 33 24 --- 33
24 --- 43 24 --- 43
24 --- 53 24 --- 53
24 --- 61 24 --- 61
24 --- 84 24 --- 84
24 ---- 85 24 ---- 85
24 <--x 97 24 --- 97
25 --- 120 25 --- 120
25 x--> 158 25 x--> 158
25 --- 193 25 --- 193
@ -755,16 +755,16 @@ flowchart LR
96 --- 273 96 --- 273
96 --- 274 96 --- 274
96 --- 275 96 --- 275
97 <--x 98 97 --- 98
99 x--> 97 99 --- 97
98 <--x 103 98 --- 103
105 x--> 99 105 --- 99
104 x--> 100 104 --- 100
100 <--x 107 100 --- 107
101 <--x 102 101 --- 102
103 x--> 101 103 --- 101
106 x--> 105 106 --- 105
107 x--> 106 107 --- 106
182 <--x 108 182 <--x 108
230 <--x 108 230 <--x 108
231 <--x 108 231 <--x 108

View File

@ -608,7 +608,7 @@ flowchart LR
83 --- 246 83 --- 246
83 --- 289 83 --- 289
90 --- 159 90 --- 159
90 x--> 193 90 x--> 192
90 --- 226 90 --- 226
90 --- 271 90 --- 271
104 --- 151 104 --- 151
@ -910,7 +910,7 @@ flowchart LR
211 <--x 191 211 <--x 191
212 <--x 191 212 <--x 191
213 <--x 191 213 <--x 191
226 <--x 192 226 <--x 193
239 <--x 195 239 <--x 195
240 <--x 195 240 <--x 195
241 <--x 195 241 <--x 195

View File

@ -709,6 +709,7 @@ flowchart LR
115 --- 179 115 --- 179
115 x--> 228 115 x--> 228
115 --- 256 115 --- 256
115 x--> 307
115 --- 308 115 --- 308
164 <--x 116 164 <--x 116
116 --- 184 116 --- 184

View File

@ -45,11 +45,11 @@ flowchart LR
3 --- 8 3 --- 8
3 --- 11 3 --- 11
3 ---- 12 3 ---- 12
3 <--x 14 3 --- 14
4 --- 9 4 --- 9
4 --- 10 4 --- 10
4 ---- 13 4 ---- 13
4 <--x 14 4 --- 14
5 --- 18 5 --- 18
5 x--> 21 5 x--> 21
5 --- 25 5 --- 25

View File

@ -57,14 +57,14 @@ flowchart LR
3 --- 11 3 --- 11
3 --- 13 3 --- 13
3 ---- 16 3 ---- 16
3 <--x 17 3 --- 17
4 --- 6 4 --- 6
4 --- 7 4 --- 7
4 --- 9 4 --- 9
4 --- 12 4 --- 12
4 --- 14 4 --- 14
4 ---- 15 4 ---- 15
4 <--x 17 4 --- 17
5 --- 24 5 --- 24
5 x--> 26 5 x--> 26
5 --- 35 5 --- 35

View File

@ -57,14 +57,14 @@ flowchart LR
3 --- 11 3 --- 11
3 --- 13 3 --- 13
3 ---- 16 3 ---- 16
3 <--x 17 3 --- 17
4 --- 6 4 --- 6
4 --- 7 4 --- 7
4 --- 9 4 --- 9
4 --- 12 4 --- 12
4 --- 14 4 --- 14
4 ---- 15 4 ---- 15
4 <--x 17 4 --- 17
5 --- 24 5 --- 24
5 x--> 26 5 x--> 26
5 --- 35 5 --- 35

View File

@ -34,6 +34,7 @@ import type {
ExtrudeFacePlane, ExtrudeFacePlane,
} from '@src/machines/modelingMachine' } from '@src/machines/modelingMachine'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { findAllChildrenAndOrderByPlaceInCode } from '@src/lang/modifyAst/boolean'
import { localModuleSafePathSplit } from '@src/lib/paths' import { localModuleSafePathSplit } from '@src/lib/paths'
export function useEngineConnectionSubscriptions() { export function useEngineConnectionSubscriptions() {
@ -148,7 +149,7 @@ export function useEngineConnectionSubscriptions() {
} }
sceneInfra.modelingSend({ sceneInfra.modelingSend({
type: 'Select default plane', type: 'Select sketch plane',
data: { data: {
type: 'defaultPlane', type: 'defaultPlane',
planeId: planeId, planeId: planeId,
@ -165,7 +166,7 @@ export function useEngineConnectionSubscriptions() {
const planeInfo = const planeInfo =
await sceneEntitiesManager.getFaceDetails(planeOrFaceId) await sceneEntitiesManager.getFaceDetails(planeOrFaceId)
sceneInfra.modelingSend({ sceneInfra.modelingSend({
type: 'Select default plane', type: 'Select sketch plane',
data: { data: {
type: 'offsetPlane', type: 'offsetPlane',
zAxis: [ zAxis: [
@ -194,7 +195,7 @@ export function useEngineConnectionSubscriptions() {
return return
} }
// Artifact is likely an extrusion face // Artifact is likely an sweep face
const faceId = planeOrFaceId const faceId = planeOrFaceId
const extrusion = getSweepFromSuspectedSweepSurface( const extrusion = getSweepFromSuspectedSweepSurface(
faceId, faceId,
@ -318,15 +319,31 @@ export function useEngineConnectionSubscriptions() {
} }
: { type: 'wall' } : { type: 'wall' }
if (err(extrusion)) {
return Promise.reject(
new Error(`Extrusion is not a valid artifact: ${extrusion}`)
)
}
const lastChild =
findAllChildrenAndOrderByPlaceInCode(
{ type: 'sweep', ...extrusion },
kclManager.artifactGraph
)[0] || null
const lastChildCodeRef =
lastChild?.type === 'compositeSolid'
? lastChild.codeRef.range
: null
const extrudePathToNode = !err(extrusion) const extrudePathToNode = !err(extrusion)
? getNodePathFromSourceRange( ? getNodePathFromSourceRange(
kclManager.ast, kclManager.ast,
extrusion.codeRef.range lastChildCodeRef || extrusion.codeRef.range
) )
: [] : []
sceneInfra.modelingSend({ sceneInfra.modelingSend({
type: 'Select default plane', type: 'Select sketch plane',
data: { data: {
type: 'extrudeFace', type: 'extrudeFace',
zAxis: [z_axis.x, z_axis.y, z_axis.z], zAxis: [z_axis.x, z_axis.y, z_axis.z],

View File

@ -461,7 +461,8 @@ export function sketchOnExtrudedFace(
const expressionIndex = Math.max( const expressionIndex = Math.max(
sketchPathToNode[1][0] as number, sketchPathToNode[1][0] as number,
extrudePathToNode[1][0] as number extrudePathToNode[1][0] as number,
node.body.length - 1
) )
_node.body.splice(expressionIndex + 1, 0, newSketch) _node.body.splice(expressionIndex + 1, 0, newSketch)
const newpathToNode: PathToNode = [ const newpathToNode: PathToNode = [

View File

@ -211,6 +211,13 @@ export function findAllChildrenAndOrderByPlaceInCode(
pushToSomething(currentId, current?.segIds) pushToSomething(currentId, current?.segIds)
} else if (current?.type === 'sweep') { } else if (current?.type === 'sweep') {
pushToSomething(currentId, current?.surfaceIds) pushToSomething(currentId, current?.surfaceIds)
const path = artifactGraph.get(current.pathId)
if (path && path.type === 'path') {
const compositeSolidId = path.compositeSolidId
if (compositeSolidId) {
result.push(compositeSolidId)
}
}
} else if (current?.type === 'wall' || current?.type === 'cap') { } else if (current?.type === 'wall' || current?.type === 'cap') {
pushToSomething(currentId, current?.pathIds) pushToSomething(currentId, current?.pathIds)
} else if (current?.type === 'segment') { } else if (current?.type === 'segment') {

View File

@ -305,7 +305,7 @@ export type ModelingMachineEvent =
} }
| { type: 'Sketch On Face' } | { type: 'Sketch On Face' }
| { | {
type: 'Select default plane' type: 'Select sketch plane'
data: DefaultPlane | ExtrudeFacePlane | OffsetPlane data: DefaultPlane | ExtrudeFacePlane | OffsetPlane
} }
| { | {
@ -4255,7 +4255,7 @@ export const modelingMachine = setup({
exit: ['hide default planes', 'set selection filter to defaults'], exit: ['hide default planes', 'set selection filter to defaults'],
on: { on: {
'Select default plane': { 'Select sketch plane': {
target: 'animating to plane', target: 'animating to plane',
actions: ['reset sketch metadata'], actions: ['reset sketch metadata'],
}, },
@ -4268,7 +4268,7 @@ export const modelingMachine = setup({
id: 'animate-to-face', id: 'animate-to-face',
input: ({ event }) => { input: ({ event }) => {
if (event.type !== 'Select default plane') return undefined if (event.type !== 'Select sketch plane') return undefined
return event.data return event.data
}, },