2023-03-25 11:59:50 +00:00
|
|
|
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
|
|
|
|
|
|
|
|
|
|
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
2024-06-29 01:27:12 +01:00
|
|
|
import { getParentDir } from '../fs/filesystem';
|
2023-03-25 11:59:50 +00:00
|
|
|
import { spawnOpenSCAD } from "./openscad-runner";
|
2023-03-25 15:23:54 +00:00
|
|
|
import { processMergedOutputs } from "./output-parser";
|
2023-03-26 15:57:09 +01:00
|
|
|
import { AbortablePromise, turnIntoDelayableExecution } from '../utils';
|
2024-07-08 02:21:33 +01:00
|
|
|
import { ParameterSet } from '../state/customizer-types';
|
2023-03-25 11:59:50 +00:00
|
|
|
|
|
|
|
|
const syntaxDelay = 300;
|
|
|
|
|
|
2024-07-08 02:21:33 +01:00
|
|
|
type SyntaxCheckOutput = {logText: string, markers: monaco.editor.IMarkerData[], parameterSet?: ParameterSet};
|
2023-03-25 15:23:54 +00:00
|
|
|
export const checkSyntax =
|
2023-03-26 07:48:04 +01:00
|
|
|
turnIntoDelayableExecution(syntaxDelay, (source: string, sourcePath: string) => {
|
2023-03-25 15:23:54 +00:00
|
|
|
// const timestamp = Date.now();
|
|
|
|
|
|
|
|
|
|
source = '$preview=true;\n' + source;
|
2023-03-25 11:59:50 +00:00
|
|
|
|
2024-07-08 02:21:33 +01:00
|
|
|
const outFile = 'out.json';
|
2023-03-25 11:59:50 +00:00
|
|
|
const job = spawnOpenSCAD({
|
2023-03-26 07:48:04 +01:00
|
|
|
inputs: [[sourcePath, source + '\n']],
|
2024-07-08 02:21:33 +01:00
|
|
|
args: [sourcePath, "-o", outFile, "--export-format=param"],
|
2023-03-26 23:39:41 +01:00
|
|
|
// workingDir: sourcePath.startsWith('/') ? getParentDir(sourcePath) : '/home'
|
2024-07-08 02:21:33 +01:00
|
|
|
outputPaths: [outFile],
|
2023-03-25 11:59:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return AbortablePromise<SyntaxCheckOutput>((res, rej) => {
|
|
|
|
|
(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await job;
|
|
|
|
|
// console.log(result);
|
2024-07-08 02:21:33 +01:00
|
|
|
|
|
|
|
|
let parameterSet: ParameterSet | undefined = undefined;
|
|
|
|
|
if (result.outputs && result.outputs.length == 1) {
|
|
|
|
|
let [[, content]] = result.outputs;
|
|
|
|
|
content = new TextDecoder().decode(content as any);
|
|
|
|
|
try {
|
|
|
|
|
parameterSet = JSON.parse(content)
|
|
|
|
|
// console.log('PARAMETER SET', JSON.stringify(parameterSet, null, 2))
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(`Error while parsing parameter set: ${e}\n${content}`);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
console.error('No output from runner!');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res({
|
|
|
|
|
...processMergedOutputs(result.mergedOutputs, {shiftSourceLines: {
|
|
|
|
|
sourcePath,
|
|
|
|
|
skipLines: 1,
|
|
|
|
|
}}),
|
|
|
|
|
parameterSet,
|
|
|
|
|
});
|
2023-03-25 11:59:50 +00:00
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
rej(e);
|
|
|
|
|
}
|
|
|
|
|
})()
|
|
|
|
|
return () => job.kill();
|
|
|
|
|
});
|
2023-03-25 15:23:54 +00:00
|
|
|
});
|
2023-03-25 11:59:50 +00:00
|
|
|
|
|
|
|
|
var renderDelay = 1000;
|
2023-03-25 20:41:16 +00:00
|
|
|
export type RenderOutput = {stlFile: File, logText: string, markers: monaco.editor.IMarkerData[], elapsedMillis: number}
|
2023-03-25 11:59:50 +00:00
|
|
|
|
2023-03-25 15:23:54 +00:00
|
|
|
export type RenderArgs = {
|
|
|
|
|
source: string,
|
2023-03-26 07:48:04 +01:00
|
|
|
sourcePath: string,
|
2024-07-08 02:21:33 +01:00
|
|
|
vars?: {[name: string]: any},
|
2023-03-25 15:23:54 +00:00
|
|
|
features?: string[],
|
|
|
|
|
extraArgs?: string[],
|
2023-03-25 15:56:21 +00:00
|
|
|
isPreview: boolean
|
2023-03-25 15:23:54 +00:00
|
|
|
}
|
2024-07-08 02:21:33 +01:00
|
|
|
|
|
|
|
|
function formatValue(any: any): string {
|
|
|
|
|
if (typeof any === 'string') {
|
|
|
|
|
return `"${any}"`;
|
|
|
|
|
} else if (any instanceof Array) {
|
|
|
|
|
return `[${any.map(formatValue).join(', ')}]`;
|
|
|
|
|
} else {
|
|
|
|
|
return `${any}`;
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-25 15:23:54 +00:00
|
|
|
export const render =
|
2024-07-08 02:21:33 +01:00
|
|
|
turnIntoDelayableExecution(renderDelay, ({sourcePath, source, isPreview, vars, features, extraArgs}: RenderArgs) => {
|
2023-03-25 20:41:16 +00:00
|
|
|
|
|
|
|
|
const prefixLines: string[] = [];
|
2023-03-26 23:39:41 +01:00
|
|
|
if (isPreview) {
|
2023-03-25 20:41:16 +00:00
|
|
|
prefixLines.push('$preview=true;');
|
|
|
|
|
}
|
2023-03-26 23:39:41 +01:00
|
|
|
source = [...prefixLines, source].join('\n');
|
|
|
|
|
|
|
|
|
|
const args = [
|
|
|
|
|
sourcePath,
|
|
|
|
|
"-o", "out.stl",
|
|
|
|
|
"--export-format=binstl",
|
2024-07-08 02:21:33 +01:00
|
|
|
...(Object.entries(vars ?? {}).flatMap(([k, v]) => [`-D${k}=${formatValue(v)}`])),
|
2023-03-26 23:39:41 +01:00
|
|
|
...(features ?? []).map(f => `--enable=${f}`),
|
|
|
|
|
...(extraArgs ?? [])
|
|
|
|
|
]
|
2023-03-25 15:23:54 +00:00
|
|
|
|
2023-03-25 11:59:50 +00:00
|
|
|
const job = spawnOpenSCAD({
|
|
|
|
|
// wasmMemory,
|
2023-03-26 23:39:41 +01:00
|
|
|
inputs: [[sourcePath, source]],
|
2023-03-25 20:41:16 +00:00
|
|
|
args,
|
2023-03-26 23:39:41 +01:00
|
|
|
outputPaths: ['out.stl'],
|
|
|
|
|
// workingDir: sourcePath.startsWith('/') ? getParentDir(sourcePath) : '/home'
|
2023-03-25 11:59:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return AbortablePromise<RenderOutput>((resolve, reject) => {
|
|
|
|
|
(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const result = await job;
|
|
|
|
|
console.log(result);
|
|
|
|
|
|
2023-03-25 15:23:54 +00:00
|
|
|
const {logText, markers} = processMergedOutputs(result.mergedOutputs, {
|
2023-03-26 08:34:28 +01:00
|
|
|
shiftSourceLines: {
|
2023-03-26 23:39:41 +01:00
|
|
|
sourcePath: sourcePath,
|
2023-03-26 22:33:14 +01:00
|
|
|
skipLines: prefixLines.length
|
2023-03-26 08:34:28 +01:00
|
|
|
}
|
2023-03-25 15:23:54 +00:00
|
|
|
});
|
2023-03-25 11:59:50 +00:00
|
|
|
|
|
|
|
|
if (result.error) {
|
|
|
|
|
reject(result.error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [output] = result.outputs ?? [];
|
2023-03-26 07:48:04 +01:00
|
|
|
if (!output) {
|
|
|
|
|
reject(new Error('No output from runner!'));
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-03-25 11:59:50 +00:00
|
|
|
const [filePath, content] = output;
|
|
|
|
|
const filePathFragments = filePath.split('/');
|
|
|
|
|
const fileName = filePathFragments[filePathFragments.length - 1];
|
|
|
|
|
|
|
|
|
|
// TODO: have the runner accept and return files.
|
|
|
|
|
const blob = new Blob([content], { type: "application/octet-stream" });
|
|
|
|
|
// console.log(new TextDecoder().decode(content));
|
|
|
|
|
const stlFile = new File([blob], fileName);
|
|
|
|
|
resolve({stlFile, logText, markers, elapsedMillis: result.elapsedMillis});
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
reject(e);
|
|
|
|
|
}
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
return () => job.kill()
|
|
|
|
|
});
|
2023-03-25 15:23:54 +00:00
|
|
|
});
|