widgets, file size and elapsed time

This commit is contained in:
ochafik 2023-03-25 20:41:16 +00:00
commit f996bbc1fb
11 changed files with 118 additions and 57 deletions

View file

@ -15,6 +15,8 @@
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"debug": "^4.3.4", "debug": "^4.3.4",
"monaco-editor": "^0.36.1", "monaco-editor": "^0.36.1",
"primeicons": "^6.0.1",
"primereact": "^9.2.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-stl-viewer": "^2.2.5", "react-stl-viewer": "^2.2.5",

View file

@ -14,6 +14,12 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.3/gh-fork-ribbon.min.css" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-fork-ribbon-css/0.2.3/gh-fork-ribbon.min.css" />
<link rel="stylesheet" href="https://unpkg.com/primeicons/primeicons.css" />
<link rel="stylesheet" href="https://unpkg.com/primereact/resources/themes/lara-light-indigo/theme.css" />
<link rel="stylesheet" href="https://unpkg.com/primereact/resources/primereact.min.css" />
<link rel="stylesheet" href="https://unpkg.com/primeflex@3.2.1/primeflex.min.css" />
<meta <meta
name="description" name="description"
content="OpenSCAD Playground: edit .scad models and render them to STL from the browser (WebAssembly build OpenSCAD, with Monaco editor and STL Viewer)" content="OpenSCAD Playground: edit .scad models and render them to STL from the browser (WebAssembly build OpenSCAD, with Monaco editor and STL Viewer)"

View file

@ -1,4 +1,4 @@
.App { /* .App {
text-align: center; text-align: center;
} }
@ -42,4 +42,4 @@
overflow-y: scroll; overflow-y: scroll;
height: calc(min(200px, 30vh)); height: calc(min(200px, 30vh));
position: relative; position: relative;
} } */

View file

@ -1,13 +1,19 @@
// Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING. // Portions of this file are Copyright 2021 Google LLC, and licensed under GPL2+. See COPYING.
import React, { useContext, useEffect, useRef, useState } from 'react'; import React, { forwardRef, useContext, useEffect, useRef, useState } from 'react';
import {ModelContext, State} from './app-state' import {ModelContext, State} from './app-state'
import Editor, { loader, Monaco } from '@monaco-editor/react'; import Editor, { loader, Monaco } from '@monaco-editor/react';
import './App.css'; // import './App.css';
import openscadEditorOptions from './language/openscad-editor-options'; import openscadEditorOptions from './language/openscad-editor-options';
import { Model } from './model'; import { Model } from './model';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import {StlViewer} from "react-stl-viewer"; import {StlViewer} from "react-stl-viewer";
import { Button } from 'primereact/button';
import { ProgressBar } from 'primereact/progressbar';
// import "primereact/resources/themes/lara-light-indigo/theme.css";
// import "primereact/resources/primereact.min.css";
// import "primeicons/primeicons.css";
let monacoInstance: Monaco let monacoInstance: Monaco
loader.init().then(mi => monacoInstance = mi); loader.init().then(mi => monacoInstance = mi);
@ -31,13 +37,13 @@ function EditorPanel() {
monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
monaco.KeyCode.F6, monaco.KeyCode.F6,
], ],
run: () => model.render({isPreview: false}) run: () => model.render({isPreview: false, now: true})
}); });
editor.addAction({ editor.addAction({
id: "openscad-preview", id: "openscad-preview",
label: "Preview OpenSCAD", label: "Preview OpenSCAD",
keybindings: [monaco.KeyCode.F5], keybindings: [monaco.KeyCode.F5],
run: () => model.render({isPreview: false}) run: () => model.render({isPreview: true, now: true})
}); });
setEditor(editor) setEditor(editor)
} }
@ -79,7 +85,7 @@ export function App({initialState}: {initialState: State}) {
const model = new Model(state, setState); const model = new Model(state, setState);
useEffect(() => model.init()); useEffect(() => model.init());
const stlModelRef = useRef(null); // const stlModelRef = forwardRef();
return ( return (
<ModelContext.Provider value={model}> <ModelContext.Provider value={model}>
@ -88,41 +94,50 @@ export function App({initialState}: {initialState: State}) {
<div style={{width: "50vw"}}> <div style={{width: "50vw"}}>
<EditorPanel/> <EditorPanel/>
</div> </div>
{state.output?.stlFileURL && <div style={{display: 'flex', flexDirection: 'column', flex: 1, position: 'relative'}}>
<StlViewer {(state.rendering || state.previewing) &&
style={{ <ProgressBar mode="indeterminate" style={{ height: '6px' }}></ProgressBar>}
flex: 1 {state.output?.stlFileURL &&
}} <StlViewer
ref={stlModelRef} style={{
showAxes={true} flex: 1
orbitControls }}
shadows // ref={stlModelRef}
modelProps={{ showAxes={true}
color: '#f9d72c', orbitControls
shadows
modelProps={{
color: '#f9d72c',
}}
url={state.output?.stlFileURL ?? ''}
/>
}
}}
url={state.output?.stlFileURL ?? ''}
/>
}
</div> </div>
<div style={{display: 'flex', flexDirection: 'row'}}> </div>
<button onClick={() => model.render({isPreview: false})}>Render</button> <div style={{display: 'flex', flexDirection: 'row', gap: '10px'}}>
{model.state.previewing && 'previewing... '} <Button onClick={() => model.render({isPreview: false, now: true})}>Render</Button>
{model.state.rendering && 'rendering... '} {state.output && state.output.formattedElapsedMillis}
{model.state.checkingSyntax && 'checking syntax... '} &nbsp;
{state.previewing && 'previewing... '}
{state.rendering && 'rendering... '}
{state.checkingSyntax && 'checking syntax... '}
{state.output && (
<a href={state.output.stlFileURL}
target="_blank"
download={state.output.isPreview ? "preview.stl" : "render.stl"}
title="STL Download">
{state.output.isPreview ? "preview.stl" : "render.stl"} ({state.output.formattedStlFileSize})
</a>
)}
<span style={{flex: 1}}></span> <span style={{flex: 1}}></span>
<a target="_blank" className="text-fragment" href="http://openscad.org/documentation.html">Docs</a> | <a target="_blank" className="text-fragment" href="http://openscad.org/documentation.html">Docs</a> |
<a target="_blank" className="text-fragment" href="http://openscad.org/cheatsheet/">Cheatsheet</a> | <a target="_blank" className="text-fragment" href="http://openscad.org/cheatsheet/">Cheatsheet</a> |
<a target="_blank" href="https://github.com/openscad/openscad-playground/blob/rewrite1/LICENSE.md">LICENSES</a> <a target="_blank" href="https://github.com/openscad/openscad-playground/blob/rewrite1/LICENSE.md">LICENSES</a>
{state.output?.stlFileURL && (
<a href={state.output?.stlFileURL}
title="STL Download">
Download Model
</a>
)}
</div> </div>
</div> </div>
</ModelContext.Provider> </ModelContext.Provider>

View file

@ -36,7 +36,7 @@ export const checkSyntax =
}); });
var renderDelay = 1000; var renderDelay = 1000;
export type RenderOutput = {stlFile: File, logText: string, markers: monaco.editor.IMarkerData[], elapsedMillis?: number} export type RenderOutput = {stlFile: File, logText: string, markers: monaco.editor.IMarkerData[], elapsedMillis: number}
export type RenderArgs = { export type RenderArgs = {
source: string, source: string,
@ -45,23 +45,26 @@ export type RenderArgs = {
isPreview: boolean isPreview: boolean
} }
export const render = export const render =
turnIntoDelayableExecution(renderDelay, (args: RenderArgs) => { turnIntoDelayableExecution(renderDelay, (params: RenderArgs) => {
const inputFile = 'input.scad';
const args = [
inputFile,
"-o", "out.stl",
"--export-format=binstl",
...(params.features ?? []).map(f => `--enable=${f}`),
...(params.extraArgs ?? [])
]
const prefixLines: string[] = []; const prefixLines: string[] = [];
if (args.isPreview) prefixLines.push('$preview=false;'); if (params.isPreview) {
const source = args.isPreview ? [...prefixLines, args.source].join('\n') : args.source prefixLines.push('$preview=true;');
const inputFile = 'input.scad'; }
const source = [...prefixLines, params.source].join('\n');
const job = spawnOpenSCAD({ const job = spawnOpenSCAD({
// wasmMemory, // wasmMemory,
inputs: [['input.scad', source]], inputs: [['input.scad', source]],
args: [ args,
inputFile,
"-o", "out.stl",
"--export-format=binstl",
...(args.features ?? []).map(f => `--enable=${f}`),
...(args.extraArgs ?? [])
],
outputPaths: ['out.stl'] outputPaths: ['out.stl']
}); });

View file

@ -22,6 +22,9 @@ export interface State {
isPreview: boolean, isPreview: boolean,
stlFile: File, stlFile: File,
stlFileURL: string, stlFileURL: string,
elapsedMillis: number,
formattedElapsedMillis: string,
formattedStlFileSize: string,
}, },
}; };

View file

@ -13,5 +13,5 @@ translate([10, 10, 10])
// //
// include <BOSL2/std.scad> // include <BOSL2/std.scad>
// translate([-100, 0, 0]) // translate([-100, 0, 0])
spheroid(d=100, style="icosa", $fn=20); // spheroid(d=100, style="icosa", $fn=20);
` `

View file

@ -1,8 +1,9 @@
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: (--font-family);
/* font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif; sans-serif; */
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }

View file

@ -42,7 +42,7 @@ export class Model {
private processSource() { private processSource() {
this.checkSyntax(); this.checkSyntax();
this.render({isPreview: true}); this.render({isPreview: true, now: false});
} }
checkSyntax() { checkSyntax() {
this.mutate(s => s.checkingSyntax = true); this.mutate(s => s.checkingSyntax = true);
@ -52,7 +52,7 @@ export class Model {
})}); })});
} }
render({isPreview}: {isPreview: boolean}) { render({isPreview, now}: {isPreview: boolean, now: boolean}) {
const setRendering = (s: State, value: boolean) => { const setRendering = (s: State, value: boolean) => {
if (isPreview) { if (isPreview) {
s.previewing = value; s.previewing = value;
@ -64,9 +64,10 @@ export class Model {
const source = this.state.params.source; const source = this.state.params.source;
const features = this.state.params.features; const features = this.state.params.features;
const renderArgs = {source, features, extraArgs: ['-D$preview=true'], isPreview}; const renderArgs = {source, features, extraArgs: [], isPreview};
// console.log('renderArgs', renderArgs);
render(renderArgs)({now: !isPreview, callback: output => { render(renderArgs)({now, callback: output => {
this.mutate(s => { this.mutate(s => {
s.lastCheckerRun = { s.lastCheckerRun = {
logText: output.logText, logText: output.logText,
@ -75,10 +76,14 @@ export class Model {
if (s.output?.stlFileURL) { if (s.output?.stlFileURL) {
URL.revokeObjectURL(s.output.stlFileURL); URL.revokeObjectURL(s.output.stlFileURL);
} }
s.output = { s.output = {
isPreview: isPreview, isPreview: isPreview,
stlFile: output.stlFile, stlFile: output.stlFile,
stlFileURL: URL.createObjectURL(output.stlFile), stlFileURL: URL.createObjectURL(output.stlFile),
elapsedMillis: output.elapsedMillis,
formattedElapsedMillis: formatMillis(output.elapsedMillis),
formattedStlFileSize: formatBytes(output.stlFile.size),
}; };
setRendering(s, false); setRendering(s, false);
}); });
@ -86,3 +91,22 @@ export class Model {
} }
} }
function formatBytes(n: number) {
if (n < 1024) {
return `${Math.floor(n)} bytes`;
}
n /= 1024;
if (n < 1024) {
return `${Math.floor(n * 10) / 10} kB`;
}
n /= 1024;
return `${Math.floor(n * 10) / 10} MB`;
}
function formatMillis(n: number) {
if (n < 1000)
return `${Math.floor(n)} millis`;
return `${Math.floor(n / 100) / 10} sec`;
}

View file

@ -27,7 +27,7 @@ export type OpenSCADInvocationResults = {
error?: string, error?: string,
outputs?: [string, string][], outputs?: [string, string][],
mergedOutputs: MergedOutputs, mergedOutputs: MergedOutputs,
elapsedMillis?: number, elapsedMillis: number,
} }
export function spawnOpenSCAD({inputs, args, outputPaths}: OpenSCADInvocation): AbortablePromise<OpenSCADInvocationResults> { export function spawnOpenSCAD({inputs, args, outputPaths}: OpenSCADInvocation): AbortablePromise<OpenSCADInvocationResults> {

View file

@ -15,9 +15,16 @@ module.exports = {
{ {
test: /\.css$/i, test: /\.css$/i,
use: [ use: [
{loader: 'css-loader', options:{url: false}} {
loader: 'css-loader',
options:{url: false},
}
] ]
} },
{
test: /\.(png|gif|woff|woff2|eot|ttf|svg)$/,
loader: "url-loader?limit=100000"
},
], ],
}, },
resolve: { resolve: {