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",
"debug": "^4.3.4",
"monaco-editor": "^0.36.1",
"primeicons": "^6.0.1",
"primereact": "^9.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"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://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
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)"

View file

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

View file

@ -1,13 +1,19 @@
// 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 Editor, { loader, Monaco } from '@monaco-editor/react';
import './App.css';
// import './App.css';
import openscadEditorOptions from './language/openscad-editor-options';
import { Model } from './model';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
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
loader.init().then(mi => monacoInstance = mi);
@ -31,13 +37,13 @@ function EditorPanel() {
monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
monaco.KeyCode.F6,
],
run: () => model.render({isPreview: false})
run: () => model.render({isPreview: false, now: true})
});
editor.addAction({
id: "openscad-preview",
label: "Preview OpenSCAD",
keybindings: [monaco.KeyCode.F5],
run: () => model.render({isPreview: false})
run: () => model.render({isPreview: true, now: true})
});
setEditor(editor)
}
@ -79,7 +85,7 @@ export function App({initialState}: {initialState: State}) {
const model = new Model(state, setState);
useEffect(() => model.init());
const stlModelRef = useRef(null);
// const stlModelRef = forwardRef();
return (
<ModelContext.Provider value={model}>
@ -88,41 +94,50 @@ export function App({initialState}: {initialState: State}) {
<div style={{width: "50vw"}}>
<EditorPanel/>
</div>
{state.output?.stlFileURL &&
<StlViewer
style={{
flex: 1
}}
ref={stlModelRef}
showAxes={true}
orbitControls
shadows
modelProps={{
color: '#f9d72c',
<div style={{display: 'flex', flexDirection: 'column', flex: 1, position: 'relative'}}>
{(state.rendering || state.previewing) &&
<ProgressBar mode="indeterminate" style={{ height: '6px' }}></ProgressBar>}
{state.output?.stlFileURL &&
<StlViewer
style={{
flex: 1
}}
// ref={stlModelRef}
showAxes={true}
orbitControls
shadows
modelProps={{
color: '#f9d72c',
}}
url={state.output?.stlFileURL ?? ''}
/>
}
}}
url={state.output?.stlFileURL ?? ''}
/>
}
</div>
<div style={{display: 'flex', flexDirection: 'row'}}>
<button onClick={() => model.render({isPreview: false})}>Render</button>
{model.state.previewing && 'previewing... '}
{model.state.rendering && 'rendering... '}
{model.state.checkingSyntax && 'checking syntax... '}
</div>
<div style={{display: 'flex', flexDirection: 'row', gap: '10px'}}>
<Button onClick={() => model.render({isPreview: false, now: true})}>Render</Button>
{state.output && state.output.formattedElapsedMillis}
&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>
<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" 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>
</ModelContext.Provider>

View file

@ -36,7 +36,7 @@ export const checkSyntax =
});
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 = {
source: string,
@ -45,23 +45,26 @@ export type RenderArgs = {
isPreview: boolean
}
export const render =
turnIntoDelayableExecution(renderDelay, (args: RenderArgs) => {
const prefixLines: string[] = [];
if (args.isPreview) prefixLines.push('$preview=false;');
const source = args.isPreview ? [...prefixLines, args.source].join('\n') : args.source
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[] = [];
if (params.isPreview) {
prefixLines.push('$preview=true;');
}
const source = [...prefixLines, params.source].join('\n');
const job = spawnOpenSCAD({
// wasmMemory,
inputs: [['input.scad', source]],
args: [
inputFile,
"-o", "out.stl",
"--export-format=binstl",
...(args.features ?? []).map(f => `--enable=${f}`),
...(args.extraArgs ?? [])
],
args,
outputPaths: ['out.stl']
});

View file

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

View file

@ -13,5 +13,5 @@ translate([10, 10, 10])
//
// include <BOSL2/std.scad>
// 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 {
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',
sans-serif;
sans-serif; */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View file

@ -42,7 +42,7 @@ export class Model {
private processSource() {
this.checkSyntax();
this.render({isPreview: true});
this.render({isPreview: true, now: false});
}
checkSyntax() {
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) => {
if (isPreview) {
s.previewing = value;
@ -64,9 +64,10 @@ export class Model {
const source = this.state.params.source;
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 => {
s.lastCheckerRun = {
logText: output.logText,
@ -75,10 +76,14 @@ export class Model {
if (s.output?.stlFileURL) {
URL.revokeObjectURL(s.output.stlFileURL);
}
s.output = {
isPreview: isPreview,
stlFile: output.stlFile,
stlFileURL: URL.createObjectURL(output.stlFile),
elapsedMillis: output.elapsedMillis,
formattedElapsedMillis: formatMillis(output.elapsedMillis),
formattedStlFileSize: formatBytes(output.stlFile.size),
};
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,
outputs?: [string, string][],
mergedOutputs: MergedOutputs,
elapsedMillis?: number,
elapsedMillis: number,
}
export function spawnOpenSCAD({inputs, args, outputPaths}: OpenSCADInvocation): AbortablePromise<OpenSCADInvocationResults> {

View file

@ -15,9 +15,16 @@ module.exports = {
{
test: /\.css$/i,
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: {