Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Point-and-click deletion of lofts, shells, and offset planes #4898

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
175aa28
Refactor 'Delete selection' as actor
pierremtb Dec 16, 2024
3628be2
WIP logging
pierremtb Dec 18, 2024
b310b18
Update from main
pierremtb Jan 2, 2025
2eb4eb6
WIP: working Solid3dGetExtrusionFaceInfo for loft
pierremtb Jan 2, 2025
29bb282
Working wall deletion of loft
pierremtb Jan 2, 2025
070fe47
Add offset plane deletion
pierremtb Jan 2, 2025
dc72284
Add feature tree deletion of shell
pierremtb Jan 2, 2025
90aa4aa
Merge branch 'main' into pierremtb/issue4662-Point-and-click-deletion…
pierremtb Jan 2, 2025
214763c
Clean up
pierremtb Jan 2, 2025
143cd49
Revert "Clean up"
pierremtb Jan 3, 2025
5faac34
Clean up rust changes, taking the sketch with the most paths
pierremtb Jan 3, 2025
43f16b3
Working cap selection and deletion
pierremtb Jan 3, 2025
7c2bf81
Clean up
pierremtb Jan 3, 2025
5b98a53
Merge branch 'main' into pierremtb/issue4662-Point-and-click-deletion…
pierremtb Jan 3, 2025
2b22308
Merge branch 'main' into pierremtb/issue4662-Point-and-click-deletion…
pierremtb Jan 3, 2025
bd1c993
Merge branch 'main' into pierremtb/issue4662-Point-and-click-deletion…
pierremtb Jan 6, 2025
64ca39b
Add test for loft and offset plane deletion via selection
pierremtb Jan 6, 2025
afa8f1d
A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-16-cores)
github-actions[bot] Jan 6, 2025
3dd23d8
A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-macos…
github-actions[bot] Jan 6, 2025
8e65fb8
Point-and-click deletion of Lofts and Offset Planes
pierremtb Jan 7, 2025
18ccc18
Set reenter: false as it was originally
pierremtb Jan 7, 2025
e9695ff
Passing test
pierremtb Jan 7, 2025
2696636
Add shell deletion via feature tree test
pierremtb Jan 7, 2025
fe5be95
Revert the migration to promise actor
pierremtb Jan 7, 2025
79f54bb
Merge branch 'main' into pierremtb/issue4662-Point-and-click-deletion…
pierremtb Jan 7, 2025
6194fb7
A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubunt…
github-actions[bot] Jan 7, 2025
a48df02
Trigger CI
pierremtb Jan 7, 2025
6a2e85a
A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubunt…
github-actions[bot] Jan 7, 2025
6acd7ef
Trigger CI
pierremtb Jan 7, 2025
2501266
A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubunt…
github-actions[bot] Jan 7, 2025
157f199
Trigger CI
pierremtb Jan 7, 2025
d6ade67
A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubunt…
github-actions[bot] Jan 7, 2025
2dbe418
Trigger CI
pierremtb Jan 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions src/lang/modifyAst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1149,11 +1149,17 @@ export async function deleteFromSelection(
((selection?.artifact?.type === 'wall' ||
selection?.artifact?.type === 'cap') &&
varDec.node.init.type === 'PipeExpression') ||
selection.artifact?.type === 'sweep'
selection.artifact?.type === 'sweep' ||
selection.artifact?.type === 'plane' ||
!selection.artifact // aka expected to be a shell at this point
) {
let extrudeNameToDelete = ''
let pathToNode: PathToNode | null = null
if (selection.artifact?.type !== 'sweep') {
if (
selection.artifact &&
selection.artifact.type !== 'sweep' &&
selection.artifact.type !== 'plane'
) {
const varDecName = varDec.node.id.name
traverse(astClone, {
enter: (node, path) => {
Expand All @@ -1169,6 +1175,17 @@ export async function deleteFromSelection(
pathToNode = path
extrudeNameToDelete = dec.id.name
}
if (
dec.init.type === 'CallExpression' &&
dec.init.callee.name === 'loft' &&
dec.init.arguments?.[0].type === 'ArrayExpression' &&
dec.init.arguments?.[0].elements.some(
(a) => a.type === 'Identifier' && a.name === varDecName
)
) {
pathToNode = path
extrudeNameToDelete = dec.id.name
}
}
},
})
Expand Down
30 changes: 28 additions & 2 deletions src/lang/std/artifactGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,15 @@ interface SegmentArtifactRich extends BaseArtifact {
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
interface SweepArtifact extends BaseArtifact {
type: 'sweep'
subType: 'extrusion' | 'revolve'
subType: 'extrusion' | 'revolve' | 'loft'
pathId: string
surfaceIds: Array<string>
edgeIds: Array<string>
codeRef: CodeRef
}
interface SweepArtifactRich extends BaseArtifact {
type: 'sweep'
subType: 'extrusion' | 'revolve'
subType: 'extrusion' | 'revolve' | 'loft'
path: PathArtifact
surfaces: Array<WallArtifact | CapArtifact>
edges: Array<SweepEdge>
Expand Down Expand Up @@ -393,6 +393,32 @@ export function getArtifactsToUpdate({
artifact: { ...path, sweepId: id },
})
return returnArr
} else if (
cmd.type === 'loft' &&
response.type === 'modeling' &&
response.data.modeling_response.type === 'loft'
) {
returnArr.push({
id,
artifact: {
type: 'sweep',
subType: 'loft',
id,
pathId: response.data.modeling_response.data.solid_id,
pierremtb marked this conversation as resolved.
Show resolved Hide resolved
surfaceIds: [],
edgeIds: [],
codeRef: { range, pathToNode },
},
})
for (const sectionId of cmd.section_ids) {
const path = getArtifact(sectionId)
if (path?.type === 'path')
returnArr.push({
id: sectionId,
artifact: { ...path, sweepId: id },
})
}
return returnArr
} else if (
cmd.type === 'solid3d_get_extrusion_face_info' &&
response?.type === 'modeling' &&
Expand Down
3 changes: 3 additions & 0 deletions src/lib/commandBarConfigs/modelingCommandConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export type ModelingCommandSchema = {
Loft: {
selection: Selections
}
'Delete selection': {
selection: Selections
}
Shell: {
selection: Selections
thickness: KclCommandValue
Expand Down
2 changes: 2 additions & 0 deletions src/lib/selections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export async function getEventForSelectWithPoint({
}

let _artifact = engineCommandManager.artifactGraph.get(data.entity_id)
console.log('found _artifact from selection', _artifact)
const codeRefs = getCodeRefsByArtifactId(
data.entity_id,
engineCommandManager.artifactGraph
Expand Down Expand Up @@ -841,6 +842,7 @@ export function updateSelections(
): Selections | Error {
if (err(ast)) return ast

console.log('updateSelections pathToNodeMap', pathToNodeMap)
const newSelections = Object.entries(pathToNodeMap)
.map(([index, pathToNode]): Selection | undefined => {
const previousSelection =
Expand Down
92 changes: 57 additions & 35 deletions src/machines/modelingMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ export type ModelingMachineEvent =
}
| {
type: 'Delete selection'
data?: ModelingCommandSchema['Delete selection']
}
| { type: 'Sketch no face' }
| { type: 'Toggle gui mode' }
Expand Down Expand Up @@ -731,38 +732,6 @@ export const modelingMachine = setup({
}
})().catch(reportRejection)
},
'AST delete selection': ({ context: { selectionRanges } }) => {
;(async () => {
const errorMessage =
'Unable to delete selection. Please edit manually in code pane.'
let ast = kclManager.ast

const modifiedAst = await deleteFromSelection(
ast,
selectionRanges.graphSelections[0],
kclManager.programMemory,
getFaceDetails
)
if (err(modifiedAst)) {
toast.error(errorMessage)
return
}

const testExecute = await executeAst({
ast: modifiedAst,
engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: ProgramMemory.empty(),
})
if (testExecute.errors.length) {
toast.error(errorMessage)
return
}

await kclManager.updateAst(modifiedAst, true)
await codeManager.updateEditorWithAstAndWriteToFile(modifiedAst)
})().catch(reportRejection)
},
'AST fillet': ({ event }) => {
if (event.type !== 'Fillet') return
if (!event.data) return
Expand Down Expand Up @@ -1689,6 +1658,45 @@ export const modelingMachine = setup({
}
}
),
deleteSelectionAstMod: fromPromise(
pierremtb marked this conversation as resolved.
Show resolved Hide resolved
async ({
input,
}: {
input: ModelingCommandSchema['Delete selection'] | undefined
}) => {
if (!input) {
return new Error('No input provided')
}

// Extract inputs
const ast = kclManager.ast
const { selection } = input

const modifiedAst = await deleteFromSelection(
ast,
selection.graphSelections[0],
kclManager.programMemory,
getFaceDetails
)
if (err(modifiedAst)) {
return
}

const testExecute = await executeAst({
ast: modifiedAst,
engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: ProgramMemory.empty(),
})
if (testExecute.errors.length) {
toast.error('Unable to delete part')
return
}

await kclManager.updateAst(modifiedAst, true)
await codeManager.updateEditorWithAstAndWriteToFile(modifiedAst)
}
),
'submit-prompt-edit': fromPromise(
async ({ input }: { input: ModelingCommandSchema['Prompt-to-edit'] }) => {
console.log('doing thing', input)
Expand Down Expand Up @@ -1759,10 +1767,9 @@ export const modelingMachine = setup({
},

'Delete selection': {
target: 'idle',
target: 'Applying selection delete',
guard: 'has valid selection for deletion',
actions: ['AST delete selection'],
reenter: false,
reenter: true,
},

'Text-to-CAD': {
Expand Down Expand Up @@ -2542,6 +2549,21 @@ export const modelingMachine = setup({
},
},

'Applying selection delete': {
invoke: {
src: 'deleteSelectionAstMod',
id: 'deleteSelectionAstMod',
input: ({ event, context }) => {
if (event.type !== 'Delete selection') return undefined
// TODO: there has to be a better way to pass `context` right?
if (!context.selectionRanges) return undefined
return { selection: context.selectionRanges }
},
onDone: ['idle'],
onError: ['idle'],
},
},

'Applying Prompt-to-edit': {
invoke: {
src: 'submit-prompt-edit',
Expand Down
6 changes: 4 additions & 2 deletions src/wasm-lib/kcl/src/std/extrude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ async fn inner_extrude(
ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}),
)
.await?;
solids.push(do_post_extrude(sketch.clone(), length, exec_state, args.clone()).await?);
solids.push(do_post_extrude(sketch.clone(), length, exec_state, args.clone(), None).await?);
}

Ok(solids.into())
Expand All @@ -136,6 +136,7 @@ pub(crate) async fn do_post_extrude(
length: f64,
exec_state: &mut ExecState,
args: Args,
force_object_id: Option<Uuid>,
pierremtb marked this conversation as resolved.
Show resolved Hide resolved
) -> Result<Box<Solid>, KclError> {
// Bring the object to the front of the scene.
// See: https://github.com/KittyCAD/modeling-app/issues/806
Expand All @@ -161,12 +162,13 @@ pub(crate) async fn do_post_extrude(
sketch.id = face.solid.sketch.id;
}

let object_id = force_object_id.unwrap_or(sketch.id);
let solid3d_info = args
.send_modeling_cmd(
exec_state.next_uuid(),
ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo {
edge_id: any_edge_id,
object_id: sketch.id,
object_id: object_id,
}),
)
.await?;
Expand Down
44 changes: 30 additions & 14 deletions src/wasm-lib/kcl/src/std/loft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ use std::num::NonZeroU32;

use anyhow::Result;
use derive_docs::stdlib;
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
use kcmc::{
each_cmd as mcmd, length_unit::LengthUnit, ok_response::OkModelingCmdResponse, websocket::OkWebSocketResponseData,
ModelingCmd,
};
use kittycad_modeling_cmds as kcmc;

use crate::{
Expand Down Expand Up @@ -142,19 +145,32 @@ async fn inner_loft(
}));
}

let id = exec_state.next_uuid();
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::Loft {
section_ids: sketches.iter().map(|group| group.id).collect(),
base_curve_index,
bez_approximate_rational,
tolerance: LengthUnit(tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
v_degree,
}),
)
.await?;
let id: uuid::Uuid = exec_state.next_uuid();
let resp = args
.send_modeling_cmd(
id,
ModelingCmd::from(mcmd::Loft {
section_ids: sketches.iter().map(|group| group.id).collect(),
base_curve_index,
bez_approximate_rational,
tolerance: LengthUnit(tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
v_degree,
}),
)
.await?;
let OkWebSocketResponseData::Modeling {
modeling_response: OkModelingCmdResponse::Loft(data),
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails {
message: format!("mcmd::Loft response was not as expected: {:?}", resp),
source_ranges: vec![args.source_range],
}));
};

#[cfg(target_arch = "wasm32")]
web_sys::console::log_1(&format!("Rust Loft solid_id={:?}", data.solid_id).into());

// Using the first sketch as the base curve, idk we might want to change this later.
do_post_extrude(sketches[0].clone(), 0.0, exec_state, args).await
do_post_extrude(sketches[0].clone(), 0.0, exec_state, args, Some(data.solid_id)).await
}
2 changes: 1 addition & 1 deletion src/wasm-lib/kcl/src/std/revolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ async fn inner_revolve(
}
}

do_post_extrude(sketch, 0.0, exec_state, args).await
do_post_extrude(sketch, 0.0, exec_state, args, None).await
}

#[cfg(test)]
Expand Down
2 changes: 1 addition & 1 deletion src/wasm-lib/kcl/src/std/sweep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,5 +99,5 @@ async fn inner_sweep(
)
.await?;

do_post_extrude(sketch, 0.0, exec_state, args).await
do_post_extrude(sketch, 0.0, exec_state, args, None).await
}
Loading