Compare commits
1 Commits
wait-for-r
...
jtran/anim
Author | SHA1 | Date | |
---|---|---|---|
bfefa0f51a |
@ -15,14 +15,14 @@ import "car-tire.kcl" as carTire
|
||||
import * from "parameters.kcl"
|
||||
|
||||
// Place the car rotor
|
||||
carRotor
|
||||
rotor = carRotor
|
||||
|> translate(x = 0, y = 0.5, z = 0)
|
||||
|
||||
// Place the car wheel
|
||||
carWheel
|
||||
|
||||
// Place the lug nuts
|
||||
lugNut
|
||||
lgnut = lugNut
|
||||
|> patternCircular3d(
|
||||
arcDegrees = 360,
|
||||
axis = [0, 1, 0],
|
||||
@ -32,8 +32,19 @@ lugNut
|
||||
)
|
||||
|
||||
// Place the brake caliper
|
||||
brakeCaliper
|
||||
cal = brakeCaliper
|
||||
|> translate(x = 0, y = 0.5, z = 0)
|
||||
|
||||
// Place the car tire
|
||||
carTire
|
||||
|
||||
|
||||
fn animate(step: number(_)) {
|
||||
angle = 0.6deg
|
||||
rotate(rotor, pitch = angle)
|
||||
rotate(lgnut, pitch = angle)
|
||||
rotate(cal, pitch = angle)
|
||||
rotate(carWheel, pitch = angle)
|
||||
rotate(carTire, pitch = angle)
|
||||
return 0
|
||||
}
|
||||
|
@ -369,7 +369,7 @@ profile007 = startProfile(
|
||||
|> line(%, endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close(%)
|
||||
profile008 = circle(sketch005, center = [0, 0], diameter = nubDiameter)
|
||||
subtract2d(profile007, tool = profile008)
|
||||
hourHand = subtract2d(profile007, tool = profile008)
|
||||
|> extrude(%, length = 5)
|
||||
|> appearance(%, color = "#404040")
|
||||
|
||||
@ -413,7 +413,7 @@ profile009 = startProfile(
|
||||
|> line(%, endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close(%)
|
||||
profile010 = circle(sketch006, center = [0, 0], diameter = 30)
|
||||
subtract2d(profile009, tool = profile010)
|
||||
minuteHand = subtract2d(profile009, tool = profile010)
|
||||
|> extrude(%, length = 5)
|
||||
|> appearance(%, color = "#404040")
|
||||
|
||||
@ -439,3 +439,8 @@ profile004 = startProfile(sketch003, at = [-slotWidth / 2, 200])
|
||||
|> extrude(%, length = -20)
|
||||
|
||||
// todo: create cavity for the screw to slide into (need csg update)
|
||||
|
||||
fn animate(step: number(_)) {
|
||||
rotate(hourHand, yaw = -0.1deg)
|
||||
return rotate(minuteHand, yaw = -0.6deg)
|
||||
}
|
||||
|
@ -805,6 +805,43 @@ impl ExecutorContext {
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
pub async fn run_additional(&self, program: crate::Program) -> Result<ExecOutcome, KclErrorWithOutputs> {
|
||||
assert!(!self.is_mock());
|
||||
|
||||
let (program, exec_state, result) = match cache::read_old_ast().await {
|
||||
Some(cached_state) => {
|
||||
let mut exec_state = cached_state.reconstitute_exec_state();
|
||||
exec_state.mut_stack().restore_env(cached_state.main.result_env);
|
||||
|
||||
let result = self.run_concurrent(&program, &mut exec_state, None, true).await;
|
||||
|
||||
(program, exec_state, result)
|
||||
}
|
||||
None => {
|
||||
let mut exec_state = ExecState::new(self);
|
||||
|
||||
let result = self.run_concurrent(&program, &mut exec_state, None, false).await;
|
||||
|
||||
(program, exec_state, result)
|
||||
}
|
||||
};
|
||||
|
||||
// Throw the error.
|
||||
let result = result?;
|
||||
|
||||
// Save this as the last successful execution to the cache.
|
||||
cache::write_old_ast(GlobalState::new(
|
||||
exec_state.clone(),
|
||||
self.settings.clone(),
|
||||
program.ast,
|
||||
result.0,
|
||||
))
|
||||
.await;
|
||||
|
||||
let outcome = exec_state.into_exec_outcome(result.0, self).await;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
/// Perform the execution of a program.
|
||||
///
|
||||
/// To access non-fatal errors and warnings, extract them from the `ExecState`.
|
||||
|
@ -111,6 +111,48 @@ impl Context {
|
||||
ctx.run_with_caching(program).await
|
||||
}
|
||||
|
||||
/// Execute an additional program using the cache.
|
||||
#[wasm_bindgen(js_name = executeAdditional)]
|
||||
pub async fn execute_additional(
|
||||
&self,
|
||||
program_ast_json: &str,
|
||||
path: Option<String>,
|
||||
settings: &str,
|
||||
) -> Result<JsValue, JsValue> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
self.execute_additional_typed(program_ast_json, path, settings)
|
||||
.await
|
||||
.and_then(|outcome| {
|
||||
JsValue::from_serde(&outcome).map_err(|e| {
|
||||
// The serde-wasm-bindgen does not work here because of weird HashMap issues.
|
||||
// DO NOT USE serde_wasm_bindgen::to_value it will break the frontend.
|
||||
KclErrorWithOutputs::no_outputs(KclError::internal(format!(
|
||||
"Could not serialize successful KCL result. {TRUE_BUG} Details: {e}"
|
||||
)))
|
||||
})
|
||||
})
|
||||
.map_err(|e: KclErrorWithOutputs| JsValue::from_serde(&e).unwrap())
|
||||
}
|
||||
|
||||
async fn execute_additional_typed(
|
||||
&self,
|
||||
program_ast_json: &str,
|
||||
path: Option<String>,
|
||||
settings: &str,
|
||||
) -> Result<ExecOutcome, KclErrorWithOutputs> {
|
||||
let program: Program = serde_json::from_str(program_ast_json).map_err(|e| {
|
||||
let err = KclError::internal(format!("Could not deserialize KCL AST. {TRUE_BUG} Details: {e}"));
|
||||
KclErrorWithOutputs::no_outputs(err)
|
||||
})?;
|
||||
let ctx = self.create_executor_ctx(settings, path, false).map_err(|e| {
|
||||
KclErrorWithOutputs::no_outputs(KclError::internal(format!(
|
||||
"Could not create KCL executor context. {TRUE_BUG} Details: {e}"
|
||||
)))
|
||||
})?;
|
||||
ctx.run_additional(program).await
|
||||
}
|
||||
|
||||
/// Reset the scene and bust the cache.
|
||||
/// ONLY use this if you absolutely need to reset the scene and bust the cache.
|
||||
#[wasm_bindgen(js_name = bustCacheAndResetScene)]
|
||||
|
@ -7,6 +7,8 @@ import type { CustomIconName } from '@src/components/CustomIcon'
|
||||
import Tooltip from '@src/components/Tooltip'
|
||||
|
||||
import styles from './ModelingPane.module.css'
|
||||
import { reportRejection } from '@src/lib/trap'
|
||||
import { kclManager } from '@src/lib/singletons'
|
||||
|
||||
export interface ModelingPaneProps {
|
||||
id: string
|
||||
@ -19,6 +21,28 @@ export interface ModelingPaneProps {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const ANIMATE_INTERVAL = 16 // milliseconds
|
||||
let animateTimeout: NodeJS.Timeout | null = null
|
||||
|
||||
function doAnimate() {
|
||||
;(async () => {
|
||||
await kclManager.executeAnimate()
|
||||
if (animateTimeout !== null) {
|
||||
animateTimeout = setTimeout(doAnimate, ANIMATE_INTERVAL)
|
||||
}
|
||||
})().catch(reportRejection)
|
||||
}
|
||||
|
||||
function onPlay() {
|
||||
console.log('Play button clicked')
|
||||
if (animateTimeout) {
|
||||
clearTimeout(animateTimeout)
|
||||
animateTimeout = null
|
||||
} else {
|
||||
animateTimeout = setTimeout(doAnimate, ANIMATE_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
export const ModelingPaneHeader = ({
|
||||
id,
|
||||
icon,
|
||||
@ -40,6 +64,20 @@ export const ModelingPaneHeader = ({
|
||||
)}
|
||||
<span data-testid={id + '-header'}>{title}</span>
|
||||
</div>
|
||||
{id === 'code' && (
|
||||
<ActionButton
|
||||
Element="button"
|
||||
iconStart={{
|
||||
icon: 'play',
|
||||
iconClassName: '!text-current',
|
||||
bgClassName: 'bg-transparent dark:bg-transparent',
|
||||
}}
|
||||
className="!p-0 !bg-transparent hover:text-primary border-transparent dark:!border-transparent hover:!border-primary dark:hover:!border-chalkboard-70 !outline-none"
|
||||
onClick={() => onPlay()}
|
||||
>
|
||||
<Tooltip position="bottom-right">Play</Tooltip>
|
||||
</ActionButton>
|
||||
)}
|
||||
{Menu instanceof Function ? <Menu /> : Menu}
|
||||
<ActionButton
|
||||
Element="button"
|
||||
|
@ -17,7 +17,12 @@ import {
|
||||
compilationErrorsToDiagnostics,
|
||||
kclErrorsToDiagnostics,
|
||||
} from '@src/lang/errors'
|
||||
import { executeAst, executeAstMock, lintAst } from '@src/lang/langHelpers'
|
||||
import {
|
||||
executeAdditional,
|
||||
executeAst,
|
||||
executeAstMock,
|
||||
lintAst,
|
||||
} from '@src/lang/langHelpers'
|
||||
import { getNodeFromPath, getSettingsAnnotation } from '@src/lang/queryAst'
|
||||
import { CommandLogType } from '@src/lang/std/commandLog'
|
||||
import type { EngineCommandManager } from '@src/lang/std/engineConnection'
|
||||
@ -106,6 +111,7 @@ export class KclManager extends EventTarget {
|
||||
preComments: [],
|
||||
commentStart: 0,
|
||||
}
|
||||
private _animateState = { step: 0 }
|
||||
private _execState: ExecState = emptyExecState()
|
||||
private _variables: VariableMap = {}
|
||||
lastSuccessfulVariables: VariableMap = {}
|
||||
@ -450,6 +456,7 @@ export class KclManager extends EventTarget {
|
||||
const ast = args.ast || this.ast
|
||||
markOnce('code/startExecuteAst')
|
||||
|
||||
this._animateState.step = 0
|
||||
const currentExecutionId = args.executionId || Date.now()
|
||||
this._cancelTokens.set(currentExecutionId, false)
|
||||
|
||||
@ -541,6 +548,39 @@ export class KclManager extends EventTarget {
|
||||
markOnce('code/endExecuteAst')
|
||||
}
|
||||
|
||||
async executeAnimate(): Promise<void> {
|
||||
if (this.isExecuting) {
|
||||
return
|
||||
}
|
||||
|
||||
const code = `animate(step = ${this._animateState.step})`
|
||||
const result = parse(code)
|
||||
if (err(result)) {
|
||||
console.error(result)
|
||||
return
|
||||
}
|
||||
const program = result.program
|
||||
if (!program) {
|
||||
console.error('No program returned from parse')
|
||||
return
|
||||
}
|
||||
|
||||
const { errors } = await executeAdditional({
|
||||
ast: program,
|
||||
path: this.singletons.codeManager.currentFilePath || undefined,
|
||||
rustContext: this.singletons.rustContext,
|
||||
})
|
||||
if (errors.length > 0) {
|
||||
console.error('Errors executing animate:', errors)
|
||||
return
|
||||
}
|
||||
|
||||
this._animateState.step += 1
|
||||
if (this._animateState.step === Number.MAX_SAFE_INTEGER) {
|
||||
this._animateState.step = 0
|
||||
}
|
||||
}
|
||||
|
||||
// DO NOT CALL THIS from codemirror ever.
|
||||
async executeAstMock(ast: Program): Promise<null | Error> {
|
||||
await this.ensureWasmInit()
|
||||
|
@ -84,6 +84,31 @@ export async function executeAst({
|
||||
}
|
||||
}
|
||||
|
||||
export async function executeAdditional({
|
||||
ast,
|
||||
rustContext,
|
||||
path,
|
||||
}: {
|
||||
ast: Node<Program>
|
||||
rustContext: RustContext
|
||||
path?: string
|
||||
}): Promise<ExecutionResult> {
|
||||
try {
|
||||
const settings = await jsAppSettings()
|
||||
const execState = await rustContext.executeAdditional(ast, settings, path)
|
||||
|
||||
await rustContext.waitForAllEngineCommands()
|
||||
return {
|
||||
logs: [],
|
||||
errors: [],
|
||||
execState,
|
||||
isInterrupted: false,
|
||||
}
|
||||
} catch (e: any) {
|
||||
return handleExecuteError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function executeAstMock({
|
||||
ast,
|
||||
rustContext,
|
||||
|
@ -96,6 +96,34 @@ export default class RustContext {
|
||||
}
|
||||
}
|
||||
|
||||
/** Execute an additional program using the cache. */
|
||||
async executeAdditional(
|
||||
node: Node<Program>,
|
||||
settings: DeepPartial<Configuration>,
|
||||
path?: string
|
||||
): Promise<ExecState> {
|
||||
const instance = await this._checkInstance()
|
||||
|
||||
try {
|
||||
const result = await instance.executeAdditional(
|
||||
JSON.stringify(node),
|
||||
path,
|
||||
JSON.stringify(settings)
|
||||
)
|
||||
// Set the default planes, safe to call after execute.
|
||||
const outcome = execStateFromRust(result)
|
||||
|
||||
this._defaultPlanes = outcome.defaultPlanes
|
||||
|
||||
// Return the result.
|
||||
return outcome
|
||||
} catch (e: any) {
|
||||
const err = errFromErrWithOutputs(e)
|
||||
this._defaultPlanes = err.defaultPlanes
|
||||
return Promise.reject(err)
|
||||
}
|
||||
}
|
||||
|
||||
/** Execute a program with in mock mode. */
|
||||
async executeMock(
|
||||
node: Node<Program>,
|
||||
|
Reference in New Issue
Block a user