Compare commits

...

8 Commits

5 changed files with 271 additions and 82 deletions

View File

@ -14,8 +14,13 @@ import { IS_ML_EXPERIMENTAL, PROJECT_ENTRYPOINT } from '@src/lib/constants'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { reportRejection } from '@src/lib/trap' import { reportRejection } from '@src/lib/trap'
import { relevantFileExtensions } from '@src/lang/wasmUtils' import { relevantFileExtensions } from '@src/lang/wasmUtils'
import { getStringAfterLastSeparator, webSafePathSplit } from '@src/lib/paths' import {
getStringAfterLastSeparator,
joinOSPaths,
webSafePathSplit,
} from '@src/lib/paths'
import { FILE_EXT } from '@src/lib/constants' import { FILE_EXT } from '@src/lib/constants'
import { getAllSubDirectoriesAtProjectRoot } from '@src/machines/systemIO/snapshotContext'
function onSubmitKCLSampleCreation({ function onSubmitKCLSampleCreation({
sample, sample,
@ -87,6 +92,26 @@ function onSubmitKCLSampleCreation({
}, },
}) })
} else { } else {
/**
* When adding assemblies to an existing project create the assembly into a unique sub directory
*/
if (!isProjectNew) {
requestedFiles.forEach((requestedFile) => {
const subDirectoryName = projectPathPart
const firstLevelDirectories = getAllSubDirectoriesAtProjectRoot({
projectFolderName: requestedFile.requestedProjectName,
})
const uniqueSubDirectoryName = getUniqueProjectName(
subDirectoryName,
firstLevelDirectories
)
requestedFile.requestedProjectName = joinOSPaths(
requestedFile.requestedProjectName,
uniqueSubDirectoryName
)
})
}
/** /**
* Bulk create the assembly and navigate to the project * Bulk create the assembly and navigate to the project
*/ */
@ -278,10 +303,9 @@ export function createApplicationCommands({
return value return value
}, },
options: ({ argumentsToSubmit }) => { options: ({ argumentsToSubmit }) => {
const samples = const samples = isDesktop()
isDesktop() && argumentsToSubmit.method !== 'existingProject' ? everyKclSample
? everyKclSample : kclSamplesManifestWithNoMultipleFiles
: kclSamplesManifestWithNoMultipleFiles
return samples.map((sample) => { return samples.map((sample) => {
return { return {
value: sample.pathFromProjectDirectoryToFirstFile, value: sample.pathFromProjectDirectoryToFirstFile,
@ -296,17 +320,10 @@ export function createApplicationCommands({
skip: true, skip: true,
options: ({ argumentsToSubmit }, _) => { options: ({ argumentsToSubmit }, _) => {
if (isDesktop() && typeof argumentsToSubmit.sample === 'string') { if (isDesktop() && typeof argumentsToSubmit.sample === 'string') {
const kclSample = findKclSample(argumentsToSubmit.sample) return [
if (kclSample && kclSample.files.length > 1) { { name: 'New project', value: 'newProject', isCurrent: true },
return [ { name: 'Existing project', value: 'existingProject' },
{ name: 'New project', value: 'newProject', isCurrent: true }, ]
]
} else {
return [
{ name: 'New project', value: 'newProject', isCurrent: true },
{ name: 'Existing project', value: 'existingProject' },
]
}
} else { } else {
return [{ name: 'Overwrite', value: 'existingProject' }] return [{ name: 'Overwrite', value: 'existingProject' }]
} }

View File

@ -28,6 +28,7 @@ import type { Selections } from '@src/lib/selections'
import { codeManager, kclManager } from '@src/lib/singletons' import { codeManager, kclManager } from '@src/lib/singletons'
import { err } from '@src/lib/trap' import { err } from '@src/lib/trap'
import type { SketchTool, modelingMachine } from '@src/machines/modelingMachine' import type { SketchTool, modelingMachine } from '@src/machines/modelingMachine'
import { isDesktop } from '../isDesktop'
type OutputFormat = Models['OutputFormat3d_type'] type OutputFormat = Models['OutputFormat3d_type']
type OutputTypeKey = OutputFormat['type'] type OutputTypeKey = OutputFormat['type']
@ -159,6 +160,10 @@ export type ModelingCommandSchema = {
nodeToEdit?: PathToNode nodeToEdit?: PathToNode
color: string color: string
} }
Insert: {
path: string
localName: string
}
Translate: { Translate: {
nodeToEdit?: PathToNode nodeToEdit?: PathToNode
selection: Selections selection: Selections
@ -1011,6 +1016,74 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
// Add more fields // Add more fields
}, },
}, },
Insert: {
description: 'Insert from a file in the current project directory',
icon: 'import',
hide: 'web',
needsReview: true,
args: {
path: {
inputType: 'options',
required: true,
// options
validation: async ({ data }) => {
const importExists = kclManager.ast.body.find(
(n) =>
n.type === 'ImportStatement' &&
((n.path.type === 'Kcl' && n.path.filename === data.path) ||
(n.path.type === 'Foreign' && n.path.path === data.path))
)
if (importExists) {
return 'This file is already imported, use the Clone command instead.'
// TODO: see if we can transition to the clone command, see #6515
}
return true
},
},
localName: {
inputType: 'string',
required: true,
defaultValue: (context: CommandBarContext) => {
if (!context.argumentsToSubmit['path']) {
return
}
const path = context.argumentsToSubmit['path'] as string
return getPathFilenameInVariableCase(path)
},
validation: async ({ data }) => {
const variableExists = kclManager.variables[data.localName]
if (variableExists) {
return 'This variable name is already in use.'
}
return true
},
},
},
// onSubmit: (data) => {
// if (!data) {
// return new Error('No input provided')
// }
// const ast = kclManager.ast
// const { path, localName } = data
// const { modifiedAst, pathToNode } = addModuleImport({
// ast,
// path,
// localName,
// })
// updateModelingState(
// modifiedAst,
// EXECUTION_TYPE_REAL,
// { kclManager, editorManager, codeManager },
// {
// focusPath: [pathToNode],
// }
// ).catch(reportRejection)
// },
},
Translate: { Translate: {
description: 'Set translation on solid or sketch.', description: 'Set translation on solid or sketch.',
icon: 'move', icon: 'move',

View File

@ -89,76 +89,76 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
} }
}, },
}, },
{ // {
name: 'Insert', // name: 'Insert',
description: 'Insert from a file in the current project directory', // description: 'Insert from a file in the current project directory',
icon: 'import', // icon: 'import',
groupId: 'code', // groupId: 'code',
hide: 'web', // hide: 'web',
needsReview: true, // needsReview: true,
args: { // args: {
path: { // path: {
inputType: 'options', // inputType: 'options',
required: true, // required: true,
options: commandProps.specialPropsForInsertCommand.providedOptions, // options: commandProps.specialPropsForInsertCommand.providedOptions,
validation: async ({ data }) => { // validation: async ({ data }) => {
const importExists = kclManager.ast.body.find( // const importExists = kclManager.ast.body.find(
(n) => // (n) =>
n.type === 'ImportStatement' && // n.type === 'ImportStatement' &&
((n.path.type === 'Kcl' && n.path.filename === data.path) || // ((n.path.type === 'Kcl' && n.path.filename === data.path) ||
(n.path.type === 'Foreign' && n.path.path === data.path)) // (n.path.type === 'Foreign' && n.path.path === data.path))
) // )
if (importExists) { // if (importExists) {
return 'This file is already imported, use the Clone command instead.' // return 'This file is already imported, use the Clone command instead.'
// TODO: see if we can transition to the clone command, see #6515 // // TODO: see if we can transition to the clone command, see #6515
} // }
return true // return true
}, // },
}, // },
localName: { // localName: {
inputType: 'string', // inputType: 'string',
required: true, // required: true,
defaultValue: (context: CommandBarContext) => { // defaultValue: (context: CommandBarContext) => {
if (!context.argumentsToSubmit['path']) { // if (!context.argumentsToSubmit['path']) {
return // return
} // }
const path = context.argumentsToSubmit['path'] as string // const path = context.argumentsToSubmit['path'] as string
return getPathFilenameInVariableCase(path) // return getPathFilenameInVariableCase(path)
}, // },
validation: async ({ data }) => { // validation: async ({ data }) => {
const variableExists = kclManager.variables[data.localName] // const variableExists = kclManager.variables[data.localName]
if (variableExists) { // if (variableExists) {
return 'This variable name is already in use.' // return 'This variable name is already in use.'
} // }
return true // return true
}, // },
}, // },
}, // },
onSubmit: (data) => { // onSubmit: (data) => {
if (!data) { // if (!data) {
return new Error('No input provided') // return new Error('No input provided')
} // }
const ast = kclManager.ast // const ast = kclManager.ast
const { path, localName } = data // const { path, localName } = data
const { modifiedAst, pathToNode } = addModuleImport({ // const { modifiedAst, pathToNode } = addModuleImport({
ast, // ast,
path, // path,
localName, // localName,
}) // })
updateModelingState( // updateModelingState(
modifiedAst, // modifiedAst,
EXECUTION_TYPE_REAL, // EXECUTION_TYPE_REAL,
{ kclManager, editorManager, codeManager }, // { kclManager, editorManager, codeManager },
{ // {
focusPath: [pathToNode], // focusPath: [pathToNode],
} // }
).catch(reportRejection) // ).catch(reportRejection)
}, // },
}, // },
{ {
name: 'format-code', name: 'format-code',
displayName: 'Format Code', displayName: 'Format Code',

View File

@ -47,6 +47,7 @@ import { updateModelingState } from '@src/lang/modelingWorkflows'
import { import {
addClone, addClone,
addHelix, addHelix,
addModuleImport,
addOffsetPlane, addOffsetPlane,
addShell, addShell,
insertNamedConstant, insertNamedConstant,
@ -381,6 +382,7 @@ export type ModelingMachineEvent =
data: ModelingCommandSchema['Delete selection'] data: ModelingCommandSchema['Delete selection']
} }
| { type: 'Appearance'; data: ModelingCommandSchema['Appearance'] } | { type: 'Appearance'; data: ModelingCommandSchema['Appearance'] }
| { type: 'Insert'; data: ModelingCommandSchema['Insert'] }
| { type: 'Translate'; data: ModelingCommandSchema['Translate'] } | { type: 'Translate'; data: ModelingCommandSchema['Translate'] }
| { type: 'Rotate'; data: ModelingCommandSchema['Rotate'] } | { type: 'Rotate'; data: ModelingCommandSchema['Rotate'] }
| { type: 'Clone'; data: ModelingCommandSchema['Clone'] } | { type: 'Clone'; data: ModelingCommandSchema['Clone'] }
@ -2736,6 +2738,34 @@ export const modelingMachine = setup({
) )
} }
), ),
insertAstMod: fromPromise(
async ({
input,
}: {
input: ModelingCommandSchema['Insert'] | undefined
}) => {
if (!input) {
return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
}
const ast = kclManager.ast
const { path, localName } = input
const { modifiedAst, pathToNode } = addModuleImport({
ast,
path,
localName,
})
await updateModelingState(
modifiedAst,
EXECUTION_TYPE_REAL,
{ kclManager, editorManager, codeManager },
{
focusPath: [pathToNode],
}
)
}
),
translateAstMod: fromPromise( translateAstMod: fromPromise(
async ({ async ({
input, input,
@ -3269,6 +3299,12 @@ export const modelingMachine = setup({
guard: 'no kcl errors', guard: 'no kcl errors',
}, },
Insert: {
target: 'Applying insert',
reenter: true,
guard: 'no kcl errors',
},
Translate: { Translate: {
target: 'Applying translate', target: 'Applying translate',
reenter: true, reenter: true,
@ -4738,6 +4774,22 @@ export const modelingMachine = setup({
}, },
}, },
'Applying insert': {
invoke: {
src: 'insertAstMod',
id: 'insertAstMod',
input: ({ event }) => {
if (event.type !== 'Insert') return undefined
return event.data
},
onDone: ['idle'],
onError: {
target: 'idle',
actions: 'toastError',
},
},
},
'Applying translate': { 'Applying translate': {
invoke: { invoke: {
src: 'translateAstMod', src: 'translateAstMod',

View File

@ -1,4 +1,6 @@
import type { FileEntry } from '@src/lib/project'
import { systemIOActor } from '@src/lib/singletons' import { systemIOActor } from '@src/lib/singletons'
import { isArray } from '@src/lib/utils'
export const folderSnapshot = () => { export const folderSnapshot = () => {
const { folders } = systemIOActor.getSnapshot().context const { folders } = systemIOActor.getSnapshot().context
@ -9,3 +11,48 @@ export const defaultProjectFolderNameSnapshot = () => {
const { defaultProjectFolderName } = systemIOActor.getSnapshot().context const { defaultProjectFolderName } = systemIOActor.getSnapshot().context
return defaultProjectFolderName return defaultProjectFolderName
} }
/**
* From the application project directory go down to a project folder and list all the folders at that directory level
* application project directory: /home/documents/zoo-modeling-app-projects/
*
* /home/documents/zoo-modeling-app-projects/car-door/
* ├── handle
* ├── main.kcl
* └── window
*
* The two folders are handle and window
*
* @param {Object} params
* @param {string} params.projectFolderName - The name with no path information.
* @returns {FileEntry[]} An array of subdirectory names found at the root level of the specified project folder.
*/
export const getAllSubDirectoriesAtProjectRoot = ({
projectFolderName,
}: { projectFolderName: string }): FileEntry[] => {
const subDirectories: FileEntry[] = []
const { folders } = systemIOActor.getSnapshot().context
const projectFolder = folders.find((folder) => {
return folder.name === projectFolderName
})
// Find the subdirectories
if (projectFolder) {
// 1st level
const children = projectFolder.children
if (children) {
children.forEach((childFileOrDirectory) => {
// 2nd level
const secondLevelChild = childFileOrDirectory.children
// if secondLevelChild is null then it is a file
if (secondLevelChild && isArray(secondLevelChild)) {
// this is a directory!
subDirectories.push(childFileOrDirectory)
}
})
}
}
return subDirectories
}