🌈 Colorful Playground (#37)

* glb (color-assimp2 branch) + model-viewer

* color support + customizable output format

* cleanup

* disable monaco on iOS

* reinstate STL viewer when output is stl

* default to stl

* fix iOS editor

* script to build openscad-wasm from a branch / local repo

* update fork link + build instructions

* Update build-openscad-wasm.sh

* Update initial-state.ts

* update openscad3 refs

* update wasm build script

* Update Dockerfile.wasm

* add jszip dep

* multimaterial: color conversion + 3MF face painting

* ignore ts error about compression apis

* Update tsconfig.json

* multimaterial support w/ extruder colors input component

* fix wasm build script

* symlinks for openscad.{js,wasm} under src

* fix error line shift for bundled files

* remove obsolete build script

* set OPENSCADPATH env var

* render-colors feature renamed

* Customizer + prepare Multi-asset support

* hide customizer button if unselected, hide it if no params,

* Create build-openscad-wasm.sh

* update build script

* complete sound

* fetchSource util

* wider rotation allowance

* use CSG-modules.scad as default

* Update CustomizerPanel.tsx

* Update index.html

* capture save, allow Ctrl+Shift+Save to save project

* customizer: fieldsets are better than accordion

* expand customizer tabs by default

* tooltip for reset buttons

* pretty c++ exceptions

* Delete inline-openscad-worker.ts

* shuffle ux

* use uzip instead of jszip for 3mf materialization

* handle legacy links

* update links to fork

* use render-modifiers feature from color-assimp2 in preview mode

* include NopSCADlib's STL files in archive

* bind global f5 / f6

* update homepage back to openscad2

* styling fixes (editor alpha, customizer margins)

* Add help link for customizer

* fix customizer sliders (also need to show value input)

* fix vector customizer component

* style fallback editor

* shorter logs button

* customizer: rm reset button tooltip

* customizer: allow dnd on viewer below, tweak background

* add lights to modelviewer using an env image (gltf lights hard to plug across assimp for now)

* update customizer styles

* Update .gitignore

* add license for UZIP.js (MIT)

* Update skybox-lights.jpg

* Update actions.ts

* include NopSCADlib's STLs

* Update LICENSE.md

* include YAPP_Box examples

* limit customizer height

* Update skybox-lights.jpg

* Update skybox-lights.jpg

* Update openscad-worker.ts

* build-openscad-wasm.sh fix: docker won't mount files outside home directory

* Use Google CDN instead of unpkg for modelviewer

* tmp updates before merge

* Add ccache support to build-openscad-wasm.sh

* Fix makefile

* Update README.md

* Fix ts imports

* Delete build-openscad-wasm.sh

* Crude off -> glb converter

* Remove assimp related code

* gltf diffs

* Build using head openscad

* readme: Update build prerequisites

* make: attempt to build wasm binary in parallel first

* Remove 3mf extruder color mapping & fix render / export flows (render all 3d to off)

* off2glb: triangulate faces + add default color

* Add simple e2e puppeteer test

* ci: update pinned build to master output artefact

* Update Makefile

* Update test.yml

* Update test.yml

* Update test.yml

* Update test.yml

* ci: use relative node version names

* Update test.yml

* state: Remove renderFormat, introduce is2D

* disable service worker in devel mode

* remove dead code

* build: fix NODE_ENV define

* ci: simplify env vars

* cleanups

* Update README.md
This commit is contained in:
Olivier Chafik 2024-12-22 22:41:35 +00:00 • committed by GitHub
commit f3cb9ae3f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 1266 additions and 540 deletions

View file

@ -1,27 +1,37 @@
// 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';
import { getParentDir } from '../fs/filesystem';
import { spawnOpenSCAD } from "./openscad-runner";
import { ProcessStreams, spawnOpenSCAD } from "./openscad-runner";
import { processMergedOutputs } from "./output-parser";
import { AbortablePromise, turnIntoDelayableExecution } from '../utils';
import { Source } from '../state/app-state';
import { VALID_EXPORT_FORMATS_2D, VALID_EXPORT_FORMATS_3D, VALID_RENDER_FORMATS } from '../state/formats';
import { ParameterSet } from '../state/customizer-types';
const syntaxDelay = 300;
type SyntaxCheckArgs = {
activePath: string,
sources: Source[],
}
type SyntaxCheckOutput = {logText: string, markers: monaco.editor.IMarkerData[], parameterSet?: ParameterSet};
export const checkSyntax =
turnIntoDelayableExecution(syntaxDelay, (source: string, sourcePath: string) => {
// const timestamp = Date.now();
turnIntoDelayableExecution(syntaxDelay, (sargs: SyntaxCheckArgs) => {
const {
activePath,
sources,
} = sargs;
source = '$preview=true;\n' + source;
const content = '$preview=true;\n' + sources[0].content;
const outFile = 'out.json';
const job = spawnOpenSCAD({
inputs: [[sourcePath, source + '\n']],
args: [sourcePath, "-o", outFile, "--export-format=param"],
// workingDir: sourcePath.startsWith('/') ? getParentDir(sourcePath) : '/home'
mountArchives: true,
inputs: sources,
args: [activePath, "-o", outFile, "--export-format=param"],
outputPaths: [outFile],
}, (streams) => {
console.log(JSON.stringify(streams));
});
return AbortablePromise<SyntaxCheckOutput>((res, rej) => {
@ -46,7 +56,7 @@ export const checkSyntax =
res({
...processMergedOutputs(result.mergedOutputs, {shiftSourceLines: {
sourcePath,
sourcePath: sources[0].path,
skipLines: 1,
}}),
parameterSet,
@ -61,15 +71,22 @@ export const checkSyntax =
});
var renderDelay = 1000;
export type RenderOutput = {stlFile: File, logText: string, markers: monaco.editor.IMarkerData[], elapsedMillis: number}
export type RenderOutput = {
outFile: File,
logText: string,
markers: monaco.editor.IMarkerData[],
elapsedMillis: number}
export type RenderArgs = {
source: string,
sourcePath: string,
scadPath: string,
sources: Source[],
vars?: {[name: string]: any},
features?: string[],
extraArgs?: string[],
isPreview: boolean
isPreview: boolean,
mountArchives: boolean,
renderFormat: keyof typeof VALID_EXPORT_FORMATS_2D | keyof typeof VALID_EXPORT_FORMATS_3D | keyof typeof VALID_RENDER_FORMATS,
streamsCallback: (ps: ProcessStreams) => void,
}
function formatValue(any: any): string {
@ -82,40 +99,63 @@ function formatValue(any: any): string {
}
}
export const render =
turnIntoDelayableExecution(renderDelay, ({sourcePath, source, isPreview, vars, features, extraArgs}: RenderArgs) => {
turnIntoDelayableExecution(renderDelay, (renderArgs: RenderArgs) => {
const {
scadPath,
sources,
isPreview,
mountArchives,
vars,
features: additionalFeatures,
extraArgs,
renderFormat,
streamsCallback,
} = renderArgs;
const prefixLines: string[] = [];
let features: string[]
if (isPreview) {
prefixLines.push('$preview=true;');
features = ['render-modifiers', ...(additionalFeatures ?? [])];
} else {
features = (additionalFeatures ?? []).filter(f => f != 'render-modifiers');
}
source = [...prefixLines, source].join('\n');
if (!scadPath.endsWith('.scad')) throw new Error('First source must be a .scad file, got ' + sources[0].path + ' instead');
const source = sources.filter(s => s.path === scadPath)[0];
if (!source) throw new Error('Active path not found in sources!');
if (source.content == null) throw new Error('Source content is null!');
const content = [...prefixLines, source.content].join('\n');
const actualRenderFormat = renderFormat == 'glb' ? 'off' : renderFormat;
const outFile = 'out.' + actualRenderFormat;
const args = [
sourcePath,
"-o", "out.stl",
"--export-format=binstl",
scadPath,
"-o", outFile,
"--backend=manifold",
"--export-format=" + (actualRenderFormat == 'stl' ? 'binstl' : actualRenderFormat),
...(Object.entries(vars ?? {}).flatMap(([k, v]) => [`-D${k}=${formatValue(v)}`])),
...(features ?? []).map(f => `--enable=${f}`),
...(extraArgs ?? [])
]
const job = spawnOpenSCAD({
// wasmMemory,
inputs: [[sourcePath, source]],
mountArchives: mountArchives,
inputs: sources.map(s => s.path === scadPath ? {path: s.path, content} : s),
args,
outputPaths: ['out.stl'],
// workingDir: sourcePath.startsWith('/') ? getParentDir(sourcePath) : '/home'
});
outputPaths: [outFile],
}, streamsCallback);
return AbortablePromise<RenderOutput>((resolve, reject) => {
(async () => {
try {
const result = await job;
console.log(result);
// console.log(result);
const {logText, markers} = processMergedOutputs(result.mergedOutputs, {
shiftSourceLines: {
sourcePath: sourcePath,
sourcePath: source.path,
skipLines: prefixLines.length
}
});
@ -131,13 +171,13 @@ export const render =
}
const [filePath, content] = output;
const filePathFragments = filePath.split('/');
const fileName = filePathFragments[filePathFragments.length - 1];
let 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});
const type = filePath.endsWith('.svg') ? 'image/svg+xml' : 'application/octet-stream';
let blob = new Blob([content]);
let outFile = new File([blob], fileName, {type});
resolve({outFile, logText, markers, elapsedMillis: result.elapsedMillis});
} catch (e) {
console.error(e);
reject(e);
@ -147,3 +187,4 @@ export const render =
return () => job.kill()
});
});