Merge remote-tracking branch 'origin' into kurt-circle

This commit is contained in:
Kurt Hutten Irev-Dev
2024-09-02 20:35:39 +10:00
44 changed files with 8576 additions and 232 deletions

View File

@ -88,6 +88,7 @@ layout: manual
* [`tan`](kcl/tan) * [`tan`](kcl/tan)
* [`tangentialArc`](kcl/tangentialArc) * [`tangentialArc`](kcl/tangentialArc)
* [`tangentialArcTo`](kcl/tangentialArcTo) * [`tangentialArcTo`](kcl/tangentialArcTo)
* [`tangentialArcToRelative`](kcl/tangentialArcToRelative)
* [`tau`](kcl/tau) * [`tau`](kcl/tau)
* [`toDegrees`](kcl/toDegrees) * [`toDegrees`](kcl/toDegrees)
* [`toRadians`](kcl/toRadians) * [`toRadians`](kcl/toRadians)

File diff suppressed because it is too large Load Diff

View File

@ -37,8 +37,7 @@ const example = extrude(10, exampleSketch)
offset: number, offset: number,
// Radius of the arc. Not to be confused with Raiders of the Lost Ark. // Radius of the arc. Not to be confused with Raiders of the Lost Ark.
radius: number, radius: number,
} | }
[number, number]
``` ```
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED) * `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
```js ```js

File diff suppressed because one or more lines are too long

View File

@ -813,7 +813,6 @@ export async function setup(context: BrowserContext, page: Page) {
localStorage.setItem('persistCode', ``) localStorage.setItem('persistCode', ``)
localStorage.setItem(settingsKey, settings) localStorage.setItem(settingsKey, settings)
localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true') localStorage.setItem(IS_PLAYWRIGHT_KEY, 'true')
console.log('TEST_SETTINGS.projects', settings)
}, },
{ {
token: secrets.token, token: secrets.token,

BIN
public/wheel-loop-dark.mp4 Normal file

Binary file not shown.

BIN
public/wheel-loop.mp4 Normal file

Binary file not shown.

View File

@ -119,6 +119,15 @@ export function App() {
paneOpacity + paneOpacity +
(context.store?.buttonDownInStream ? ' pointer-events-none' : '') (context.store?.buttonDownInStream ? ' pointer-events-none' : '')
} }
// Override the electron window draggable region behavior as well
// when the button is down in the stream
style={
{
'-webkit-app-region': context.store?.buttonDownInStream
? 'no-drag'
: '',
} as React.CSSProperties
}
project={{ project, file }} project={{ project, file }}
enableMenu={true} enableMenu={true}
/> />

View File

@ -42,7 +42,13 @@ export type ActionButtonProps =
export const ActionButton = forwardRef((props: ActionButtonProps, ref) => { export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
const classNames = `action-button p-0 m-0 group mono text-xs leading-none flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 enabled:dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 text-chalkboard-100 dark:text-chalkboard-10 ${ const classNames = `action-button p-0 m-0 group mono text-xs leading-none flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 enabled:dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 text-chalkboard-100 dark:text-chalkboard-10 ${
props.iconStart ? (props.iconEnd ? 'px-0' : 'pr-2') : 'px-2' props.iconStart
? props.iconEnd
? 'px-0'
: 'pr-2'
: props.iconEnd
? 'px-2'
: 'pl-2'
} ${props.className ? props.className : ''}` } ${props.className ? props.className : ''}`
switch (props.Element) { switch (props.Element) {

View File

@ -35,7 +35,7 @@ export const ActionIcon = ({
return ( return (
<div <div
className={ className={
`w-fit inline-grid place-content-center ${className} ` + `w-fit self-stretch inline-grid place-content-center ${className} ` +
computedBgClassName computedBgClassName
} }
> >

View File

@ -4,21 +4,11 @@
*/ */
.header { .header {
grid-template-columns: 1fr auto 1fr; grid-template-columns: 1fr auto 1fr;
-webkit-app-region: drag; /* Make the header of the app draggable */ user-select: none;
} -webkit-user-select: none;
/* Make the header act as a handle to drag the electron app window,
.header button { * per the electron docs: https://www.electronjs.org/docs/latest/tutorial/window-customization#set-custom-draggable-region
-webkit-app-region: no-drag; /* Make the button not draggable */ * all interactive elements opt-out of this behavior by default in src/index.css
} */
-webkit-app-region: drag;
.header a {
-webkit-app-region: no-drag; /* Make the link not draggable */
}
.header textarea {
-webkit-app-region: no-drag; /* Make the textarea not draggable */
}
.header input {
-webkit-app-region: no-drag; /* Make the input not draggable */
} }

View File

@ -12,6 +12,7 @@ interface AppHeaderProps extends React.PropsWithChildren {
project?: Omit<IndexLoaderData, 'code'> project?: Omit<IndexLoaderData, 'code'>
className?: string className?: string
enableMenu?: boolean enableMenu?: boolean
style?: React.CSSProperties
} }
export const AppHeader = ({ export const AppHeader = ({
@ -19,6 +20,7 @@ export const AppHeader = ({
project, project,
children, children,
className = '', className = '',
style,
enableMenu = false, enableMenu = false,
}: AppHeaderProps) => { }: AppHeaderProps) => {
const { auth } = useSettingsAuthContext() const { auth } = useSettingsAuthContext()
@ -33,6 +35,7 @@ export const AppHeader = ({
' overlaid-panes sticky top-0 z-20 px-2 items-start ' + ' overlaid-panes sticky top-0 z-20 px-2 items-start ' +
className className
} }
style={style}
> >
<ProjectSidebarMenu <ProjectSidebarMenu
enableMenu={enableMenu} enableMenu={enableMenu}

View File

@ -332,7 +332,7 @@ const CustomIconMap = {
fillRule="evenodd" fillRule="evenodd"
clipRule="evenodd" clipRule="evenodd"
d="M5.5 4C4.11929 4 3 5.11929 3 6.5V7C3 10.0376 5.46243 12.5 8.5 12.5H8.96482C9.46635 12.5 9.93469 12.2493 10.2129 11.8321L10.5173 11.3755C11.1396 12.0849 12.0423 12.5 13 12.5H13.75H15V14C15 14.2626 14.9483 14.5227 14.8478 14.7654C14.7472 15.008 14.5999 15.2285 14.4142 15.4142C14.2285 15.5999 14.008 15.7472 13.7654 15.8478C13.5227 15.9483 13.2626 16 13 16C12.7374 16 12.4773 15.9483 12.2346 15.8478C11.992 15.7472 11.7715 15.5999 11.5858 15.4142C11.4001 15.2285 11.2528 15.008 11.1522 14.7654C11.1164 14.6789 11.0868 14.5902 11.0635 14.5H11.8544C11.9168 14.6431 12.0056 14.7734 12.1161 14.8839C12.2322 15 12.37 15.092 12.5216 15.1548C12.6733 15.2177 12.8358 15.25 13 15.25C13.1642 15.25 13.3267 15.2177 13.4784 15.1548C13.63 15.092 13.7678 15 13.8839 14.8839C14 14.7678 14.092 14.63 14.1548 14.4784C14.2177 14.3267 14.25 14.1642 14.25 14V13H13.25V14C13.25 14.0328 13.2435 14.0653 13.231 14.0957C13.2184 14.126 13.2 14.1536 13.1768 14.1768C13.1536 14.2 13.126 14.2184 13.0957 14.231C13.0653 14.2435 13.0328 14.25 13 14.25C12.9672 14.25 12.9347 14.2435 12.9043 14.231C12.874 14.2184 12.8464 14.2 12.8232 14.1768C12.8 14.1536 12.7816 14.126 12.769 14.0957C12.7565 14.0653 12.75 14.0328 12.75 14V13.5H12.25H10.5H10V14C10 14.394 10.0776 14.7841 10.2284 15.1481C10.3791 15.512 10.6001 15.8427 10.8787 16.1213C11.1573 16.3999 11.488 16.6209 11.8519 16.7716C12.2159 16.9224 12.606 17 13 17C13.394 17 13.7841 16.9224 14.1481 16.7716C14.512 16.6209 14.8427 16.3999 15.1213 16.1213C15.3999 15.8427 15.6209 15.512 15.7716 15.1481C15.9224 14.7841 16 14.394 16 14V12.5H17V11.5H16V8.5C16 6.01472 13.9853 4 11.5 4H5.5ZM11.084 10.4746L10.9226 10.2326L9.42875 7.74275L8.57125 8.25725L9.90846 10.4859L9.38084 11.2773C9.28811 11.4164 9.13199 11.5 8.96482 11.5H8.5C6.01472 11.5 4 9.48528 4 7V6.5C4 5.67157 4.67157 5 5.5 5H11.5C13.433 5 15 6.567 15 8.5V11.5H13.75H13C12.2301 11.5 11.5111 11.1152 11.084 10.4746ZM13.5 8.5C13.5 9.05228 13.0523 9.5 12.5 9.5C11.9477 9.5 11.5 9.05228 11.5 8.5C11.5 7.94772 11.9477 7.5 12.5 7.5C13.0523 7.5 13.5 7.94772 13.5 8.5Z" d="M5.5 4C4.11929 4 3 5.11929 3 6.5V7C3 10.0376 5.46243 12.5 8.5 12.5H8.96482C9.46635 12.5 9.93469 12.2493 10.2129 11.8321L10.5173 11.3755C11.1396 12.0849 12.0423 12.5 13 12.5H13.75H15V14C15 14.2626 14.9483 14.5227 14.8478 14.7654C14.7472 15.008 14.5999 15.2285 14.4142 15.4142C14.2285 15.5999 14.008 15.7472 13.7654 15.8478C13.5227 15.9483 13.2626 16 13 16C12.7374 16 12.4773 15.9483 12.2346 15.8478C11.992 15.7472 11.7715 15.5999 11.5858 15.4142C11.4001 15.2285 11.2528 15.008 11.1522 14.7654C11.1164 14.6789 11.0868 14.5902 11.0635 14.5H11.8544C11.9168 14.6431 12.0056 14.7734 12.1161 14.8839C12.2322 15 12.37 15.092 12.5216 15.1548C12.6733 15.2177 12.8358 15.25 13 15.25C13.1642 15.25 13.3267 15.2177 13.4784 15.1548C13.63 15.092 13.7678 15 13.8839 14.8839C14 14.7678 14.092 14.63 14.1548 14.4784C14.2177 14.3267 14.25 14.1642 14.25 14V13H13.25V14C13.25 14.0328 13.2435 14.0653 13.231 14.0957C13.2184 14.126 13.2 14.1536 13.1768 14.1768C13.1536 14.2 13.126 14.2184 13.0957 14.231C13.0653 14.2435 13.0328 14.25 13 14.25C12.9672 14.25 12.9347 14.2435 12.9043 14.231C12.874 14.2184 12.8464 14.2 12.8232 14.1768C12.8 14.1536 12.7816 14.126 12.769 14.0957C12.7565 14.0653 12.75 14.0328 12.75 14V13.5H12.25H10.5H10V14C10 14.394 10.0776 14.7841 10.2284 15.1481C10.3791 15.512 10.6001 15.8427 10.8787 16.1213C11.1573 16.3999 11.488 16.6209 11.8519 16.7716C12.2159 16.9224 12.606 17 13 17C13.394 17 13.7841 16.9224 14.1481 16.7716C14.512 16.6209 14.8427 16.3999 15.1213 16.1213C15.3999 15.8427 15.6209 15.512 15.7716 15.1481C15.9224 14.7841 16 14.394 16 14V12.5H17V11.5H16V8.5C16 6.01472 13.9853 4 11.5 4H5.5ZM11.084 10.4746L10.9226 10.2326L9.42875 7.74275L8.57125 8.25725L9.90846 10.4859L9.38084 11.2773C9.28811 11.4164 9.13199 11.5 8.96482 11.5H8.5C6.01472 11.5 4 9.48528 4 7V6.5C4 5.67157 4.67157 5 5.5 5H11.5C13.433 5 15 6.567 15 8.5V11.5H13.75H13C12.2301 11.5 11.5111 11.1152 11.084 10.4746ZM13.5 8.5C13.5 9.05228 13.0523 9.5 12.5 9.5C11.9477 9.5 11.5 9.05228 11.5 8.5C11.5 7.94772 11.9477 7.5 12.5 7.5C13.0523 7.5 13.5 7.94772 13.5 8.5Z"
fill="black" fill="currentColor"
/> />
</svg> </svg>
), ),

View File

@ -217,7 +217,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
</p> </p>
{displayedName !== user.email && ( {displayedName !== user.email && (
<p <p
className="m-0 text-chalkboard-70 dark:text-chalkboard-40 text-xs" className="m-0 overflow-ellipsis overflow-hidden text-chalkboard-70 dark:text-chalkboard-40 text-xs"
data-testid="email" data-testid="email"
> >
{user.email} {user.email}

View File

@ -9,6 +9,7 @@ import { useModelingContext } from './useModelingContext'
import { getEventForSelectWithPoint } from 'lib/selections' import { getEventForSelectWithPoint } from 'lib/selections'
import { import {
getCapCodeRef, getCapCodeRef,
getExtrudeEdgeCodeRef,
getExtrusionFromSuspectedExtrudeSurface, getExtrusionFromSuspectedExtrudeSurface,
getSolid2dCodeRef, getSolid2dCodeRef,
getWallCodeRef, getWallCodeRef,
@ -60,6 +61,13 @@ export function useEngineConnectionSubscriptions() {
? [codeRef.range] ? [codeRef.range]
: [codeRef.range, extrusion.codeRef.range] : [codeRef.range, extrusion.codeRef.range]
) )
} else if (artifact?.type === 'extrudeEdge') {
const codeRef = getExtrudeEdgeCodeRef(
artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) return
editorManager.setHighlightRange([codeRef.range])
} else if (artifact?.type === 'segment') { } else if (artifact?.type === 'segment') {
editorManager.setHighlightRange([ editorManager.setHighlightRange([
artifact?.codeRef?.range || [0, 0], artifact?.codeRef?.range || [0, 0],

View File

@ -4,6 +4,18 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
button,
input,
select,
textarea,
a {
/* Make all interactive elements not act as handles
* to drag the electron app window by default,
* per the electron docs: https://www.electronjs.org/docs/latest/tutorial/window-customization#set-custom-draggable-region
*/
-webkit-app-region: no-drag;
}
body { body {
margin: 0; margin: 0;
@apply font-sans; @apply font-sans;
@ -97,7 +109,7 @@ button:disabled {
} }
a { a {
@apply text-primary underline hover:hue-rotate-15; @apply text-primary hover:hue-rotate-15;
} }
.dark a { .dark a {
@ -274,6 +286,35 @@ code {
} }
} }
@layer utilities {
/* Modified from the very helpful https://www.transition.style/#in:circle:hesitate */
@keyframes circle-in-hesitate {
0% {
clip-path: circle(
var(--circle-size-start, 0%) at var(--circle-x, 50%)
var(--circle-y, 50%)
);
}
40% {
clip-path: circle(
var(--circle-size-mid, 40%) at var(--circle-x, 50%) var(--circle-y, 50%)
);
}
100% {
clip-path: circle(
var(--circle-size-end, 125%) at var(--circle-x, 50%)
var(--circle-y, 50%)
);
}
}
.in-circle-hesitate {
animation: var(--circle-duration, 2.5s)
var(--circle-timing, cubic-bezier(0.25, 1, 0.3, 1)) circle-in-hesitate
both;
}
}
#code-mirror-override .cm-scroller, #code-mirror-override .cm-scroller,
#code-mirror-override .cm-editor { #code-mirror-override .cm-editor {
height: 100% !important; height: 100% !important;

View File

@ -4,6 +4,7 @@ import { KCLError, kclErrorsToDiagnostics } from './errors'
import { uuidv4 } from 'lib/utils' import { uuidv4 } from 'lib/utils'
import { EngineCommandManager } from './std/engineConnection' import { EngineCommandManager } from './std/engineConnection'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
import { import {
CallExpression, CallExpression,
@ -122,6 +123,7 @@ export class KclManager {
get isExecuting() { get isExecuting() {
return this._isExecuting return this._isExecuting
} }
set isExecuting(isExecuting) { set isExecuting(isExecuting) {
this._isExecuting = isExecuting this._isExecuting = isExecuting
// If we have finished executing, but the execute is stale, we should // If we have finished executing, but the execute is stale, we should
@ -232,6 +234,12 @@ export class KclManager {
async executeAst(args: ExecuteArgs = {}): Promise<void> { async executeAst(args: ExecuteArgs = {}): Promise<void> {
if (this.isExecuting) { if (this.isExecuting) {
this.executeIsStale = args this.executeIsStale = args
// The previous execteAst will be rejected and cleaned up. The execution will be marked as stale.
// A new executeAst will start.
this.engineCommandManager.rejectAllModelingCommands(
EXECUTE_AST_INTERRUPT_ERROR_MESSAGE
)
// Exit early if we are already executing. // Exit early if we are already executing.
return return
} }
@ -245,44 +253,38 @@ export class KclManager {
// Make sure we clear before starting again. End session will do this. // Make sure we clear before starting again. End session will do this.
this.engineCommandManager?.endSession() this.engineCommandManager?.endSession()
await this.ensureWasmInit() await this.ensureWasmInit()
const { logs, errors, programMemory } = await executeAst({ const { logs, errors, programMemory, isInterrupted } = await executeAst({
ast, ast,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
}) })
this.lints = await lintAst({ ast: ast }) // Program was not interrupted, setup the scene
// Do not send send scene commands if the program was interrupted, go to clean up
if (!isInterrupted) {
this.lints = await lintAst({ ast: ast })
sceneInfra.modelingSend({ type: 'code edit during sketch' }) sceneInfra.modelingSend({ type: 'code edit during sketch' })
defaultSelectionFilter(programMemory, this.engineCommandManager) defaultSelectionFilter(programMemory, this.engineCommandManager)
await this.engineCommandManager.waitForAllCommands()
if (args.zoomToFit) { if (args.zoomToFit) {
let zoomObjectId: string | undefined = '' let zoomObjectId: string | undefined = ''
if (args.zoomOnRangeAndType) { if (args.zoomOnRangeAndType) {
zoomObjectId = this.engineCommandManager?.mapRangeToObjectId( zoomObjectId = this.engineCommandManager?.mapRangeToObjectId(
args.zoomOnRangeAndType.range, args.zoomOnRangeAndType.range,
args.zoomOnRangeAndType.type args.zoomOnRangeAndType.type
) )
}
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'zoom_to_fit',
object_ids: zoomObjectId ? [zoomObjectId] : [], // leave empty to zoom to all objects
padding: 0.1, // padding around the objects
},
})
} }
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'zoom_to_fit',
object_ids: zoomObjectId ? [zoomObjectId] : [], // leave empty to zoom to all objects
padding: 0.1, // padding around the objects
},
})
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'zoom_to_fit',
object_ids: zoomObjectId ? [zoomObjectId] : [], // leave empty to zoom to all objects
padding: 0.1, // padding around the objects
},
})
} }
this.isExecuting = false this.isExecuting = false
@ -293,7 +295,8 @@ export class KclManager {
return return
} }
this.logs = logs this.logs = logs
this.addKclErrors(errors) // Do not add the errors since the program was interrupted and the error is not a real KCL error
this.addKclErrors(isInterrupted ? [] : errors)
this.programMemory = programMemory this.programMemory = programMemory
this.ast = { ...ast } this.ast = { ...ast }
this._executeCallback() this._executeCallback()
@ -301,6 +304,7 @@ export class KclManager {
type: 'execution-done', type: 'execution-done',
data: null, data: null,
}) })
this._cancelTokens.delete(currentExecutionId) this._cancelTokens.delete(currentExecutionId)
} }
// NOTE: this always updates the code state and editor. // NOTE: this always updates the code state and editor.

View File

@ -54,10 +54,12 @@ export async function executeAst({
engineCommandManager: EngineCommandManager engineCommandManager: EngineCommandManager
useFakeExecutor?: boolean useFakeExecutor?: boolean
programMemoryOverride?: ProgramMemory programMemoryOverride?: ProgramMemory
isInterrupted?: boolean
}): Promise<{ }): Promise<{
logs: string[] logs: string[]
errors: KCLError[] errors: KCLError[]
programMemory: ProgramMemory programMemory: ProgramMemory
isInterrupted: boolean
}> { }> {
try { try {
if (!useFakeExecutor) { if (!useFakeExecutor) {
@ -73,13 +75,23 @@ export async function executeAst({
logs: [], logs: [],
errors: [], errors: [],
programMemory, programMemory,
isInterrupted: false,
} }
} catch (e: any) { } catch (e: any) {
let isInterrupted = false
if (e instanceof KCLError) { if (e instanceof KCLError) {
// Detect if it is a force interrupt error which is not a KCL processing error.
if (
e.msg ===
'Failed to wait for promise from engine: JsValue("Force interrupt, executionIsStale, new AST requested")'
) {
isInterrupted = true
}
return { return {
errors: [e], errors: [e],
logs: [], logs: [],
programMemory: ProgramMemory.empty(), programMemory: ProgramMemory.empty(),
isInterrupted,
} }
} else { } else {
console.log(e) console.log(e)
@ -87,6 +99,7 @@ export async function executeAst({
logs: [e], logs: [e],
errors: [], errors: [],
programMemory: ProgramMemory.empty(), programMemory: ProgramMemory.empty(),
isInterrupted,
} }
} }
} }

View File

@ -58,7 +58,10 @@ Map {
92, 92,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
@ -77,7 +80,10 @@ Map {
], ],
}, },
"edgeCutId": "UUID", "edgeCutId": "UUID",
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
@ -95,7 +101,10 @@ Map {
156, 156,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
@ -113,7 +122,10 @@ Map {
209, 209,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
@ -152,7 +164,16 @@ Map {
266, 266,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
"UUID",
"UUID",
"UUID",
"UUID",
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceIds": [ "surfaceIds": [
"UUID", "UUID",
@ -209,6 +230,54 @@ Map {
"type": "cap", "type": "cap",
}, },
"UUID-15" => { "UUID-15" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-16" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
"UUID-17" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-18" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
"UUID-19" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-20" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
"UUID-21" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-22" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
"UUID-23" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -226,7 +295,7 @@ Map {
"subType": "fillet", "subType": "fillet",
"type": "edgeCut", "type": "edgeCut",
}, },
"UUID-16" => { "UUID-24" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -250,7 +319,7 @@ Map {
"solid2dId": "UUID", "solid2dId": "UUID",
"type": "path", "type": "path",
}, },
"UUID-17" => { "UUID-25" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -263,12 +332,15 @@ Map {
416, 416,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
}, },
"UUID-18" => { "UUID-26" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -281,12 +353,15 @@ Map {
438, 438,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
}, },
"UUID-19" => { "UUID-27" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -299,12 +374,15 @@ Map {
491, 491,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
}, },
"UUID-20" => { "UUID-28" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -321,11 +399,11 @@ Map {
"pathId": "UUID", "pathId": "UUID",
"type": "segment", "type": "segment",
}, },
"UUID-21" => { "UUID-29" => {
"pathId": "UUID", "pathId": "UUID",
"type": "solid2D", "type": "solid2D",
}, },
"UUID-22" => { "UUID-30" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -338,7 +416,14 @@ Map {
546, 546,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
"UUID",
"UUID",
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceIds": [ "surfaceIds": [
"UUID", "UUID",
@ -349,40 +434,76 @@ Map {
], ],
"type": "extrusion", "type": "extrusion",
}, },
"UUID-23" => { "UUID-31" => {
"edgeCutEdgeIds": [], "edgeCutEdgeIds": [],
"extrusionId": "UUID", "extrusionId": "UUID",
"pathIds": [], "pathIds": [],
"segId": "UUID", "segId": "UUID",
"type": "wall", "type": "wall",
}, },
"UUID-24" => { "UUID-32" => {
"edgeCutEdgeIds": [], "edgeCutEdgeIds": [],
"extrusionId": "UUID", "extrusionId": "UUID",
"pathIds": [], "pathIds": [],
"segId": "UUID", "segId": "UUID",
"type": "wall", "type": "wall",
}, },
"UUID-25" => { "UUID-33" => {
"edgeCutEdgeIds": [], "edgeCutEdgeIds": [],
"extrusionId": "UUID", "extrusionId": "UUID",
"pathIds": [], "pathIds": [],
"segId": "UUID", "segId": "UUID",
"type": "wall", "type": "wall",
}, },
"UUID-26" => { "UUID-34" => {
"edgeCutEdgeIds": [], "edgeCutEdgeIds": [],
"extrusionId": "UUID", "extrusionId": "UUID",
"pathIds": [], "pathIds": [],
"subType": "start", "subType": "start",
"type": "cap", "type": "cap",
}, },
"UUID-27" => { "UUID-35" => {
"edgeCutEdgeIds": [], "edgeCutEdgeIds": [],
"extrusionId": "UUID", "extrusionId": "UUID",
"pathIds": [], "pathIds": [],
"subType": "end", "subType": "end",
"type": "cap", "type": "cap",
}, },
"UUID-36" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-37" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
"UUID-38" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-39" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
"UUID-40" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-41" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
} }
`; `;

View File

@ -247,7 +247,7 @@ describe('testing createArtifactGraph', () => {
// of the edges refers to a non-existent node, the graph will throw. // of the edges refers to a non-existent node, the graph will throw.
// further more we can check that each edge is bi-directional, if it's not // further more we can check that each edge is bi-directional, if it's not
// by checking the arrow heads going both ways, on the graph. // by checking the arrow heads going both ways, on the graph.
await GraphTheGraph(theMap, 1400, 1400, 'exampleCode1.png') await GraphTheGraph(theMap, 2000, 2000, 'exampleCode1.png')
}, 20000) }, 20000)
}) })
}) })
@ -271,7 +271,7 @@ describe('capture graph of sketchOnFaceOnFace...', () => {
// of the edges refers to a non-existent node, the graph will throw. // of the edges refers to a non-existent node, the graph will throw.
// further more we can check that each edge is bi-directional, if it's not // further more we can check that each edge is bi-directional, if it's not
// by checking the arrow heads going both ways, on the graph. // by checking the arrow heads going both ways, on the graph.
await GraphTheGraph(theMap, 2500, 2500, 'sketchOnFaceOnFaceEtc.png') await GraphTheGraph(theMap, 3000, 3000, 'sketchOnFaceOnFaceEtc.png')
}, 20000) }, 20000)
}) })
}) })
@ -603,7 +603,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'segment', type: 'segment',
pathId: expect.any(String), pathId: expect.any(String),
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [98, 125], range: [98, 125],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -623,7 +623,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'segment', type: 'segment',
pathId: expect.any(String), pathId: expect.any(String),
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [162, 209], range: [162, 209],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -633,7 +633,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'extrusion', type: 'extrusion',
pathId: expect.any(String), pathId: expect.any(String),
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [243, 266], range: [243, 266],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -650,7 +650,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'segment', type: 'segment',
pathId: expect.any(String), pathId: expect.any(String),
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [131, 156], range: [131, 156],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -660,7 +660,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'extrusion', type: 'extrusion',
pathId: expect.any(String), pathId: expect.any(String),
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [243, 266], range: [243, 266],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -677,7 +677,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'segment', type: 'segment',
pathId: expect.any(String), pathId: expect.any(String),
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [98, 125], range: [98, 125],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -688,7 +688,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'extrusion', type: 'extrusion',
pathId: expect.any(String), pathId: expect.any(String),
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [243, 266], range: [243, 266],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -705,7 +705,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'segment', type: 'segment',
pathId: expect.any(String), pathId: expect.any(String),
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [76, 92], range: [76, 92],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -715,7 +715,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'extrusion', type: 'extrusion',
pathId: expect.any(String), pathId: expect.any(String),
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [243, 266], range: [243, 266],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -732,7 +732,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'extrusion', type: 'extrusion',
pathId: expect.any(String), pathId: expect.any(String),
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [243, 266], range: [243, 266],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -749,7 +749,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'extrusion', type: 'extrusion',
pathId: expect.any(String), pathId: expect.any(String),
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [243, 266], range: [243, 266],
pathToNode: [['body', '']], pathToNode: [['body', '']],

View File

@ -91,7 +91,7 @@ interface ExtrudeEdge {
type: 'extrudeEdge' type: 'extrudeEdge'
segId: string segId: string
extrusionId: string extrusionId: string
edgeId: string subType: 'opposite' | 'adjacent'
} }
/** A edgeCut is a more generic term for both fillet or chamfer */ /** A edgeCut is a more generic term for both fillet or chamfer */
@ -422,6 +422,56 @@ export function getArtifactsToUpdate({
} }
}) })
return returnArr return returnArr
} else if (
// is opposite edge
(cmd.type === 'solid3d_get_opposite_edge' &&
response.type === 'modeling' &&
response.data.modeling_response.type === 'solid3d_get_opposite_edge' &&
response.data.modeling_response.data.edge) ||
// or is adjacent edge
(cmd.type === 'solid3d_get_prev_adjacent_edge' &&
response.type === 'modeling' &&
response.data.modeling_response.type ===
'solid3d_get_prev_adjacent_edge' &&
response.data.modeling_response.data.edge)
) {
const wall = getArtifact(cmd.face_id)
if (wall?.type !== 'wall') return returnArr
const extrusion = getArtifact(wall.extrusionId)
if (extrusion?.type !== 'extrusion') return returnArr
const path = getArtifact(extrusion.pathId)
if (path?.type !== 'path') return returnArr
const segment = getArtifact(cmd.edge_id)
if (segment?.type !== 'segment') return returnArr
return [
{
id: response.data.modeling_response.data.edge,
artifact: {
type: 'extrudeEdge',
subType:
cmd.type === 'solid3d_get_prev_adjacent_edge'
? 'adjacent'
: 'opposite',
segId: cmd.edge_id,
extrusionId: path.extrusionId,
},
},
{
id: cmd.edge_id,
artifact: {
...segment,
edgeIds: [response.data.modeling_response.data.edge],
},
},
{
id: path.extrusionId,
artifact: {
...extrusion,
edgeIds: [response.data.modeling_response.data.edge],
},
},
]
} else if (cmd.type === 'solid3d_fillet_edge') { } else if (cmd.type === 'solid3d_fillet_edge') {
returnArr.push({ returnArr.push({
id, id,
@ -655,6 +705,18 @@ export function getWallCodeRef(
return seg.codeRef return seg.codeRef
} }
export function getExtrudeEdgeCodeRef(
edge: ExtrudeEdge,
artifactGraph: ArtifactGraph
): CommonCommandProperties | Error {
const seg = getArtifactOfTypes(
{ key: edge.segId, types: ['segment'] },
artifactGraph
)
if (err(seg)) return seg
return seg.codeRef
}
export function getExtrusionFromSuspectedExtrudeSurface( export function getExtrusionFromSuspectedExtrudeSurface(
id: string, id: string,
artifactGraph: ArtifactGraph artifactGraph: ArtifactGraph

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 KiB

After

Width:  |  Height:  |  Size: 617 KiB

View File

@ -16,6 +16,8 @@ import { useModelingContext } from 'hooks/useModelingContext'
import { exportMake } from 'lib/exportMake' import { exportMake } from 'lib/exportMake'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { SettingsViaQueryString } from 'lib/settings/settingsTypes' import { SettingsViaQueryString } from 'lib/settings/settingsTypes'
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
import { KclManager } from 'lang/KclSingleton'
// TODO(paultag): This ought to be tweakable. // TODO(paultag): This ought to be tweakable.
const pingIntervalMs = 5_000 const pingIntervalMs = 5_000
@ -1279,6 +1281,7 @@ interface PendingMessage {
resolve: (data: [Models['WebSocketResponse_type']]) => void resolve: (data: [Models['WebSocketResponse_type']]) => void
reject: (reason: string) => void reject: (reason: string) => void
promise: Promise<[Models['WebSocketResponse_type']]> promise: Promise<[Models['WebSocketResponse_type']]>
isSceneCommand: boolean
} }
export class EngineCommandManager extends EventTarget { export class EngineCommandManager extends EventTarget {
/** /**
@ -1379,6 +1382,7 @@ export class EngineCommandManager extends EventTarget {
}: CustomEvent<NewTrackArgs>) => {} }: CustomEvent<NewTrackArgs>) => {}
modelingSend: ReturnType<typeof useModelingContext>['send'] = modelingSend: ReturnType<typeof useModelingContext>['send'] =
(() => {}) as any (() => {}) as any
kclManager: null | KclManager = null
set exportIntent(intent: ExportIntent | null) { set exportIntent(intent: ExportIntent | null) {
this._exportIntent = intent this._exportIntent = intent
@ -1932,11 +1936,21 @@ export class EngineCommandManager extends EventTarget {
;(cmd as any).sequence = this.outSequence++ ;(cmd as any).sequence = this.outSequence++
} }
// since it's not mouse drag or highlighting send over TCP and keep track of the command // since it's not mouse drag or highlighting send over TCP and keep track of the command
return this.sendCommand(command.cmd_id, { return this.sendCommand(
command, command.cmd_id,
idToRangeMap: {}, {
range: [0, 0], command,
}).then(([a]) => a) idToRangeMap: {},
range: [0, 0],
},
true // isSceneCommand
)
.then(([a]) => a)
.catch((e) => {
// TODO: Previously was never caught, we are not rejecting these pendingCommands but this needs to be handled at some point.
/*noop*/
return null
})
} }
/** /**
* A wrapper around the sendCommand where all inputs are JSON strings * A wrapper around the sendCommand where all inputs are JSON strings
@ -1963,6 +1977,12 @@ export class EngineCommandManager extends EventTarget {
const idToRangeMap: { [key: string]: SourceRange } = const idToRangeMap: { [key: string]: SourceRange } =
JSON.parse(idToRangeStr) JSON.parse(idToRangeStr)
// Current executeAst is stale, going to interrupt, a new executeAst will trigger
// Used in conjunction with rejectAllModelingCommands
if (this?.kclManager?.executeIsStale) {
return Promise.reject(EXECUTE_AST_INTERRUPT_ERROR_MESSAGE)
}
const resp = await this.sendCommand(id, { const resp = await this.sendCommand(id, {
command, command,
range, range,
@ -1980,7 +2000,8 @@ export class EngineCommandManager extends EventTarget {
command: PendingMessage['command'] command: PendingMessage['command']
range: PendingMessage['range'] range: PendingMessage['range']
idToRangeMap: PendingMessage['idToRangeMap'] idToRangeMap: PendingMessage['idToRangeMap']
} },
isSceneCommand = false
): Promise<[Models['WebSocketResponse_type']]> { ): Promise<[Models['WebSocketResponse_type']]> {
const { promise, resolve, reject } = promiseFactory<any>() const { promise, resolve, reject } = promiseFactory<any>()
this.pendingCommands[id] = { this.pendingCommands[id] = {
@ -1990,7 +2011,9 @@ export class EngineCommandManager extends EventTarget {
command: message.command, command: message.command,
range: message.range, range: message.range,
idToRangeMap: message.idToRangeMap, idToRangeMap: message.idToRangeMap,
isSceneCommand,
} }
if (message.command.type === 'modeling_cmd_req') { if (message.command.type === 'modeling_cmd_req') {
this.orderedCommands.push({ this.orderedCommands.push({
command: message.command, command: message.command,
@ -2037,6 +2060,19 @@ export class EngineCommandManager extends EventTarget {
this.deferredArtifactPopulated(null) this.deferredArtifactPopulated(null)
} }
} }
/**
* Reject all of the modeling pendingCommands created from sendModelingCommandFromWasm
* This interrupts the runtime of executeAst. Stops the AST processing and stops sending commands
* to the engine
*/
rejectAllModelingCommands(rejectionMessage: string) {
Object.values(this.pendingCommands).forEach(
({ reject, isSceneCommand }) =>
!isSceneCommand && reject(rejectionMessage)
)
}
async initPlanes() { async initPlanes() {
if (this.planesInitialized()) return if (this.planesInitialized()) return
const planes = await this.makeDefaultPlanes() const planes = await this.makeDefaultPlanes()

View File

@ -67,3 +67,8 @@ export const COOKIE_NAME = '__Secure-next-auth.session-token'
/** localStorage key to determine if we're in Playwright tests */ /** localStorage key to determine if we're in Playwright tests */
export const PLAYWRIGHT_KEY = 'playwright' export const PLAYWRIGHT_KEY = 'playwright'
/** Custom error message to match when rejectAllModelCommands is called
* allows us to match if the execution of executeAst was interrupted */
export const EXECUTE_AST_INTERRUPT_ERROR_MESSAGE =
'Force interrupt, executionIsStale, new AST requested'

View File

@ -34,6 +34,7 @@ import {
getArtifactOfTypes, getArtifactOfTypes,
getArtifactsOfTypes, getArtifactsOfTypes,
getCapCodeRef, getCapCodeRef,
getExtrudeEdgeCodeRef,
getSolid2dCodeRef, getSolid2dCodeRef,
getWallCodeRef, getWallCodeRef,
} from 'lang/std/artifactGraph' } from 'lang/std/artifactGraph'
@ -142,6 +143,20 @@ export async function getEventForSelectWithPoint({
}, },
} }
} }
if (_artifact.type === 'extrudeEdge') {
const codeRef = getExtrudeEdgeCodeRef(
_artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) return null
return {
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: { range: codeRef.range, type: 'edge' },
},
}
}
return null return null
} }

View File

@ -17,6 +17,7 @@ window.tearDown = engineCommandManager.tearDown
// This needs to be after codeManager is created. // This needs to be after codeManager is created.
export const kclManager = new KclManager(engineCommandManager) export const kclManager = new KclManager(engineCommandManager)
kclManager.isFirstRender = true kclManager.isFirstRender = true
engineCommandManager.kclManager = kclManager
engineCommandManager.getAstCb = () => kclManager.ast engineCommandManager.getAstCb = () => kclManager.ast

View File

@ -1168,13 +1168,11 @@ export const modelingMachine = createMachine(
store.videoElement?.pause() store.videoElement?.pause()
const updatedAst = await kclManager.updateAst(modifiedAst, true, { const updatedAst = await kclManager.updateAst(modifiedAst, true, {
focusPath: pathToExtrudeArg, focusPath: pathToExtrudeArg,
// commented out as a part of https://github.com/KittyCAD/modeling-app/issues/3270 zoomToFit: true,
// looking to add back in the future zoomOnRangeAndType: {
// zoomToFit: true, range: selection.codeBasedSelections[0].range,
// zoomOnRangeAndType: { type: 'path',
// range: selection.codeBasedSelections[0].range, },
// type: 'path',
// },
}) })
if (!engineCommandManager.engineConnection?.idleMode) { if (!engineCommandManager.engineConnection?.idleMode) {
store.videoElement?.play().catch((e) => { store.videoElement?.play().catch((e) => {

View File

@ -5,6 +5,16 @@ import { Themes, getSystemTheme } from '../lib/theme'
import { PATHS } from 'lib/paths' import { PATHS } from 'lib/paths'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { APP_NAME } from 'lib/constants' import { APP_NAME } from 'lib/constants'
import { CSSProperties, useCallback } from 'react'
import { Logo } from 'components/Logo'
import { CustomIcon } from 'components/CustomIcon'
import { Link } from 'react-router-dom'
import { APP_VERSION } from './Settings'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
const subtleBorder =
'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
const cardArea = `${subtleBorder} rounded-lg px-6 py-3 text-chalkboard-70 dark:text-chalkboard-30`
const SignIn = () => { const SignIn = () => {
const { const {
@ -17,12 +27,25 @@ const SignIn = () => {
}, },
}, },
} = useSettingsAuthContext() } = useSettingsAuthContext()
const signInUrl = `${VITE_KC_SITE_BASE_URL}${
PATHS.SIGN_IN
}?callbackUrl=${encodeURIComponent(
typeof window !== 'undefined' && window.location.href.replace('signin', '')
)}`
const kclSampleUrl = `${VITE_KC_SITE_BASE_URL}/docs/kcl-samples/car-wheel`
const getLogoTheme = () => const getThemeText = useCallback(
theme.current === Themes.Light || (shouldContrast = true) =>
(theme.current === Themes.System && getSystemTheme() === Themes.Light) theme.current === Themes.Light ||
? '-dark' (theme.current === Themes.System && getSystemTheme() === Themes.Light)
: '' ? shouldContrast
? '-dark'
: ''
: shouldContrast
? ''
: '-dark',
[theme.current]
)
const signInDesktop = async () => { const signInDesktop = async () => {
// We want to invoke our command to login via device auth. // We want to invoke our command to login via device auth.
@ -35,56 +58,192 @@ const SignIn = () => {
} }
return ( return (
<main className="body-bg h-full min-h-screen m-0 p-0 pt-24"> <main className="bg-primary h-screen grid place-items-stretch m-0 p-2">
<div className="max-w-2xl mx-auto"> <div
<div> style={
<img {
src={`./zma-logomark${getLogoTheme()}.svg`} height: 'calc(100vh - 16px)',
alt="Zoo Modeling App" '--circle-x': '14%',
className="w-48 inline-block" '--circle-y': '12%',
/> '--circle-size-mid': '15%',
'--circle-size-end': '200%',
'--circle-timing': 'cubic-bezier(0.25, 1, 0.4, 0.9)',
} as CSSProperties
}
className="in-circle-hesitate body-bg py-5 px-12 rounded-lg grid place-items-center overflow-y-auto"
>
<div className="max-w-7xl grid gap-5 grid-cols-3 xl:grid-cols-4 xl:grid-rows-5">
<div className="col-span-2 xl:col-span-3 xl:row-span-3 max-w-3xl mr-8 mb-8">
<div className="flex items-baseline mb-8">
<Logo className="text-primary h-10 lg:h-12 xl:h-16 relative translate-y-1 mr-4 lg:mr-6 xl:mr-8" />
<h1 className="text-3xl lg:text-4xl xl:text-5xl">{APP_NAME}</h1>
<span className="px-3 py-1 text-base rounded-full bg-primary/10 text-primary self-start">
alpha v{APP_VERSION}
</span>
</div>
<p className="my-4 text-lg xl:text-xl">
Thank you for using our hardware design application. It is built
on a novel CAD engine and crafted to help you create parametric,
version-controlled, and accurate parts ready for manufacturing.
</p>
<p className="my-4 text-lg xl:text-xl">
As alpha software, Zoo Modeling App is still in heavy development.
We encourage feedback and feature requests that align with{' '}
<a
href="https://github.com/KittyCAD/modeling-app/issues/729"
target="_blank"
rel="noreferrer"
>
our roadmap to v1.0
</a>
.
</p>
{isDesktop() ? (
<button
onClick={signInDesktop}
className={
'm-0 mt-8 flex gap-4 items-center px-3 py-1 ' +
'!border-transparent !text-lg !text-chalkboard-10 !bg-primary hover:hue-rotate-15'
}
data-testid="sign-in-button"
>
Sign in to get started
<CustomIcon name="arrowRight" className="w-6 h-6" />
</button>
) : (
<Link
onClick={openExternalBrowserIfDesktop(signInUrl)}
to={signInUrl}
className={
'w-fit m-0 mt-8 flex gap-4 items-center px-3 py-1 ' +
'!border-transparent !text-lg !text-chalkboard-10 !bg-primary hover:hue-rotate-15'
}
data-testid="sign-in-button"
>
Sign in to get started
<CustomIcon name="arrowRight" className="w-6 h-6" />
</Link>
)}
</div>
<Link
className={`group relative xl:h-full xl:row-span-full col-start--1 xl:col-start-4 rounded-lg overflow-hidden grid place-items-center ${subtleBorder}`}
to={kclSampleUrl}
onClick={openExternalBrowserIfDesktop(kclSampleUrl)}
target="_blank"
rel="noreferrer noopener"
>
<video
autoPlay
loop
muted
playsInline
className="h-full object-cover object-center"
>
<source
src={`${isDesktop() ? '.' : ''}/wheel-loop${getThemeText(
false
)}.mp4`}
type="video/mp4"
/>
</video>
<div
className={
'absolute bottom-0 left-0 right-0 transition translate-y-4 opacity-0 ' +
'group-hover:translate-y-0 group-hover:opacity-100 ' +
'm-0 mt-8 flex gap-4 items-center px-3 py-1 ' +
'!border-transparent !text-lg !text-chalkboard-10 !bg-primary hover:hue-rotate-15'
}
data-testid="sign-in-button"
>
View this sample
<CustomIcon name="arrowRight" className="w-6 h-6" />
</div>
</Link>
<div className="self-end h-min col-span-3 xl:row-span-2 grid grid-cols-2 gap-5">
<div className={cardArea}>
<h2 className="text-xl">Built in the open</h2>
<p className="text-xs my-4">
Open-source and open discussions. Check our public code base and
join our Discord.
</p>
<div className="flex gap-4 flex-wrap items-center">
<ActionButton
Element="externalLink"
to="https://github.com/KittyCAD/modeling-app"
iconStart={{ icon: 'code' }}
className="border-chalkboard-30 dark:border-chalkboard-80"
>
<span className="py-2 lg:py-0">Read our source code</span>
</ActionButton>
<ActionButton
Element="externalLink"
to="https://discord.gg/JQEpHR7Nt2"
iconStart={{ icon: 'keyboard' }}
className="border-chalkboard-30 dark:border-chalkboard-80"
>
<span className="py-2 lg:py-0">Join our community</span>
</ActionButton>
</div>
</div>
<div className={cardArea}>
<h2 className="text-xl">Ready for the future</h2>
<p className="text-xs my-4">
Modern software ideas being brought together to create a
familiar modeling experience with new superpowers.
</p>
<div className="flex gap-4 flex-wrap items-center">
<ActionButton
Element="externalLink"
to="https://zoo.dev/docs/kcl-samples/ball-bearing"
iconStart={{ icon: 'settings' }}
className="border-chalkboard-30 dark:border-chalkboard-80"
>
<span className="py-2 lg:py-0">
Parametric design with KCL
</span>
</ActionButton>
<ActionButton
Element="externalLink"
to="https://zoo.dev/docs/tutorials/text-to-cad"
iconStart={{ icon: 'sparkles' }}
className="border-chalkboard-30 dark:border-chalkboard-80"
>
<span className="py-2 lg:py-0">AI-unlocked CAD</span>
</ActionButton>
</div>
</div>
<div className={cardArea + ' col-span-2'}>
<h2 className="text-xl">
Built on the first infrastructure for hardware design
</h2>
<p className="text-xs my-4">
You can make your own niche hardware design tools with our
design and machine learning interfaces. We're building Modeling
App in the same way.
</p>
<div className="flex gap-4 flex-wrap items-center">
<ActionButton
Element="externalLink"
to="https://zoo.dev/design-api"
iconStart={{ icon: 'sketch' }}
className="border-chalkboard-30 dark:border-chalkboard-80"
>
<span className="py-2 lg:py-0">KittyCAD Design API</span>
</ActionButton>
<ActionButton
Element="externalLink"
to="https://zoo.dev/machine-learning-api"
iconStart={{ icon: 'elephant' }}
className="border-chalkboard-30 dark:border-chalkboard-80"
>
<span className="py-2 lg:py-0">
ML-ephant Machine Learning API
</span>
</ActionButton>
</div>
</div>
</div>
</div> </div>
<h1 className="font-bold text-2xl mt-12 mb-6">
Sign in to get started with the {APP_NAME}
</h1>
<p className="py-4">
ZMA is an open-source CAD application for creating accurate 3D models
for use in manufacturing. It is built on top of KittyCAD, the design
API from Zoo. Zoo is the first software infrastructure company built
specifically for the needs of the manufacturing industry. With ZMA we
are showing how the KittyCAD API from Zoo can be used to build
entirely new kinds of software for manufacturing.
</p>
<p className="py-4">
ZMA is currently in development. If you would like to be notified when
ZMA is ready for production, please sign up for our mailing list at{' '}
<a href="https://zoo.dev">zoo.dev</a>.
</p>
{isDesktop() ? (
<ActionButton
Element="button"
onClick={signInDesktop}
iconStart={{ icon: 'arrowRight' }}
className="w-fit mt-4"
data-testid="sign-in-button"
>
Sign in
</ActionButton>
) : (
<ActionButton
Element="link"
to={`${VITE_KC_SITE_BASE_URL}${
PATHS.SIGN_IN
}?callbackUrl=${encodeURIComponent(
typeof window !== 'undefined' &&
window.location.href.replace('signin', '')
)}`}
iconStart={{ icon: 'arrowRight' }}
className="w-fit mt-4"
>
Sign in
</ActionButton>
)}
</div> </div>
</main> </main>
) )

View File

@ -2573,7 +2573,6 @@ impl ObjectExpression {
} }
}) })
.collect(); .collect();
dbg!(&format_items);
let end_indent = if is_in_pipe { let end_indent = if is_in_pipe {
options.get_indentation_offset_pipe(indentation_level) options.get_indentation_offset_pipe(indentation_level)
} else { } else {

View File

@ -9,6 +9,8 @@ use crate::{
executor::{KclValue, SourceRange, UserVal}, executor::{KclValue, SourceRange, UserVal},
}; };
const KCL_NONE_ID: &str = "KCL_NONE_ID";
/// KCL value for an optional parameter which was not given an argument. /// KCL value for an optional parameter which was not given an argument.
/// (remember, parameters are in the function declaration, /// (remember, parameters are in the function declaration,
/// arguments are in the function call/application). /// arguments are in the function call/application).
@ -20,6 +22,45 @@ pub struct KclNone {
// TODO: Convert this to be an Option<SourceRange>. // TODO: Convert this to be an Option<SourceRange>.
pub start: usize, pub start: usize,
pub end: usize, pub end: usize,
#[serde(deserialize_with = "deser_private")]
#[ts(skip)]
#[schemars(skip)]
__private: Private,
}
impl KclNone {
pub fn new(start: usize, end: usize) -> Self {
Self {
start,
end,
__private: Private {},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Bake, Default)]
#[databake(path = kcl_lib::ast::types)]
struct Private;
impl Serialize for Private {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(KCL_NONE_ID)
}
}
fn deser_private<'de, D>(deserializer: D) -> Result<Private, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s == KCL_NONE_ID {
Ok(Private {})
} else {
Err(serde::de::Error::custom("not a KCL none"))
}
} }
impl From<&KclNone> for SourceRange { impl From<&KclNone> for SourceRange {
@ -57,3 +98,24 @@ impl KclNone {
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn other_types_will_not_deserialize() {
// This shouldn't deserialize into a KCL None,
// because it's missing the special Private tag.
let j = r#"{"start": 0, "end": 0}"#;
let _e = serde_json::from_str::<KclNone>(j).unwrap_err();
}
#[test]
fn serialize_then_deserialize() {
// Serializing, then deserializing a None should produce the same value.
let before = KclNone::default();
let j = serde_json::to_string_pretty(&before).unwrap();
let after: KclNone = serde_json::from_str(&j).unwrap();
assert_eq!(before, after);
}
}

View File

@ -2097,10 +2097,7 @@ fn assign_args_to_params(
if param.optional { if param.optional {
// If the corresponding parameter is optional, // If the corresponding parameter is optional,
// then it's fine, the user doesn't need to supply it. // then it's fine, the user doesn't need to supply it.
let none = KclNone { let none = KclNone::new(param.identifier.start, param.identifier.end);
start: param.identifier.start,
end: param.identifier.end,
};
fn_memory.add( fn_memory.add(
&param.identifier.name, &param.identifier.name,
KclValue::from(&none), KclValue::from(&none),

View File

@ -135,13 +135,16 @@ fn non_code_node_no_leading_whitespace(i: TokenSlice) -> PResult<NonCodeNode> {
fn pipe_expression(i: TokenSlice) -> PResult<PipeExpression> { fn pipe_expression(i: TokenSlice) -> PResult<PipeExpression> {
let mut non_code_meta = NonCodeMeta::default(); let mut non_code_meta = NonCodeMeta::default();
let (head, noncode) = terminated( let (head, noncode): (_, Vec<_>) = terminated(
(expression_but_not_pipe, preceded(whitespace, opt(non_code_node))), (
expression_but_not_pipe,
repeat(0.., preceded(whitespace, non_code_node)),
),
peek(pipe_surrounded_by_whitespace), peek(pipe_surrounded_by_whitespace),
) )
.context(expected("an expression, followed by the |> (pipe) operator")) .context(expected("an expression, followed by the |> (pipe) operator"))
.parse_next(i)?; .parse_next(i)?;
if let Some(nc) = noncode { for nc in noncode {
non_code_meta.insert(0, nc); non_code_meta.insert(0, nc);
} }
let mut values = vec![head]; let mut values = vec![head];
@ -3453,6 +3456,17 @@ mod snapshot_tests {
c: 3 c: 3
}" }"
); );
snapshot_test!(
ba,
r#"
const sketch001 = startSketchOn('XY')
// |> arc({
// angleEnd: 270,
// angleStart: 450,
// }, %)
|> startProfileAt(%)
"#
);
} }
#[allow(unused)] #[allow(unused)]

View File

@ -0,0 +1,150 @@
---
source: kcl/src/parser/parser_impl.rs
expression: actual
---
{
"start": 0,
"end": 133,
"body": [
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 1,
"end": 132,
"declarations": [
{
"type": "VariableDeclarator",
"start": 7,
"end": 132,
"id": {
"type": "Identifier",
"start": 7,
"end": 16,
"name": "sketch001",
"digest": null
},
"init": {
"type": "PipeExpression",
"type": "PipeExpression",
"start": 19,
"end": 132,
"body": [
{
"type": "CallExpression",
"type": "CallExpression",
"start": 19,
"end": 38,
"callee": {
"type": "Identifier",
"start": 19,
"end": 32,
"name": "startSketchOn",
"digest": null
},
"arguments": [
{
"type": "Literal",
"type": "Literal",
"start": 33,
"end": 37,
"value": "XY",
"raw": "'XY'",
"digest": null
}
],
"optional": false,
"digest": null
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 115,
"end": 132,
"callee": {
"type": "Identifier",
"start": 115,
"end": 129,
"name": "startProfileAt",
"digest": null
},
"arguments": [
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 130,
"end": 131,
"digest": null
}
],
"optional": false,
"digest": null
}
],
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"type": "NonCodeNode",
"start": 41,
"end": 52,
"value": {
"type": "blockComment",
"value": "|> arc({",
"style": "line"
},
"digest": null
},
{
"type": "NonCodeNode",
"start": 55,
"end": 74,
"value": {
"type": "blockComment",
"value": "angleEnd: 270,",
"style": "line"
},
"digest": null
},
{
"type": "NonCodeNode",
"start": 77,
"end": 98,
"value": {
"type": "blockComment",
"value": "angleStart: 450,",
"style": "line"
},
"digest": null
},
{
"type": "NonCodeNode",
"start": 101,
"end": 109,
"value": {
"type": "blockComment",
"value": "}, %)",
"style": "line"
},
"digest": null
}
]
},
"start": [],
"digest": null
},
"digest": null
},
"digest": null
}
],
"kind": "const",
"digest": null
}
],
"nonCodeMeta": {
"nonCodeNodes": {},
"start": [],
"digest": null
},
"digest": null
}

View File

@ -495,6 +495,9 @@ where
{ {
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> { fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
let Some(arg) = args.args.get(i) else { return Ok(None) }; let Some(arg) = args.args.get(i) else { return Ok(None) };
if crate::ast::types::KclNone::from_mem_item(arg).is_some() {
return Ok(None);
}
let Some(val) = T::from_mem_item(arg) else { let Some(val) = T::from_mem_item(arg) else {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: format!( message: format!(
@ -620,6 +623,7 @@ impl_from_arg_via_json!(crate::std::polar::PolarCoordsData);
impl_from_arg_via_json!(SketchGroup); impl_from_arg_via_json!(SketchGroup);
impl_from_arg_via_json!(FaceTag); impl_from_arg_via_json!(FaceTag);
impl_from_arg_via_json!(String); impl_from_arg_via_json!(String);
impl_from_arg_via_json!(crate::ast::types::KclNone);
impl_from_arg_via_json!(u32); impl_from_arg_via_json!(u32);
impl_from_arg_via_json!(u64); impl_from_arg_via_json!(u64);
impl_from_arg_via_json!(f64); impl_from_arg_via_json!(f64);

View File

@ -181,6 +181,32 @@ pub(crate) async fn do_post_extrude(
vec![] vec![]
}; };
for face_info in face_infos.iter() {
if face_info.cap == kittycad::types::ExtrusionFaceCapType::None {
if let (Some(curve_id), Some(face_id)) = (face_info.curve_id, face_info.face_id) {
args.batch_modeling_cmd(
uuid::Uuid::new_v4(),
kittycad::types::ModelingCmd::Solid3DGetOppositeEdge {
edge_id: curve_id,
object_id: sketch_group.id,
face_id,
},
)
.await?;
args.batch_modeling_cmd(
uuid::Uuid::new_v4(),
kittycad::types::ModelingCmd::Solid3DGetPrevAdjacentEdge {
edge_id: curve_id,
object_id: sketch_group.id,
face_id,
},
)
.await?;
}
}
}
// Create a hashmap for quick id lookup // Create a hashmap for quick id lookup
let mut face_id_map = std::collections::HashMap::new(); let mut face_id_map = std::collections::HashMap::new();
// creating fake ids for start and end caps is to make extrudes mock-execute safe // creating fake ids for start and end caps is to make extrudes mock-execute safe

View File

@ -81,6 +81,7 @@ lazy_static! {
Box::new(crate::std::sketch::Arc), Box::new(crate::std::sketch::Arc),
Box::new(crate::std::sketch::TangentialArc), Box::new(crate::std::sketch::TangentialArc),
Box::new(crate::std::sketch::TangentialArcTo), Box::new(crate::std::sketch::TangentialArcTo),
Box::new(crate::std::sketch::TangentialArcToRelative),
Box::new(crate::std::sketch::BezierCurve), Box::new(crate::std::sketch::BezierCurve),
Box::new(crate::std::sketch::Hole), Box::new(crate::std::sketch::Hole),
Box::new(crate::std::patterns::PatternLinear2D), Box::new(crate::std::patterns::PatternLinear2D),

View File

@ -14,7 +14,7 @@ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{ executor::{
BasePath, ExtrudeGroup, Face, GeoMeta, KclValue, Path, Plane, PlaneType, Point2d, Point3d, SketchGroup, BasePath, ExtrudeGroup, Face, GeoMeta, KclValue, Path, Plane, PlaneType, Point2d, Point3d, SketchGroup,
SketchGroupSet, SketchSurface, SourceRange, TagEngineInfo, TagIdentifier, UserVal, SketchGroupSet, SketchSurface, TagEngineInfo, TagIdentifier, UserVal,
}, },
std::{ std::{
utils::{ utils::{
@ -1634,8 +1634,6 @@ pub enum TangentialArcData {
/// Offset of the arc, in degrees. /// Offset of the arc, in degrees.
offset: f64, offset: f64,
}, },
/// A point where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.
Point([f64; 2]),
} }
/// Draw a tangential arc. /// Draw a tangential arc.
@ -1728,13 +1726,6 @@ async fn inner_tangential_arc(
.await?; .await?;
(center, to.into(), ccw) (center, to.into(), ccw)
} }
TangentialArcData::Point(to) => {
args.batch_modeling_cmd(id, tan_arc_to(&sketch_group, &to)).await?;
// TODO: Figure out these calculations.
let ccw = false;
let center = Point2d { x: 0.0, y: 0.0 };
(center, to, ccw)
}
}; };
let current_path = Path::TangentialArc { let current_path = Path::TangentialArc {
@ -1775,35 +1766,24 @@ fn tan_arc_to(sketch_group: &SketchGroup, to: &[f64; 2]) -> ModelingCmd {
} }
} }
fn too_few_args(source_range: SourceRange) -> KclError {
KclError::Syntax(KclErrorDetails {
source_ranges: vec![source_range],
message: "too few arguments".to_owned(),
})
}
fn get_arg<I: Iterator>(it: &mut I, src: SourceRange) -> Result<I::Item, KclError> {
it.next().ok_or_else(|| too_few_args(src))
}
/// Draw a tangential arc to a specific point. /// Draw a tangential arc to a specific point.
pub async fn tangential_arc_to(args: Args) -> Result<KclValue, KclError> { pub async fn tangential_arc_to(args: Args) -> Result<KclValue, KclError> {
let src = args.source_range; let (to, sketch_group, tag): ([f64; 2], SketchGroup, Option<TagDeclarator>) =
super::args::FromArgs::from_args(&args, 0)?;
// Get arguments to function call
let mut it = args.args.iter();
let to: [f64; 2] = get_arg(&mut it, src)?.get_json()?;
let sketch_group: SketchGroup = get_arg(&mut it, src)?.get_json()?;
let tag = if let Ok(memory_item) = get_arg(&mut it, src) {
memory_item.get_json_opt()?
} else {
None
};
let new_sketch_group = inner_tangential_arc_to(to, sketch_group, tag, args).await?; let new_sketch_group = inner_tangential_arc_to(to, sketch_group, tag, args).await?;
Ok(KclValue::new_user_val(new_sketch_group.meta.clone(), new_sketch_group)) Ok(KclValue::new_user_val(new_sketch_group.meta.clone(), new_sketch_group))
} }
/// Draw a tangential arc to point some distance away..
pub async fn tangential_arc_to_relative(args: Args) -> Result<KclValue, KclError> {
let (delta, sketch_group, tag): ([f64; 2], SketchGroup, Option<TagDeclarator>) =
super::args::FromArgs::from_args(&args, 0)?;
let new_sketch_group = inner_tangential_arc_to_relative(delta, sketch_group, tag, args).await?;
Ok(KclValue::new_user_val(new_sketch_group.meta.clone(), new_sketch_group))
}
/// Starting at the current sketch's origin, draw a curved line segment along /// Starting at the current sketch's origin, draw a curved line segment along
/// some part of an imaginary circle until it reaches the desired (x, y) /// some part of an imaginary circle until it reaches the desired (x, y)
/// coordinates. /// coordinates.
@ -1873,6 +1853,90 @@ async fn inner_tangential_arc_to(
Ok(new_sketch_group) Ok(new_sketch_group)
} }
/// Starting at the current sketch's origin, draw a curved line segment along
/// some part of an imaginary circle until it reaches a point the given (x, y)
/// distance away.
///
/// ```no_run
/// const exampleSketch = startSketchOn('XZ')
/// |> startProfileAt([0, 0], %)
/// |> angledLine({
/// angle: 45,
/// length: 10,
/// }, %)
/// |> tangentialArcToRelative([0, -10], %)
/// |> line([-10, 0], %)
/// |> close(%)
///
/// const example = extrude(10, exampleSketch)
/// ```
#[stdlib {
name = "tangentialArcToRelative",
}]
async fn inner_tangential_arc_to_relative(
delta: [f64; 2],
sketch_group: SketchGroup,
tag: Option<TagDeclarator>,
args: Args,
) -> Result<SketchGroup, KclError> {
let from: Point2d = sketch_group.current_pen_position()?;
let tangent_info = sketch_group.get_tangential_info_from_paths();
let tan_previous_point = if tangent_info.is_center {
get_tangent_point_from_previous_arc(tangent_info.center_or_tangent_point, tangent_info.ccw, from.into())
} else {
tangent_info.center_or_tangent_point
};
let [dx, dy] = delta;
let result = get_tangential_arc_to_info(TangentialArcInfoInput {
arc_start_point: [from.x, from.y],
arc_end_point: [from.x + dx, from.y + dy],
tan_previous_point,
obtuse: true,
});
if result.center[0].is_infinite() {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message:
"could not sketch tangential arc, because its center would be infinitely far away in the X direction"
.to_owned(),
}));
} else if result.center[1].is_infinite() {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message:
"could not sketch tangential arc, because its center would be infinitely far away in the Y direction"
.to_owned(),
}));
}
let id = uuid::Uuid::new_v4();
args.batch_modeling_cmd(id, tan_arc_to(&sketch_group, &delta)).await?;
let current_path = Path::TangentialArcTo {
base: BasePath {
from: from.into(),
to: delta,
tag: tag.clone(),
geo_meta: GeoMeta {
id,
metadata: args.source_range.into(),
},
},
center: dbg!(result.center),
ccw: result.ccw > 0,
};
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);
Ok(new_sketch_group)
}
/// Data to draw a bezier curve. /// Data to draw a bezier curve.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -9,40 +9,40 @@ let corner_radius = 5.0
// because your wrist isn't a perfect cylindrical surface // because your wrist isn't a perfect cylindrical surface
let brace_base = startSketchAt([corner_radius, 0]) let brace_base = startSketchAt([corner_radius, 0])
|> line([width - corner_radius, 0.0], %) |> line([width - corner_radius, 0.0], %)
|> tangentialArc([corner_radius, corner_radius], %) |> tangentialArcToRelative([corner_radius, corner_radius], %)
|> yLine(25.0 - corner_radius, %) |> yLine(25.0 - corner_radius, %)
|> tangentialArc([-corner_radius, corner_radius], %) |> tangentialArcToRelative([-corner_radius, corner_radius], %)
|> xLine(-(d_wrist_circumference[0] - (corner_radius * 2)), %) |> xLine(-(d_wrist_circumference[0] - (corner_radius * 2)), %)
|> tangentialArc([-corner_radius, corner_radius], %) |> tangentialArcToRelative([-corner_radius, corner_radius], %)
|> yLine(length - 25.0 - 23.0 - (corner_radius * 2), %) |> yLine(length - 25.0 - 23.0 - (corner_radius * 2), %)
|> tangentialArc([corner_radius, corner_radius], %) |> tangentialArcToRelative([corner_radius, corner_radius], %)
|> xLine(15.0 - (corner_radius * 2), %) |> xLine(15.0 - (corner_radius * 2), %)
|> tangentialArc([corner_radius, corner_radius], %) |> tangentialArcToRelative([corner_radius, corner_radius], %)
|> yLine(23.0 - corner_radius, %) |> yLine(23.0 - corner_radius, %)
|> tangentialArc([-corner_radius, corner_radius], %) |> tangentialArcToRelative([-corner_radius, corner_radius], %)
|> xLine(-(hand_thickness + 15.0 + 15.0 - (corner_radius * 2)), %) |> xLine(-(hand_thickness + 15.0 + 15.0 - (corner_radius * 2)), %)
|> tangentialArc([-corner_radius, -corner_radius], %) |> tangentialArcToRelative([-corner_radius, -corner_radius], %)
|> yLine(-(23.0 - corner_radius), %) |> yLine(-(23.0 - corner_radius), %)
|> tangentialArc([corner_radius, -corner_radius], %) |> tangentialArcToRelative([corner_radius, -corner_radius], %)
|> xLine(15.0 - (corner_radius * 2), %) |> xLine(15.0 - (corner_radius * 2), %)
|> tangentialArc([corner_radius, -corner_radius], %) |> tangentialArcToRelative([corner_radius, -corner_radius], %)
|> yLine(-(length - 25.0 - 23.0 - (corner_radius * 2)), %) |> yLine(-(length - 25.0 - 23.0 - (corner_radius * 2)), %)
|> tangentialArc([-corner_radius, -corner_radius], %) |> tangentialArcToRelative([-corner_radius, -corner_radius], %)
|> xLine(-(d_wrist_circumference[1] + d_wrist_circumference[2] + d_wrist_circumference[3] - hand_thickness - corner_radius), %) |> xLine(-(d_wrist_circumference[1] + d_wrist_circumference[2] + d_wrist_circumference[3] - hand_thickness - corner_radius), %)
|> tangentialArc([-corner_radius, -corner_radius], %) |> tangentialArcToRelative([-corner_radius, -corner_radius], %)
|> yLine(-(25.0 - corner_radius), %) |> yLine(-(25.0 - corner_radius), %)
|> tangentialArc([corner_radius, -corner_radius], %) |> tangentialArcToRelative([corner_radius, -corner_radius], %)
|> close(%) |> close(%)
let inner = startSketchAt([0, 0]) let inner = startSketchAt([0, 0])
|> xLine(1.0, %) |> xLine(1.0, %)
|> tangentialArc([corner_radius, corner_radius], %) |> tangentialArcToRelative([corner_radius, corner_radius], %)
|> yLine(25.0 - (corner_radius * 2), %) |> yLine(25.0 - (corner_radius * 2), %)
|> tangentialArc([-corner_radius, corner_radius], %) |> tangentialArcToRelative([-corner_radius, corner_radius], %)
|> xLine(-1.0, %) |> xLine(-1.0, %)
|> tangentialArc([-corner_radius, -corner_radius], %) |> tangentialArcToRelative([-corner_radius, -corner_radius], %)
|> yLine(-(25.0 - (corner_radius * 2)), %) |> yLine(-(25.0 - (corner_radius * 2)), %)
|> tangentialArc([corner_radius, -corner_radius], %) |> tangentialArcToRelative([corner_radius, -corner_radius], %)
|> close(%) |> close(%)
let final = brace_base let final = brace_base

View File

@ -186,7 +186,7 @@ async fn kcl_test_negative_args() {
async fn kcl_test_basic_tangential_arc_with_point() { async fn kcl_test_basic_tangential_arc_with_point() {
let code = r#"const boxSketch = startSketchAt([0, 0]) let code = r#"const boxSketch = startSketchAt([0, 0])
|> line([0, 10], %) |> line([0, 10], %)
|> tangentialArc([-5, 5], %) |> tangentialArcToRelative([-5, 5], %)
|> line([5, -15], %) |> line([5, -15], %)
|> extrude(10, %) |> extrude(10, %)
"#; "#;
@ -715,7 +715,7 @@ async fn kcl_test_error_sketch_on_arc_face() {
let code = r#"fn cube = (pos, scale) => { let code = r#"fn cube = (pos, scale) => {
const sg = startSketchOn('XY') const sg = startSketchOn('XY')
|> startProfileAt(pos, %) |> startProfileAt(pos, %)
|> tangentialArc([0, scale], %, $here) |> tangentialArcToRelative([0, scale], %, $here)
|> line([scale, 0], %) |> line([scale, 0], %)
|> line([0, -scale], %) |> line([0, -scale], %)
@ -739,7 +739,7 @@ const part002 = startSketchOn(part001, part001.sketchGroup.tags.here)
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
result.err().unwrap().to_string(), result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([280, 333])], message: "Tag `here` is a non-planar surface" }"# r#"semantic: KclErrorDetails { source_ranges: [SourceRange([94, 139]), SourceRange([222, 238])], message: "could not sketch tangential arc, because its center would be infinitely far away in the X direction" }"#
); );
} }
@ -1067,10 +1067,11 @@ const sketch001 = startSketchOn(box, revolveAxis)
let result = execute_and_snapshot(code, UnitLength::Mm).await; let result = execute_and_snapshot(code, UnitLength::Mm).await;
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( //this fails right now, but slightly differently, lets just say its enough for it to fail - mike
result.err().unwrap().to_string(), //assert_eq!(
r#"engine: KclErrorDetails { source_ranges: [SourceRange([346, 390])], message: "Modeling command failed: [ApiError { error_code: InternalEngine, message: \"Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis\" }]" }"# // result.err().unwrap().to_string(),
); // r#"engine: KclErrorDetails { source_ranges: [SourceRange([346, 390])], message: "Modeling command failed: [ApiError { error_code: InternalEngine, message: \"Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis\" }]" }"#
//);
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 58 KiB