widgets, file size and elapsed time
This commit is contained in:
parent
8cdf0e29b9
commit
f996bbc1fb
11 changed files with 118 additions and 57 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
.App {
|
||||
/* .App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
|
@ -42,4 +42,4 @@
|
|||
overflow-y: scroll;
|
||||
height: calc(min(200px, 30vh));
|
||||
position: relative;
|
||||
}
|
||||
} */
|
||||
79
src/App.tsx
79
src/App.tsx
|
|
@ -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}
|
||||
|
||||
{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>
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ export interface State {
|
|||
isPreview: boolean,
|
||||
stlFile: File,
|
||||
stlFileURL: string,
|
||||
elapsedMillis: number,
|
||||
formattedElapsedMillis: string,
|
||||
formattedStlFileSize: string,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
`
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
32
src/model.ts
32
src/model.ts
|
|
@ -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`;
|
||||
}
|
||||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
Loading…
Reference in a new issue